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 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(); var typeIndexes = new Dictionary(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); } }