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;
namespace SparkClient.Views.UserControl.ViewportData.Helper;
public class ViewportHelperPro
{
private static readonly ILog Logger = LogManager.GetLogger(typeof(ViewportHelperPro));
///
/// 对指定类型的面进行标色
///
///
///
///
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 已经确定和调整好的方法
///
/// 通过三角形实体集合生成面模型(生成并添加)
///
///
///
public static MeshGeometryModel3D GenerateModelByEntity(Viewport3DX viewport, List entities, Color4? color = null)
{
var geometryModel = GenerateModelByEntity(entities, color);
viewport.Items.Add(geometryModel);
return geometryModel;
}
///
/// 通过三角形实体集合生成面模型(只生成不添加)
///
///
///
///
public static MeshGeometryModel3D GenerateModelByEntity(List entities, Color4? color = null)
{
var meshBuilder = new MeshBuilder(true, false);
foreach (var entity in entities)
{
if (entity.PlaneType == PlaneType.Girdle)
{
meshBuilder.AddTriangleFan(new List() { entity.Point1, entity.Point2, entity.Point3 },
new List() { new Vector3(0, 0, 0), new Vector3(0, 0, 0), new Vector3(0, 0, 0) });
}
else
{
meshBuilder.AddPolygon(new List() { 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,
};
}
///
/// 通过三角形实体集合生成面模型(只生成不添加)
///
///
///
///
public static List GenerateModelByEntityGroupByType(List entities)
{
var groupedDict = entities
.GroupBy(e => e.PlaneType)
.ToDictionary(g => g.Key, g => g.ToList());
var result = new List();
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;
}
///
/// 保存模型截图
///
///
///
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 pngList = await generationTask;
await VideoHelper.CreateVideoFromPngListAsync(pngList, filePath);
}
///
/// 导出模型
///
///
///
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())
{
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");
}
}
///
/// 导出模型
///
///
///
public static async Task ExportModelsToStlASync(string filePath)
{
Viewport3DX viewport = ViewportManager.GetViewport3D();
// 创建一个集合来存储几何信息
List, IList>> geometries = new List, IList>>();
// 在 UI 线程中收集几何信息
await Application.Current.Dispatcher.InvokeAsync(() =>
{
foreach (var model in viewport.Items.OfType())
{
if (model.Geometry is HelixToolkit.Wpf.SharpDX.Geometry3D geometry)
{
geometries.Add(new Tuple, IList>(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);
}
///
/// 通过三角形实体集合生成每个面的边框线
///
///
///
public static List GentrateLineByEntity(Viewport3DX viewport, List entities, Color4? color = null, double thickness = 1.0)
{
List result = GentrateLineByEntity(entities, color, thickness);
result.ForEach(e => viewport.Items.Add(e));
return result;
}
///
/// 通过三角形实体集合生成每个面的边框线(只生成不添加)
///
///
///
///
///
public static List GentrateLineGirdleByEntity(List entities,
Color4? color = null, double thickness = 1.0)
{
List result = new List();
//按面分组,腰面特殊单独生成
List waistList = entities
.Where(entity => entity.PlaneType == PlaneType.Girdle)
.ToList();
List selFaceVector = new List();
List> lines = new List>();
if (waistList.Count > 0)
{
foreach (var entity in waistList)
{
selFaceVector.Add(entity.Point1);
selFaceVector.Add(entity.Point2);
selFaceVector.Add(entity.Point3);
}
HashSet uniqueVectors = new HashSet(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(nowItem, nextItem);
if (IsLineSegmentParallelToYAxis(line))
{
lines.Add(line);
result.Add(DisplayLineModel3D(new List() { nowItem, nextItem }, color ?? ViewportManager.ColorConfig.MainBorderColor, thickness));
}
}
}
CalculateLineSegmentStats(lines, out ViewportManager.MainModelGirdleMaxLines, out ViewportManager.MainModelGirdleMinLines, out ViewportManager.MainModelGirdleAvgLines);
ViewportManager.MainModelGirdleLines = lines;
return result;
}
///
/// 通过三角形实体集合生成每个面的边框线(只生成不添加)
///
///
///
///
///
public static List GentrateLineByEntity(List entities, Color4? color = null, double thickness = 1.0)
{
List result = new List();
//按面分组,腰面特殊单独生成
List waistList = entities
.Where(entity => entity.PlaneType == PlaneType.Girdle)
.ToList();
Dictionary> 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 temp = new List();
foreach (var entity in item.Value)
{
temp.Add(entity.Point1);
temp.Add(entity.Point2);
temp.Add(entity.Point3);
}
result.Add(DisplayLineModel3D(VectorClockwiseSort(new HashSet(temp).ToList()), color ?? ViewportManager.ColorConfig.MainBorderColor, thickness));
}
//腰 - 特殊
List selFaceVector = new List();
if (waistList.Count > 0)
{
//顶线和底线
foreach (var entity in waistList)
{
selFaceVector.Add(entity.Point1);
selFaceVector.Add(entity.Point2);
selFaceVector.Add(entity.Point3);
}
HashSet uniqueVectors = new HashSet(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 GenerateLineTextModelsByType(PlaneType type, Color4? textColor = null,
bool showAll = false)
{
List entities = new List();
string planCode = "";
ViewportManager.ViewportTriangle.ForEach(e =>
{
if (e.PlaneType == type && (e.PlaneCode == planCode || string.IsNullOrEmpty(planCode)))
{
planCode = e.PlaneCode;
entities.Add(e);
}
});
return entities;
}
///
/// 选择面生成文本信息
///
/// 三角形集合
/// 指定数据集
///
public static List GenerateLineTextModels(List entities, string valKey = "")
{
Logger.Info("【面文本生成】开始生成面相关文本信息");
var selFacet = entities;
var selFacetType = entities.First().PlaneType;
var result = new List();
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>(){ViewportManager.MainModelGirdleMaxLines}, new Color4(1f, 0, 0, 1f) ));
// result.Add(DisplayLineModel3D(new List>(){ViewportManager.MainModelGirdleMinLines}, new Color4(0f, 1f, 0, 1f) ));
// result.Add(DisplayLineModel3D(new List>(){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 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 facetPoints = new List();
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>() { longestLine1 }, new Color4(1f, 0, 0, 1f), 2f));
break;
case 1:
case 2:
var longestLine2 = GetLongestOrShortestLineSegment(facetPoints, returnLongest: false);
result.Add(DisplayLineModel3D(new List>() { 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 facetPoints = new List();
// 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 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>();
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 facetPoints = new List();
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>() { 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 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 facetPoints = new List();
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();
// 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;
}
///
/// 生成选择的视图()
///
/// 三角形ID
/// 生成范围
///
public static List 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();
}
///
/// 生成选择的视图(只生成,不添加!!)
///
/// 选中的实体
/// 生成范围
///
///
public static List 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 result = new List();
//选中的面
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;
}
///
/// 生成线段夹角文本(不绘制)
///
/// 线集合
/// 文本颜色(默认红)
/// 是否显示全部(默认否)
///
public static List GenerateLineAngleTextModels(List entities,
Color4? textColor = null, bool showAll = false)
{
var result = new List();
var uniqueAngles = new HashSet();
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
///
/// 旋转主体
///
/// 中心
/// 是否包含线
/// 旋转时间 秒
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);
}
///
/// 在场景中添加文字
///
/// 文字
/// 位置
///
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;
}
///
/// 在场景中添加文字
///
///
///
///
///
///
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()
{
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;
}
///
/// 生成线对象
///
/// 点集合(自动闭环)
/// 线颜色
/// 线粗细 默认1
///
public static LineGeometryModel3D DisplayLineModel3D(List points, Color4 lineColor,
double thickness = 1.0)
{
var edgeLines = new List>();
for (int i = 0; i < points.Count - 1; i++)
{
var nowItem = points[i];
var nextItem = points[i + 1];
edgeLines.Add(new Tuple(nowItem, nextItem));
}
edgeLines.Add(new Tuple(points.Last(), points.First()));
return DisplayLineModel3D(edgeLines, lineColor, thickness);
}
///
/// 在正方向生成相机
///
///
///
///
///
///
public static HelixToolkit.Wpf.SharpDX.PerspectiveCamera 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; // 朝向模型中心
// 创建并返回相机
return new HelixToolkit.Wpf.SharpDX.PerspectiveCamera
{
Position = cameraPosition.ToPoint3D(),
LookDirection = lookDirection.ToVector3D(),
UpDirection = new Vector3D(0, 1, 0), // 默认全局 Y 轴为上方向
FieldOfView = fieldOfView,
};
}
///
/// 绘制箭头(相机指向模型中心)
///
/// 相机位置
/// 中心位置
/// 模型
/// 箭头总长度 D:5
/// 圆柱部分占比 0.7
/// 箭头直径 1
/// 圆锥直径与圆柱直径的比例 1.5
/// 模型边界范围 1
///
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
};
}
///
/// 生成线对象
///
/// 线段集合
/// 线段颜色
/// 线段粗细
///
public static LineGeometryModel3D DisplayLineModel3D(List> 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;
}
///
/// 根据视图中模型生成光照
///
///
///
public static List GenerateLightingForModel(Viewport3DX viewport)
{
List result = new List();
var models = viewport.Items.OfType();
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
{
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
// {
// 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 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 girdleTopLines, List 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;
}
///
/// 删除视图中的照明
///
///
public static void RemoveLightingInViewport(Viewport3DX viewport)
{
List lights = new List();
foreach (var item in viewport.Items)
{
if (item is Light3D light3D)
{
lights.Add(light3D);
}
}
lights.ForEach(item => viewport.Items.Remove(item));
}
private static Dictionary moveLines = new Dictionary();
public static List ShowMeshLines(List entities, double thickness = 1.0)
{
moveLines = new();
List lines = new();
var 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>();
Color4 XlineColor =new Color4(80f, 0f, 0f, 1f);
var x1 = 5;
var z1 = lineCal.calZ(x1);
var x2 = -5;
var z2 = lineCal.calZ(x2);
edgeLines.Add(new Tuple(new Vector3(x1, Y, z1), new Vector3(x2, Y, z2)));
lines.Add(DisplayLineModel3D(edgeLines, XlineColor));
edgeLines = new List>();
edgeLines.Add(lineCal.calLineOfOffset(new Tuple(new Vector3(x1, Y, z1), new Vector3(x2, Y, z2)),2));
var lineA = DisplayLineModel3D(edgeLines, XlineColor);
lines.Add(lineA);
edgeLines = new List>();
edgeLines.Add(lineCal.calLineOfOffset(new Tuple(new Vector3(x1, Y, z1), new Vector3(x2, Y, z2)), -2));
var lineB = DisplayLineModel3D(edgeLines, XlineColor);
lines.Add(lineB);
bindingMoveLine(lineA, lineB);
edgeLines = new List>();
edgeLines.Add(lineCal.calLineOfOffset(new Tuple(new Vector3(x1, Y, z1), new Vector3(x2, Y, z2)), 4));
var lineE = DisplayLineModel3D(edgeLines, XlineColor);
lines.Add(lineE);
edgeLines = new List>();
edgeLines.Add(lineCal.calLineOfOffset(new Tuple(new Vector3(x1, Y, z1), new Vector3(x2, Y, z2)), -4));
var lineF = DisplayLineModel3D(edgeLines, XlineColor);
lines.Add(lineF);
bindingMoveLine(lineE, lineF);
Color4 YlineColor = new Color4(0F, 80f, 0f, 1f);
var x3 = 5;
var z3 = lineCal.calZVertical(x3);
var x4 = -5;
var z4 = lineCal.calZVertical(x4);
edgeLines = new List>();
edgeLines.Add(new Tuple(new Vector3(x3, Y, z3), new Vector3(x4, Y, z4)));
lines.Add(DisplayLineModel3D(edgeLines, YlineColor));
edgeLines = new List>();
edgeLines.Add(lineCal.calLineOfOffset(new Tuple(new Vector3(x3, Y, z3), new Vector3(x4, Y, z4)), 2));
var lineC = DisplayLineModel3D(edgeLines, YlineColor);
lines.Add(lineC);
edgeLines = new List>();
edgeLines.Add(lineCal.calLineOfOffset(new Tuple(new Vector3(x3, Y, z3), new Vector3(x4, Y, z4)), -2));
var lineD = DisplayLineModel3D(edgeLines, YlineColor);
lines.Add(lineD);
bindingMoveLine(lineC, lineD);
edgeLines = new List>();
edgeLines.Add(lineCal.calLineOfOffset(new Tuple(new Vector3(x3, Y, z3), new Vector3(x4, Y, z4)), 4));
var lineG = DisplayLineModel3D(edgeLines, YlineColor);
lines.Add(lineG);
edgeLines = new List>();
edgeLines.Add(lineCal.calLineOfOffset(new Tuple(new Vector3(x3, Y, z3), new Vector3(x4, Y, z4)), -4));
var lineH = DisplayLineModel3D(edgeLines, YlineColor);
lines.Add(lineH);
bindingMoveLine(lineG, lineH);
return lines;
}
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 void LineA_MouseMove3D(object sender, RoutedEventArgs e)
{
if (isDrawing)
{
if (e is HelixToolkit.Wpf.SharpDX.MouseMove3DEventArgs ev)
{
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 = 0.1; // 缩放因子
// 计算垂直距离
double perpendicularDistance = Vector3D.DotProduct(moveDirection, perpendicularDirection) * scaleFactor;
System.Console.WriteLine("距离:"+ perpendicularDistance);
// 垂直方向偏移量
Vector3D projectedVector = perpendicularDistance * perpendicularDirection;
projectedVector.Y = 0;
var transform = new TranslateTransform3D(projectedVector);
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 )
{
Vector3D projectedVector_other = -perpendicularDistance * perpendicularDirection;
projectedVector_other.Y = 0;
var transform_other = new TranslateTransform3D(projectedVector_other);
otherLine.Transform = transform_other;
}
}
}
}
private static void LineA_MouseUp3D(object sender, RoutedEventArgs e)
{
isDrawing = false;
}
private static void LineA_MouseDown3D(object sender, RoutedEventArgs e)
{
isDrawing = true;
if(e is HelixToolkit.Wpf.SharpDX.MouseDown3DEventArgs ev){
// 获取鼠标点击的位置
var mousePosition = ev.Position;
previousMousePosition = Get3DPointFromMouse(mousePosition);
}
}
private static Point3D Get3DPointFromMouse(Point mousePosition)
{
// 使用 HelixToolkit 的 Ray3D 和 Plane3D
var Viewport = ViewportManager.GetViewport3D();
var camera = Viewport.Camera as PerspectiveCamera;
// 创建射线
var ray = new Ray3D(
camera.Position,
new Vector3D(mousePosition.X - Viewport.ActualWidth / 2, -(mousePosition.Y - Viewport.ActualHeight / 2), -camera.Position.Z)
);
// 创建平面(假设平面为 Y=0)
var plane = new Plane3D(new Point3D(0, 0, 0), new Vector3D(0, 0, 1));
// 计算射线与平面的交点
var intersection = GetIntersection(ray, plane);
return intersection ?? new Point3D(0, 0, 0);
}
private static Point3D? GetIntersection(Ray3D ray, Plane3D plane)
{
// 将射线转换为直线(起点和终点)
Point3D rayEnd = ray.Origin + ray.Direction * 1000; // 延长射线
// 使用 Plane3D.LineIntersection 方法计算交点
return plane.LineIntersection(ray.Origin, rayEnd);
}
public static LineGeometryModel3D ShowCircleLine(double radius = 1.0, double thickness = 1.0)
{
var Y = -0.01F;
var center = ViewportManager.CenterVector;
center.Y = Y;
return UpdateCircleGeometry(center);
}
private static LineGeometryModel3D UpdateCircleGeometry(Vector3 center, double radius = 1.0)
{
// 生成圆形线的点
int segments = 100; // 圆的细分段数
var positions = new List();
var indices = new List();
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);
}
// 更新圆形线的几何形状
return DisplayLineModel3D(positions, Color4.Black);
}
#region 私有方法
///
/// 向量按中心点顺时针排序
///
///
///
public static List VectorClockwiseSort(List points)
{
Vector3 center = GetCentroid(points);
return VectorClockwiseSort(points, center);
}
///
/// 向量按中心点顺时针排序
///
///
///
///
public static List VectorClockwiseSort(List 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 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);
}
///
/// 计算点集合的中心点(几何质心)
///
///
///
public static Vector3 GetCentroid(List 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 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;
}
///
/// 计算夹角度数
///
///
///
///
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 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 GetLongestOrShortestLineSegment(List facetPoints, bool returnLongest = true)
{
Tuple 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(currentPoint, nextPoint)))
{
// 计算线段的长度
float segmentLength = GetDistance(currentPoint, nextPoint);
// 根据需要选择较长或较短的线段
if ((returnLongest && segmentLength > resultLength) || (!returnLongest && segmentLength < resultLength))
{
resultSegment = new Tuple(currentPoint, nextPoint);
resultLength = segmentLength;
}
}
}
return resultSegment; // 返回符合条件的线段
}
private static void CalculateLineSegmentStats(List> lines,
out Tuple maxLine,
out Tuple minLine,
out Tuple 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 line)
{
Vector3 startPoint = line.Item1;
Vector3 endPoint = line.Item2;
return Math.Round(Vector3.Distance(startPoint, endPoint), 2);
}
///
/// 获取四边形内平行于Y轴且位于左侧的边线
///
/// 四边形的顶点列表
/// 是否返回最长的边线
/// 符合条件的边线,如果不存在则返回null
public static Tuple GetLeftParallelLineSegment(
List 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 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(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
}