270 lines
9.6 KiB
C#
270 lines
9.6 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, planet.Size, 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,
|
|
float planetSize,
|
|
int planetIndex)
|
|
{
|
|
var radial = NormalizeOrFallback(planetPosition, new Vector3(1f, 0f, 0f));
|
|
var tangential = new Vector3(-radial.Z, 0f, radial.X);
|
|
var offset = ComputePlanetLocalLagrangeOffset(orbitRadius, planetSize, planetIndex);
|
|
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", Add(planetPosition, Scale(radial, -(offset * 1.2f))));
|
|
yield return new LagrangePointPlacement(
|
|
"L4",
|
|
Add(
|
|
planetPosition,
|
|
Add(
|
|
Scale(radial, offset * MathF.Cos(triangularAngle)),
|
|
Scale(tangential, offset * MathF.Sin(triangularAngle)))));
|
|
yield return new LagrangePointPlacement(
|
|
"L5",
|
|
Add(
|
|
planetPosition,
|
|
Add(
|
|
Scale(radial, offset * MathF.Cos(triangularAngle)),
|
|
Scale(tangential, -offset * MathF.Sin(triangularAngle)))));
|
|
}
|
|
|
|
private static float ComputePlanetLocalLagrangeOffset(float orbitRadius, float planetSize, int planetIndex)
|
|
{
|
|
var orbitalScale = MathF.Min(orbitRadius * 0.016f, 96f + (planetIndex * 4f));
|
|
var sizeScale = (planetSize * 1.9f) + 10f;
|
|
return MathF.Max(22f + (planetIndex * 2f), MathF.Max(orbitalScale, sizeScale));
|
|
}
|
|
|
|
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 NodeRuntime? ResolveResourceNodeAnchor(SystemSpatialGraph graph, ResourceNodeDefinition definition)
|
|
{
|
|
if (definition.AnchorPlanetIndex is not int planetIndex || planetIndex < 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (definition.AnchorMoonIndex is int moonIndex && moonIndex >= 0)
|
|
{
|
|
var moonNodeId = $"node-{graph.SystemId}-planet-{planetIndex + 1}-moon-{moonIndex + 1}";
|
|
return graph.Nodes.FirstOrDefault((node) => node.Id == moonNodeId);
|
|
}
|
|
|
|
var planetNodeId = $"node-{graph.SystemId}-planet-{planetIndex + 1}";
|
|
return graph.Nodes.FirstOrDefault((node) => node.Id == planetNodeId);
|
|
}
|
|
|
|
private static Vector3 ComputeResourceNodePosition(NodeRuntime? anchorNode, ResourceNodeDefinition definition, float yPlane)
|
|
{
|
|
var verticalOffset = MathF.Sin(DegreesToRadians(definition.InclinationDegrees)) * MathF.Min(definition.RadiusOffset * 0.18f, 28f);
|
|
var offset = new Vector3(
|
|
MathF.Cos(definition.Angle) * definition.RadiusOffset,
|
|
verticalOffset,
|
|
MathF.Sin(definition.Angle) * definition.RadiusOffset);
|
|
|
|
if (anchorNode is null)
|
|
{
|
|
return new Vector3(offset.X, yPlane + offset.Y, offset.Z);
|
|
}
|
|
|
|
return Add(anchorNode.Position, offset);
|
|
}
|
|
|
|
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);
|
|
}
|