You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

192 lines
6.1 KiB

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<Viewport3DTriangleEntity> entities, string outputPath)
{
string obj = GenerateObj(entities);
using (var writer = new StreamWriter(outputPath))
{
writer.Write(obj);
}
}
public static string GenerateObj(IEnumerable<Viewport3DTriangleEntity> triangles)
{
var groups = triangles.GroupBy(t => t.PlaneCode);
List<Vector3> allVertices = new List<Vector3>();
List<Vector3> allNormals = new List<Vector3>();
List<string> faceLines = new List<string>();
foreach (var group in groups)
{
var localVertices = new Dictionary<Vector3, int>();
var groupVertices = new List<Vector3>();
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<int, int>();
foreach (var v in groupVertices)
{
allVertices.Add(v);
vertexMap[localVertices[v]] = allVertices.Count; // OBJ索引从1开始
}
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}");
}
}
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();
}
/// <summary>
/// 凸包排序(Andrew's Monotone Chain 算法)
/// </summary>
private static List<Vector3> ConvexHullSort(List<Vector3> points)
{
if (points.Count <= 3) return points;
var sorted = points.OrderBy(p => p.X).ThenBy(p => p.Y).ToList();
var lower = new List<Vector3>();
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<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);
}
lower.RemoveAt(lower.Count - 1);
upper.RemoveAt(upper.Count - 1);
return lower.Concat(upper).ToList();
}
/// <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)
{
normal = -normal;
}
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;
}
}