XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 0
UTF-8
using Sandbox.Definitions;
using SpaceEngineersBlueprintEditor.Model;
using VRage.Game;
using VRageMath;

namespace SpaceEngineersBlueprintEditor.Utilities;

public sealed class Blueprint3DSceneBuildResult
{
    public required Blueprint3DScene Scene { get; init; }

    public required IReadOnlyDictionary<int, Blueprint3DBlockReference> BlockReferences { get; init; }
}

public sealed class Blueprint3DBlockReference
{
    public required MyObjectBuilder_CubeGrid Grid { get; init; }

    public required MyObjectBuilder_CubeBlock Block { get; init; }

    public required int GridIndex { get; init; }
}

public static class Blueprint3DSceneBuilder
{
    public static Blueprint3DSceneBuildResult Build(MyObjectBuilder_ShipBlueprintDefinition blueprint)
    {
        var scene = new Blueprint3DScene { Name = blueprint.DisplayName ?? "Blueprint" };
        var references = new Dictionary<int, Blueprint3DBlockReference>();
        var typeIndexes = new Dictionary<string, int>(StringComparer.Ordinal);
        var grids = blueprint.CubeGrids ?? [];
        var worldOrigin = GetWorldOrigin(grids);
        var id = 0;

        for (var gridIndex = 0; gridIndex < grids.Length; gridIndex++)
        {
            var grid = grids[gridIndex];
            scene.Grids.Add(string.IsNullOrWhiteSpace(grid.DisplayName) ? $"Grid {gridIndex + 1}" : grid.DisplayName);
            var basis = GetGridBasis(grid);
            var cellSize = grid.GridSizeEnum == MyCubeSize.Large ? 2.5 : 0.5;

            foreach (var block in grid.CubeBlocks ?? [])
            {
                var definition = TryGetDefinition(block);
                var definitionSize = definition?.Size ?? Vector3I.One;
                var blockForward = DirectionVector(block.BlockOrientation.Forward);
                var blockUp = DirectionVector(block.BlockOrientation.Up);
                var blockRight = SceneVector.Cross(blockForward, blockUp);
                var blockBackward = -blockForward;
                var occupiedSize = GetOccupiedSize(definitionSize, blockRight, blockUp, blockBackward);
                var localCenter = new SceneVector(
                    block.Min.X + (occupiedSize.X - 1) / 2d,
                    block.Min.Y + (occupiedSize.Y - 1) / 2d,
                    block.Min.Z + (occupiedSize.Z - 1) / 2d) * cellSize;
                var worldCenter = basis.Origin + basis.Transform(localCenter) - worldOrigin;
                var axisX = basis.Transform(blockRight).Normalized();
                var axisY = basis.Transform(blockUp).Normalized();
                var axisZ = basis.Transform(blockBackward).Normalized();
                var subtype = definition?.Id.SubtypeName ?? block.SubtypeName ?? block.GetType().Name;
                var displayName = definition?.DisplayNameText;
                if (string.IsNullOrWhiteSpace(displayName))
                {
                    displayName = subtype;
                }

                var typeKey = $"{displayName}\0{subtype}";
                if (!typeIndexes.TryGetValue(typeKey, out var typeIndex))
                {
                    typeIndex = scene.Types.Count;
                    typeIndexes[typeKey] = typeIndex;
                    scene.Types.Add(new Blueprint3DBlockType { Name = displayName, Subtype = subtype });
                }

                scene.Blocks.Add(new Blueprint3DSceneBlock
                {
                    Id = id,
                    GridIndex = gridIndex,
                    TypeIndex = typeIndex,
                    Position = worldCenter.ToFloatArray(),
                    Size =
                    [
                        (float)(definitionSize.X * cellSize),
                        (float)(definitionSize.Y * cellSize),
                        (float)(definitionSize.Z * cellSize)
                    ],
                    AxisX = axisX.ToFloatArray(),
                    AxisY = axisY.ToFloatArray(),
                    AxisZ = axisZ.ToFloatArray(),
                    Color = ConvertColor(block),
                    Minimum = [block.Min.X, block.Min.Y, block.Min.Z]
                });
                references[id] = new Blueprint3DBlockReference
                {
                    Grid = grid,
                    Block = block,
                    GridIndex = gridIndex
                };
                id++;
            }
        }

        return new Blueprint3DSceneBuildResult { Scene = scene, BlockReferences = references };
    }

    private static MyCubeBlockDefinition? TryGetDefinition(MyObjectBuilder_CubeBlock block)
    {
        try
        {
            return MyDefinitionManager.Static.GetCubeBlockDefinition(block);
        }
        catch
        {
            return null;
        }
    }

