Refactor simulation and viewer architecture
This commit is contained in:
256
apps/backend/Simulation/SimulationEngine.OrbitalSystem.cs
Normal file
256
apps/backend/Simulation/SimulationEngine.OrbitalSystem.cs
Normal file
@@ -0,0 +1,256 @@
|
||||
using SpaceGame.Simulation.Api.Data;
|
||||
|
||||
namespace SpaceGame.Simulation.Api.Simulation;
|
||||
|
||||
public sealed partial class SimulationEngine
|
||||
{
|
||||
private static Vector3 ComputePlanetPosition(PlanetDefinition planet, float timeSeconds)
|
||||
{
|
||||
var eccentricity = Math.Clamp(planet.OrbitEccentricity, 0f, 0.85f);
|
||||
var meanAnomaly = DegreesToRadians(planet.OrbitPhaseAtEpoch) + (timeSeconds * planet.OrbitSpeed);
|
||||
var eccentricAnomaly = meanAnomaly
|
||||
+ (eccentricity * MathF.Sin(meanAnomaly))
|
||||
+ (0.5f * eccentricity * eccentricity * MathF.Sin(2f * meanAnomaly));
|
||||
var semiMajorAxis = planet.OrbitRadius;
|
||||
var semiMinorAxis = semiMajorAxis * MathF.Sqrt(MathF.Max(1f - (eccentricity * eccentricity), 0.05f));
|
||||
var local = new Vector3(
|
||||
semiMajorAxis * (MathF.Cos(eccentricAnomaly) - eccentricity),
|
||||
0f,
|
||||
semiMinorAxis * MathF.Sin(eccentricAnomaly));
|
||||
|
||||
local = RotateAroundY(local, DegreesToRadians(planet.OrbitArgumentOfPeriapsis));
|
||||
local = RotateAroundX(local, DegreesToRadians(planet.OrbitInclination));
|
||||
local = RotateAroundY(local, DegreesToRadians(planet.OrbitLongitudeOfAscendingNode));
|
||||
return local;
|
||||
}
|
||||
|
||||
private static Vector3 ComputeMoonOffset(PlanetDefinition planet, int moonIndex, float timeSeconds)
|
||||
{
|
||||
var orbitRadius = ComputeMoonOrbitRadius(planet, moonIndex);
|
||||
var speed = ComputeMoonOrbitSpeed(planet, moonIndex);
|
||||
var phase = HashUnit($"{planet.Label}:{moonIndex}:phase") * MathF.PI * 2f;
|
||||
var inclination = DegreesToRadians((HashUnit($"{planet.Label}:{moonIndex}:inclination") - 0.5f) * 28f);
|
||||
var ascendingNode = DegreesToRadians(HashUnit($"{planet.Label}:{moonIndex}:node") * 360f);
|
||||
var angle = phase + (timeSeconds * speed);
|
||||
|
||||
var local = new Vector3(
|
||||
MathF.Cos(angle) * orbitRadius,
|
||||
0f,
|
||||
MathF.Sin(angle) * orbitRadius);
|
||||
local = RotateAroundX(local, inclination);
|
||||
local = RotateAroundY(local, ascendingNode);
|
||||
return local;
|
||||
}
|
||||
|
||||
private static float ComputeMoonOrbitRadius(PlanetDefinition planet, int moonIndex)
|
||||
{
|
||||
var spacing = planet.Size * 1.4f;
|
||||
var variance = HashUnit($"{planet.Label}:{moonIndex}:radius") * planet.Size * 0.9f;
|
||||
return (planet.Size * 1.8f) + (moonIndex * spacing) + variance;
|
||||
}
|
||||
|
||||
private static float ComputeMoonOrbitSpeed(PlanetDefinition planet, int moonIndex)
|
||||
{
|
||||
var radius = ComputeMoonOrbitRadius(planet, moonIndex);
|
||||
return 0.9f / MathF.Sqrt(MathF.Max(radius, 1f)) + (moonIndex * 0.003f);
|
||||
}
|
||||
|
||||
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 Vector3 NormalizeOrFallback(Vector3 value, Vector3 fallback)
|
||||
{
|
||||
var length = MathF.Sqrt(value.LengthSquared());
|
||||
if (length <= 0.0001f)
|
||||
{
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return value.Divide(length);
|
||||
}
|
||||
|
||||
private static Vector3 RotateAroundX(Vector3 value, float angle)
|
||||
{
|
||||
var cos = MathF.Cos(angle);
|
||||
var sin = MathF.Sin(angle);
|
||||
return new Vector3(
|
||||
value.X,
|
||||
(value.Y * cos) - (value.Z * sin),
|
||||
(value.Y * sin) + (value.Z * cos));
|
||||
}
|
||||
|
||||
private static Vector3 RotateAroundY(Vector3 value, float angle)
|
||||
{
|
||||
var cos = MathF.Cos(angle);
|
||||
var sin = MathF.Sin(angle);
|
||||
return new Vector3(
|
||||
(value.X * cos) + (value.Z * sin),
|
||||
value.Y,
|
||||
(-value.X * sin) + (value.Z * cos));
|
||||
}
|
||||
|
||||
private static Vector3 Add(Vector3 left, Vector3 right) => new(left.X + right.X, left.Y + right.Y, left.Z + right.Z);
|
||||
|
||||
private static Vector3 Scale(Vector3 value, float scalar) => new(value.X * scalar, value.Y * scalar, value.Z * scalar);
|
||||
|
||||
private static float DegreesToRadians(float degrees) => degrees * (MathF.PI / 180f);
|
||||
|
||||
private static float HashUnit(string input)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hash = 2166136261u;
|
||||
foreach (var character in input)
|
||||
{
|
||||
hash ^= character;
|
||||
hash *= 16777619u;
|
||||
}
|
||||
|
||||
return (hash & 0x00FFFFFF) / (float)0x01000000;
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateOrbitalState(SimulationWorld world, DateTimeOffset nowUtc)
|
||||
{
|
||||
var worldTimeSeconds = (float)(nowUtc.ToUnixTimeMilliseconds() / 1000d) + (world.Seed * 97f);
|
||||
var spatialNodesById = world.SpatialNodes.ToDictionary(node => node.Id, StringComparer.Ordinal);
|
||||
|
||||
foreach (var system in world.Systems)
|
||||
{
|
||||
var starNodeId = $"node-{system.Definition.Id}-star";
|
||||
if (spatialNodesById.TryGetValue(starNodeId, out var starNode))
|
||||
{
|
||||
starNode.Position = Vector3.Zero;
|
||||
}
|
||||
|
||||
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}";
|
||||
if (!spatialNodesById.TryGetValue(planetNodeId, out var planetNode))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var planetPosition = ComputePlanetPosition(planet, worldTimeSeconds);
|
||||
planetNode.Position = planetPosition;
|
||||
|
||||
foreach (var lagrange in EnumeratePlanetLagrangePoints(planetPosition, planet.OrbitRadius, planetIndex))
|
||||
{
|
||||
var lagrangeId = $"node-{system.Definition.Id}-planet-{planetIndex + 1}-{lagrange.Designation.ToLowerInvariant()}";
|
||||
if (spatialNodesById.TryGetValue(lagrangeId, out var lagrangeNode))
|
||||
{
|
||||
lagrangeNode.Position = lagrange.Position;
|
||||
}
|
||||
}
|
||||
|
||||
for (var moonIndex = 0; moonIndex < planet.MoonCount; moonIndex += 1)
|
||||
{
|
||||
var moonId = $"node-{system.Definition.Id}-planet-{planetIndex + 1}-moon-{moonIndex + 1}";
|
||||
if (!spatialNodesById.TryGetValue(moonId, out var moonNode))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
moonNode.Position = Add(planetPosition, ComputeMoonOffset(planet, moonIndex, worldTimeSeconds));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var station in world.Stations)
|
||||
{
|
||||
if (station.AnchorNodeId is null || !spatialNodesById.TryGetValue(station.AnchorNodeId, out var anchorNode))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
station.Position = anchorNode.Position;
|
||||
if (station.NodeId is not null && spatialNodesById.TryGetValue(station.NodeId, out var stationNode))
|
||||
{
|
||||
stationNode.Position = station.Position;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var ship in world.Ships.Where(ship => ship.DockedStationId is not null))
|
||||
{
|
||||
var station = world.Stations.FirstOrDefault(candidate => candidate.Id == ship.DockedStationId);
|
||||
if (station is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var dockedPosition = GetShipDockedPosition(ship, station);
|
||||
ship.Position = dockedPosition;
|
||||
ship.TargetPosition = dockedPosition;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SyncSpatialState(SimulationWorld world)
|
||||
{
|
||||
foreach (var bubble in world.LocalBubbles)
|
||||
{
|
||||
bubble.OccupantShipIds.Clear();
|
||||
}
|
||||
|
||||
foreach (var ship in world.Ships)
|
||||
{
|
||||
ship.SpatialState.CurrentSystemId = ship.SystemId;
|
||||
ship.SpatialState.LocalPosition = ship.Position;
|
||||
ship.SpatialState.SystemPosition = ship.Position;
|
||||
if (ship.SpatialState.Transit is not null)
|
||||
{
|
||||
ship.SpatialState.CurrentNodeId = null;
|
||||
ship.SpatialState.CurrentBubbleId = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
ship.SpatialState.SpaceLayer = SpaceLayerKinds.LocalSpace;
|
||||
ship.SpatialState.MovementRegime = MovementRegimeKinds.LocalFlight;
|
||||
var nearestNode = world.SpatialNodes
|
||||
.Where(candidate => candidate.SystemId == ship.SystemId)
|
||||
.OrderBy(candidate => candidate.Position.DistanceTo(ship.Position))
|
||||
.FirstOrDefault();
|
||||
ship.SpatialState.CurrentNodeId = nearestNode?.Id;
|
||||
ship.SpatialState.CurrentBubbleId = nearestNode?.BubbleId;
|
||||
|
||||
if (nearestNode is not null)
|
||||
{
|
||||
var nearestBubble = world.LocalBubbles.FirstOrDefault(candidate => candidate.Id == nearestNode.BubbleId);
|
||||
nearestBubble?.OccupantShipIds.Add(ship.Id);
|
||||
}
|
||||
|
||||
if (ship.DockedStationId is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var station = world.Stations.FirstOrDefault(candidate => candidate.Id == ship.DockedStationId);
|
||||
if (station?.BubbleId is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ship.SpatialState.CurrentNodeId = station.NodeId;
|
||||
ship.SpatialState.CurrentBubbleId = station.BubbleId;
|
||||
var bubble = world.LocalBubbles.FirstOrDefault(candidate => candidate.Id == station.BubbleId);
|
||||
bubble?.OccupantShipIds.Add(ship.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly record struct LagrangePointPlacement(string Designation, Vector3 Position);
|
||||
}
|
||||
Reference in New Issue
Block a user