XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
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) };
    }
}