You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2056 lines
84 KiB
2056 lines
84 KiB
using System.Collections.ObjectModel; |
|
using System.IO; |
|
using System.Windows; |
|
using System.Windows.Media; |
|
using System.Windows.Media.Animation; |
|
using System.Windows.Media.Imaging; |
|
using System.Windows.Media.Media3D; |
|
using HelixToolkit.Wpf.SharpDX; |
|
using log4net; |
|
using SharpDX; |
|
using SharpDX.Direct3D11; |
|
using SparkClient.Views.UserControl.ViewportData.Enum; |
|
using SparkClient.Views.UserControl.ViewportData.Entity; |
|
using Color = SharpDX.Color; |
|
using GeometryModel3D = HelixToolkit.Wpf.SharpDX.GeometryModel3D; |
|
using MeshGeometry3D = HelixToolkit.Wpf.SharpDX.MeshGeometry3D; |
|
using Point = System.Windows.Point; |
|
using PerspectiveCamera = HelixToolkit.Wpf.SharpDX.PerspectiveCamera; |
|
using System.Windows.Controls; |
|
using HelixToolkit.Wpf; |
|
using MeshBuilder = HelixToolkit.Wpf.SharpDX.MeshBuilder; |
|
using OrthographicCamera = HelixToolkit.Wpf.SharpDX.OrthographicCamera; |
|
using System.Windows.Input; |
|
|
|
|
|
namespace SparkClient.Views.UserControl.ViewportData.Helper; |
|
|
|
public class ViewportHelperPro |
|
{ |
|
private static readonly ILog Logger = LogManager.GetLogger(typeof(ViewportHelperPro)); |
|
/// <summary> |
|
/// 对指定类型的面进行标色 |
|
/// </summary> |
|
/// <param name="planeType"></param> |
|
/// <param name="color"></param> |
|
/// <returns></returns> |
|
public static MeshGeometryModel3D GenerateTypePanelHot(PlaneType planeType, Color4? color = null) |
|
{ |
|
var entities = ViewportManager.ViewportTriangle.Where(e => e.PlaneType == planeType).ToList(); |
|
return GenerateModelByEntity(entities, color ?? ViewportManager.ColorConfig.ErrFacetColor); |
|
} |
|
|
|
#region 已经确定和调整好的方法 |
|
/// <summary> |
|
/// 通过三角形实体集合生成面模型(生成并添加) |
|
/// </summary> |
|
/// <param name="viewport"></param> |
|
/// <param name="entities"></param> |
|
public static MeshGeometryModel3D GenerateModelByEntity(Viewport3DX viewport, List<Viewport3DTriangleEntity> entities, Color4? color = null) |
|
{ |
|
var geometryModel = GenerateModelByEntity(entities, color); |
|
viewport.Items.Add(geometryModel); |
|
return geometryModel; |
|
} |
|
|
|
/// <summary> |
|
/// 通过三角形实体集合生成面模型(只生成不添加) |
|
/// </summary> |
|
/// <param name="entities"></param> |
|
/// <param name="color"></param> |
|
/// <returns></returns> |
|
public static MeshGeometryModel3D GenerateModelByEntity(List<Viewport3DTriangleEntity> entities, Color4? color = null) |
|
{ |
|
var meshBuilder = new MeshBuilder(true, false); |
|
foreach (var entity in entities) |
|
{ |
|
if (entity.PlaneType == PlaneType.Girdle) |
|
{ |
|
meshBuilder.AddTriangleFan(new List<Vector3>() { entity.Point1, entity.Point2, entity.Point3 }, |
|
new List<Vector3>() { new Vector3(0, 0, 0), new Vector3(0, 0, 0), new Vector3(0, 0, 0) }); |
|
} |
|
else |
|
{ |
|
meshBuilder.AddPolygon(new List<Vector3>() { entity.Point1, entity.Point2, entity.Point3 }); |
|
} |
|
} |
|
var mesh = meshBuilder.ToMeshGeometry3D(); |
|
|
|
var material = new PBRMaterial |
|
{ |
|
AlbedoColor = new Color4(0.0f, 0f,0f,0.8f), // 黑色,避免其他光照影响 |
|
EmissiveColor =color ?? ViewportManager.ColorConfig.MainFacetColor , // LightGray #D3D3D3 |
|
MetallicFactor = 0.0, // 非金属 |
|
RoughnessFactor = 1.0, // 高粗糙度,避免反射效果 |
|
ReflectanceFactor = 0.0, // 无反射 |
|
ClearCoatStrength = 0.0, // 无清漆效果 |
|
ClearCoatRoughness = 1.0, // 高粗糙度 |
|
SurfaceMapSampler = new SamplerStateDescription |
|
{ |
|
Filter = Filter.MinMagMipLinear, |
|
AddressU = TextureAddressMode.Wrap, |
|
AddressV = TextureAddressMode.Wrap, |
|
AddressW = TextureAddressMode.Wrap |
|
}, |
|
// NormalMap = texture |
|
}; |
|
return new MeshGeometryModel3D |
|
{ |
|
Geometry = mesh, |
|
Material = material, |
|
}; |
|
} |
|
|
|
/// <summary> |
|
/// 通过三角形实体集合生成面模型(只生成不添加) |
|
/// </summary> |
|
/// <param name="entities"></param> |
|
/// <param name="color"></param> |
|
/// <returns></returns> |
|
public static List<MeshGeometryModel3D> GenerateModelByEntityGroupByType(List<Viewport3DTriangleEntity> entities) |
|
{ |
|
var groupedDict = entities |
|
.GroupBy(e => e.PlaneType) |
|
.ToDictionary(g => g.Key, g => g.ToList()); |
|
|
|
var result = new List<MeshGeometryModel3D>(); |
|
foreach (var group in groupedDict) |
|
{ |
|
var key = group.Key; |
|
result.Add(GenerateModelByEntity(group.Value, GenFaceColor4(key))); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
private static Color4? GenFaceColor4(PlaneType planeType) |
|
{ |
|
switch (planeType) |
|
{ |
|
case PlaneType.Girdle: |
|
return ViewportManager.ColorConfig.GirdleFacetColor; |
|
case PlaneType.TableFacet: |
|
return ViewportManager.ColorConfig.TableFacetColor; |
|
case PlaneType.UpperMainFacet: |
|
return ViewportManager.ColorConfig.UpperMainFacetColor; |
|
case PlaneType.StarFacet: |
|
return ViewportManager.ColorConfig.StarFacetColor; |
|
case PlaneType.UpperGirdleFacet: |
|
return ViewportManager.ColorConfig.UpperGirdleFacetColor; |
|
case PlaneType.PavilionMainFacet: |
|
return ViewportManager.ColorConfig.PavilionFacetColor; |
|
case PlaneType.LowerGirdleFact: |
|
return ViewportManager.ColorConfig.LowerGirdleFacetColor; |
|
case PlaneType.Culet: |
|
return ViewportManager.ColorConfig.CuletFacetColor; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
/// <summary> |
|
/// 保存模型截图 |
|
/// </summary> |
|
/// <param name="viewport"></param> |
|
/// <param name="filePath"></param> |
|
public static void SaveViewportAsImage(Viewport3DX viewport, string filePath) |
|
{ |
|
// 定义图像大小 |
|
int width = (int)viewport.ActualWidth; |
|
int height = (int)viewport.ActualHeight; |
|
|
|
// 创建 RenderTargetBitmap,捕获 Viewport3DX 的内容 |
|
var renderTargetBitmap = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32); |
|
renderTargetBitmap.Render(viewport); |
|
|
|
// 使用 PngBitmapEncoder 保存为 PNG 文件 |
|
var encoder = new PngBitmapEncoder(); |
|
encoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap)); |
|
|
|
using (var fileStream = new FileStream(filePath, FileMode.Create)) |
|
{ |
|
encoder.Save(fileStream); |
|
} |
|
} |
|
|
|
|
|
public static async Task ExportModelsToVideo(Viewport3DX viewport, string filePath) |
|
{ |
|
if (viewport == null) |
|
viewport = ViewportManager.GetViewport3D(); |
|
|
|
var generationTask = VideoHelper.StartGenerationAndRotation(viewport); |
|
|
|
List<PngBitmapEncoder> pngList = await generationTask; |
|
|
|
await VideoHelper.CreateVideoFromPngListAsync(pngList, filePath); |
|
} |
|
|
|
/// <summary> |
|
/// 导出模型 |
|
/// </summary> |
|
/// <param name="viewport"></param> |
|
/// <param name="filePath"></param> |
|
public static void ExportModelsToStl(Viewport3DX viewport, string filePath) |
|
{ |
|
if (viewport == null) |
|
viewport = ViewportManager.GetViewport3D(); |
|
// 打开文件写入流 |
|
using (StreamWriter writer = new StreamWriter(filePath)) |
|
{ |
|
writer.WriteLine("solid exportedModel"); |
|
foreach (var model in viewport.Items.OfType<MeshGeometryModel3D>()) |
|
{ |
|
|
|
if (model.Geometry is HelixToolkit.Wpf.SharpDX.Geometry3D geometry) |
|
{ |
|
var positions = geometry.Positions; |
|
var indices = geometry.Indices; |
|
|
|
// 每三个索引构成一个三角形 |
|
for (int i = 0; i < indices.Count; i += 3) |
|
{ |
|
int index0 = indices[i]; |
|
int index1 = indices[i + 1]; |
|
int index2 = indices[i + 2]; |
|
|
|
var p0 = positions[index0]; |
|
var p1 = positions[index1]; |
|
var p2 = positions[index2]; |
|
|
|
// 计算法线 |
|
var normal = CalculateNormal(p0, p1, p2); |
|
|
|
// 写入三角形信息到 STL 文件 |
|
writer.WriteLine($" facet normal {normal.X} {normal.Z} {normal.Y}"); |
|
writer.WriteLine(" outer loop"); |
|
writer.WriteLine($" vertex {p0.X} {p0.Z} {p0.Y}"); |
|
writer.WriteLine($" vertex {p1.X} {p1.Z} {p1.Y}"); |
|
writer.WriteLine($" vertex {p2.X} {p2.Z} {p2.Y}"); |
|
writer.WriteLine(" endloop"); |
|
writer.WriteLine(" endfacet"); |
|
} |
|
} |
|
} |
|
|
|
// 写入 STL 文件结束 |
|
writer.WriteLine("endsolid exportedModel"); |
|
} |
|
} |
|
/// <summary> |
|
/// 导出模型 |
|
/// </summary> |
|
/// <param name="viewport"></param> |
|
/// <param name="filePath"></param> |
|
public static async Task ExportModelsToStlASync(string filePath) |
|
{ |
|
Viewport3DX viewport = ViewportManager.GetViewport3D(); |
|
|
|
// 创建一个集合来存储几何信息 |
|
List<Tuple<IList<Vector3>, IList<int>>> geometries = new List<Tuple<IList<Vector3>, IList<int>>>(); |
|
|
|
// 在 UI 线程中收集几何信息 |
|
await Application.Current.Dispatcher.InvokeAsync(() => |
|
{ |
|
foreach (var model in viewport.Items.OfType<MeshGeometryModel3D>()) |
|
{ |
|
if (model.Geometry is HelixToolkit.Wpf.SharpDX.Geometry3D geometry) |
|
{ |
|
geometries.Add(new Tuple<IList<Vector3>, IList<int>>(geometry.Positions, geometry.Indices)); |
|
} |
|
} |
|
}); |
|
|
|
// 在后台线程中处理几何信息并写入 STL 文件 |
|
await Task.Run(() => |
|
{ |
|
using (StreamWriter writer = new StreamWriter(filePath)) |
|
{ |
|
writer.WriteLine("solid exportedModel"); |
|
|
|
foreach (var geometry in geometries) |
|
{ |
|
var positions = geometry.Item1; |
|
var indices = geometry.Item2; |
|
|
|
// 每三个索引构成一个三角形 |
|
for (int i = 0; i < indices.Count; i += 3) |
|
{ |
|
int index0 = indices[i]; |
|
int index1 = indices[i + 1]; |
|
int index2 = indices[i + 2]; |
|
|
|
var p0 = positions[index0]; |
|
var p1 = positions[index1]; |
|
var p2 = positions[index2]; |
|
|
|
// 计算法线 |
|
var normal = CalculateNormal(p0, p1, p2); |
|
|
|
// 写入三角形信息到 STL 文件 |
|
writer.WriteLine($" facet normal {normal.X} {normal.Y} {normal.Z}"); |
|
writer.WriteLine(" outer loop"); |
|
writer.WriteLine($" vertex {p0.X} {p0.Y} {p0.Z}"); |
|
writer.WriteLine($" vertex {p1.X} {p1.Y} {p1.Z}"); |
|
writer.WriteLine($" vertex {p2.X} {p2.Y} {p2.Z}"); |
|
writer.WriteLine(" endloop"); |
|
writer.WriteLine(" endfacet"); |
|
} |
|
} |
|
|
|
// 写入 STL 文件结束 |
|
writer.WriteLine("endsolid exportedModel"); |
|
} |
|
}).ConfigureAwait(false); |
|
|
|
} |
|
/// <summary> |
|
/// 通过三角形实体集合生成每个面的边框线 |
|
/// </summary> |
|
/// <param name="viewport"></param> |
|
/// <param name="entities"></param> |
|
public static List<LineGeometryModel3D> GentrateLineByEntity(Viewport3DX viewport, List<Viewport3DTriangleEntity> entities, Color4? color = null, double thickness = 1.0) |
|
{ |
|
List<LineGeometryModel3D> result = GentrateLineByEntity(entities, color, thickness); |
|
result.ForEach(e => viewport.Items.Add(e)); |
|
return result; |
|
} |
|
|
|
/// <summary> |
|
/// 通过三角形实体集合生成每个面的边框线(只生成不添加) |
|
/// </summary> |
|
/// <param name="entities"></param> |
|
/// <param name="color"></param> |
|
/// <param name="thickness"></param> |
|
/// <returns></returns> |
|
public static List<LineGeometryModel3D> GentrateLineGirdleByEntity(List<Viewport3DTriangleEntity> entities, |
|
Color4? color = null, double thickness = 1.0) |
|
{ |
|
List<LineGeometryModel3D> result = new List<LineGeometryModel3D>(); |
|
//按面分组,腰面特殊单独生成 |
|
List<Viewport3DTriangleEntity> waistList = entities |
|
.Where(entity => entity.PlaneType == PlaneType.Girdle) |
|
.ToList(); |
|
List<Vector3> selFaceVector = new List<Vector3>(); |
|
List<Tuple<Vector3, Vector3>> lines = new List<Tuple<Vector3, Vector3>>(); |
|
if (waistList.Count > 0) |
|
{ |
|
foreach (var entity in waistList) |
|
{ |
|
selFaceVector.Add(entity.Point1); |
|
selFaceVector.Add(entity.Point2); |
|
selFaceVector.Add(entity.Point3); |
|
} |
|
HashSet<Vector3> uniqueVectors = new HashSet<Vector3>(selFaceVector); |
|
|
|
for (int i = 0; i < selFaceVector.ToList().Count - 1; i++) |
|
{ |
|
var nowItem = selFaceVector.ToList()[i]; |
|
var nextItem = selFaceVector.ToList()[i + 1]; |
|
var line = new Tuple<Vector3, Vector3>(nowItem, nextItem); |
|
if (IsLineSegmentParallelToYAxis(line)) |
|
{ |
|
lines.Add(line); |
|
result.Add(DisplayLineModel3D(new List<Vector3>() { nowItem, nextItem }, color ?? ViewportManager.ColorConfig.MainBorderColor, thickness)); |
|
} |
|
} |
|
} |
|
|
|
CalculateLineSegmentStats(lines, out ViewportManager.MainModelGirdleMaxLines, out ViewportManager.MainModelGirdleMinLines, out ViewportManager.MainModelGirdleAvgLines); |
|
ViewportManager.MainModelGirdleLines = lines; |
|
return result; |
|
} |
|
|
|
/// <summary> |
|
/// 通过三角形实体集合生成每个面的边框线(只生成不添加) |
|
/// </summary> |
|
/// <param name="entities"></param> |
|
/// <param name="color"></param> |
|
/// <param name="thickness"></param> |
|
/// <returns></returns> |
|
public static List<LineGeometryModel3D> GentrateLineByEntity(List<Viewport3DTriangleEntity> entities, Color4? color = null, double thickness = 1.0) |
|
{ |
|
List<LineGeometryModel3D> result = new List<LineGeometryModel3D>(); |
|
//按面分组,腰面特殊单独生成 |
|
List<Viewport3DTriangleEntity> waistList = entities |
|
.Where(entity => entity.PlaneType == PlaneType.Girdle) |
|
.ToList(); |
|
|
|
Dictionary<string, List<Viewport3DTriangleEntity>> feactList = entities |
|
.Where(entity => entity.PlaneType != PlaneType.Girdle) |
|
.GroupBy(entity => entity.PlaneCode) |
|
.ToDictionary(group => group.Key, group => group.ToList()); |
|
|
|
foreach (var item in feactList) |
|
{ |
|
//GenerateLineTextModelByEntity(viewport, item.Value, Viewport3DManager.Gray); |
|
List<Vector3> temp = new List<Vector3>(); |
|
foreach (var entity in item.Value) |
|
{ |
|
temp.Add(entity.Point1); |
|
temp.Add(entity.Point2); |
|
temp.Add(entity.Point3); |
|
} |
|
result.Add(DisplayLineModel3D(VectorClockwiseSort(new HashSet<Vector3>(temp).ToList()), color ?? ViewportManager.ColorConfig.MainBorderColor, thickness)); |
|
} |
|
|
|
//腰 - 特殊 |
|
List<Vector3> selFaceVector = new List<Vector3>(); |
|
if (waistList.Count > 0) |
|
{ |
|
//顶线和底线 |
|
foreach (var entity in waistList) |
|
{ |
|
selFaceVector.Add(entity.Point1); |
|
selFaceVector.Add(entity.Point2); |
|
selFaceVector.Add(entity.Point3); |
|
} |
|
HashSet<Vector3> uniqueVectors = new HashSet<Vector3>(selFaceVector); |
|
float maxY = uniqueVectors.Max(v => v.Y); |
|
float minY = uniqueVectors.Min(v => v.Y); |
|
float mid = (maxY + minY) / 2; |
|
ViewportManager.GirdleTopLines.Clear(); |
|
ViewportManager.GirdleBottomLines.Clear(); |
|
foreach (var vector in uniqueVectors) |
|
{ |
|
if (vector.Y < mid) |
|
ViewportManager.GirdleBottomLines.Add(vector); |
|
else |
|
ViewportManager.GirdleTopLines.Add(vector); |
|
} |
|
// result.Add(DisplayLineModel3D( VectorClockwiseSort(ViewportManager.GirdleBottomLines), color??ViewportManager.ColorConfig.MainBorderColor, thickness)); |
|
// result.Add(DisplayLineModel3D( VectorClockwiseSort(ViewportManager.GirdleTopLines), color??ViewportManager.ColorConfig.MainBorderColor, thickness)); |
|
|
|
} |
|
|
|
return result; |
|
} |
|
|
|
public static List<Viewport3DTriangleEntity> GenerateLineTextModelsByType(PlaneType type, Color4? textColor = null, |
|
bool showAll = false) |
|
{ |
|
|
|
List<Viewport3DTriangleEntity> entities = new List<Viewport3DTriangleEntity>(); |
|
string planCode = ""; |
|
ViewportManager.ViewportTriangle.ForEach(e => |
|
{ |
|
if (e.PlaneType == type && (e.PlaneCode == planCode || string.IsNullOrEmpty(planCode))) |
|
{ |
|
planCode = e.PlaneCode; |
|
entities.Add(e); |
|
} |
|
}); |
|
|
|
return entities; |
|
} |
|
|
|
/// <summary> |
|
/// 选择面生成文本信息 |
|
/// </summary> |
|
/// <param name="entities">三角形集合</param> |
|
/// <param name="valKey">指定数据集</param> |
|
/// <returns></returns> |
|
public static List<GeometryModel3D> GenerateLineTextModels(List<Viewport3DTriangleEntity> entities, string valKey = "") |
|
{ |
|
Logger.Info("【面文本生成】开始生成面相关文本信息"); |
|
var selFacet = entities; |
|
var selFacetType = entities.First().PlaneType; |
|
|
|
var result = new List<GeometryModel3D>(); |
|
if (selFacetType == PlaneType.Girdle && string.IsNullOrWhiteSpace(valKey)) |
|
{ |
|
Logger.Info($"【面文本生成】 命中面{selFacetType},是腰"); |
|
// if (ViewportManager.MainModelGirdleMaxLines != null || ViewportManager.MainModelGirdleMinLines != null || |
|
// ViewportManager.MainModelGirdleAvgLines != null) |
|
// { |
|
// result.Add(DisplayLineModel3D(new List<Tuple<Vector3,Vector3>>(){ViewportManager.MainModelGirdleMaxLines}, new Color4(1f, 0, 0, 1f) )); |
|
// result.Add(DisplayLineModel3D(new List<Tuple<Vector3,Vector3>>(){ViewportManager.MainModelGirdleMinLines}, new Color4(0f, 1f, 0, 1f) )); |
|
// result.Add(DisplayLineModel3D(new List<Tuple<Vector3,Vector3>>(){ViewportManager.MainModelGirdleAvgLines}, new Color4(1f, 0.5f, 0, 1f) )); |
|
// result.Add(DisplayText3D($"{CalculateLength(ViewportManager.MainModelGirdleMaxLines)}mm", ViewportManager.MainModelGirdleMaxLines.Item1, textColor)); |
|
// result.Add(DisplayText3D($"{CalculateLength(ViewportManager.MainModelGirdleMinLines)}mm", ViewportManager.MainModelGirdleMinLines.Item1, textColor)); |
|
// result.Add(DisplayText3D($"{CalculateLength(ViewportManager.MainModelGirdleAvgLines)}mm", ViewportManager.MainModelGirdleAvgLines.Item1, textColor)); |
|
// } |
|
List<Viewport3DTriangleEntity> facetTypeAll = |
|
ViewportManager.ViewportTriangle.FindAll(e => e.PlaneType == selFacetType); |
|
var groupedDic = facetTypeAll.GroupBy(entity => entity.PlaneCode) |
|
.ToDictionary(group => group.Key, group => group.ToList()); |
|
Logger.Info($"【面文本生成】 腰由{groupedDic.Count}个面组成"); |
|
|
|
var selPlaneCode = entities.First().PlaneCode; |
|
Logger.Info($"【面文本生成】 当前选择{selPlaneCode}"); |
|
|
|
List<Vector3> facetPoints = new List<Vector3>(); |
|
entities.ForEach(e => { facetPoints.Add(e.Point1); facetPoints.Add(e.Point2); facetPoints.Add(e.Point3); }); |
|
var facetIndex = -1; |
|
int.TryParse(selPlaneCode.Split("_")[1], out facetIndex); |
|
|
|
if (facetIndex == -1) |
|
{ |
|
Logger.Info($"【面文本生成】 面索引解析失败{selPlaneCode}"); |
|
return result; |
|
} |
|
int linePointType = facetIndex % 4; |
|
|
|
switch (linePointType) |
|
{ |
|
case 0: |
|
case 3: |
|
var longestLine1 = GetLongestOrShortestLineSegment(facetPoints, returnLongest: true); |
|
result.Add(DisplayLineModel3D(new List<Tuple<Vector3, Vector3>>() { longestLine1 }, new Color4(1f, 0, 0, 1f), 2f)); |
|
break; |
|
case 1: |
|
case 2: |
|
var longestLine2 = GetLongestOrShortestLineSegment(facetPoints, returnLongest: false); |
|
result.Add(DisplayLineModel3D(new List<Tuple<Vector3, Vector3>>() { longestLine2 }, new Color4(1f, 0, 0, 1f), 2f)); |
|
break; |
|
} |
|
//开始找值 |
|
string param = string.Empty; |
|
var valueIndex = 0; |
|
if (facetIndex % 8 == 0 || (facetIndex + 1) % 8 == 0) |
|
{ |
|
//上腰面 |
|
Logger.Info($"【面文本生成】 腰面值 波峰【上腰面】"); |
|
valueIndex = (int)Math.Ceiling(facetIndex / 8.0) == 8 ? 0 : (int)Math.Ceiling(facetIndex / 8.0); |
|
param = "GIRDLE_BONE"; |
|
} else if (linePointType == 3 || linePointType == 0) |
|
{ |
|
//风筝面 |
|
Logger.Info($"【面文本生成】 腰面值 波峰【风筝面】"); |
|
valueIndex = (facetIndex / 8); |
|
param = "GIRDLE_BEZEL"; |
|
} |
|
else |
|
{ |
|
//腰厚比 |
|
Logger.Info($"【面文本生成】 腰面值 波峰【腰厚比】"); |
|
valueIndex = (facetIndex / 4); |
|
param = "GIRDLE_VALLEY"; |
|
} |
|
|
|
valueIndex += 1; |
|
var detail = ViewportManager.DiamondData[$"{param}_DETAIL"]; |
|
if (detail == null) |
|
{ |
|
Logger.Info($"【面文本生成】 {param}_DETAIL Key不存在"); |
|
return result; |
|
} |
|
Logger.Info($"【面文本生成】 {param}_DETAIL == {detail}"); |
|
var paramValue = detail[$"{param}_{valueIndex}"]; |
|
if (paramValue == null) |
|
{ |
|
Logger.Info($"【面文本生成】 {param}_DETAIL.{param}_{valueIndex} Key不存在"); |
|
return result; |
|
} |
|
Logger.Info($"【面文本生成】 {param}_DETAIL.{param}_{valueIndex} ==={paramValue}"); |
|
var valueFloat = (Math.Floor(float.Parse(paramValue.ToString()) * 1000) / 10).ToString(); |
|
Logger.Info($"【面文本生成】 {valueFloat} -- {valueIndex}"); |
|
var facetTextPoint = GetOffsetCenter(facetPoints, ViewportManager.CenterVector); |
|
result.Add(DisplayText3D($"{valueFloat}", facetTextPoint)); |
|
// foreach (var kv in groupedDic) |
|
// { |
|
// var value = kv.Value; |
|
// var key = kv.Key; |
|
// var facetIndex = -1; |
|
// int.TryParse(key.Split("_")[1], out facetIndex); |
|
// List<Vector3> facetPoints = new List<Vector3>(); |
|
// value.ForEach(e => { facetPoints.Add(e.Point1); facetPoints.Add(e.Point2); facetPoints.Add(e.Point3); }); |
|
// |
|
// int linePointType = facetIndex % 4; |
|
// /*** |
|
// * 0 1 2 3 |
|
// * 4 5 6 7 |
|
// * 面index+1 % 4 = 0 |
|
// * 面id-loop4 = 0 - 高亮左边的竖线 - 波峰 - |
|
// * 面id-loop4 = 1 - 高亮右边的竖线 - 波谷 |
|
// * 面id-loop4 = 2 - 高亮左边的竖线 - 波谷 |
|
// * 面id-loop4 = 3 - 高亮右边的竖线 - 波峰 |
|
// */ |
|
// |
|
// } |
|
} else if (selFacetType == PlaneType.Girdle && !string.IsNullOrWhiteSpace(valKey)) |
|
{ |
|
Logger.Info($"【面文本生成】 命中面{selFacetType},是腰,显示值{valKey}"); |
|
/*** |
|
* GIRDLE_BEZEL 风筝 |
|
* GIRDLE_BONE 上腰 |
|
* GIRDLE_VALLEY 波谷 |
|
*/ |
|
List<Viewport3DTriangleEntity> facetTypeAll = |
|
ViewportManager.ViewportTriangle.FindAll(e => e.PlaneType == selFacetType); |
|
var groupedDic = facetTypeAll.GroupBy(entity => entity.PlaneCode) |
|
.ToDictionary(group => group.Key, group => group.ToList()); |
|
var resultDic = new Dictionary<string, List<Viewport3DTriangleEntity>>(); |
|
for (int i = 0; i < groupedDic.Count; i++) |
|
{ |
|
switch (valKey) |
|
{ |
|
case "GIRDLE_BEZEL": if (i % 8 == 4) resultDic.Add($"0_{i}", groupedDic[$"0_{i}"]); |
|
break; |
|
case "GIRDLE_BONE": if (i % 8 == 0) resultDic.Add($"0_{i}", groupedDic[$"0_{i}"]); |
|
break; |
|
case "GIRDLE_VALLEY": if (i % 4 == 2) resultDic.Add($"0_{i}", groupedDic[$"0_{i}"]); |
|
break; |
|
} |
|
} |
|
|
|
if (resultDic.Count == 0) |
|
{ |
|
Logger.Info($"【面文本生成】 关联面获取失败"); |
|
} |
|
|
|
foreach (var dic in resultDic) |
|
{ |
|
//高亮四边形左边的线,并绑定值 |
|
List<Vector3> facetPoints = new List<Vector3>(); |
|
dic.Value.ForEach(e => { facetPoints.Add(e.Point1); facetPoints.Add(e.Point2); facetPoints.Add(e.Point3); }); |
|
//高亮的线 |
|
var showLine = GetLeftParallelLineSegment(facetPoints); |
|
if (showLine == null) continue; |
|
|
|
//文字显示位置 |
|
var facetTextPoint = GetOffsetCenter(facetPoints, ViewportManager.CenterVector); |
|
var resIndex = resultDic.Keys.ToList().IndexOf(dic.Key); |
|
if (resIndex == -1) continue; |
|
resIndex += 1; |
|
var detail = ViewportManager.DiamondData[$"{valKey}_DETAIL"]; |
|
if (detail == null) |
|
{ |
|
Logger.Info($"【面文本生成】 {valKey}_DETAIL Key不存在"); |
|
return result; |
|
} |
|
Logger.Info($"【面文本生成】 {valKey}_DETAIL == {detail}"); |
|
var paramValue = detail[$"{valKey}_{resIndex}"]; |
|
if (paramValue == null) |
|
{ |
|
Logger.Info($"【面文本生成】 {valKey}_DETAIL.{valKey}_{resIndex} Key不存在"); |
|
return result; |
|
} |
|
result.Add(DisplayLineModel3D(new List<Tuple<Vector3, Vector3>>() { showLine }, new Color4(1f, 0, 0, 1f), 2f)); |
|
var valueFloat = ValueFormat(paramValue.ToString(), valKey); |
|
result.Add(DisplayText3D($" {resIndex} \r\n {valueFloat}", facetTextPoint)); |
|
} |
|
|
|
} |
|
else |
|
{ |
|
Logger.Info($"【面文本生成】 命中面{selFacetType},不是腰"); |
|
//查找同类面 |
|
List<Viewport3DTriangleEntity> facetTypeAll = |
|
ViewportManager.ViewportTriangle.FindAll(e => e.PlaneType == selFacetType); |
|
var groupedDic = facetTypeAll.GroupBy(entity => entity.PlaneCode) |
|
.ToDictionary(group => group.Key, group => group.ToList()); |
|
Logger.Info($"【面文本生成】 同类面{groupedDic.Count}个"); |
|
var valueIndex = 1; |
|
foreach (var kv in groupedDic) |
|
{ |
|
var value = kv.Value; |
|
var key = kv.Key; |
|
var facetIndex = -1; |
|
int.TryParse(key.Split("_")[1], out facetIndex); |
|
List<Vector3> facetPoints = new List<Vector3>(); |
|
value.ForEach(e => { facetPoints.Add(e.Point1); facetPoints.Add(e.Point2); facetPoints.Add(e.Point3); }); |
|
var param = string.IsNullOrWhiteSpace(valKey) ? ViewportManager.DicFacetToValueParam.ContainsKey(selFacetType) ? ViewportManager.DicFacetToValueParam[selFacetType] : null : valKey; |
|
if (param == null) |
|
{ |
|
continue; |
|
} |
|
|
|
if (groupedDic.Count == 1) |
|
{ |
|
var paramValue = ViewportManager.DiamondData[$"{param}"]; |
|
if (paramValue == null) |
|
{ |
|
Logger.Info($"【面文本生成】 {param} Key不存在"); |
|
continue; |
|
} |
|
var valueFloat = ValueFormat(paramValue.ToString(), param); |
|
Logger.Info($"【面文本生成】 {valueFloat} -- {facetIndex}"); |
|
var facetTextPoint = GetOffsetCenter(facetPoints, ViewportManager.CenterVector); |
|
result.Add(DisplayText3D($"{valueFloat}", facetTextPoint)); |
|
|
|
} else if (groupedDic.Count == 16) |
|
{ |
|
facetIndex += 1; |
|
valueIndex = facetIndex == 16 ? 1 : facetIndex % 2 == 0 ? valueIndex + 1 : valueIndex; |
|
var detail = ViewportManager.DiamondData[$"{param}_DETAIL"]; |
|
if (detail == null) |
|
{ |
|
Logger.Info($"【面文本生成】 {param}_DETAIL Key不存在"); |
|
continue; |
|
} |
|
Logger.Info($"【面文本生成】 {param}_DETAIL == {detail}"); |
|
var paramValue = detail[$"{param}_{valueIndex}"]; |
|
if (paramValue == null) |
|
{ |
|
Logger.Info($"【面文本生成】 {param}_DETAIL.{param}_{valueIndex} Key不存在"); |
|
continue; |
|
} |
|
Logger.Info($"【面文本生成】 {param}_DETAIL.{param}_{valueIndex} ==={paramValue}"); |
|
|
|
var valueFloat = ValueFormat(paramValue.ToString(), param); |
|
Logger.Info($"【面文本生成】 {valueFloat} -- {valueIndex}"); |
|
var facetTextPoint = GetOffsetCenter(facetPoints, ViewportManager.CenterVector); |
|
result.Add(DisplayText3D($" {facetIndex} \r\n {valueFloat}", new Vector3(facetTextPoint.X, facetTextPoint.Y + 0.1f, facetTextPoint.Z))); |
|
|
|
} else if (ViewportManager.DiamondData.Count > 1 && facetIndex != -1) |
|
{ |
|
facetIndex += 1; |
|
|
|
var detail = ViewportManager.DiamondData[$"{param}_DETAIL"]; |
|
if (detail == null) |
|
{ |
|
Logger.Info($"【面文本生成】 {param}_DETAIL Key不存在"); |
|
continue; |
|
} |
|
Logger.Info($"【面文本生成】 {param}_DETAIL == {detail}"); |
|
var paramValue = detail[$"{param}_{facetIndex}"]; |
|
if (paramValue == null) |
|
{ |
|
Logger.Info($"【面文本生成】 {param}_DETAIL.{param}_{facetIndex} Key不存在"); |
|
continue; |
|
} |
|
Logger.Info($"【面文本生成】 {param}_DETAIL.{param}_{facetIndex} ==={paramValue}"); |
|
|
|
var valueFloat = ValueFormat(paramValue.ToString(), param); |
|
Logger.Info($"【面文本生成】 {valueFloat} -- {facetIndex}"); |
|
var facetTextPoint = GetOffsetCenter(facetPoints, ViewportManager.CenterVector); |
|
result.Add(DisplayText3D($" {facetIndex} \r\n {valueFloat}", facetTextPoint)); |
|
} |
|
} |
|
|
|
/*** |
|
* 思路: |
|
* 1、以面分组,1组为台面、底面,8组、16组、64组、other |
|
* 2、每组数据区别 |
|
* 1组:取第一个数据 |
|
* 8组:按索引0-7 -> 1-8 |
|
* 16组:1-2、3-4、5-6、7-8、9-10、11-12、13-14、15-0共享数据 |
|
* 64组:这组特殊理论上是腰,按照else里的东西来 |
|
* 3、这个方法是主动点击,被动触发理论上通用 |
|
* 4、主动触发:循环遍历面组,根据面类型获取算法存储的数据,在Manager里制定面-参数的一对多关系,主动触发时,取参数的首位为默认 |
|
* 5、被动触发:根据结果页传入的参数编号,找到对应的面,循环遍历面组,根据面类型获取算法存储的数据,根据被动传入参数找到值 |
|
* 6、 ①组面可能由至少两个三角形组成,计算出三角形拼接出的多边形的中心作为文字显示的中心 |
|
* ②在从算法结果数据对应好面组的索引,做成值-组面id的文本,计算出这个文本的大小 |
|
* ③在拼接的图片中心,平行于平面,向中心点外偏移“文本大小高度+0.1"个单位 显示文本 |
|
*/ |
|
|
|
// var selFaceVector = entities |
|
// .SelectMany(entity => new[] { entity.Point1, entity.Point2, entity.Point3 }) |
|
// .Distinct() |
|
// .ToList(); |
|
// var uniqueLines = new HashSet<string>(); |
|
// var sortedVectors = VectorClockwiseSort(selFaceVector); |
|
// for (int i = 0; i < sortedVectors.Count; i++) |
|
// { |
|
// var current = sortedVectors[i]; |
|
// var next = sortedVectors[(i + 1) % sortedVectors.Count]; |
|
// |
|
// double length = (next - current).Length(); |
|
// string lineKey = $"{length:F2}"; |
|
// if (showAll == false) |
|
// { |
|
// if (uniqueLines.Contains(lineKey)) continue; |
|
// uniqueLines.Add(lineKey); |
|
// } |
|
// var midPoint = (current + next) / 2; |
|
// var text = $"{length:F2}mm"; |
|
// var textY = midPoint.Y > ViewportManager.CenterVector.Y ? midPoint.Y + 0.3f : midPoint.Y - 0.3f; |
|
// var lengthTextModel = DisplayText3D(text, new Vector3(midPoint.X, textY, midPoint.Z),next - current, textColor); |
|
// result.Add(lengthTextModel); |
|
// } |
|
} |
|
|
|
|
|
return result; |
|
} |
|
|
|
/// <summary> |
|
/// 生成选择的视图() |
|
/// </summary> |
|
/// <param name="triangleCode">三角形ID</param> |
|
/// <param name="selType">生成范围</param> |
|
/// <returns></returns> |
|
public static List<GeometryModel3D> GentrateChosenView(string triangleCode, string valKey = "", params SelShowType[] selType) |
|
{ |
|
var res = ViewportManager.ViewportTriangle.Find(e => triangleCode.Equals(e.TriangleCode)); |
|
if (res != null) |
|
return GentrateChosenView(res, valKey, selType); |
|
return new List<GeometryModel3D>(); |
|
} |
|
|
|
/// <summary> |
|
/// 生成选择的视图(只生成,不添加!!) |
|
/// </summary> |
|
/// <param name="entity">选中的实体</param> |
|
/// <param name="selType">生成范围</param> |
|
/// <returns></returns> |
|
/// <exception cref="Exception"></exception> |
|
public static List<GeometryModel3D> GentrateChosenView(Viewport3DTriangleEntity entity, string valKey = "", params SelShowType[] selType) |
|
{ |
|
if (entity == null || entity.TriangleCode == null || entity.TriangleCode.Length == 0) |
|
{ |
|
throw new Exception("Invalid Viewport3DTriangleEntity object!"); |
|
} |
|
|
|
if (selType == null || selType.Length == 0) |
|
{ |
|
throw new Exception("Invalid SelShowType collection"); |
|
} |
|
List<GeometryModel3D> result = new List<GeometryModel3D>(); |
|
|
|
//选中的面 |
|
var selPanel = ViewportManager.ViewportTriangle.Where(e => entity.PlaneCode.Equals(e.PlaneCode)).ToList(); |
|
//选中同类的面 |
|
var selPanelType = ViewportManager.ViewportTriangle.Where(e => entity.PlaneType.Equals(e.PlaneType)).ToList(); |
|
selPanel.ForEach(e => selPanelType.Remove(e)); |
|
foreach (var type in selType) |
|
{ |
|
switch (type) |
|
{ |
|
case SelShowType.SelPanel: |
|
//选中面的高亮 |
|
result.Add(GenerateModelByEntity(selPanel, ViewportManager.ColorConfig.SelFacetColor)); |
|
break; |
|
case SelShowType.Border: |
|
//选中面边框的高亮 |
|
result.AddRange(GentrateLineByEntity(selPanel, ViewportManager.ColorConfig.SelBorderColor)); |
|
break; |
|
case SelShowType.IsTypePanel: |
|
//选中面的同类面高亮 |
|
result.Add(GenerateModelByEntity(selPanelType, ViewportManager.ColorConfig.SelTypeColor)); |
|
break; |
|
case SelShowType.LengthText: |
|
//选中面 每条边长度标记 |
|
// if(PlaneType.Girdle == entity.PlaneType)break; |
|
result.AddRange(GenerateLineTextModels(selPanel, valKey)); |
|
break; |
|
case SelShowType.BorderAngle: |
|
//选中面 每条边向内的夹角 |
|
if (PlaneType.Girdle == entity.PlaneType) break; |
|
result.AddRange(GenerateLineAngleTextModels(selPanel)); |
|
break; |
|
} |
|
} |
|
//生成的需要添加的元素 |
|
return result; |
|
} |
|
|
|
|
|
/// <summary> |
|
/// 生成线段夹角文本(不绘制) |
|
/// </summary> |
|
/// <param name="entities">线集合</param> |
|
/// <param name="textColor">文本颜色(默认红)</param> |
|
/// <param name="showAll">是否显示全部(默认否)</param> |
|
/// <returns></returns> |
|
public static List<GeometryModel3D> GenerateLineAngleTextModels(List<Viewport3DTriangleEntity> entities, |
|
Color4? textColor = null, bool showAll = false) |
|
{ |
|
var result = new List<GeometryModel3D>(); |
|
var uniqueAngles = new HashSet<string>(); |
|
var selFaceVector = entities |
|
.SelectMany(entity => new[] { entity.Point1, entity.Point2, entity.Point3 }) |
|
.Distinct() |
|
.ToList(); |
|
|
|
var sortedVectors = VectorClockwiseSort(selFaceVector); |
|
|
|
for (int i = 0; i < sortedVectors.Count; i++) |
|
{ |
|
int prevIndex = (i - 1 + sortedVectors.Count) % sortedVectors.Count; |
|
int nextIndex = (i + 1) % sortedVectors.Count; |
|
|
|
var p1 = sortedVectors[prevIndex]; |
|
var p2 = sortedVectors[i]; |
|
var p3 = sortedVectors[nextIndex]; |
|
|
|
// 向量 |
|
var v1 = Vector3.Normalize(p1 - p2); |
|
var v2 = Vector3.Normalize(p3 - p2); |
|
|
|
// 计算内角(考虑方向性) |
|
double angle = Math.Acos(Vector3.Dot(v1, v2)) * (180 / Math.PI); |
|
|
|
string angleKey = $"{angle:F2}"; // 唯一标识 |
|
if (showAll == false) |
|
{ |
|
if (uniqueAngles.Contains(angleKey)) continue; |
|
uniqueAngles.Add(angleKey); |
|
} |
|
|
|
// 显示内角 |
|
var text = $"{angle:F2}°"; |
|
var textY = p2.Y > ViewportManager.CenterVector.Y ? p2.Y + 0.3f : p2.Y - 0.3f; |
|
var angleTextModel = DisplayText3D(text, new Vector3(p2.X, textY, p2.Z), textColor); |
|
result.Add(angleTextModel); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
#endregion |
|
|
|
/// <summary> |
|
/// 旋转主体 |
|
/// </summary> |
|
/// <param name="axis">中心</param> |
|
/// <param name="hasLine">是否包含线</param> |
|
/// <param name="speed">旋转时间 秒</param> |
|
public static void RotateModel(Vector3D axis, bool hasLine = true, int speed = 7, int to = 360) |
|
{ |
|
ViewportManager.ClearDicModels(); |
|
// 设置旋转的中心点和旋转轴 |
|
var rotateTransform = new RotateTransform3D(); |
|
var rotation = new AxisAngleRotation3D(axis, 0); |
|
rotateTransform.Rotation = rotation; |
|
rotateTransform.CenterX = ViewportManager.CenterVector.X; |
|
rotateTransform.CenterY = ViewportManager.CenterVector.Y; |
|
rotateTransform.CenterZ = ViewportManager.CenterVector.Z; |
|
|
|
// 将旋转变换应用到模型 |
|
ViewportManager.MainModel3D.Transform = rotateTransform; |
|
if (hasLine) |
|
ViewportManager.MainModelLines.ForEach(e => e.Transform = rotateTransform); |
|
|
|
double currentAngle = rotation.Angle; |
|
// 创建旋转动画 |
|
var rotateAnimation = new DoubleAnimation |
|
{ |
|
From = currentAngle, |
|
To = to + currentAngle, |
|
Duration = new Duration(TimeSpan.FromSeconds(speed)), |
|
FillBehavior = FillBehavior.HoldEnd |
|
}; |
|
|
|
// 应用动画 |
|
rotation.BeginAnimation(AxisAngleRotation3D.AngleProperty, rotateAnimation); |
|
} |
|
|
|
/// <summary> |
|
/// 在场景中添加文字 |
|
/// </summary> |
|
/// <param name="text">文字</param> |
|
/// <param name="position">位置</param> |
|
/// <returns></returns> |
|
public static BillboardTextModel3D DisplayText3D(string text, Vector3 position, Color4? color = null) |
|
{ |
|
var billboardTextModel = new BillboardTextModel3D(); |
|
var billboardText = new BillboardText3D(); |
|
billboardText.TextInfo.Add(new TextInfo(text, position) |
|
{ |
|
Foreground = color ?? ViewportManager.ColorConfig.SelFontColor, |
|
Scale = 0.8f, |
|
VerticalAlignment = BillboardVerticalAlignment.Center, |
|
HorizontalAlignment = BillboardHorizontalAlignment.Center |
|
|
|
}); |
|
billboardTextModel.Geometry = billboardText; |
|
return billboardTextModel; |
|
} |
|
/// <summary> |
|
/// 在场景中添加文字 |
|
/// </summary> |
|
/// <param name="text"></param> |
|
/// <param name="position"></param> |
|
/// <param name="direction"></param> |
|
/// <param name="color"></param> |
|
/// <returns></returns> |
|
public static BillboardTextModel3D DisplayText3D(string text, Vector3 position, Vector3 direction, Color4? color = null) |
|
{ |
|
// 计算线段方向的角度 |
|
double angle = Math.Atan2(direction.Y, direction.X) * (180 / Math.PI); |
|
|
|
var billboardTextModel = new BillboardTextModel3D() |
|
{ |
|
Geometry = new BillboardText3D() |
|
{ |
|
TextInfo = new ObservableCollection<TextInfo>() |
|
{ |
|
new TextInfo(text, position){ |
|
Foreground = color ?? ViewportManager.ColorConfig.SelFontColor, |
|
Scale = 0.5f, |
|
Offset = new Vector2(0.2f, 0.2f), |
|
// Angle = (float)angle // 设置文字的旋转角度 |
|
} |
|
} |
|
}, |
|
DepthBias = -99, |
|
SlopeScaledDepthBias = 1.0f, |
|
IsDepthClipEnabled = true |
|
}; |
|
|
|
|
|
return billboardTextModel; |
|
} |
|
/// <summary> |
|
/// 生成线对象 |
|
/// </summary> |
|
/// <param name="points">点集合(自动闭环)</param> |
|
/// <param name="lineColor">线颜色</param> |
|
/// <param name="thickness">线粗细 默认1</param> |
|
/// <returns></returns> |
|
public static LineGeometryModel3D DisplayLineModel3D(List<Vector3> points, Color4 lineColor, |
|
double thickness = 1.0) |
|
{ |
|
var edgeLines = new List<Tuple<Vector3, Vector3>>(); |
|
for (int i = 0; i < points.Count - 1; i++) |
|
{ |
|
var nowItem = points[i]; |
|
var nextItem = points[i + 1]; |
|
edgeLines.Add(new Tuple<Vector3, Vector3>(nowItem, nextItem)); |
|
|
|
} |
|
edgeLines.Add(new Tuple<Vector3, Vector3>(points.Last(), points.First())); |
|
return DisplayLineModel3D(edgeLines, lineColor, thickness); |
|
} |
|
/// <summary> |
|
/// 在正方向生成相机 |
|
/// </summary> |
|
/// <param name="positiveDirection"></param> |
|
/// <param name="boundingBox"></param> |
|
/// <param name="fieldOfView"></param> |
|
/// <param name="aspectRatio"></param> |
|
/// <returns></returns> |
|
public static HelixToolkit.Wpf.SharpDX.OrthographicCamera CalculateCamera(Vector3 positiveDirection, BoundingBox boundingBox, double fieldOfView = 45, double aspectRatio = 16.0 / 9.0) |
|
{ |
|
// 单位化正方向 |
|
var normalizedDirection = Vector3.Normalize(positiveDirection); |
|
|
|
// 计算模型中心和最大尺寸 |
|
var center = boundingBox.Center; // 包围盒中心 |
|
var maxDimension = boundingBox.Size.Length(); // 包围盒对角线长度 |
|
|
|
// 计算视场角的一半(弧度制) |
|
var halfFov = Math.PI * fieldOfView / 360.0; |
|
|
|
// 计算相机距离 |
|
var distance = maxDimension / (2 * Math.Tan(halfFov)); |
|
|
|
// 相机位置和朝向 |
|
var cameraPosition = center - normalizedDirection * (float)distance; // 沿正方向放置相机 |
|
var lookDirection = center - cameraPosition; // 朝向模型中心 |
|
|
|
double width = ViewportManager.calCameraWidth(); |
|
|
|
// 创建并返回相机 |
|
return new HelixToolkit.Wpf.SharpDX.OrthographicCamera |
|
{ |
|
Position = cameraPosition.ToPoint3D(), |
|
LookDirection = lookDirection.ToVector3D(), |
|
UpDirection = new Vector3D(0, 1, 0), // 默认全局 Y 轴为上方向 |
|
NearPlaneDistance = 0.1f, |
|
FarPlaneDistance=1000, |
|
Width = width |
|
}; |
|
} |
|
|
|
/// <summary> |
|
/// 绘制箭头(相机指向模型中心) |
|
/// </summary> |
|
/// <param name="cameraPosition">相机位置</param> |
|
/// <param name="modelCenter">中心位置</param> |
|
/// <param name="modelBounds">模型</param> |
|
/// <param name="totalLength">箭头总长度 D:5</param> |
|
/// <param name="cylinderRatio">圆柱部分占比 0.7</param> |
|
/// <param name="diameter">箭头直径 1</param> |
|
/// <param name="headDiameterRatio">圆锥直径与圆柱直径的比例 1.5</param> |
|
/// <param name="padding">模型边界范围 1</param> |
|
/// <returns></returns> |
|
public static MeshGeometryModel3D CreateArrow( |
|
Vector3 cameraPosition, |
|
Vector3 modelCenter, |
|
BoundingBox modelBounds, |
|
float totalLength = 1.5f, // 箭头总长度 |
|
float cylinderRatio = 0.7f,// 圆柱部分占比 |
|
float diameter = 0.25f, // 箭头直径 |
|
float headDiameterRatio = 2f, // 圆锥直径与圆柱直径的比例 |
|
float padding = 1.0f |
|
) |
|
{ |
|
// 计算模型的半径(对角线的一半) |
|
var modelSize = modelBounds.Size; |
|
var modelRadius = modelSize.Length() / 2; |
|
|
|
// 计算方向向量(从相机位置指向模型中心) |
|
var direction = Vector3.Normalize(modelCenter - cameraPosition); |
|
|
|
// 调整箭头起点,避免与模型交集 |
|
var adjustedStart = modelCenter - direction * (modelRadius + padding); |
|
|
|
// 计算圆柱和圆锥部分长度 |
|
float cylinderLength = totalLength * cylinderRatio; |
|
float coneLength = totalLength - cylinderLength; |
|
|
|
// 箭头圆柱终点 |
|
var cylinderEnd = adjustedStart + direction * cylinderLength; |
|
|
|
// 箭头终点(圆锥部分终点) |
|
var arrowEnd = cylinderEnd + direction * coneLength; |
|
|
|
// 创建箭头几何体 |
|
var builder = new MeshBuilder(); |
|
|
|
// 添加圆柱部分 |
|
builder.AddCylinder(adjustedStart, cylinderEnd, diameter, 20); |
|
|
|
// 添加圆锥部分 |
|
builder.AddCone(cylinderEnd, arrowEnd, diameter * headDiameterRatio, true, 20); |
|
|
|
// 设置箭头自发光材质 |
|
var material = new PhongMaterial |
|
{ |
|
DiffuseColor = Color.Red, // 箭头的基本颜色 |
|
EmissiveColor = Color.Red // 箭头自发光颜色 |
|
}; |
|
|
|
|
|
|
|
// 创建 3D 模型 |
|
return new MeshGeometryModel3D |
|
{ |
|
Geometry = builder.ToMeshGeometry3D(), |
|
Material = material |
|
}; |
|
} |
|
|
|
|
|
/// <summary> |
|
/// 生成线对象 |
|
/// </summary> |
|
/// <param name="points">线段集合</param> |
|
/// <param name="lineColor">线段颜色</param> |
|
/// <param name="thickness">线段粗细</param> |
|
/// <returns></returns> |
|
public static LineGeometryModel3D DisplayLineModel3D(List<Tuple<Vector3, Vector3>> points, Color4 lineColor, |
|
double thickness = 1.0) |
|
{ |
|
var lineBuilder = new LineBuilder(); |
|
foreach (var line in points) |
|
{ |
|
lineBuilder.AddLine(line.Item1, line.Item2); |
|
} |
|
|
|
|
|
var edgeLinesModel = new LineGeometryModel3D |
|
{ |
|
Geometry = lineBuilder.ToLineGeometry3D(), |
|
Color = lineColor.ToColor(), |
|
Thickness = thickness, |
|
}; |
|
return edgeLinesModel; |
|
} |
|
|
|
/// <summary> |
|
/// 根据视图中模型生成光照 |
|
/// </summary> |
|
/// <param name="viewport"></param> |
|
/// <returns></returns> |
|
public static List<Light3D> GenerateLightingForModel(Viewport3DX viewport) |
|
{ |
|
|
|
List<Light3D> result = new List<Light3D>(); |
|
|
|
var models = viewport.Items.OfType<MeshGeometryModel3D>(); |
|
if (!models.Any()) throw new Exception("Model in view not found"); |
|
var largestModel = models |
|
.OrderByDescending(m => GetBoundingBoxVolume(m.Geometry.Bound)) |
|
.FirstOrDefault(); |
|
if (largestModel == null) throw new Exception("Model in view not found"); |
|
var boundingBox = largestModel.Geometry.Bound; |
|
var size = boundingBox.Size; |
|
var center = boundingBox.Center; |
|
ViewportManager.CenterVector = center; |
|
ViewportManager.ModelBounds = boundingBox; |
|
var yao = CalculateCenter(ViewportManager.GirdleTopLines, ViewportManager.GirdleBottomLines); |
|
var corners = new List<Point3D> |
|
{ |
|
new Point3D(boundingBox.Minimum.X-0.5, boundingBox.Minimum.Y-boundingBox.Maximum.Y/2, boundingBox.Minimum.Z-0.5), // 右下后 |
|
new Point3D(boundingBox.Maximum.X+0.5, boundingBox.Minimum.Y-boundingBox.Maximum.Y/2, boundingBox.Minimum.Z-0.5), // 右下后 |
|
new Point3D(boundingBox.Minimum.X-0.5, boundingBox.Minimum.Y-boundingBox.Maximum.Y/2, boundingBox.Maximum.Z+0.5), // 左下前 |
|
new Point3D(boundingBox.Maximum.X+0.5, boundingBox.Minimum.Y-boundingBox.Maximum.Y/2, boundingBox.Maximum.Z+0.5), // 右下前 |
|
|
|
// new Point3D(boundingBox.Minimum.X-1, yao.Y, boundingBox.Minimum.Z-1), // 右下后 |
|
// new Point3D(boundingBox.Maximum.X+1, yao.Y, boundingBox.Minimum.Z-1), // 右下后 |
|
// new Point3D(boundingBox.Minimum.X-1, yao.Y, boundingBox.Maximum.Z+1), // 左下前 |
|
// new Point3D(boundingBox.Maximum.X+1, yao.Y, boundingBox.Maximum.Z+1), // 右下前 |
|
|
|
new Point3D(boundingBox.Minimum.X-0.5, boundingBox.Maximum.Y+boundingBox.Maximum.Y/4, boundingBox.Minimum.Z-0.5), // 左上后 |
|
new Point3D(boundingBox.Maximum.X+0.5, boundingBox.Maximum.Y+boundingBox.Maximum.Y/4,boundingBox.Maximum.Z+0.5), // 右上前 |
|
new Point3D(boundingBox.Maximum.X+0.5, boundingBox.Maximum.Y+boundingBox.Maximum.Y/4, boundingBox.Minimum.Z-0.5), // 右上后 |
|
new Point3D(boundingBox.Minimum.X-0.5, boundingBox.Maximum.Y+boundingBox.Maximum.Y/4, boundingBox.Maximum.Z+0.5), // 左上前 |
|
}; |
|
for (int i = 0; i < corners.Count; i++) |
|
{ |
|
var corner = corners[i]; |
|
var color = i % 2 == 0 ? Colors.LightGoldenrodYellow : Colors.LightSkyBlue; |
|
var pointLight = new PointLight3D |
|
{ |
|
Position = corner, |
|
Color = color, |
|
Range = (float)size.Length() |
|
}; |
|
result.Add(pointLight); |
|
} |
|
// var topLightPositions = new List<Point3D> |
|
// { |
|
// new Point3D(center.X, center.Y, center.Z), |
|
// }; |
|
// foreach (var position in topLightPositions) |
|
// { |
|
// var topLight = new PointLight3D |
|
// { |
|
// Position = position, |
|
// Color = Colors.LightGoldenrodYellow, |
|
// Range = (float)size.Length() * 0.5, // 增加光的照射范围 |
|
// Attenuation = new Vector3D(1, 0.1f, 0.05f) // 控制光的衰减,使光在距离内更有效 |
|
// }; |
|
// result.Add(topLight); |
|
// } |
|
// 添加环境光以柔化整体效果并增加亮度 |
|
result.Add(new AmbientLight3D |
|
{ |
|
Color = Colors.LightGray |
|
}); |
|
|
|
result.Add(new AmbientLight3D |
|
{ |
|
Color = Colors.Gray // 设置环境光颜色 |
|
}); |
|
|
|
RemoveLightingInViewport(viewport); |
|
result.ForEach(e => viewport.Items.Add(e)); |
|
return result; |
|
} |
|
|
|
public static Vector3 GetCenterOfTriangles(List<Viewport3DTriangleEntity> triangles) |
|
{ |
|
if (triangles == null || triangles.Count == 0) |
|
throw new ArgumentException("The list of triangles cannot be null or empty."); |
|
|
|
// 累加所有顶点的坐标 |
|
Vector3 total = Vector3.Zero; |
|
int vertexCount = 0; |
|
|
|
foreach (var triangle in triangles) |
|
{ |
|
total += triangle.Point1; |
|
total += triangle.Point2; |
|
total += triangle.Point3; |
|
vertexCount += 3; // 每个三角形有3个顶点 |
|
} |
|
|
|
// 计算平均坐标 |
|
return total / vertexCount; |
|
} |
|
|
|
public static Vector3 CalculateCenter(List<Vector3> girdleTopLines, List<Vector3> girdleBottomLines) |
|
{ |
|
// 计算 GirdleTopLines 的中心点 |
|
Vector3 topCenter = GetCenter(girdleTopLines); |
|
|
|
// 计算 GirdleBottomLines 的中心点 |
|
Vector3 bottomCenter = GetCenter(girdleBottomLines); |
|
|
|
// 计算两个中心点的中点作为垂直方向的中心点 |
|
Vector3 centerInVertical = new Vector3( |
|
(topCenter.X + bottomCenter.X) / 2, |
|
(topCenter.Y + bottomCenter.Y) / 2, |
|
(topCenter.Z + bottomCenter.Z) / 2 |
|
); |
|
|
|
return centerInVertical; |
|
} |
|
/// <summary> |
|
/// 删除视图中的照明 |
|
/// </summary> |
|
/// <param name="viewport"></param> |
|
public static void RemoveLightingInViewport(Viewport3DX viewport) |
|
{ |
|
List<Light3D> lights = new List<Light3D>(); |
|
foreach (var item in viewport.Items) |
|
{ |
|
if (item is Light3D light3D) |
|
{ |
|
lights.Add(light3D); |
|
} |
|
} |
|
lights.ForEach(item => viewport.Items.Remove(item)); |
|
} |
|
private static Dictionary<Guid, Guid> moveLines = new Dictionary<Guid, Guid>(); |
|
/// <summary> |
|
/// 网状参考线 |
|
/// </summary> |
|
/// <param name="entities"></param> |
|
/// <param name="thickness"></param> |
|
/// <returns></returns> |
|
public static List<LineGeometryModel3D> ShowMeshLines(List<Viewport3DTriangleEntity> entities, bool isCrown = true, double thickness = 1.0) |
|
{ |
|
if (isCrown) |
|
{ |
|
moveLines = new(); |
|
} |
|
List<LineGeometryModel3D> lines = new(); |
|
var Y = -0.01F; |
|
if (!isCrown) |
|
{ |
|
Y = ViewportManager.ModelBounds.Maximum.Y + 0.01f; |
|
} |
|
var center = ViewportManager.CenterVector; |
|
center.Y = Y; |
|
Viewport3DTriangleEntity firstPoint = entities.Where(x => x.PlaneType == PlaneType.TableFacet).FirstOrDefault(); |
|
|
|
//var b = ((center.Z * firstPoint.Point1.X) - (firstPoint.Point1.Z * center.X)) / (firstPoint.Point1.X - center.X); |
|
//var a = (firstPoint.Point1.Z - b) / firstPoint.Point1.X; |
|
var lineCal = new LineCalculationHelper(firstPoint.Point1, center); |
|
|
|
var edgeLines = new List<Tuple<Vector3, Vector3>>(); |
|
Color4 XlineColor =new Color4(80f, 0f, 0f, 1f); |
|
float r =5; |
|
if(double.TryParse(ViewportManager.DiamondData["M2"].ToString(), out var v)) |
|
{ |
|
r = (float)(v*0.51); |
|
} |
|
//var x1 = r; |
|
//var z1 = lineCal.calZ(x1); |
|
//var x2 = -r; |
|
//var z2 = lineCal.calZ(x2); |
|
//edgeLines.Add(new Tuple<Vector3, Vector3>(new Vector3(x1, Y, z1), new Vector3(x2, Y, z2))); |
|
//lines.Add(DisplayLineModel3D(edgeLines, XlineColor)); |
|
|
|
edgeLines = new List<Tuple<Vector3, Vector3>>(); |
|
var XLine = lineCal.calXline(r); |
|
edgeLines.Add(XLine); |
|
lines.Add(DisplayLineModel3D(edgeLines, XlineColor)); |
|
|
|
edgeLines = new List<Tuple<Vector3, Vector3>>(); |
|
edgeLines.Add(lineCal.calLineOfOffset(XLine, 2)); |
|
var lineA = DisplayLineModel3D(edgeLines, XlineColor); |
|
lines.Add(lineA); |
|
|
|
edgeLines = new List<Tuple<Vector3, Vector3>>(); |
|
edgeLines.Add(lineCal.calLineOfOffset(XLine, -2)); |
|
var lineB = DisplayLineModel3D(edgeLines, XlineColor); |
|
lines.Add(lineB); |
|
bindingMoveLine(lineA, lineB); |
|
|
|
edgeLines = new List<Tuple<Vector3, Vector3>>(); |
|
edgeLines.Add(lineCal.calLineOfOffset(XLine, 4)); |
|
var lineE = DisplayLineModel3D(edgeLines, XlineColor); |
|
lines.Add(lineE); |
|
|
|
edgeLines = new List<Tuple<Vector3, Vector3>>(); |
|
edgeLines.Add(lineCal.calLineOfOffset(XLine, -4)); |
|
var lineF = DisplayLineModel3D(edgeLines, XlineColor); |
|
lines.Add(lineF); |
|
bindingMoveLine(lineE, lineF); |
|
|
|
Color4 YlineColor = new Color4(0F, 80f, 0f, 1f); |
|
//var x3 = r; |
|
//var z3 = lineCal.calZVertical(x3); |
|
//var x4 = -r; |
|
//var z4 = lineCal.calZVertical(x4); |
|
|
|
//edgeLines = new List<Tuple<Vector3, Vector3>>(); |
|
//edgeLines.Add(new Tuple<Vector3, Vector3>(new Vector3(x3, Y, z3), new Vector3(x4, Y, z4))); |
|
//lines.Add(DisplayLineModel3D(edgeLines, YlineColor)); |
|
|
|
edgeLines = new List<Tuple<Vector3, Vector3>>(); |
|
var YLine = lineCal.calYline(r); |
|
edgeLines.Add(YLine); |
|
lines.Add(DisplayLineModel3D(edgeLines, YlineColor)); |
|
|
|
edgeLines = new List<Tuple<Vector3, Vector3>>(); |
|
edgeLines.Add(lineCal.calLineOfOffset(YLine, 2)); |
|
var lineC = DisplayLineModel3D(edgeLines, YlineColor); |
|
lines.Add(lineC); |
|
|
|
edgeLines = new List<Tuple<Vector3, Vector3>>(); |
|
edgeLines.Add(lineCal.calLineOfOffset(YLine, -2)); |
|
var lineD = DisplayLineModel3D(edgeLines, YlineColor); |
|
lines.Add(lineD); |
|
bindingMoveLine(lineC, lineD); |
|
|
|
edgeLines = new List<Tuple<Vector3, Vector3>>(); |
|
edgeLines.Add(lineCal.calLineOfOffset(YLine, 4)); |
|
var lineG = DisplayLineModel3D(edgeLines, YlineColor); |
|
lines.Add(lineG); |
|
|
|
edgeLines = new List<Tuple<Vector3, Vector3>>(); |
|
edgeLines.Add(lineCal.calLineOfOffset(YLine, -4)); |
|
var lineH = DisplayLineModel3D(edgeLines, YlineColor); |
|
lines.Add(lineH); |
|
bindingMoveLine(lineG, lineH); |
|
return lines; |
|
} |
|
/// <summary> |
|
/// 网状参考线 对向移动绑定 |
|
/// </summary> |
|
/// <param name="lineA"></param> |
|
/// <param name="lineB"></param> |
|
private static void bindingMoveLine(LineGeometryModel3D lineA, LineGeometryModel3D lineB) |
|
{ |
|
lineA.MouseDown3D += LineA_MouseDown3D; |
|
lineA.MouseUp3D += LineA_MouseUp3D; |
|
lineA.MouseMove3D += LineA_MouseMove3D; |
|
moveLines.Add(lineA.GUID, lineB.GUID); |
|
lineB.MouseDown3D += LineA_MouseDown3D; |
|
lineB.MouseUp3D += LineA_MouseUp3D; |
|
lineB.MouseMove3D += LineA_MouseMove3D; |
|
moveLines.Add(lineB.GUID, lineA.GUID); |
|
} |
|
private static bool isDrawing = false; |
|
private static Point3D previousMousePosition; |
|
private static bool isOutOfRange = false; |
|
|
|
private static void LineA_MouseMove3D(object sender, RoutedEventArgs e) |
|
{ |
|
if (isDrawing) |
|
{ |
|
if (e is HelixToolkit.Wpf.SharpDX.MouseMove3DEventArgs ev) |
|
{ |
|
// 定义中心点和半径 |
|
var Y = -0.01F; |
|
if (!IsCrown()) |
|
{ |
|
Y = ViewportManager.ModelBounds.Maximum.Y + 0.01f; |
|
} |
|
var center = ViewportManager.CenterVector; |
|
center.Y = Y; |
|
double radius = 5; |
|
if (double.TryParse(ViewportManager.DiamondData["M2"].ToString(), out var v)) |
|
{ |
|
radius = (v * 0.5); |
|
} |
|
double minDistance = 0.01; // 最小距离 |
|
var line = sender as LineGeometryModel3D; |
|
// 直线方向分量 |
|
var lineDirection = line.Geometry.Positions[1] - line.Geometry.Positions[0]; |
|
lineDirection.Normalize(); |
|
// 现在鼠标对于3d模型的位置 |
|
var mousePosition = ev.Position; |
|
var currentMousePosition = Get3DPointFromMouse(mousePosition); |
|
|
|
// 计算鼠标移动的方向 |
|
var moveDirection = previousMousePosition - currentMousePosition; |
|
|
|
// 计算垂直于线的移动分量 |
|
var perpendicularDirection = Vector3D.CrossProduct(lineDirection.ToVector3D(), Vector3D.CrossProduct(moveDirection, lineDirection.ToVector3D())); |
|
perpendicularDirection.Normalize(); |
|
|
|
double scaleFactor = 13000; // 缩放因子 |
|
// 计算垂直距离 |
|
double perpendicularDistance = Vector3D.DotProduct(moveDirection, perpendicularDirection) * scaleFactor; |
|
// 垂直方向偏移量 |
|
Vector3D projectedVector = perpendicularDistance * perpendicularDirection; |
|
projectedVector.Y = 0; |
|
|
|
// 计算直线的新位置 |
|
var transform = new TranslateTransform3D(projectedVector); |
|
var newPosition1 = line.Geometry.Positions[0].ToVector3D() + projectedVector; |
|
var newPosition2 = line.Geometry.Positions[1].ToVector3D() + projectedVector; |
|
|
|
// 计算直线的新中心点 |
|
Point3D newCenter = new Point3D( |
|
(newPosition1.X + newPosition2.X) / 2, |
|
(newPosition1.Y + newPosition2.Y) / 2, |
|
(newPosition1.Z + newPosition2.Z) / 2 |
|
); |
|
|
|
// 计算当前中心点与中心点的距离 |
|
double currentDistance = (newCenter - center.ToPoint3D()).Length; |
|
|
|
// 计算上一帧中心点与中心点的距离 |
|
Point3D previousCenter = new Point3D( |
|
(line.Geometry.Positions[0].X + line.Geometry.Positions[1].X) / 2, |
|
(line.Geometry.Positions[0].Y + line.Geometry.Positions[1].Y) / 2, |
|
(line.Geometry.Positions[0].Z + line.Geometry.Positions[1].Z) / 2 |
|
); |
|
|
|
double previousDistance = (previousCenter - center.ToPoint3D()).Length; |
|
// 检查移动方向 |
|
bool isMovingAway = currentDistance > previousDistance; // 是否远离中心点 |
|
if (isMovingAway) |
|
{ |
|
isOutOfRange = false; |
|
}else if (isOutOfRange) |
|
{ |
|
Logger.Info("超出范围,停止移动"); |
|
} |
|
// 检查新中心点是否满足距离条件 |
|
if (IsPointInRange(newCenter, center.ToPoint3D(), radius, minDistance)) |
|
{ |
|
|
|
} |
|
else if(isMovingAway) |
|
{ |
|
//// 如果新中心点不满足条件,将位置限制在范围内 |
|
//projectedVector = LimitToRange(newCenter, center.ToPoint3D(), radius, minDistance) - newCenter; |
|
// 如果新中心点超出范围,将直线位置固定到最大允许位置 |
|
Point3D maxCenter = LimitToMaxPosition(center.ToPoint3D(), radius, newCenter); |
|
// 计算直线的起点和终点在最大允许位置 |
|
projectedVector = maxCenter - previousCenter; |
|
projectedVector.Y = 0; |
|
transform = new TranslateTransform3D(projectedVector); |
|
//Logger.Info($"X:{projectedVector.X},Z:{projectedVector.Z}"); |
|
Logger.Info("超出范围,停止移动"); |
|
}else if(!isMovingAway) |
|
{ |
|
isOutOfRange = true; |
|
Logger.Info("isOutOfRange = true;"); |
|
} |
|
line.Transform = transform; |
|
// 更新上一帧的鼠标位置 |
|
//previousMousePosition = currentMousePosition; |
|
var otherLineGuid = moveLines[line.GUID]; |
|
var Viewport = ViewportManager.GetViewport3D(); |
|
var otherLine = Viewport.Items.Where(x => x.GUID == otherLineGuid).FirstOrDefault(); |
|
if (otherLine != null) |
|
{ |
|
var transform_other = new TranslateTransform3D(-projectedVector); |
|
otherLine.Transform = transform_other; |
|
} |
|
} |
|
} |
|
} |
|
// 检查点是否在范围内(距离大于 minDistance 且小于等于 radius) |
|
private static bool IsPointInRange(Point3D point, Point3D center, double radius, double minDistance) |
|
{ |
|
double distance = (point - center).Length; |
|
Logger.Info("距离:" + distance); |
|
return distance > minDistance && distance <= radius; |
|
} |
|
|
|
private static Point3D LimitToMaxPosition(Point3D center, double radius, Point3D currentCenter) |
|
{ |
|
// 计算当前中心点与中心点的方向 |
|
Vector3D direction = currentCenter - center; |
|
direction.Normalize(); |
|
|
|
// 计算最大允许位置 |
|
return center + direction * radius; |
|
} |
|
|
|
// 将点限制在范围内(距离大于 minDistance 且小于等于 radius) |
|
private static Point3D LimitToRange(Point3D point, Point3D center, double radius, double minDistance) |
|
{ |
|
Vector3D direction = point - center; |
|
double distance = direction.Length; |
|
|
|
if (distance <= minDistance) |
|
{ |
|
// 如果距离小于等于最小距离,将点偏移到最小距离处 |
|
direction.Normalize(); |
|
return center + direction * minDistance; |
|
} |
|
else if (distance > radius) |
|
{ |
|
// 如果距离大于半径,将点限制在半径范围内 |
|
direction.Normalize(); |
|
return center + direction * radius; |
|
} |
|
|
|
// 如果距离在范围内,直接返回点 |
|
return point; |
|
} |
|
|
|
private static void LineA_MouseUp3D(object sender, RoutedEventArgs e) |
|
{ |
|
isDrawing = false; |
|
var Viewport = ViewportManager.GetViewport3D(); |
|
Viewport.Cursor = Cursors.Arrow; |
|
} |
|
|
|
private static void LineA_MouseDown3D(object sender, RoutedEventArgs e) |
|
{ |
|
isDrawing = true; |
|
if(e is HelixToolkit.Wpf.SharpDX.MouseDown3DEventArgs ev){ |
|
var Viewport = ViewportManager.GetViewport3D(); |
|
Viewport.Cursor = Cursors.SizeAll; |
|
// 获取鼠标点击的位置 |
|
var mousePosition = ev.Position; |
|
previousMousePosition = Get3DPointFromMouse(mousePosition); |
|
} |
|
} |
|
private static Point3D Get3DPointFromMouse(Point mousePosition) |
|
{ |
|
// 使用 HelixToolkit 的 Ray3D 和 Plane3D |
|
var Viewport = ViewportManager.GetViewport3D(); |
|
|
|
// 获取射线 |
|
var ray = GetRayFromMouse(mousePosition, Viewport); |
|
|
|
// 创建平面(假设平面为 Y=0) |
|
var plane = new Plane3D(new Point3D(0, 0, 0), new Vector3D(0, 1, 0)); |
|
|
|
// 计算射线与平面的交点 |
|
var intersection = GetIntersection(ray, plane); |
|
|
|
if (intersection == null) |
|
{ |
|
// 如果没有交点,返回默认值或抛出异常 |
|
throw new InvalidOperationException("No intersection found."); |
|
} |
|
return intersection.Value; |
|
} |
|
|
|
private static Ray3D GetRayFromMouse(Point mousePosition, Viewport3DX viewport) |
|
{ |
|
// 将鼠标坐标转换为归一化设备坐标 (NDC) |
|
var ndc = new Vector2( |
|
(float)(2.0 * mousePosition.X / viewport.ActualWidth - 1.0), |
|
(float)(1.0 - 2.0 * mousePosition.Y / viewport.ActualHeight) |
|
); |
|
|
|
// 使用 UnProject 方法获取射线 |
|
var ray = viewport.UnProject(ndc); |
|
|
|
// 返回射线 |
|
return new Ray3D(ray.Position.ToPoint3D(), ray.Direction.ToPoint3D()); |
|
} |
|
|
|
//private static Point3D? GetIntersection(Ray3D ray, Plane3D plane) |
|
//{ |
|
// // 计算射线方向与平面法向量的点积 |
|
// double denominator = Vector3D.DotProduct(ray.Direction, plane.Normal); |
|
|
|
// // 如果点积为 0,说明射线与平面平行,没有交点 |
|
// if (Math.Abs(denominator) < 1e-6) |
|
// return null; |
|
|
|
// // 计算射线起点到平面的距离 |
|
// double t = Vector3D.DotProduct(plane.Position - ray.Origin, plane.Normal) / denominator; |
|
|
|
// // 如果 t 为负数,说明交点在射线起点后方 |
|
// if (t < 0) |
|
// return null; |
|
|
|
// // 返回交点 |
|
// return ray.Origin + ray.Direction * t; |
|
//} |
|
|
|
private static Point3D? GetIntersection(Ray3D ray, Plane3D plane) |
|
{ |
|
// 将射线转换为直线(起点和终点) |
|
Point3D rayEnd = ray.Origin + ray.Direction * 1000; // 延长射线 |
|
|
|
// 使用 Plane3D.LineIntersection 方法计算交点 |
|
return plane.LineIntersection(ray.Origin, rayEnd); |
|
} |
|
private static double initialRadius = 1.0; // 初始半径 |
|
private static double endRadius = 1.0; // 拖拽后半径 |
|
private static double initDistance = 1.0; // 初始与圆心之间距离 |
|
private static double endDistance = 1.0; // 拖拽后与圆心之间距离 |
|
private static Point dragStartPoint; |
|
/// <summary> |
|
/// 圆圈参考线 |
|
/// </summary> |
|
/// <param name="radius"></param> |
|
/// <param name="thickness"></param> |
|
/// <returns></returns> |
|
public static LineGeometryModel3D ShowCircleLine(bool isCrown = true, double radius = 1.0, double thickness = 1.0) |
|
{ |
|
var Y = -0.01F; |
|
if (!isCrown) |
|
{ |
|
Y = ViewportManager.ModelBounds.Maximum.Y + 0.01f; |
|
} |
|
var center = ViewportManager.CenterVector; |
|
center.Y = Y; |
|
return UpdateCircleGeometry(center, initialRadius); |
|
} |
|
private static LineGeometryModel3D UpdateCircleGeometry(Vector3 center, double radius = 1.0) |
|
{ |
|
// 生成圆形线的点 |
|
int segments = 100; // 圆的细分段数 |
|
var positions = new List<Vector3>(); |
|
var indices = new List<int>(); |
|
|
|
for (int i = 0; i < segments; i++) |
|
{ |
|
float angle = (float)(2 * Math.PI * i / segments); |
|
float x = (float)(center.X + radius * Math.Cos(angle)); |
|
float y = (float)(center.Y); |
|
float z = (float)(center.Z + radius * Math.Sin(angle)); |
|
|
|
positions.Add(new Vector3(x, y, z)); |
|
indices.Add(i); |
|
indices.Add((i + 1) % segments); |
|
} |
|
var Circle = DisplayLineModel3D(positions, Color4.Black); |
|
Circle.MouseDown3D += Circle_MouseDown3D; |
|
Circle.MouseUp3D += Circle_MouseUp3D; |
|
Circle.MouseMove3D += Circle_MouseMove3D; |
|
// 更新圆形线的几何形状 |
|
return Circle; |
|
} |
|
|
|
private static void Circle_MouseMove3D(object sender, RoutedEventArgs e) |
|
{ |
|
if (isDrawing) |
|
{ |
|
if (e is HelixToolkit.Wpf.SharpDX.MouseMove3DEventArgs ev) |
|
{ |
|
var line = sender as LineGeometryModel3D; |
|
|
|
if (line==null) |
|
{ |
|
return; |
|
} |
|
var MinRadius = 0.1; |
|
var MaxRadius = (ViewportManager.ModelBounds.Maximum.X - ViewportManager.ModelBounds.Minimum.X) * 0.6; |
|
// 现在鼠标对于3d模型的位置 |
|
var mousePosition = ev.Position; |
|
var Y = -0.01F; |
|
if (!IsCrown()) |
|
{ |
|
Y = ViewportManager.ModelBounds.Maximum.Y + 0.01F; |
|
} |
|
var center = ViewportManager.CenterVector; |
|
center.Y = Y; |
|
|
|
Point3D mouseWorldPosition = Get3DPointFromMouse(mousePosition); |
|
|
|
// 计算鼠标与圆心的距离 |
|
double distance = CalculateDistance(mouseWorldPosition, center.ToPoint3D()); |
|
Console.WriteLine($"与圆心之间距离{distance}"); |
|
double scaleFactor = 0; |
|
|
|
if (distance < initDistance) |
|
{ |
|
// 鼠标靠近圆心,缩小半径 |
|
scaleFactor = -1; |
|
} |
|
else |
|
{ |
|
// 鼠标远离圆心,扩大半径 |
|
scaleFactor = +1; |
|
} |
|
|
|
// 计算鼠标移动的距离(以屏幕坐标为单位) |
|
double delta = (dragStartPoint - mousePosition).Length; |
|
|
|
// 计算新的半径 |
|
double newRadius = initialRadius + (distance - initDistance)*11000; // 缩放因子 |
|
|
|
// 限制半径范围 |
|
newRadius = Math.Clamp(newRadius, MinRadius, MaxRadius); |
|
|
|
var scaleTransform = new ScaleTransform3D(newRadius, 1, newRadius); // 缩放变换 |
|
line.Transform = scaleTransform; |
|
endRadius = newRadius; |
|
endDistance = distance; |
|
} |
|
} |
|
} |
|
// 计算两点之间的距离 |
|
private static double CalculateDistance(Point3D p1, Point3D p2) |
|
{ |
|
double dx = p2.X - p1.X; |
|
double dy = p2.Y - p1.Y; |
|
double dz = p2.Z - p1.Z; |
|
|
|
return Math.Sqrt(dx * dx + dy * dy + dz * dz); |
|
} |
|
private static void Circle_MouseUp3D(object sender, RoutedEventArgs e) |
|
{ |
|
isDrawing = false; |
|
var Viewport = ViewportManager.GetViewport3D(); |
|
Viewport.Cursor = Cursors.Arrow; |
|
initialRadius = endRadius; |
|
initDistance = endDistance; |
|
// 释放鼠标捕获 |
|
Viewport.ReleaseMouseCapture(); |
|
} |
|
|
|
private static void Circle_MouseDown3D(object sender, RoutedEventArgs e) |
|
{ |
|
isDrawing = true; |
|
if (e is HelixToolkit.Wpf.SharpDX.MouseDown3DEventArgs ev) |
|
{ |
|
var line = sender as LineGeometryModel3D; |
|
|
|
// 获取鼠标点击的位置 |
|
dragStartPoint = ev.Position; |
|
Point3D mouseWorldPosition = Get3DPointFromMouse(dragStartPoint); |
|
var Y = -0.01F; |
|
if(!IsCrown()) |
|
{ |
|
Y = ViewportManager.ModelBounds.Maximum.Y + 0.01F; |
|
} |
|
var center = ViewportManager.CenterVector; |
|
center.Y = Y; |
|
initDistance = CalculateDistance(mouseWorldPosition, center.ToPoint3D()); |
|
var Viewport = ViewportManager.GetViewport3D(); |
|
Viewport.Cursor = Cursors.SizeAll; |
|
// 捕获鼠标 |
|
Viewport.CaptureMouse(); |
|
} |
|
} |
|
public static bool IsCrown() |
|
{ |
|
bool isCrown = false; |
|
var camera = ViewportManager.GetViewport3D().Camera; |
|
if (camera.LookDirection.Y >= 0 && camera.UpDirection.Y <= 0) |
|
{ |
|
isCrown = true; |
|
} |
|
return isCrown; |
|
} |
|
#region 私有方法 |
|
|
|
/// <summary> |
|
/// 向量按中心点顺时针排序 |
|
/// </summary> |
|
/// <param name="points"></param> |
|
/// <returns></returns> |
|
public static List<Vector3> VectorClockwiseSort(List<Vector3> points) |
|
{ |
|
Vector3 center = GetCentroid(points); |
|
return VectorClockwiseSort(points, center); |
|
} |
|
|
|
/// <summary> |
|
/// 向量按中心点顺时针排序 |
|
/// </summary> |
|
/// <param name="points"></param> |
|
/// <param name="center"></param> |
|
/// <returns></returns> |
|
public static List<Vector3> VectorClockwiseSort(List<Vector3> points, Vector3 center) |
|
{ |
|
points.Sort((v1, v2) => |
|
{ |
|
double angle1 = Math.Atan2(v1.Z - center.Z, v1.X - center.X); |
|
double angle2 = Math.Atan2(v2.Z - center.Z, v2.X - center.X); |
|
return angle1.CompareTo(angle2); |
|
}); |
|
return points; |
|
} |
|
|
|
private static Vector3 GetCenter(List<Vector3> points) |
|
{ |
|
float x = points.Average(p => p.X); |
|
float y = points.Average(p => p.Y); |
|
float z = points.Average(p => p.Z); |
|
return new Vector3(x, y, z); |
|
} |
|
|
|
/// <summary> |
|
/// 计算点集合的中心点(几何质心) |
|
/// </summary> |
|
/// <param name="vectors"></param> |
|
/// <returns></returns> |
|
public static Vector3 GetCentroid(List<Vector3> vectors) |
|
{ |
|
float x = vectors.Sum(v => v.X) / vectors.Count; |
|
float y = vectors.Sum(v => v.Y) / vectors.Count; |
|
float z = vectors.Sum(v => v.Z) / vectors.Count; |
|
return new Vector3(x, y, z); |
|
} |
|
public static Vector3 GetOffsetCenter(List<Vector3> facetVector, Vector3 center, float dev = 0.4f) |
|
{ |
|
// 1. 去重 facetVector |
|
var distinctFacetVector = facetVector.Distinct().ToList(); |
|
|
|
// 2. 计算多边形的几何中心 |
|
Vector3 polygonCenter = GetCentroid(distinctFacetVector); |
|
|
|
// 3. 计算从模型中心点到几何中心的向量 |
|
Vector3 direction = polygonCenter - center; |
|
|
|
// 4. 归一化方向向量并根据偏移量 dev 计算最终的偏移位置 |
|
Vector3 offsetCenter = polygonCenter + Vector3.Normalize(direction) * dev; |
|
|
|
return offsetCenter; |
|
} |
|
/// <summary> |
|
/// 计算夹角度数 |
|
/// </summary> |
|
/// <param name="v1"></param> |
|
/// <param name="v2"></param> |
|
/// <returns></returns> |
|
private static float AngleBetween(Vector3 v1, Vector3 v2) |
|
{ |
|
// 计算两个向量的点积 |
|
float dotProduct = Vector3.Dot(v1, v2); |
|
|
|
// 计算每个向量的模长 |
|
float magnitudeV1 = v1.Length(); |
|
float magnitudeV2 = v2.Length(); |
|
|
|
// 防止除以零错误 |
|
if (magnitudeV1 == 0 || magnitudeV2 == 0) |
|
{ |
|
return 0f; |
|
} |
|
|
|
// 计算夹角的余弦值 |
|
float cosTheta = dotProduct / (magnitudeV1 * magnitudeV2); |
|
|
|
// 限制cosTheta的值范围在 -1 到 1 之间,以防计算机浮点误差 |
|
cosTheta = Math.Max(-1f, Math.Min(1f, cosTheta)); |
|
|
|
// 返回角度,单位是度 |
|
return (float)(Math.Acos(cosTheta) * (180.0 / Math.PI)); |
|
} |
|
|
|
private static double GetBoundingBoxVolume(BoundingBox bound) |
|
{ |
|
var size = bound.Size; |
|
return size.X * size.Y * size.Z; |
|
} |
|
|
|
private static Vector3 CalculateNormal(Vector3 p0, Vector3 p1, Vector3 p2) |
|
{ |
|
var u = p1 - p0; |
|
var v = p2 - p0; |
|
return Vector3.Cross(u, v); |
|
} |
|
private static bool IsLineSegmentParallelToYAxis(Tuple<Vector3, Vector3> lineSegment,float epsilon = 1e-6f) |
|
{ |
|
Vector3 nowItem = lineSegment.Item1; |
|
Vector3 nextItem = lineSegment.Item2; |
|
|
|
if (Math.Abs(nowItem.X - nextItem.X) < epsilon && Math.Abs(nowItem.Z - nextItem.Z) < epsilon) |
|
{ |
|
return true; // 平行于 Y 轴 |
|
} |
|
return false; // 不平行于 Y 轴 |
|
} |
|
// 计算两个点之间的距离 |
|
private static float GetDistance(Vector3 point1, Vector3 point2) |
|
{ |
|
return (float)Math.Sqrt(Math.Pow(point2.X - point1.X, 2) + Math.Pow(point2.Y - point1.Y, 2) + Math.Pow(point2.Z - point1.Z, 2)); |
|
} |
|
|
|
// 判断是否平行于Y轴的线段,返回较长或者较短的线段 |
|
private static Tuple<Vector3, Vector3> GetLongestOrShortestLineSegment(List<Vector3> facetPoints, bool returnLongest = true) |
|
{ |
|
Tuple<Vector3, Vector3> resultSegment = null; |
|
float resultLength = returnLongest ? float.MinValue : float.MaxValue; // 初始化为最小或最大长度 |
|
|
|
// 遍历所有相邻的点,构成线段 |
|
for (int i = 0; i < facetPoints.Count; i++) |
|
{ |
|
Vector3 currentPoint = facetPoints[i]; |
|
Vector3 nextPoint = facetPoints[(i + 1) % facetPoints.Count]; // 用模运算实现环形结构,最后一个点与第一个点连接 |
|
|
|
// 计算线段是否平行于 Y 轴 |
|
if (IsLineSegmentParallelToYAxis(new Tuple<Vector3, Vector3>(currentPoint, nextPoint))) |
|
{ |
|
// 计算线段的长度 |
|
float segmentLength = GetDistance(currentPoint, nextPoint); |
|
|
|
// 根据需要选择较长或较短的线段 |
|
if ((returnLongest && segmentLength > resultLength) || (!returnLongest && segmentLength < resultLength)) |
|
{ |
|
resultSegment = new Tuple<Vector3, Vector3>(currentPoint, nextPoint); |
|
resultLength = segmentLength; |
|
} |
|
} |
|
} |
|
|
|
return resultSegment; // 返回符合条件的线段 |
|
} |
|
private static void CalculateLineSegmentStats(List<Tuple<Vector3, Vector3>> lines, |
|
out Tuple<Vector3, Vector3> maxLine, |
|
out Tuple<Vector3, Vector3> minLine, |
|
out Tuple<Vector3, Vector3> avgLine) |
|
{ |
|
// 计算所有线段的长度 |
|
var lineLengths = lines.Select(line => new |
|
{ |
|
Line = line, |
|
Length = CalculateLength(line) |
|
}).ToList(); |
|
|
|
// 找到最大、最小和平均长度对应的线段 |
|
maxLine = lineLengths.OrderByDescending(l => l.Length).First().Line; |
|
minLine = lineLengths.OrderBy(l => l.Length).First().Line; |
|
avgLine = lineLengths.OrderBy(l => Math.Abs(l.Length - lineLengths.Average(ll => ll.Length))) |
|
.First().Line; |
|
} |
|
private static double CalculateLength(Tuple<Vector3, Vector3> line) |
|
{ |
|
Vector3 startPoint = line.Item1; |
|
Vector3 endPoint = line.Item2; |
|
return Math.Round(Vector3.Distance(startPoint, endPoint), 2); |
|
} |
|
|
|
/// <summary> |
|
/// 获取四边形内平行于Y轴且位于左侧的边线 |
|
/// </summary> |
|
/// <param name="facetPoints">四边形的顶点列表</param> |
|
/// <param name="returnLongest">是否返回最长的边线</param> |
|
/// <returns>符合条件的边线,如果不存在则返回null</returns> |
|
public static Tuple<Vector3, Vector3> GetLeftParallelLineSegment( |
|
List<Vector3> facetPoints) |
|
{ |
|
if (facetPoints == null || facetPoints.Count < 4) |
|
throw new ArgumentException("facetPoints must contain at least four points."); |
|
|
|
// 计算多边形(四边形)的中心点 |
|
Vector3 center = GetCenter(facetPoints); |
|
|
|
float minX = float.MaxValue; |
|
Tuple<Vector3, Vector3> resultLine = null; |
|
|
|
// 遍历所有边线 |
|
for (int i = 0; i < facetPoints.Count; i++) |
|
{ |
|
Vector3 p1 = facetPoints[i]; |
|
Vector3 p2 = facetPoints[(i + 1) % facetPoints.Count]; // 环形连接 |
|
|
|
var lineSegment = new Tuple<Vector3, Vector3>(p1, p2); |
|
|
|
// 判断该边线是否平行于 Y 轴 |
|
if (IsLineSegmentParallelToYAxis(lineSegment)) |
|
{ |
|
return lineSegment; |
|
} |
|
} |
|
|
|
return resultLine; |
|
} |
|
|
|
|
|
|
|
private static string ValueFormat(string value, string type, bool hasUnit = false) |
|
{ |
|
if (double.TryParse(value, out var v) ) |
|
{ |
|
switch (type) |
|
{ |
|
case "GIRDLE_BEZEL": |
|
case "GIRDLE_BONE": |
|
case "GIRDLE_VALLEY": |
|
v = Math.Floor(v * 1000) / 1000; |
|
return hasUnit ? $"{(v*100).ToString("F1")}mm" : (v*100).ToString("F1"); |
|
case "DIAMETER": |
|
v = Math.Floor(v * 10) / 10; |
|
return hasUnit ? $"{v.ToString("F1")}mm" : v.ToString("F1"); |
|
case "PAV_ANGLE": |
|
case "TWIST": |
|
v = Math.Floor(v * 10) / 10; |
|
return hasUnit ? $"{v.ToString("F1")}°" : v.ToString("F1"); |
|
case "CROWN_ANGLE": |
|
v = Math.Floor(v * 10) / 10; |
|
return hasUnit ? $"{v.ToString("F1")}°" :v.ToString("F1"); |
|
case "TABLE": |
|
// case "CROWN HEIGHT": |
|
// case "PAV DEPTH": |
|
v *= 100; |
|
return hasUnit ? $"{(Math.Round(v / 0.5) * 0.5).ToString("F1")}%" : (Math.Round(v / 0.5) * 0.5).ToString("F1"); |
|
default: |
|
v = Math.Floor(v * 1000) / 1000; |
|
return hasUnit ? $"{(v*100).ToString("F1")}%" : (v*100).ToString("F1"); |
|
} |
|
} |
|
|
|
return "--"; |
|
} |
|
|
|
#endregion |
|
|
|
} |
|
|
|
|