|
|
|
@ -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<Viewport3DTriangleEntity> entities, string outputPath) |
|
|
|
|
{ |
|
|
|
|
// 分组:按面代码 PlaneCode 分组 |
|
|
|
|
var faceGroups = entities |
|
|
|
|
.GroupBy(e => e.PlaneCode) |
|
|
|
|
.ToDictionary(g => g.Key, g => g.ToList()); |
|
|
|
|
|
|
|
|
|
// 存储每个面的顶点和法线 |
|
|
|
|
var faceData = new Dictionary<string, FaceData>(); |
|
|
|
|
|
|
|
|
|
// Step 1: 处理每个面,生成顶点列表并计算法线 |
|
|
|
|
foreach (var group in faceGroups) |
|
|
|
|
{ |
|
|
|
|
//分组 |
|
|
|
|
Dictionary<string, List<Viewport3DTriangleEntity>> feactList = entities |
|
|
|
|
.Where(entity => entity.PlaneType != PlaneType.Girdle) |
|
|
|
|
.GroupBy(entity => entity.PlaneCode) |
|
|
|
|
.ToDictionary(group => group.Key, group => group.ToList()); |
|
|
|
|
//腰 单组 |
|
|
|
|
// List<Viewport3DTriangleEntity> waistList = entities |
|
|
|
|
// .Where(entity => entity.PlaneType == PlaneType.Girdle) |
|
|
|
|
// .ToList(); |
|
|
|
|
Dictionary<string, List<Viewport3DTriangleEntity>> gridleList = entities |
|
|
|
|
.Where(entity => entity.PlaneType == PlaneType.Girdle) |
|
|
|
|
.GroupBy(entity => entity.PlaneCode) |
|
|
|
|
.ToDictionary(group => group.Key, group => group.ToList()); |
|
|
|
|
|
|
|
|
|
//同一个面只保留外边框(除了腰) |
|
|
|
|
Dictionary<string, List<Vector3>> resultPoints = new Dictionary<string, List<Vector3>>(); |
|
|
|
|
|
|
|
|
|
foreach (var dic in feactList) |
|
|
|
|
{ |
|
|
|
|
List<Vector3> tempPoints = new List<Vector3>(); |
|
|
|
|
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<Vector3>(tempPoints).ToList())); |
|
|
|
|
} |
|
|
|
|
// 合并所有顶点并去重 |
|
|
|
|
var vertices = group.Value |
|
|
|
|
.SelectMany(e => new[] { e.Point1, e.Point2, e.Point3 }) |
|
|
|
|
.Distinct() |
|
|
|
|
.ToList(); |
|
|
|
|
|
|
|
|
|
foreach (var dic in gridleList) |
|
|
|
|
{ |
|
|
|
|
List<Vector3> tempPoints = new List<Vector3>(); |
|
|
|
|
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<Vector3>(tempPoints).ToList())); |
|
|
|
|
} |
|
|
|
|
// List<Vector3> selFaceVector = new List<Vector3>(); |
|
|
|
|
// 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<Vector3> uniqueVertices = new List<Vector3>(); |
|
|
|
|
Dictionary<Vector3, int> vertexIndexMap = new Dictionary<Vector3, int>(); |
|
|
|
|
|
|
|
|
|
// 1. 对每个面生成顶点和面 |
|
|
|
|
foreach (var face in resultPoints) |
|
|
|
|
// 按凸包算法排序顶点(避免交叉) |
|
|
|
|
var sortedVertices = ConvexHullSort(vertices); |
|
|
|
|
|
|
|
|
|
// 计算面的法线(基于排序后的顶点) |
|
|
|
|
var normal = CalculateFaceNormal(sortedVertices, ViewportManager.CenterVector); |
|
|
|
|
|
|
|
|
|
// 存储面数据 |
|
|
|
|
faceData[group.Key] = new FaceData |
|
|
|
|
{ |
|
|
|
|
// 对每个面,获取它的顶点列表 |
|
|
|
|
List<Vector3> 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<Vector3, int>(); |
|
|
|
|
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) |
|
|
|
|
// 写入法线(每个面一个法线) |
|
|
|
|
var normalIndexMap = new Dictionary<Vector3, int>(); |
|
|
|
|
index = 1; |
|
|
|
|
foreach (var face in faceData.Values) |
|
|
|
|
{ |
|
|
|
|
sb.AppendLine($"v {vertex.X} {vertex.Y} {vertex.Z}"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 3. 写入每个面(f行),包括正面和反面 |
|
|
|
|
foreach (var face in resultPoints) |
|
|
|
|
{ |
|
|
|
|
sb.AppendLine($"# Face: {face.Key}"); |
|
|
|
|
|
|
|
|
|
// 获取面上的所有顶点并排序 |
|
|
|
|
List<Vector3> 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<Vector3> vertices) |
|
|
|
|
/// <summary> |
|
|
|
|
/// 凸包排序(Andrew's Monotone Chain 算法) |
|
|
|
|
/// </summary> |
|
|
|
|
private static List<Vector3> ConvexHullSort(List<Vector3> 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<Vector3>(); |
|
|
|
|
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; |
|
|
|
|
var upper = new List<Vector3>(); |
|
|
|
|
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); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 在XY平面上计算角度 |
|
|
|
|
float angle = (float)Math.Atan2(direction.Y, direction.X); // 返回的是弧度,[-π, π] |
|
|
|
|
// 合并凸包(移除重复点) |
|
|
|
|
lower.RemoveAt(lower.Count - 1); |
|
|
|
|
upper.RemoveAt(upper.Count - 1); |
|
|
|
|
return lower.Concat(upper).ToList(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 如果你想要角度范围 [0, 2π],可以做如下处理 |
|
|
|
|
if (angle < 0) |
|
|
|
|
/// <summary> |
|
|
|
|
/// 计算叉积 (p2-p0) × (p1-p0) |
|
|
|
|
/// </summary> |
|
|
|
|
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); |
|
|
|
|
} |
|
|
|
|
/// <summary> |
|
|
|
|
/// 计算面的法线(基于顶点顺序) |
|
|
|
|
/// </summary> |
|
|
|
|
private static Vector3 CalculateFaceNormal(List<Vector3> 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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// 计算顶点列表的中心点 |
|
|
|
|
/// </summary> |
|
|
|
|
private static Vector3 GetCenter(List<Vector3> vertices) |
|
|
|
|
{ |
|
|
|
|
var center = Vector3.Zero; |
|
|
|
|
foreach (var v in vertices) center += v; |
|
|
|
|
return center / vertices.Count; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class FaceData |
|
|
|
|
{ |
|
|
|
|
public List<Vector3> Vertices { get; set; } |
|
|
|
|
public Vector3 Normal { get; set; } |
|
|
|
|
} |