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) { string obj = GenerateObj(entities); // Step 2: 写入 OBJ 文件 using (var writer = new StreamWriter(outputPath)) { writer.Write(obj); } } public static string GenerateObj(IEnumerable triangles) { var groups = triangles.GroupBy(t => t.PlaneCode); List allVertices = new List(); List allNormals = new List(); List faceLines = new List(); foreach (var group in groups) { // Step 1: 组内顶点去重 var localVertices = new Dictionary(); var groupVertices = new List(); foreach (var triangle in group) { AddVertex(triangle.Point1); AddVertex(triangle.Point2); AddVertex(triangle.Point3); } void AddVertex(Vector3 v) { if (!localVertices.ContainsKey(v)) { localVertices[v] = groupVertices.Count; groupVertices.Add(v); } } // 映射组内索引到全局索引 var vertexMap = new Dictionary(); foreach (var v in groupVertices) { allVertices.Add(v); vertexMap[localVertices[v]] = allVertices.Count; // OBJ索引从1开始 } // Step 2: 处理法线和面 Vector3 baseNormal = Vector3.Zero; bool isFirstTriangle = true; int vnIndex = 0; foreach (var triangle in group) { Vector3 p1 = triangle.Point1; Vector3 p2 = triangle.Point2; Vector3 p3 = triangle.Point3; int localI1 = localVertices[p1]; int localI2 = localVertices[p2]; int localI3 = localVertices[p3]; int globalI1 = vertexMap[localI1]; int globalI2 = vertexMap[localI2]; int globalI3 = vertexMap[localI3]; // 计算法线 Vector3 edge1 = p2 - p1; Vector3 edge2 = p3 - p1; Vector3 normal = Vector3.Cross(edge1, edge2); normal.Normalize(); if (isFirstTriangle) { baseNormal = normal; isFirstTriangle = false; allNormals.Add(baseNormal); vnIndex = allNormals.Count; } // 调整法线方向 float dot = Vector3.Dot(normal, baseNormal); if (dot < 0) { (globalI2, globalI3) = (globalI3, globalI2); } faceLines.Add($"f {globalI1}//{vnIndex} {globalI2}//{vnIndex} {globalI3}//{vnIndex}"); } } // 构建OBJ内容 StringBuilder objBuilder = new StringBuilder(); // 顶点 foreach (var v in allVertices) { objBuilder.AppendLine($"v {v.X:F6} {v.Y:F6} {v.Z:F6}"); } // 法线 foreach (var vn in allNormals) { objBuilder.AppendLine($"vn {vn.X:F6} {vn.Y:F6} {vn.Z:F6}"); } // 面 foreach (var face in faceLines) { objBuilder.AppendLine(face); } return objBuilder.ToString(); } /// /// 凸包排序(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; } }