Files
space-game/apps/backend/Simulation/ScenarioLoader.Spatial.cs

218 lines
7.9 KiB
C#

using SpaceGame.Simulation.Api.Data;
namespace SpaceGame.Simulation.Api.Simulation;
public sealed partial class ScenarioLoader
{
private static SystemSpatialGraph BuildSystemSpatialGraph(SystemRuntime system)
{
var nodes = new List<NodeRuntime>();
var bubbles = new List<LocalBubbleRuntime>();
var lagrangeNodesByPlanetIndex = new Dictionary<int, Dictionary<string, NodeRuntime>>();
var starNode = AddSpatialNode(
nodes,
bubbles,
id: $"node-{system.Definition.Id}-star",
systemId: system.Definition.Id,
kind: SpatialNodeKind.Star,
position: Vector3.Zero,
radius: MathF.Max(system.Definition.GravityWellRadius + StarBubbleRadiusPadding, 180f));
for (var planetIndex = 0; planetIndex < system.Definition.Planets.Count; planetIndex += 1)
{
var planet = system.Definition.Planets[planetIndex];
var planetNodeId = $"node-{system.Definition.Id}-planet-{planetIndex + 1}";
var planetPosition = ComputePlanetPosition(planet);
var planetNode = AddSpatialNode(
nodes,
bubbles,
id: planetNodeId,
systemId: system.Definition.Id,
kind: SpatialNodeKind.Planet,
position: planetPosition,
radius: MathF.Max(planet.Size + PlanetBubbleRadiusPadding, 120f),
parentNodeId: starNode.Id);
var lagrangeNodes = new Dictionary<string, NodeRuntime>(StringComparer.Ordinal);
foreach (var point in EnumeratePlanetLagrangePoints(planetPosition, planet.OrbitRadius, planetIndex))
{
var lagrangeNode = AddSpatialNode(
nodes,
bubbles,
id: $"node-{system.Definition.Id}-planet-{planetIndex + 1}-{point.Designation.ToLowerInvariant()}",
systemId: system.Definition.Id,
kind: SpatialNodeKind.LagrangePoint,
position: point.Position,
radius: LagrangeBubbleRadius,
parentNodeId: planetNode.Id,
orbitReferenceId: point.Designation);
lagrangeNodes[point.Designation] = lagrangeNode;
}
lagrangeNodesByPlanetIndex[planetIndex] = lagrangeNodes;
if (planet.MoonCount <= 0)
{
continue;
}
var moonOrbitRadius = MathF.Max(planet.Size + 48f, 42f);
for (var moonIndex = 0; moonIndex < planet.MoonCount; moonIndex += 1)
{
var moonPosition = ComputeMoonPosition(planetPosition, moonOrbitRadius, moonIndex, planetIndex);
AddSpatialNode(
nodes,
bubbles,
id: $"node-{system.Definition.Id}-planet-{planetIndex + 1}-moon-{moonIndex + 1}",
systemId: system.Definition.Id,
kind: SpatialNodeKind.Moon,
position: moonPosition,
radius: MoonBubbleRadiusPadding + 24f,
parentNodeId: planetNode.Id);
moonOrbitRadius += 30f;
}
}
return new SystemSpatialGraph(system.Definition.Id, nodes, bubbles, lagrangeNodesByPlanetIndex);
}
private static NodeRuntime AddSpatialNode(
ICollection<NodeRuntime> nodes,
ICollection<LocalBubbleRuntime> bubbles,
string id,
string systemId,
SpatialNodeKind kind,
Vector3 position,
float radius,
string? parentNodeId = null,
string? orbitReferenceId = null)
{
var bubbleId = $"bubble-{id}";
var node = new NodeRuntime
{
Id = id,
SystemId = systemId,
Kind = kind,
Position = position,
BubbleId = bubbleId,
ParentNodeId = parentNodeId,
OrbitReferenceId = orbitReferenceId,
};
nodes.Add(node);
bubbles.Add(new LocalBubbleRuntime
{
Id = bubbleId,
NodeId = id,
SystemId = systemId,
Radius = radius,
});
return node;
}
private static IEnumerable<LagrangePointPlacement> EnumeratePlanetLagrangePoints(Vector3 planetPosition, float orbitRadius, int planetIndex)
{
var radial = NormalizeOrFallback(planetPosition, new Vector3(1f, 0f, 0f));
var tangential = new Vector3(-radial.Z, 0f, radial.X);
var offset = MathF.Max(orbitRadius * 0.18f, 72f + (planetIndex * 6f));
var triangularAngle = MathF.PI / 3f;
yield return new LagrangePointPlacement("L1", Add(planetPosition, Scale(radial, -offset)));
yield return new LagrangePointPlacement("L2", Add(planetPosition, Scale(radial, offset)));
yield return new LagrangePointPlacement("L3", Scale(radial, -orbitRadius));
yield return new LagrangePointPlacement(
"L4",
Add(Scale(radial, orbitRadius * MathF.Cos(triangularAngle)), Scale(tangential, orbitRadius * MathF.Sin(triangularAngle))));
yield return new LagrangePointPlacement(
"L5",
Add(Scale(radial, orbitRadius * MathF.Cos(triangularAngle)), Scale(tangential, -orbitRadius * MathF.Sin(triangularAngle))));
}
private static StationPlacement ResolveStationPlacement(
InitialStationDefinition plan,
SystemRuntime system,
SystemSpatialGraph graph,
IReadOnlyCollection<NodeRuntime> existingNodes)
{
if (plan.PlanetIndex is int planetIndex &&
graph.LagrangeNodesByPlanetIndex.TryGetValue(planetIndex, out var lagrangeNodes))
{
var designation = ResolveLagrangeDesignation(plan.LagrangeSide);
if (lagrangeNodes.TryGetValue(designation, out var lagrangeNode))
{
return new StationPlacement(lagrangeNode, lagrangeNode.Position);
}
}
if (plan.Position is { Length: 3 })
{
var targetPosition = NormalizeScenarioPoint(system, plan.Position);
var preferredNode = existingNodes
.Where((node) => node.SystemId == system.Definition.Id && node.Kind == SpatialNodeKind.LagrangePoint)
.OrderBy((node) => node.Position.DistanceTo(targetPosition))
.FirstOrDefault()
?? existingNodes
.Where((node) => node.SystemId == system.Definition.Id)
.OrderBy((node) => node.Position.DistanceTo(targetPosition))
.First();
return new StationPlacement(preferredNode, preferredNode.Position);
}
var fallbackNode = graph.Nodes
.FirstOrDefault((node) => node.Kind == SpatialNodeKind.LagrangePoint && string.IsNullOrEmpty(node.OccupyingStructureId))
?? graph.Nodes.First((node) => node.Kind == SpatialNodeKind.Planet);
return new StationPlacement(fallbackNode, fallbackNode.Position);
}
private static string ResolveLagrangeDesignation(int? lagrangeSide) => lagrangeSide switch
{
< 0 => "L4",
> 0 => "L5",
_ => "L1",
};
private static Vector3 ComputePlanetPosition(PlanetDefinition planet)
{
var angle = DegreesToRadians(planet.OrbitPhaseAtEpoch);
var x = MathF.Cos(angle) * planet.OrbitRadius;
var z = MathF.Sin(angle) * planet.OrbitRadius;
return new Vector3(x, 0f, z);
}
private static Vector3 ComputeMoonPosition(Vector3 planetPosition, float orbitRadius, int moonIndex, int planetIndex)
{
var angle = ((MathF.PI * 2f) / MathF.Max(1, moonIndex + 3)) * (moonIndex + 1) + (planetIndex * 0.37f);
return Add(planetPosition, new Vector3(MathF.Cos(angle) * orbitRadius, 0f, MathF.Sin(angle) * orbitRadius));
}
private static ShipSpatialStateRuntime CreateInitialShipSpatialState(string systemId, Vector3 position, IReadOnlyCollection<NodeRuntime> nodes)
{
var nearestNode = nodes
.Where((node) => node.SystemId == systemId)
.OrderBy((node) => node.Position.DistanceTo(position))
.FirstOrDefault();
return new ShipSpatialStateRuntime
{
CurrentSystemId = systemId,
SpaceLayer = SpaceLayerKinds.LocalSpace,
CurrentNodeId = nearestNode?.Id,
CurrentBubbleId = nearestNode?.BubbleId,
LocalPosition = position,
SystemPosition = position,
MovementRegime = MovementRegimeKinds.LocalFlight,
};
}
private sealed record SystemSpatialGraph(
string SystemId,
List<NodeRuntime> Nodes,
List<LocalBubbleRuntime> Bubbles,
Dictionary<int, Dictionary<string, NodeRuntime>> LagrangeNodesByPlanetIndex);
private sealed record LagrangePointPlacement(string Designation, Vector3 Position);
private sealed record StationPlacement(NodeRuntime AnchorNode, Vector3 Position);
}