306 lines
12 KiB
C#
306 lines
12 KiB
C#
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 float ComputeResourceNodeOrbitSpeed(ResourceNodeRuntime node)
|
|
{
|
|
var baseSpeed = node.SourceKind == "gas-cloud" ? 0.16f : 0.24f;
|
|
return baseSpeed / MathF.Sqrt(MathF.Max(node.OrbitRadius / 180f, 0.45f));
|
|
}
|
|
|
|
private static Vector3 ComputeResourceNodeOffset(ResourceNodeRuntime node, float timeSeconds)
|
|
{
|
|
var angle = node.OrbitPhase + (timeSeconds * ComputeResourceNodeOrbitSpeed(node));
|
|
var orbit = new Vector3(
|
|
MathF.Cos(angle) * node.OrbitRadius,
|
|
0f,
|
|
MathF.Sin(angle) * node.OrbitRadius);
|
|
return RotateAroundX(orbit, node.OrbitInclination);
|
|
}
|
|
|
|
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 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 void UpdateOrbitalState(SimulationWorld world)
|
|
{
|
|
var worldTimeSeconds = (float)world.OrbitalTimeSeconds;
|
|
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, planet.Size, 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 node in world.Nodes)
|
|
{
|
|
if (node.AnchorNodeId is null || !spatialNodesById.TryGetValue(node.AnchorNodeId, out var anchorNode))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
node.Position = Add(anchorNode.Position, ComputeResourceNodeOffset(node, worldTimeSeconds));
|
|
if (spatialNodesById.TryGetValue(node.Id, out var resourceNode))
|
|
{
|
|
resourceNode.Position = node.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);
|
|
}
|