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