using System.IO; using System.Text; using HelixToolkit.SharpDX.Core; using SharpDX; using SparkClient.Views.UserControl.ViewportData.Entity; using SparkClient.Views.UserControl.ViewportData.Enum; 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) { // 合并所有顶点并去重 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 { Vertices = sortedVertices, Normal = normal }; } // 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] = index++; writer.WriteLine($"v {vertex.X} {vertex.Y} {vertex.Z}"); } } } // 写入法线(每个面一个法线) var normalIndexMap = new Dictionary(); index = 1; foreach (var face in faceData.Values) { if (!normalIndexMap.ContainsKey(face.Normal)) { 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]; // 正面(顺时针) writer.Write("f "); foreach (var vertex in face.Vertices) { writer.Write($"{vertexIndexMap[vertex]}//{normalIndex} "); } writer.WriteLine(); // 反面(逆时针) writer.Write("f "); foreach (var vertex in face.Vertices.AsEnumerable().Reverse()) { writer.Write($"{vertexIndexMap[vertex]}//{normalIndex} "); } writer.WriteLine(); } } } /// /// 凸包排序(Andrew's Monotone Chain 算法) /// private static List ConvexHullSort(List points) { 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) { while (lower.Count >= 2 && Cross(lower[lower.Count - 2], lower[lower.Count - 1], p) <= 0) lower.RemoveAt(lower.Count - 1); lower.Add(p); } 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); } // 合并凸包(移除重复点) 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) { normal = -normal; } 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; } }