From 547a2e1c00d0a63ec3a8716b8c5d68d01b9ff4c1 Mon Sep 17 00:00:00 2001 From: Tongg Date: Wed, 27 Nov 2024 17:05:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B5=8C=E5=85=A53D=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Language/zh_CN.xaml | 7 + Model/Entity/Viewport3DTriangleEntity.cs | 129 ++++ Model/Enums/PlaneType.cs | 31 + Model/Helper/Viewport3DHelper.cs | 825 +++++++++++++++++++++++ Resource/Images/Temp/rtop.png | Bin 0 -> 10364 bytes SparkClient.csproj | 18 + ViewModel/BaseWindow/HomeWindowVM.cs | 2 +- ViewModel/Grading/GradingResultVM.cs | 17 +- Views/Grading/GradingResult.xaml | 79 ++- Views/Grading/GradingResult.xaml.cs | 111 +++ 10 files changed, 1215 insertions(+), 4 deletions(-) create mode 100644 Model/Entity/Viewport3DTriangleEntity.cs create mode 100644 Model/Enums/PlaneType.cs create mode 100644 Model/Helper/Viewport3DHelper.cs create mode 100644 Resource/Images/Temp/rtop.png diff --git a/Language/zh_CN.xaml b/Language/zh_CN.xaml index 4702bef..8a69a87 100644 --- a/Language/zh_CN.xaml +++ b/Language/zh_CN.xaml @@ -26,6 +26,13 @@ 美化Json 压缩Json + + 平均 + 圆度 + 深度 + 最小值 + 最大值 + SYM等级 是否退出程序? 是否退出 diff --git a/Model/Entity/Viewport3DTriangleEntity.cs b/Model/Entity/Viewport3DTriangleEntity.cs new file mode 100644 index 0000000..d24c1c4 --- /dev/null +++ b/Model/Entity/Viewport3DTriangleEntity.cs @@ -0,0 +1,129 @@ +using System.Security.Cryptography; +using System.Text; +using SharpDX; +using SparkClient.Model.Enums; + +namespace SparkClient.Model.Entity; +/// +/// 三角形 +/// +public class Viewport3DTriangleEntity +{ + /// + /// 点1 + /// + public Vector3 Point1 { get; set; } + /// + /// 点2 + /// + public Vector3 Point2 { get; set; } + /// + /// 点3 + /// + public Vector3 Point3 { get; set; } + /// + /// 三角形代码[生成] + /// 按顺序:p1.x,p1.y,p1.z;p2.x,p2.y,p2.z;p3.x,p3.y,p3.z 拼接后使用生成大写16位md5 + /// + public String TriangleCode { get; set; } + /// + /// 面代码 + /// 由多个三角形组成的面的代码:entity1、entity2组成了一个正方形,那么他俩的TriangleCode属性值一致 + /// + public String PlaneCode { get; set; } + /// + /// 面类型 + /// 比如这个面时钻石的腰部分,那么type统一,当面类型为err时则该面是一个异常面 + /// 可选值:PlaneType + /// + public PlaneType PlaneType { get; set; } + + public static Viewport3DTriangleEntity CreateByString(string str) + { + if (string.IsNullOrWhiteSpace(str)) + { + throw new ArgumentException("Input string cannot be null or empty"); + } + + // 分割为四段 + var parts = str.Split('|'); + if (parts.Length != 4) + { + throw new ArgumentException("Input string format is incorrect. Expected 4 parts separated by '|'."); + } + + // 解析第一段:坐标 + var coordinates = parts[0].Split(';'); + if (coordinates.Length != 3) + { + throw new ArgumentException("Coordinates part must contain exactly 3 points separated by ';'."); + } + + var points = coordinates.Select(ParseVector3).ToArray(); + + // 解析第二段:TriangleCode [内部标记] + + // 解析第三段:PlaneCode + string planeCode = parts[2]; + if (string.IsNullOrWhiteSpace(planeCode)) + { + throw new ArgumentException("PlaneCode cannot be null or empty"); + } + + // 解析第四段:PlaneType + if (!int.TryParse(parts[3], out int planeTypeValue) || !Enum.IsDefined(typeof(PlaneType), planeTypeValue)) + { + throw new ArgumentException("Invalid PlaneType value"); + } + PlaneType planeType = (PlaneType)planeTypeValue; + + // 生成 TriangleCode 格式:p1.x,p1.y,p1.z;p2.x,p2.y,p2.z;p3.x,p3.y,p3.z 生成 MD5 + string concatenatedPoints = string.Join(";", points.Select(p => $"{p.X},{p.Y},{p.Z}")); + string generatedTriangleCode = GenerateMD5Hash(concatenatedPoints); + + return new Viewport3DTriangleEntity + { + Point1 = points[0], + Point2 = points[1], + Point3 = points[2], + TriangleCode = generatedTriangleCode, + PlaneCode = planeCode, + PlaneType = planeType + }; + } + + // public static Viewport3DTriangleEntity CreateByJsonStr(string str) + // { + // + // } + + private static Vector3 ParseVector3(string coordinate) + { + var values = coordinate.Split(','); + if (values.Length != 3) + { + throw new ArgumentException("Each coordinate must contain exactly 3 values separated by ','"); + } + + if (!float.TryParse(values[0], out float x) || + !float.TryParse(values[1], out float y) || + !float.TryParse(values[2], out float z)) + { + throw new ArgumentException("Coordinate values must be valid floating-point numbers"); + } + + return new Vector3(x, y, z); + } + + public static string GenerateMD5Hash(string input) + { + using (var md5 = MD5.Create()) + { + var inputBytes = Encoding.UTF8.GetBytes(input); + var hashBytes = md5.ComputeHash(inputBytes); + + // 转换为大写的16位十六进制字符串 + return string.Concat(hashBytes.Take(8).Select(b => b.ToString("X2"))); + } + } +} \ No newline at end of file diff --git a/Model/Enums/PlaneType.cs b/Model/Enums/PlaneType.cs new file mode 100644 index 0000000..5468d79 --- /dev/null +++ b/Model/Enums/PlaneType.cs @@ -0,0 +1,31 @@ +using System.ComponentModel; + +namespace SparkClient.Model.Enums; + +public enum PlaneType +{ + [Description("瑕疵面")] + Error = -1, + [Description("腰")] + Girdle = 0, + [Description("冠部")] + Crown = 1, + [Description("亭部")] + Pavilion = 2, + [Description("台面")] + TableFacet = 11, + [Description("冠部主刻面(风筝面)")] + UpperMainFacet = 12, + [Description("星刻面")] + StarFacet = 13, + [Description("上腰面")] + UpperGirdleFacet = 14, + [Description("亭部主刻面")] + PavilionMainFacet = 21, + [Description("下腰面")] + LowerGirdleFact = 22, + [Description("底尖(或底小面)")] + Culet = 23, + [Description("其他")] + Other = 99 +} \ No newline at end of file diff --git a/Model/Helper/Viewport3DHelper.cs b/Model/Helper/Viewport3DHelper.cs new file mode 100644 index 0000000..65399e7 --- /dev/null +++ b/Model/Helper/Viewport3DHelper.cs @@ -0,0 +1,825 @@ +using System.IO; +using System.Windows.Media; +using System.Windows.Media.Media3D; +using SharpDX.Direct3D11; +using SparkClient.Model.Entity; + +namespace SparkClient.Model.Helper; + +using HelixToolkit.Wpf.SharpDX; +using SharpDX; +using System.Collections.Generic; + +public class Viewport3DHelper +{ + // 加载环境贴图 + // public static Stream stream = + // File.Open("D:\\WorkSpace\\spark\\HelixToolkitSharpDX-Demo\\morning_racing_circuit_16k.dds", FileMode.Open); + + /// + /// 生成一个多边形钻石 + /// + /// 控件 + /// 边数 + public static void GenerateDiamond(Viewport3DX viewport, int sides) + { + // 启用法线生成 + var meshBuilder = new MeshBuilder(true, false); + + //设置抗锯齿 + viewport.RenderHost.MSAA = MSAALevel.Maximum; + + + double radius = 3.2915; + double smallRadius = radius / 2; + double topHeight = 1.67; + double bottomHeight = 4.43; + double smallOffset = 1.25; + + var largeVertices = new List(); + var smallVertices = new List(); + + var edgeLines = new List>(); + + // 生成大八边形顶点 + for (int i = 0; i < sides; i++) + { + double angle = i * Math.PI / (sides / 2); + largeVertices.Add(new Vector3((float)(radius * Math.Cos(angle)), (float)topHeight, + (float)(radius * Math.Sin(angle)))); + } + + // 生成小八边形顶点 + for (int i = 0; i < sides; i++) + { + double angle = i * Math.PI / (sides / 2); + smallVertices.Add(new Vector3((float)(smallRadius * Math.Cos(angle)), (float)(topHeight + smallOffset), + (float)(smallRadius * Math.Sin(angle)))); + } + + var bottomPoint = new Vector3(0, (float)(topHeight - bottomHeight), 0); + var topPoint = new Vector3(0, (float)topHeight, 0); + + // 生成底部面 + for (int i = 0; i < sides; i++) + { + int nextIndex = (i + 1) % sides; + + // 计算法线 + // Vector3 edge1 = largeVertices[nextIndex] - bottomPoint; + // Vector3 edge2 = largeVertices[i] - bottomPoint; + // Vector3 normal = Vector3.Cross(edge1, edge2); + // normal.Normalize(); + + + List fanPositions2 = new List() + { bottomPoint, largeVertices[nextIndex], largeVertices[i] }; + List fanNormals2 = new List() + { new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(0, 1, 0) }; + meshBuilder.AddTriangleFan(fanPositions2, fanNormals2); + // meshBuilder.AddTriangle(bottomPoint, largeVertices[nextIndex], largeVertices[i]); + // meshBuilder.AddTriangle(topPoint, largeVertices[i], largeVertices[nextIndex]); + + + // 添加边框线 + edgeLines.Add(new Tuple(bottomPoint, largeVertices[nextIndex])); + edgeLines.Add(new Tuple(largeVertices[nextIndex], largeVertices[i])); + edgeLines.Add(new Tuple(largeVertices[i], bottomPoint)); + } + + // 生成法线并添加小八边形的面 + var smallNormals = new List(); + for (int i = 0; i < smallVertices.Count; i++) + { + smallNormals.Add(new Vector3(0, 1, 0)); // 小八边形法线向上 + } + + meshBuilder.AddTriangleFan(smallVertices, smallNormals); + + // 生成大八边形和小八边形的连接面 + for (int i = 0; i < sides; i++) + { + int nextIndex = (i + 1) % sides; + + + // 计算法线 + // Vector3 edge1 = largeVertices[nextIndex] - largeVertices[i]; + // Vector3 edge2 = smallVertices[nextIndex] - largeVertices[i]; + // Vector3 normal1 = Vector3.Cross(edge1, edge2); + // normal1.Normalize(); + // + // Vector3 edge3 = smallVertices[nextIndex] - largeVertices[i]; + // Vector3 edge4 = smallVertices[i] - largeVertices[i]; + // Vector3 normal2 = Vector3.Cross(edge3, edge4); + // normal2.Normalize(); + + + List fanPositions1 = new List() + { largeVertices[i], largeVertices[nextIndex], smallVertices[nextIndex] }; + List fanNormals1 = new List() + { new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(0, 1, 0) }; + meshBuilder.AddTriangleFan(fanPositions1, fanNormals1); + + //meshBuilder.AddTriangle(largeVertices[i], largeVertices[nextIndex], smallVertices[nextIndex]); + + List fanPositions2 = new List() + { largeVertices[i], smallVertices[nextIndex], smallVertices[i] }; + List fanNormals2 = new List() + { new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(0, 1, 0) }; + meshBuilder.AddTriangleFan(fanPositions2, fanNormals2); + //meshBuilder.AddTriangle(largeVertices[i], smallVertices[nextIndex], smallVertices[i]); + + // 添加边框线 + edgeLines.Add(new Tuple(largeVertices[i], largeVertices[nextIndex])); + edgeLines.Add(new Tuple(largeVertices[nextIndex], smallVertices[nextIndex])); + edgeLines.Add(new Tuple(smallVertices[nextIndex], smallVertices[i])); + edgeLines.Add(new Tuple(smallVertices[i], largeVertices[i])); + } + + var mesh = meshBuilder.ToMeshGeometry3D(); + + + var material = new PBRMaterial + { + AlbedoColor = new Color4(0.11f, 0.56f, 1f, 0.6f), // 纯白色 + MetallicFactor = 0.0, + RoughnessFactor = 0.01, + ReflectanceFactor = 1.0, + ClearCoatStrength = 1.0, + ClearCoatRoughness = 0.0, + // RenderEnvironmentMap = true, + // IrradianceMap = TextureModel.Create(stream), // 环境贴图 + }; + + // 设置材质的采样器状态 + material.SurfaceMapSampler = new SamplerStateDescription + { + Filter = Filter.MinMagMipLinear, + AddressU = TextureAddressMode.Wrap, + AddressV = TextureAddressMode.Wrap, + AddressW = TextureAddressMode.Wrap + }; + + //边框 + var lineBuilder = new LineBuilder(); + foreach (var line in edgeLines) + { + lineBuilder.AddLine(line.Item1, line.Item2); + } + + var edgeLinesModel = new LineGeometryModel3D + { + Geometry = lineBuilder.ToLineGeometry3D(), + Color = System.Windows.Media.Colors.Black, + Thickness = 1.0 + }; + + viewport.Items.Add(edgeLinesModel); + + var geometryModel = new MeshGeometryModel3D + { + Geometry = mesh, + Material = material + }; + + viewport.Items.Add(geometryModel); + + viewport.ZoomExtents(); + } + + public static void GenerateModelByEntity(Viewport3DX viewport, List entities) + { + var meshBuilder = new MeshBuilder(true, false); + viewport.RenderHost.MSAA = MSAALevel.Maximum; + foreach (var entity in entities) + { + List vertices = new List(){entity.Point1, entity.Point2, entity.Point3}; + meshBuilder.AddPolygon(vertices); + } + + var mesh = meshBuilder.ToMeshGeometry3D(); + var material = new PBRMaterial + { + AlbedoColor = new Color4(0.11f, 0.56f, 1f, 0.6f), + MetallicFactor = 0.0, + RoughnessFactor = 0.01, + ReflectanceFactor = 1.0, + ClearCoatStrength = 1.0, + ClearCoatRoughness = 0.0, + }; + + // 设置材质的采样器状态 + material.SurfaceMapSampler = new SamplerStateDescription + { + Filter = Filter.MinMagMipLinear, + AddressU = TextureAddressMode.Wrap, + AddressV = TextureAddressMode.Wrap, + AddressW = TextureAddressMode.Wrap + }; + var geometryModel = new MeshGeometryModel3D + { + Geometry = mesh, + Material = material + }; + viewport.Items.Add(geometryModel); + } + + /// + /// 传入一个三角形,找到关联面 + /// + /// 控件 + /// 三角形顶点 + /// 选择主体颜色 + /// 选择关联颜色(null不渲染) + /// 选择边框颜色(null不渲染) + /// + public static List HighlightAssociatedFaces(Viewport3DX viewport, + Tuple triangleIndex, Color4 selColor, Color4? linkColor = null, + Color4? borderColor = null) + { + List result = new List(); + //选中平面上的点(实现选中高亮、边框线) + List selFaceVector = new List(); + selFaceVector.Add(triangleIndex.Item1); + selFaceVector.Add(triangleIndex.Item2); + selFaceVector.Add(triangleIndex.Item3); + + foreach (var item in viewport.Items) + { + if (item is MeshGeometryModel3D geometryModel) + { + var geometry = geometryModel.Geometry; + if (geometry != null) + { + var positions = geometry.Positions; // 顶点列表 + var indices = geometry.Indices; // 索引列表 + + // 确保 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 vertex0 = positions[index0]; + var vertex1 = positions[index1]; + var vertex2 = positions[index2]; + + List temp = new List() { vertex0, vertex1, vertex2 }; + + if (ArePointsOnSamePlane(triangleIndex, temp)) + { + //所有点共面,两种情况,三角形自己,三角形相连在同一个平面 + if (temp.Count == 3 && temp.Contains(triangleIndex.Item1) && + temp.Contains(triangleIndex.Item2) && temp.Contains(triangleIndex.Item3)) + continue; + foreach (var vector in temp) + { + // if(!selFaceVector.Contains(vector)) + selFaceVector.Add(vector); + } + } + } + } + } + } + + + //当前选中的平面 + var meshBuilder = new MeshBuilder(true, false); + List fanNormals2 = new List(); + foreach (var vertex in selFaceVector) + fanNormals2.Add(new Vector3(0, 1, 0)); + meshBuilder.AddTriangleFan(selFaceVector, fanNormals2); + var mesh = meshBuilder.ToMeshGeometry3D(); + + var material = new PBRMaterial + { + AlbedoColor = selColor, // 纯白色 + MetallicFactor = 0, + RoughnessFactor = 1, + ReflectanceFactor = 0.5, + }; + var geometryModel1 = new MeshGeometryModel3D + { + Geometry = mesh, + Material = material + }; + + //当前选中平面边框线 + var edgeLines = new List>(); + var lengths = new List(); // 长度列表 + var angles = new List(); // 夹角列表 + HashSet uniqueVectors = new HashSet(selFaceVector); + List sortedVectors = uniqueVectors.ToList(); + Vector3 center = GetCentroid(sortedVectors); + // 按极角顺时针排序 + sortedVectors.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); // 按角度从小到大排序 + }); + + for (int i = 0; i < sortedVectors.Count - 1; i++) + { + var nowItem = sortedVectors[i]; + var nextItem = sortedVectors[i + 1]; + edgeLines.Add(new Tuple(nowItem, nextItem)); + // 计算当前线段的长度 + lengths.Add((nextItem - nowItem).Length()); + } + + edgeLines.Add(new Tuple(sortedVectors.Last(), sortedVectors.First())); + lengths.Add((sortedVectors.Last() - sortedVectors.First()).Length()); + //计算夹角 + 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 = p2 - p1; + var v2 = p3 - p2; + double angle = AngleBetween(v1, v2); + angles.Add(angle); // 夹角,单位是度 + } + + if (sortedVectors.Count == lengths.Count && sortedVectors.Count == angles.Count) + { + for (int i = 0; i < sortedVectors.Count; i++) + { + var point = sortedVectors[i]; + var pointNext = i + 1 >= sortedVectors.Count ? sortedVectors.First() : sortedVectors[i + 1]; + var lengthText = $"L: {lengths[i]:F2}"; + var angleText = $"Angle: {angles[i]:F2}°"; + + var lengthTextPoint = (pointNext + point) / 2; + + // 在合适位置显示文本 + var text3DL = DisplayText3D(lengthText, + new Vector3(lengthTextPoint.X, lengthTextPoint.Y + 0.5f, lengthTextPoint.Z)); + var text3DA = DisplayText3D(angleText, new Vector3(point.X, point.Y + 0.5f, point.Z + 0.5f)); + viewport.Items.Add(text3DL); + viewport.Items.Add(text3DA); + result.Add(text3DL); + result.Add(text3DA); + } + } + else + { + // 如果列表长度不一致,给出提示信息 + Console.WriteLine("The lengths or angles list size does not match the sorted vectors list."); + } + + var lineBuilder = new LineBuilder(); + foreach (var line in edgeLines) + { + lineBuilder.AddLine(line.Item1, line.Item2); + } + + var edgeLinesModel = new LineGeometryModel3D + { + Geometry = lineBuilder.ToLineGeometry3D(), + Color = System.Windows.Media.Colors.Red, + Thickness = 1.0 + }; + + viewport.Items.Add(edgeLinesModel); + viewport.Items.Add(geometryModel1); + + + result.Add(geometryModel1); + result.Add(edgeLinesModel); + return result; + } + + /// + /// 遍历视图中的所有三角形 + /// + /// + /// + public static List> ForeachViewPortTriangle(Viewport3DX viewport) + { + List> result = new List>(); + foreach (var item in viewport.Items) + { + if (item is MeshGeometryModel3D geometryModel) + { + var geometry = geometryModel.Geometry; + if (geometry != null) + { + var positions = geometry.Positions; // 顶点列表 + var indices = geometry.Indices; // 索引列表 + + // 确保 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 vertex0 = positions[index0]; + var vertex1 = positions[index1]; + var vertex2 = positions[index2]; + result.Add(new Tuple(vertex0, vertex1, vertex2)); + } + } + } + } + + return result; + } + + private static BillboardTextModel3D DisplayText3D(string text, Vector3 position) + { + var billboardTextModel = new BillboardTextModel3D(); + var billboardText = new BillboardText3D(); + billboardText.TextInfo.Add(new TextInfo(text, position) + { + Foreground = Color.Green, + Scale = 0.5f, + }); + billboardTextModel.Geometry = billboardText; + return billboardTextModel; + } + + public 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 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 bool ArePointsOnSamePlane(Tuple triangle, List points) + { + // 获取三角形的三个点 + var P1 = triangle.Item1; + var P2 = triangle.Item2; + var P3 = triangle.Item3; + + // 计算平面的法线 + Vector3 V1 = P2 - P1; // 向量P1P2 + Vector3 V2 = P3 - P1; // 向量P1P3 + Vector3 normal = Vector3.Cross(V1, V2); // 叉积得到平面法线 + + // 对每个点进行检查 + foreach (var point in points) + { + if (!IsPointInPlane(P1, normal, point)) + { + return false; // 如果有点不在平面上,返回false + } + } + + return true; // 如果所有点都在平面上,返回true + } + + /// + /// 判断点是否在平面上 + /// + /// + private static bool IsPointInPlane(Vector3 planePoint, Vector3 normal, Vector3 point) + { + // 计算点到平面的距离 + // 点到平面的距离公式:d = (point - planePoint) · normal / |normal| + float distance = Vector3.Dot(point - planePoint, normal) / normal.Length(); + + // 如果距离为0,说明点在平面上 + return Math.Abs(distance) < 1e-6; // 容忍一个小的误差 + } + + public static void GenerateLightingForModel(Viewport3DX viewport) + { + // 移除现有的灯光 + List lights = new List(); + foreach (var item in viewport.Items) + { + if (item is Light3D) + { + lights.Add(item as Light3D); + } + } + lights.ForEach(item => viewport.Items.Remove(item)); + + // 获取场景中最大的模型 + var models = viewport.Items.OfType(); + if (!models.Any()) return; + + var largestModel = models + .OrderByDescending(m => GetBoundingBoxVolume(m.Geometry.Bound)) + .FirstOrDefault(); + + if (largestModel == null) return; + + // 获取最大模型的边界立方体 + var boundingBox = largestModel.Geometry.Bound; + var size = boundingBox.Size; + var center = boundingBox.Center; + + // 计算 Bounding Box 的八个顶点 + var corners = new List + { + boundingBox.Minimum.ToPoint3D(), // 左下后 + new Point3D(boundingBox.Maximum.X, boundingBox.Minimum.Y, boundingBox.Minimum.Z), // 右下后 + new Point3D(boundingBox.Minimum.X, boundingBox.Maximum.Y, boundingBox.Minimum.Z), // 左上后 + new Point3D(boundingBox.Minimum.X, boundingBox.Minimum.Y, boundingBox.Maximum.Z), // 左下前 + boundingBox.Maximum.ToPoint3D(), // 右上前 + new Point3D(boundingBox.Maximum.X, boundingBox.Maximum.Y, boundingBox.Minimum.Z), // 右上后 + new Point3D(boundingBox.Minimum.X, boundingBox.Maximum.Y, boundingBox.Maximum.Z), // 左上前 + new Point3D(boundingBox.Maximum.X, boundingBox.Minimum.Y, boundingBox.Maximum.Z) // 右下前 + }; + + // 在每个顶点处添加光源 + 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() * 4 // 光照范围覆盖整个模型 + }; + + viewport.Items.Add(pointLight); + } + + // 添加顶部的多处光源(从上向下照射,模拟太阳光) + double topLightDistance = size.Y * 1.2; // 适当调整顶部光源的高度 + var topLightPositions = new List + { + new Point3D(center.X, boundingBox.Maximum.Y + topLightDistance, center.Z), + new Point3D(boundingBox.Minimum.X - size.X / 3, boundingBox.Maximum.Y + topLightDistance, center.Z), + new Point3D(boundingBox.Maximum.X + size.X / 3, boundingBox.Maximum.Y + topLightDistance, 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) // 控制光的衰减,使光在距离内更有效 + }; + viewport.Items.Add(topLight); + } + // 添加环境光以柔化整体效果并增加亮度 + var ambientLight = new AmbientLight3D + { + Color = Colors.LightGray + }; + viewport.Items.Add(ambientLight); + + + var ambientLigh1t = new AmbientLight3D + { + Color = Colors.Gray // 设置环境光颜色 + }; + viewport.Items.Add(ambientLigh1t); + + + // 添加环境光以柔化整体效果并增加亮度 + // var ambientLight = new AmbientLight3D + // { + // Color = Colors.LightGray + // }; + // viewport.Items.Add(ambientLight); + // 添加底部的光源(从底部中心向上照射) + // var bottomLightPosition = new Point3D(center.X, boundingBox.Minimum.Y - 1.0f, center.Z); // 位于底部中心点下方 + // var bottomLight = new PointLight3D + // { + // Position = bottomLightPosition, + // Color = Colors.White, // 白色光源 + // Range = (float)size.Length() * 2 // 设置光照范围 + // }; + // viewport.Items.Add(bottomLight); + } + + // 辅助方法:获取边界体积 + private static double GetBoundingBoxVolume(BoundingBox bound) + { + var size = bound.Size; + return size.X * size.Y * size.Z; + } + + public static List InitDemo(Viewport3DX viewport) + { + List entities = new List(); + try + { + using (FileStream fileStream = new FileStream(AppDomain.CurrentDomain.BaseDirectory + @"Resource\DimDemo.txt", FileMode.Open, FileAccess.Read)) + using (StreamReader reader = new StreamReader(fileStream)) + { + string line; + while ((line = reader.ReadLine()) != null) + { + if(string.IsNullOrWhiteSpace(line))continue; + if(line.StartsWith("-- "))continue; + entities.Add(Viewport3DTriangleEntity.CreateByString(line)); + } + } + } + catch (Exception ex) + { + Console.WriteLine($"读取文件时出错:{ex.Message}"); + } + GenerateModelByEntity(viewport, entities); + GenerateLightingForModel(viewport); + + return entities; + } + + public static List GenerateEmissiveModelByEntity(Viewport3DX viewport, List entities, Color4 emissiveColor) + { + // 创建 MeshBuilder,启用法线生成 + var meshBuilder = new MeshBuilder(true, false); + viewport.RenderHost.MSAA = MSAALevel.Maximum; + + // 通过实体构建三角形面 + foreach (var entity in entities) + { + List vertices = new List() { entity.Point1, entity.Point2, entity.Point3 }; + meshBuilder.AddPolygon(vertices); + } + + // 生成网格 + var mesh = meshBuilder.ToMeshGeometry3D(); + + // 创建自发光材质 + var material = new PBRMaterial + { + AlbedoColor = new Color4(0, 0, 0, 1.0f), // 黑色,避免其他光照影响 + EmissiveColor = emissiveColor, // 自发光颜色 + MetallicFactor = 0.0, // 非金属 + RoughnessFactor = 1.0, // 高粗糙度,避免反射效果 + ReflectanceFactor = 0.0, // 无反射 + ClearCoatStrength = 0.0, // 无清漆效果 + ClearCoatRoughness = 1.0, // 高粗糙度 + }; + + // 设置材质的采样器状态 + material.SurfaceMapSampler = new SamplerStateDescription + { + Filter = Filter.MinMagMipLinear, + AddressU = TextureAddressMode.Wrap, + AddressV = TextureAddressMode.Wrap, + AddressW = TextureAddressMode.Wrap + }; + + // 创建几何模型 + var geometryModel = new MeshGeometryModel3D + { + Geometry = mesh, + Material = material + }; + + // 将几何模型添加到视图中 + viewport.Items.Add(geometryModel); + List result = new List(); + result.Add(geometryModel); + return result; + } + + public static List GenerateLineTextModelByEntity(Viewport3DX viewport, List entities) + { + List result = new List(); + List selFaceVector = new List(); + foreach (var entity in entities) + { + selFaceVector.Add(entity.Point1); + selFaceVector.Add(entity.Point2); + selFaceVector.Add(entity.Point3); + } + var edgeLines = new List>(); + var lengths = new List(); // 长度列表 + var angles = new List(); // 夹角列表 + HashSet uniqueVectors = new HashSet(selFaceVector); + List sortedVectors = uniqueVectors.ToList(); + Vector3 center = GetCentroid(sortedVectors); + sortedVectors.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); // 按角度从小到大排序 + }); + for (int i = 0; i < sortedVectors.Count - 1; i++) + { + var nowItem = sortedVectors[i]; + var nextItem = sortedVectors[i + 1]; + edgeLines.Add(new Tuple(nowItem, nextItem)); + // 计算当前线段的长度 + lengths.Add((nextItem - nowItem).Length()); + } + edgeLines.Add(new Tuple(sortedVectors.Last(), sortedVectors.First())); + lengths.Add((sortedVectors.Last() - sortedVectors.First()).Length()); + //计算夹角 + 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 = p2 - p1; + var v2 = p3 - p2; + double angle = AngleBetween(v1, v2); + angles.Add(angle); // 夹角,单位是度 + } + if (sortedVectors.Count == lengths.Count && sortedVectors.Count == angles.Count) + { + for (int i = 0; i < sortedVectors.Count; i++) + { + var point = sortedVectors[i]; + var pointNext = i + 1 >= sortedVectors.Count ? sortedVectors.First() : sortedVectors[i + 1]; + var lengthText = $"L: {lengths[i]:F2}"; + var angleText = $"Angle: {angles[i]:F2}°"; + + var lengthTextPoint = (pointNext + point) / 2; + + // 在合适位置显示文本 + var text3DL = DisplayText3D(lengthText, + new Vector3(lengthTextPoint.X, lengthTextPoint.Y + 0.5f, lengthTextPoint.Z)); + var text3DA = DisplayText3D(angleText, new Vector3(point.X, point.Y + 0.5f, point.Z + 0.5f)); + viewport.Items.Add(text3DL); + viewport.Items.Add(text3DA); + result.Add(text3DL); + result.Add(text3DA); + } + } + else + { + // 如果列表长度不一致,给出提示信息 + Console.WriteLine("The lengths or angles list size does not match the sorted vectors list."); + } + var lineBuilder = new LineBuilder(); + foreach (var line in edgeLines) + { + lineBuilder.AddLine(line.Item1, line.Item2); + } + + var edgeLinesModel = new LineGeometryModel3D + { + Geometry = lineBuilder.ToLineGeometry3D(), + Color = System.Windows.Media.Colors.Red, + Thickness = 1.0 + }; + + viewport.Items.Add(edgeLinesModel); + result.Add(edgeLinesModel); + return result; + } + + +} \ No newline at end of file diff --git a/Resource/Images/Temp/rtop.png b/Resource/Images/Temp/rtop.png new file mode 100644 index 0000000000000000000000000000000000000000..cf8c984ca83eb81fad05727d021d64170d839873 GIT binary patch literal 10364 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 + @@ -57,6 +58,18 @@ + + + Always + + + Always + + + + + + @@ -80,6 +93,11 @@ Wpf Designer + + + + + diff --git a/ViewModel/BaseWindow/HomeWindowVM.cs b/ViewModel/BaseWindow/HomeWindowVM.cs index 69274c9..a29e27e 100644 --- a/ViewModel/BaseWindow/HomeWindowVM.cs +++ b/ViewModel/BaseWindow/HomeWindowVM.cs @@ -38,7 +38,7 @@ public class HomeWindowVM : BaseViewModel public void ShowDiamlondSelPage(object parameter) { - BaseControlVM vm = new BaseControlVM(new DiamondSelectVM(), MultilingualHelper.getString("DiamondSelect")); + BaseControlVM vm = new BaseControlVM(new GradingResultVM(null), MultilingualHelper.getString("DetectionResult")); WindowManager.mainViewModel.Content = vm; WindowManager.openContent.Add(vm); } diff --git a/ViewModel/Grading/GradingResultVM.cs b/ViewModel/Grading/GradingResultVM.cs index aeecf58..9140d89 100644 --- a/ViewModel/Grading/GradingResultVM.cs +++ b/ViewModel/Grading/GradingResultVM.cs @@ -1,13 +1,27 @@ +using System.Data; +using System.Windows.Input; +using HandyControl.Controls; + namespace SparkClient.ViewModel.Grading; -public class GradingResultVM: BaseViewModel +public class GradingResultVM : BaseViewModel { + + private DataTable _dtResults; + + public ICommand ChangeNormCommand { get; } + public DataTable DtResults{ get { return _dtResults; } set { _dtResults = value; OnPropertyChanged("DtResults"); } } + /// /// 构造 /// /// 检测结果 public GradingResultVM(object result) { + ChangeNormCommand = new RelayCommand(ChangeNorm); + _dtResults = new DataTable(); + + } @@ -17,6 +31,7 @@ public class GradingResultVM: BaseViewModel /// public void ChangeNorm(object norm) { + } #region 钻石操作相关 diff --git a/Views/Grading/GradingResult.xaml b/Views/Grading/GradingResult.xaml index 33c7015..796c950 100644 --- a/Views/Grading/GradingResult.xaml +++ b/Views/Grading/GradingResult.xaml @@ -3,10 +3,85 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:hc="https://handyorg.github.io/handycontrol" xmlns:local="clr-namespace:SparkClient.Views.Grading" + xmlns:hx="http://helix-toolkit.org/wpf/SharpDX" mc:Ignorable="d" + d:DesignWidth="1000" + d:DesignHeight="600" > - - 检测结果 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Views/Grading/GradingResult.xaml.cs b/Views/Grading/GradingResult.xaml.cs index b4dd005..cd2f562 100644 --- a/Views/Grading/GradingResult.xaml.cs +++ b/Views/Grading/GradingResult.xaml.cs @@ -1,11 +1,122 @@ using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media.Media3D; +using HelixToolkit.Wpf.SharpDX; +using SharpDX; +using SparkClient.Model.Entity; +using SparkClient.Model.Helper; +using SparkClient.ViewModel.Grading; +using GeometryModel3D = HelixToolkit.Wpf.SharpDX.GeometryModel3D; +using MeshGeometry3D = HelixToolkit.Wpf.SharpDX.MeshGeometry3D; +using PerspectiveCamera = HelixToolkit.Wpf.SharpDX.PerspectiveCamera; namespace SparkClient.Views.Grading; public partial class GradingResult { + List triangles = new List(); + List mouseAddModels = new List(); public GradingResult() { InitializeComponent(); + + DataContext = new GradingResultVM(null); + + this.Viewport3Dx.EffectsManager = new DefaultEffectsManager(); + this.Viewport3Dx.Camera = new PerspectiveCamera() + { + Position = new Point3D(0, 0, 5), + LookDirection = new Vector3D(0, 0, -1), + UpDirection = new Vector3D(0, 1, 0), + FarPlaneDistance = 1000, + NearPlaneDistance = 0.1 + }; + + Loaded += (sender, args) => + { + var entities = Viewport3DHelper.InitDemo(Viewport3Dx); + triangles.AddRange(entities); + }; + } + + + /// + /// 点击模型 + /// + /// + /// + private void Viewport3Dx_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + // 获取鼠标点击位置 + var mousePosition = e.GetPosition(Viewport3Dx); + + // 查找鼠标点击的 3D 对象 + var hits = Viewport3Dx.FindHits(mousePosition); + + // 如果没有命中任何 3D 对象 + if (hits == null || hits.Count == 0) + { + Console.WriteLine("未点击到任何模型"); + foreach (var item in mouseAddModels) + { + Viewport3Dx.Items.Remove(item); + } + mouseAddModels.Clear(); + return; + } + + // 获取第一个命中的对象 + var hit = hits[0]; + + // 检查是否是 MeshGeometryModel3D + if (hit.ModelHit is MeshGeometryModel3D modelHit) + { + foreach (var item in mouseAddModels) + { + Viewport3Dx.Items.Remove(item); + } + mouseAddModels.Clear(); + // 获取几何信息 + var geometry = modelHit.Geometry as MeshGeometry3D; + if (geometry != null) + { + // 获取命中的三角形索引 + var triangleIndex = hit.TriangleIndices; + + // 获取三角形顶点 + var vertex1 = geometry.Positions[triangleIndex.Item1]; + var vertex2 = geometry.Positions[triangleIndex.Item2]; + var vertex3 = geometry.Positions[triangleIndex.Item3]; + + string strPoint = vertex1.X + ","+ vertex1.Y+","+ vertex1.Z + ";"+ vertex2.X + ","+ vertex2.Y + ","+ vertex2.Z+";"+vertex3.X + ","+ vertex3.Y + ","+ vertex3.Z; + string triangleCode = Viewport3DTriangleEntity.GenerateMD5Hash(strPoint); + + //命中实体 + Viewport3DTriangleEntity res = triangles.Find(e => triangleCode.Equals(e.TriangleCode)); + + if (res == null) + return; + + //命中面 + List facet = triangles.Where(e => res.PlaneCode.Equals(e.PlaneCode)).ToList(); + var colorFacet = new Color4(1.0f, 1.0f, 0.0f, 1.0f); + var data1 = Viewport3DHelper.GenerateEmissiveModelByEntity(Viewport3Dx,facet,colorFacet); + mouseAddModels.AddRange(data1); + //命中面标线 + var data2 = Viewport3DHelper.GenerateLineTextModelByEntity(Viewport3Dx, facet); + mouseAddModels.AddRange(data2); + //命中同类面 + List facetType = triangles.Where(e => res.PlaneType.Equals(e.PlaneType)).ToList(); + //排除自己 + facet.ForEach(e=>facetType.Remove(e)); + var colorFacetType = new Color4(0.9f, 0.9f, 0.7f, 1.0f); + var data3 = Viewport3DHelper.GenerateEmissiveModelByEntity(Viewport3Dx,facetType,colorFacetType); + mouseAddModels.AddRange(data3); + } + } + else + { + Console.WriteLine("点击的对象不是 MeshGeometryModel3D"); + } } } \ No newline at end of file