diff --git a/SparkClient.csproj b/SparkClient.csproj
index 8d59447..ee99a1c 100644
--- a/SparkClient.csproj
+++ b/SparkClient.csproj
@@ -6,7 +6,7 @@
enable
enable
true
- logo.ico
+ logo256.ico
SparkClient - 星辉
SparkClient Team
SparkClient钻石检测工具
diff --git a/Views/UserControl/Viewport3D.xaml.cs b/Views/UserControl/Viewport3D.xaml.cs
index c1c3c6b..22b2d69 100644
--- a/Views/UserControl/Viewport3D.xaml.cs
+++ b/Views/UserControl/Viewport3D.xaml.cs
@@ -220,7 +220,7 @@ public partial class Viewport3D
break;
case "BtnShow3DView":
ObjExporter.ExportToObj2(ViewportManager.ViewportTriangle, @"D:\id03.obj");
- UnityHelper.GenerateRender(ViewportManager.ViewportTriangle.First().TriangleCode, "123");
+ // UnityHelper.GenerateRender(ViewportManager.ViewportTriangle.First().TriangleCode, "123");
break;
}
diff --git a/Views/UserControl/ViewportData/Helper/ObjExporter.cs b/Views/UserControl/ViewportData/Helper/ObjExporter.cs
index 3064d1d..64391e0 100644
--- a/Views/UserControl/ViewportData/Helper/ObjExporter.cs
+++ b/Views/UserControl/ViewportData/Helper/ObjExporter.cs
@@ -1,5 +1,6 @@
using System.IO;
using System.Text;
+using HelixToolkit.SharpDX.Core;
using SharpDX;
using SparkClient.Views.UserControl.ViewportData.Entity;
using SparkClient.Views.UserControl.ViewportData.Enum;
@@ -8,154 +9,171 @@ namespace SparkClient.Views.UserControl.ViewportData.Helper;
public class ObjExporter
{
-
public static void ExportToObj2(List entities, string outputPath)
+{
+ // 分组:按面代码 PlaneCode 分组
+ var faceGroups = entities
+ .GroupBy(e => e.PlaneCode)
+ .ToDictionary(g => g.Key, g => g.ToList());
+
+ // 存储每个面的顶点和法线
+ var faceData = new Dictionary();
+
+ // Step 1: 处理每个面,生成顶点列表并计算法线
+ foreach (var group in faceGroups)
{
- //分组
- Dictionary> feactList = entities
- .Where(entity => entity.PlaneType != PlaneType.Girdle)
- .GroupBy(entity => entity.PlaneCode)
- .ToDictionary(group => group.Key, group => group.ToList());
- //腰 单组
- // List waistList = entities
- // .Where(entity => entity.PlaneType == PlaneType.Girdle)
- // .ToList();
- Dictionary> gridleList = entities
- .Where(entity => entity.PlaneType == PlaneType.Girdle)
- .GroupBy(entity => entity.PlaneCode)
- .ToDictionary(group => group.Key, group => group.ToList());
-
- //同一个面只保留外边框(除了腰)
- Dictionary> resultPoints = new Dictionary>();
-
- foreach (var dic in feactList)
- {
- List tempPoints = new List();
- foreach (var entity in dic.Value)
- {
- tempPoints.Add(entity.Point1);
- tempPoints.Add(entity.Point2);
- tempPoints.Add(entity.Point3);
- }
- resultPoints.Add(dic.Key, ViewportHelperPro.VectorClockwiseSort(new HashSet(tempPoints).ToList()));
- }
-
- foreach (var dic in gridleList)
- {
- List tempPoints = new List();
- foreach (var entity in dic.Value)
- {
- tempPoints.Add(entity.Point1);
- tempPoints.Add(entity.Point2);
- tempPoints.Add(entity.Point3);
- }
- resultPoints.Add(dic.Key, ViewportHelperPro.VectorClockwiseSort(new HashSet(tempPoints).ToList()));
- }
- // 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);
- // }
- // }
- // resultPoints.Add("yao", selFaceVector);
-
- StringBuilder sb = new StringBuilder();
-
- // 顶点列表
- List uniqueVertices = new List();
- Dictionary vertexIndexMap = new Dictionary();
-
- // 1. 对每个面生成顶点和面
- foreach (var face in resultPoints)
+ // 合并所有顶点并去重
+ var vertices = group.Value
+ .SelectMany(e => new[] { e.Point1, e.Point2, e.Point3 })
+ .Distinct()
+ .ToList();
+
+ // 按凸包算法排序顶点(避免交叉)
+ var sortedVertices = ConvexHullSort(vertices);
+
+ // 计算面的法线(基于排序后的顶点)
+ var normal = CalculateFaceNormal(sortedVertices, ViewportManager.CenterVector);
+
+ // 存储面数据
+ faceData[group.Key] = new FaceData
{
- // 对每个面,获取它的顶点列表
- List faceVertices = face.Value;
+ Vertices = sortedVertices,
+ Normal = normal
+ };
+ }
- // 去重顶点
- foreach (var vertex in faceVertices)
+ // Step 2: 写入 OBJ 文件
+ using (var writer = new StreamWriter(outputPath))
+ {
+ // 写入顶点
+ var vertexIndexMap = new Dictionary();
+ int index = 1;
+ foreach (var face in faceData.Values)
+ {
+ foreach (var vertex in face.Vertices)
{
if (!vertexIndexMap.ContainsKey(vertex))
{
- vertexIndexMap[vertex] = uniqueVertices.Count;
- uniqueVertices.Add(vertex);
+ vertexIndexMap[vertex] = index++;
+ writer.WriteLine($"v {vertex.X} {vertex.Y} {vertex.Z}");
}
}
}
- // 2. 写入顶点数据
- foreach (var vertex in uniqueVertices)
- {
- sb.AppendLine($"v {vertex.X} {vertex.Y} {vertex.Z}");
- }
-
- // 3. 写入每个面(f行),包括正面和反面
- foreach (var face in resultPoints)
+ // 写入法线(每个面一个法线)
+ var normalIndexMap = new Dictionary();
+ index = 1;
+ foreach (var face in faceData.Values)
{
- sb.AppendLine($"# Face: {face.Key}");
-
- // 获取面上的所有顶点并排序
- List faceVertices = face.Value;
- if (!face.Key.StartsWith("11_"))
+ if (!normalIndexMap.ContainsKey(face.Normal))
{
- Vector3 center = GetCenterOfVertices(faceVertices);
- faceVertices.Sort((v1, v2) => GetAngle(v1, center).CompareTo(GetAngle(v2, center)));
+ normalIndexMap[face.Normal] = index++;
+ // writer.WriteLine($"vn {face.Normal.X} {face.Normal.Y} {face.Normal.Z}");
}
+ }
-
+ // 写入面
+ foreach (var faceEntry in faceData)
+ {
+ var face = faceEntry.Value;
+ var normalIndex = normalIndexMap[face.Normal];
- // 正面:按顺时针顺序输出
- sb.Append("f");
- foreach (var vertex in faceVertices)
+ // 正面(顺时针)
+ writer.Write("f ");
+ foreach (var vertex in face.Vertices)
{
- sb.Append($" {vertexIndexMap[vertex] + 1}");
+ writer.Write($"{vertexIndexMap[vertex]}//{normalIndex} ");
}
- sb.AppendLine();
+ writer.WriteLine();
- // 反面:按逆时针顺序输出(反转顶点顺序)
- sb.Append("f");
- for (int i = faceVertices.Count - 1; i >= 0; i--)
+ // 反面(逆时针)
+ writer.Write("f ");
+ foreach (var vertex in face.Vertices.AsEnumerable().Reverse())
{
- sb.Append($" {vertexIndexMap[faceVertices[i]] + 1}");
+ writer.Write($"{vertexIndexMap[vertex]}//{normalIndex} ");
}
- sb.AppendLine();
+ writer.WriteLine();
}
-
- // 4. 写入文件
- File.WriteAllText(outputPath, sb.ToString());
-
}
-
- // 计算一组顶点的中心点(用于排序)
- private static Vector3 GetCenterOfVertices(List vertices)
+}
+
+ ///
+ /// 凸包排序(Andrew's Monotone Chain 算法)
+ ///
+ private static List ConvexHullSort(List points)
{
- float centerX = 0, centerY = 0, centerZ = 0;
- foreach (var vertex in vertices)
+ if (points.Count <= 3) return points;
+
+ // 按 X 坐标排序,若相同则按 Y 排序
+ var sorted = points.OrderBy(p => p.X).ThenBy(p => p.Y).ToList();
+
+ // 构建下凸包和上凸包
+ var lower = new List();
+ foreach (var p in sorted)
{
- centerX += vertex.X;
- centerY += vertex.Y;
- centerZ += vertex.Z;
+ while (lower.Count >= 2 && Cross(lower[lower.Count - 2], lower[lower.Count - 1], p) <= 0)
+ lower.RemoveAt(lower.Count - 1);
+ lower.Add(p);
}
- return new Vector3(centerX / vertices.Count, centerY / vertices.Count, centerZ / vertices.Count);
- }
-
- private static float GetAngle(Vector3 vertex, Vector3 center)
- {
- // 计算顶点与中心的方向向量
- Vector3 direction = vertex - center;
- // 在XY平面上计算角度
- float angle = (float)Math.Atan2(direction.Y, direction.X); // 返回的是弧度,[-π, π]
+ var upper = new List();
+ foreach (var p in sorted.AsEnumerable().Reverse())
+ {
+ while (upper.Count >= 2 && Cross(upper[upper.Count - 2], upper[upper.Count - 1], p) <= 0)
+ upper.RemoveAt(upper.Count - 1);
+ upper.Add(p);
+ }
- // 如果你想要角度范围 [0, 2π],可以做如下处理
- if (angle < 0)
+ // 合并凸包(移除重复点)
+ lower.RemoveAt(lower.Count - 1);
+ upper.RemoveAt(upper.Count - 1);
+ return lower.Concat(upper).ToList();
+ }
+
+ ///
+ /// 计算叉积 (p2-p0) × (p1-p0)
+ ///
+ private static float Cross(Vector3 p0, Vector3 p1, Vector3 p2)
+ {
+ return (p1.X - p0.X) * (p2.Z - p0.Z) - (p1.Z - p0.Z) * (p2.X - p0.X);
+ }
+ ///
+ /// 计算面的法线(基于顶点顺序)
+ ///
+ private static Vector3 CalculateFaceNormal(List vertices, Vector3 modelCenter)
+ {
+ // 使用前三个顶点计算初始法线
+ var p0 = vertices[0];
+ var p1 = vertices[1];
+ var p2 = vertices[2];
+ var edge1 = p1 - p0;
+ var edge2 = p2 - p0;
+ var normal = Vector3.Cross(edge1, edge2).Normalized();
+
+ // 确保法线朝外(面中心到模型中心的向量与法线方向一致)
+ var faceCenter = GetCenter(vertices);
+ var toModelCenter = modelCenter - faceCenter;
+ if (Vector3.Dot(normal, toModelCenter) > 0)
{
- angle += MathF.PI * 2;
+ normal = -normal;
}
- return angle;
+ return normal;
}
+
+ ///
+ /// 计算顶点列表的中心点
+ ///
+ private static Vector3 GetCenter(List vertices)
+ {
+ var center = Vector3.Zero;
+ foreach (var v in vertices) center += v;
+ return center / vertices.Count;
+ }
+}
+
+class FaceData
+{
+ public List Vertices { get; set; }
+ public Vector3 Normal { get; set; }
}
\ No newline at end of file
diff --git a/logo256.ico b/logo256.ico
new file mode 100644
index 0000000..1d4e59d
Binary files /dev/null and b/logo256.ico differ