using System.Globalization;
using System.Numerics;
using System.Text;
namespace XFEEDToolBox.Core.Rendering;
public readonly record struct MeshVertex(Vector3 Position, Vector3 Normal);
public sealed record ShipMesh(MeshVertex[] Vertices, string Source, string Sha256)
{
public int Triangles => Vertices.Length / 3;
}
/// <summary>Local mesh reader. Geometry is validated before any GPU allocation.</summary>
public static class ShipMeshReader
{
public const int MaximumTriangles = 2_000_000;
public const long MaximumFileBytes = 256 * 1024 * 1024;
public static ShipMesh Read(string path)
{
var file = new FileInfo(path);
if (!file.Exists) throw new FileNotFoundException("飞船模型文件不存在。", path);
if (file.Length is < 16 or > MaximumFileBytes) throw new InvalidDataException("模型文件大小超出限制。");
var bytes = File.ReadAllBytes(path);
var vertices = Path.GetExtension(path).ToLowerInvariant() switch
{
".obj" => ReadObj(Encoding.UTF8.GetString(bytes)),
".stl" => ReadStl(bytes),
_ => throw new InvalidDataException("模型格式应为 OBJ 或 STL。")
};
Normalize(vertices);
return new(vertices, path, Convert.ToHexString(System.Security.Cryptography.SHA256.HashData(bytes)));
}
public static ShipMesh Create(MeshVertex[] triangles, string source, string sha256)
{
if (triangles.Length == 0 || triangles.Length % 3 != 0 || triangles.Length / 3 > MaximumTriangles) throw new InvalidDataException("船体三角面数量无效。");
foreach (var vertex in triangles) { Validate(vertex.Position); Validate(vertex.Normal); }
Normalize(triangles);
return new(triangles, source, sha256);
}
public static MeshVertex[] ReadObj(string text)
{
List<Vector3> positions = [];
List<Vector3> normals = [];
List<MeshVertex> output = [];
using var reader = new StringReader(text);
while (reader.ReadLine() is { } line)
{
var comment = line.IndexOf('#');
if (comment >= 0) line = line[..comment];
var parts = line.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 0) continue;
if (parts[0] == "v" && parts.Length >= 4)
{
if (positions.Count >= MaximumTriangles * 3) throw new InvalidDataException("模型顶点过多。");
positions.Add(Vector(parts));
}
else if (parts[0] == "vn" && parts.Length >= 4) normals.Add(Vector(parts));
else if (parts[0] == "f" && parts.Length >= 4)
{
if (parts.Length > 4097) throw new InvalidDataException("模型多边形过大。");
(Vector3 Position, Vector3? Normal) Point(string face)
{
var indices = face.Split('/');
var p = Index(indices[0], positions.Count);
Vector3? normal = indices.Length > 2 && indices[2].Length > 0 ? normals[Index(indices[2], normals.Count)] : null;
return (positions[p], normal);
}
var first = Point(parts[1]);
for (var i = 2; i + 1 < parts.Length; i++)
{
var second = Point(parts[i]); var third = Point(parts[i + 1]);
AddTriangle(output, first.Position, second.Position, third.Position, first.Normal, second.Normal, third.Normal);
}
}
}
if (output.Count == 0) throw new InvalidDataException("模型没有可绘制的三角形。");
return output.ToArray();
}
public static MeshVertex[] ReadStl(byte[] data)
{
List<MeshVertex> output = [];
if (data.Length >= 84)
{
var count = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(data.AsSpan(80, 4));
if (84L + count * 50L == data.Length)
{
if (count > MaximumTriangles) throw new InvalidDataException("模型三角形过多。");
using var reader = new BinaryReader(new MemoryStream(data, false));
reader.BaseStream.Position = 84;
for (var i = 0; i < count; i++)
{
_ = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
var a = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
var b = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
var c = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
reader.ReadUInt16(); AddTriangle(output, a, b, c);
}
if (output.Count == 0) throw new InvalidDataException("模型没有有效三角形。");
return output.ToArray();
}
}
using var lines = new StringReader(Encoding.ASCII.GetString(data));
List<Vector3> triangle = [];
while (lines.ReadLine() is { } line)
{
var parts = line.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 4 && parts[0] == "vertex")
{
triangle.Add(Vector(parts));
if (triangle.Count == 3) { AddTriangle(output, triangle[0], triangle[1], triangle[2]); triangle.Clear(); }
}
}
if (output.Count == 0 || triangle.Count != 0) throw new InvalidDataException("STL 模型不完整。");
return output.ToArray();
}
private static int Index(string value, int count)
{
if (!int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var n) || n == 0) throw new InvalidDataException("OBJ 顶点索引无效。");
var result = n > 0 ? n - 1 : count + n;
if ((uint)result >= count) throw new InvalidDataException("OBJ 顶点索引越界。");
return result;
}
private static Vector3 Vector(string[] parts)
{
if (!float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var x) ||
!float.TryParse(parts[2], NumberStyles.Float, CultureInfo.InvariantCulture, out var y) ||
!float.TryParse(parts[3], NumberStyles.Float, CultureInfo.InvariantCulture, out var z)) throw new InvalidDataException("模型坐标无效。");
var result = new Vector3(x, y, z); Validate(result); return result;
}
private static void Validate(Vector3 v)
{
if (!float.IsFinite(v.X) || !float.IsFinite(v.Y) || !float.IsFinite(v.Z) || v.LengthSquared() > 1e20f) throw new InvalidDataException("模型坐标超出范围。");
}
private static Vector3 Normal(Vector3? given, Vector3 fallback) => given is { } n && float.IsFinite(n.LengthSquared()) && n.LengthSquared() > 1e-12f ? Vector3.Normalize(n) : fallback;
private static void AddTriangle(List<MeshVertex> output, Vector3 a, Vector3 b, Vector3 c, Vector3? na = null, Vector3? nb = null, Vector3? nc = null)
{
Validate(a); Validate(b); Validate(c);
var normal = Vector3.Cross(b - a, c - a);
if (normal.LengthSquared() <= 1e-16f) return;
normal = Vector3.Normalize(normal);
if (output.Count / 3 >= MaximumTriangles) throw new InvalidDataException("模型三角形过多。");
output.Add(new(a, Normal(na, normal))); output.Add(new(b, Normal(nb, normal))); output.Add(new(c, Normal(nc, normal)));
}
private static void Normalize(MeshVertex[] vertices)
{
var min = new Vector3(float.PositiveInfinity); var max = new Vector3(float.NegativeInfinity);
foreach (var v in vertices) { min = Vector3.Min(min, v.Position); max = Vector3.Max(max, v.Position); }
var centre = (min + max) * .5f; var size = max - min; var span = Math.Max(size.X, Math.Max(size.Y, size.Z));
if (!float.IsFinite(span) || span <= 1e-8f) throw new InvalidDataException("模型尺寸无效。");
for (var i = 0; i < vertices.Length; i++) vertices[i] = vertices[i] with { Position = (vertices[i].Position - centre) * (2 / span) };
}
}
using System.Globalization;
using System.Numerics;
using System.Text;
namespace XFEEDToolBox.Core.Rendering;
public readonly record struct MeshVertex(Vector3 Position, Vector3 Normal);
public sealed record ShipMesh(MeshVertex[] Vertices, string Source, string Sha256)
{
public int Triangles => Vertices.Length / 3;
}
/// <summary>Local mesh reader. Geometry is validated before any GPU allocation.</summary>
public static class ShipMeshReader
{
public const int MaximumTriangles = 2_000_000;
public const long MaximumFileBytes = 256 * 1024 * 1024;
public static ShipMesh Read(string path)
{
var file = new FileInfo(path);
if (!file.Exists) throw new FileNotFoundException("飞船模型文件不存在。", path);
if (file.Length is < 16 or > MaximumFileBytes) throw new InvalidDataException("模型文件大小超出限制。");
var bytes = File.ReadAllBytes(path);
var vertices = Path.GetExtension(path).ToLowerInvariant() switch
{
".obj" => ReadObj(Encoding.UTF8.GetString(bytes)),
".stl" => ReadStl(bytes),
_ => throw new InvalidDataException("模型格式应为 OBJ 或 STL。")
};
Normalize(vertices);
return new(vertices, path, Convert.ToHexString(System.Security.Cryptography.SHA256.HashData(bytes)));
}
public static ShipMesh Create(MeshVertex[] triangles, string source, string sha256)
{
if (triangles.Length == 0 || triangles.Length % 3 != 0 || triangles.Length / 3 > MaximumTriangles) throw new InvalidDataException("船体三角面数量无效。");
foreach (var vertex in triangles) { Validate(vertex.Position); Validate(vertex.Normal); }
Normalize(triangles);
return new(triangles, source, sha256);
}
public static MeshVertex[] ReadObj(string text)
{
List<Vector3> positions = [];
List<Vector3> normals = [];
List<MeshVertex> output = [];
using var reader = new StringReader(text);
while (reader.ReadLine() is { } line)
{
var comment = line.IndexOf('#');
if (comment >= 0) line = line[..comment];
var parts = line.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 0) continue;
if (parts[0] == "v" && parts.Length >= 4)
{
if (positions.Count >= MaximumTriangles * 3) throw new InvalidDataException("模型顶点过多。");
positions.Add(Vector(parts));
}
else if (parts[0] == "vn" && parts.Length >= 4) normals.Add(Vector(parts));
else if (parts[0] == "f" && parts.Length >= 4)
{
if (parts.Length > 4097) throw new InvalidDataException("模型多边形过大。");
(Vector3 Position, Vector3? Normal) Point(string face)
{
var indices = face.Split('/');
var p = Index(indices[0], positions.Count);
Vector3? normal = indices.Length > 2 && indices[2].Length > 0 ? normals[Index(indices[2], normals.Count)] : null;
return (positions[p], normal);
}
var first = Point(parts[1]);
for (var i = 2; i + 1 < parts.Length; i++)
{
var second = Point(parts[i]); var third = Point(parts[i + 1]);
AddTriangle(output, first.Position, second.Position, third.Position, first.Normal, second.Normal, third.Normal);
}
}
}
if (output.Count == 0) throw new InvalidDataException("模型没有可绘制的三角形。");
return output.ToArray();
}
public static MeshVertex[] ReadStl(byte[] data)
{
List<MeshVertex> output = [];
if (data.Length >= 84)
{
var count = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(data.AsSpan(80, 4));
if (84L + count * 50L == data.Length)
{
if (count > MaximumTriangles) throw new InvalidDataException("模型三角形过多。");
using var reader = new BinaryReader(new MemoryStream(data, false));
reader.BaseStream.Position = 84;
for (var i = 0; i < count; i++)
{
_ = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
var a = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
var b = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
var c = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
reader.ReadUInt16(); AddTriangle(output, a, b, c);
}
if (output.Count == 0) throw new InvalidDataException("模型没有有效三角形。");
return output.ToArray();
}
}
using var lines = new StringReader(Encoding.ASCII.GetString(data));
List<Vector3> triangle = [];
while (lines.ReadLine() is { } line)
{
var parts = line.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 4 && parts[0] == "vertex")
{
triangle.Add(Vector(parts));
if (triangle.Count == 3) { AddTriangle(output, triangle[0], triangle[1], triangle[2]); triangle.Clear(); }
}
}
if (output.Count == 0 || triangle.Count != 0) throw new InvalidDataException("STL 模型不完整。");
return output.ToArray();
}
private static int Index(string value, int count)
{
if (!int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var n) || n == 0) throw new InvalidDataException("OBJ 顶点索引无效。");
var result = n > 0 ? n - 1 : count + n;
if ((uint)result >= count) throw new InvalidDataException("OBJ 顶点索引越界。");
return result;
}
private static Vector3 Vector(string[] parts)
{
if (!float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var x) ||
!float.TryParse(parts[2], NumberStyles.Float, CultureInfo.InvariantCulture, out var y) ||
!float.TryParse(parts[3], NumberStyles.Float, CultureInfo.InvariantCulture, out var z)) throw new InvalidDataException("模型坐标无效。");
var result = new Vector3(x, y, z); Validate(result); return result;
}
private static void Validate(Vector3 v)
{
if (!float.IsFinite(v.X) || !float.IsFinite(v.Y) || !float.IsFinite(v.Z) || v.LengthSquared() > 1e20f) throw new InvalidDataException("模型坐标超出范围。");
}
private static Vector3 Normal(Vector3? given, Vector3 fallback) => given is { } n && float.IsFinite(n.LengthSquared()) && n.LengthSquared() > 1e-12f ? Vector3.Normalize(n) : fallback;
private static void AddTriangle(List<MeshVertex> output, Vector3 a, Vector3 b, Vector3 c, Vector3? na = null, Vector3? nb = null, Vector3? nc = null)
{
Validate(a); Validate(b); Validate(c);
var normal = Vector3.Cross(b - a, c - a);
if (normal.LengthSquared() <= 1e-16f) return;
normal = Vector3.Normalize(normal);
if (output.Count / 3 >= MaximumTriangles) throw new InvalidDataException("模型三角形过多。");
output.Add(new(a, Normal(na, normal))); output.Add(new(b, Normal(nb, normal))); output.Add(new(c, Normal(nc, normal)));
}
private static void Normalize(MeshVertex[] vertices)
{
var min = new Vector3(float.PositiveInfinity); var max = new Vector3(float.NegativeInfinity);
foreach (var v in vertices) { min = Vector3.Min(min, v.Position); max = Vector3.Max(max, v.Position); }
var centre = (min + max) * .5f; var size = max - min; var span = Math.Max(size.X, Math.Max(size.Y, size.Z));
if (!float.IsFinite(span) || span <= 1e-8f) throw new InvalidDataException("模型尺寸无效。");
for (var i = 0; i < vertices.Length; i++) vertices[i] = vertices[i] with { Position = (vertices[i].Position - centre) * (2 / span) };
}
}