    private static SceneVector GetWorldOrigin(MyObjectBuilder_CubeGrid[] grids)
    {
        foreach (var grid in grids)
        {
            if (grid.PositionAndOrientation is { } pose)
            {
                return new SceneVector(pose.Position.X, pose.Position.Y, pose.Position.Z);
            }
        }
        return SceneVector.Zero;
    }

    private static GridBasis GetGridBasis(MyObjectBuilder_CubeGrid grid)
    {
        if (grid.PositionAndOrientation is { } pose)
        {
            var forward = new SceneVector(pose.Forward.X, pose.Forward.Y, pose.Forward.Z).Normalized();
            var up = new SceneVector(pose.Up.X, pose.Up.Y, pose.Up.Z).Normalized();
            return new GridBasis(
                new SceneVector(pose.Position.X, pose.Position.Y, pose.Position.Z),
                SceneVector.Cross(forward, up).Normalized(),
                up,
                -forward);
        }

        return new GridBasis(SceneVector.Zero, SceneVector.UnitX, SceneVector.UnitY, SceneVector.UnitZ);
    }

    private static SceneVector GetOccupiedSize(
        Vector3I size,
        SceneVector right,
        SceneVector up,
        SceneVector backward)
    {
        return new SceneVector(
            Math.Abs(right.X) * size.X + Math.Abs(up.X) * size.Y + Math.Abs(backward.X) * size.Z,
            Math.Abs(right.Y) * size.X + Math.Abs(up.Y) * size.Y + Math.Abs(backward.Y) * size.Z,
            Math.Abs(right.Z) * size.X + Math.Abs(up.Z) * size.Y + Math.Abs(backward.Z) * size.Z);
    }

    private static SceneVector DirectionVector(Base6Directions.Direction direction)
    {
        return direction switch
        {
            Base6Directions.Direction.Forward => new SceneVector(0, 0, -1),
            Base6Directions.Direction.Backward => new SceneVector(0, 0, 1),
            Base6Directions.Direction.Left => new SceneVector(-1, 0, 0),
            Base6Directions.Direction.Right => new SceneVector(1, 0, 0),
            Base6Directions.Direction.Up => new SceneVector(0, 1, 0),
            Base6Directions.Direction.Down => new SceneVector(0, -1, 0),
            _ => new SceneVector(0, 0, -1)
        };
    }

    private static float[] ConvertColor(MyObjectBuilder_CubeBlock block)
    {
        var hue = block.ColorMaskHSV.X % 1f;
        if (hue < 0)
        {
            hue += 1f;
        }
        var saturation = Math.Clamp(block.ColorMaskHSV.Y + 0.8f, 0f, 1f);
        var value = Math.Clamp(block.ColorMaskHSV.Z + 0.55f, 0.12f, 1f);
        var chroma = value * saturation;
        var sector = hue * 6f;
        var second = chroma * (1f - Math.Abs(sector % 2f - 1f));
        var (red, green, blue) = sector switch
        {
            < 1f => (chroma, second, 0f),
            < 2f => (second, chroma, 0f),
            < 3f => (0f, chroma, second),
            < 4f => (0f, second, chroma),
            < 5f => (second, 0f, chroma),
            _ => (chroma, 0f, second)
        };
        var match = value - chroma;
        return [red + match, green + match, blue + match];
    }

    private readonly record struct GridBasis(SceneVector Origin, SceneVector Right, SceneVector Up, SceneVector Backward)
    {
        public SceneVector Transform(SceneVector value)
        {
            return Right * value.X + Up * value.Y + Backward * value.Z;
        }
    }

    private readonly record struct SceneVector(double X, double Y, double Z)
    {
        public static SceneVector Zero => new(0, 0, 0);
        public static SceneVector UnitX => new(1, 0, 0);
        public static SceneVector UnitY => new(0, 1, 0);
        public static SceneVector UnitZ => new(0, 0, 1);

        public double Length => Math.Sqrt(X * X + Y * Y + Z * Z);

        public SceneVector Normalized() => Length <= double.Epsilon ? Zero : this * (1d / Length);

        public float[] ToFloatArray() => [(float)X, (float)Y, (float)Z];

        public static SceneVector Cross(SceneVector left, SceneVector right)
        {
            return new SceneVector(
                left.Y * right.Z - left.Z * right.Y,
                left.Z * right.X - left.X * right.Z,
                left.X * right.Y - left.Y * right.X);
        }

        public static SceneVector operator +(SceneVector left, SceneVector right) =>
            new(left.X + right.X, left.Y + right.Y, left.Z + right.Z);

        public static SceneVector operator -(SceneVector left, SceneVector right) =>
            new(left.X - right.X, left.Y - right.Y, left.Z - right.Z);

        public static SceneVector operator -(SceneVector value) => new(-value.X, -value.Y, -value.Z);

        public static SceneVector operator *(SceneVector value, double scale) =>
            new(value.X * scale, value.Y * scale, value.Z * scale);
    }
}