fix: 模型导出优化、图标质量优化

master
Tongg 4 months ago
parent 70a0d5a292
commit eb78978e50
  1. 2
      SparkClient.csproj
  2. 2
      Views/UserControl/Viewport3D.xaml.cs
  3. 254
      Views/UserControl/ViewportData/Helper/ObjExporter.cs
  4. BIN
      logo256.ico

@ -6,7 +6,7 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<ApplicationIcon>logo.ico</ApplicationIcon>
<ApplicationIcon>logo256.ico</ApplicationIcon>
<Title>SparkClient - 星辉</Title>
<Authors>SparkClient Team</Authors>
<Description>SparkClient钻石检测工具</Description>

@ -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;
}

@ -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()));
}
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 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<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)
{
sb.AppendLine($"v {vertex.X} {vertex.Y} {vertex.Z}");
}
// 3. 写入每个面(f行),包括正面和反面
foreach (var face in resultPoints)
// 写入法线(每个面一个法线)
var normalIndexMap = new Dictionary<Vector3, int>();
index = 1;
foreach (var face in faceData.Values)
{
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;
// 在XY平面上计算角度
float angle = (float)Math.Atan2(direction.Y, direction.X); // 返回的是弧度,[-π, π]
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);
}
// 如果你想要角度范围 [0, 2π],可以做如下处理
if (angle < 0)
// 合并凸包(移除重复点)
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)
{
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; }
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Loading…
Cancel
Save