tidssystem
This commit is contained in:
194
tests/Universe.Tests/CelestialSimulationTests.cs
Normal file
194
tests/Universe.Tests/CelestialSimulationTests.cs
Normal file
@@ -0,0 +1,194 @@
|
||||
using System.Numerics;
|
||||
using Universe.App.Graphics;
|
||||
using Universe.App.Runtime;
|
||||
using Universe.Core.Configuration;
|
||||
using Universe.Core.Generation;
|
||||
using Universe.Core.Math;
|
||||
using Universe.Core.Physics;
|
||||
using Universe.Core.World;
|
||||
|
||||
namespace Universe.Tests;
|
||||
|
||||
public sealed class CelestialSimulationTests
|
||||
{
|
||||
private static readonly Lazy<StarSystemData> Sol = new(() => KnownSolarSystemCatalog.Load(
|
||||
Path.Combine(AppContext.BaseDirectory, "KnownSolarSystem", "solar-systems.json")).GetById("sol"));
|
||||
|
||||
[Fact]
|
||||
public void ClockRunsAtRealTimePausesAndAcceleratesWithoutDroppingSlowFrames()
|
||||
{
|
||||
var clock = new SimulationClock(new());
|
||||
Assert.Equal(1, clock.TimeScale);
|
||||
clock.Advance(2.5);
|
||||
Assert.Equal(2.5, clock.SecondsSinceJ2000);
|
||||
clock.TimeScale = 0; clock.Advance(120);
|
||||
Assert.Equal(2.5, clock.ElapsedSeconds);
|
||||
clock.TimeScale = 100; clock.Advance(60);
|
||||
Assert.Equal(6002.5, clock.ElapsedSeconds);
|
||||
Assert.Equal(SimulationClock.J2000.AddSeconds(6002.5), clock.Utc);
|
||||
Assert.Throws<InvalidDataException>(() => new SimulationClock(new() { TimeScale = 101 }));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => clock.Advance(double.NaN));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(.20563593)]
|
||||
[InlineData(.9999)]
|
||||
public void EllipseHasCorrectApsesAndClosesAfterAnOrbit(double eccentricity)
|
||||
{
|
||||
const double axis = 1e8, period = 100000;
|
||||
var orbit = new KeplerOrbit(axis, eccentricity, 0, period, new(1, 0, 0), new(0, 0, 1));
|
||||
Assert.InRange(System.Math.Abs(orbit.Position(0).Length - axis * (1 - eccentricity)), 0, 1e-6);
|
||||
Assert.InRange(System.Math.Abs(orbit.Position(period * .5).Length - axis * (1 + eccentricity)), 0, 1e-6);
|
||||
Assert.InRange((orbit.Position(1200) - orbit.Position(period * 1000000 + 1200)).Length, 0, 1e-5);
|
||||
Assert.InRange((orbit.Position(-1200) - orbit.Position(period - 1200)).Length, 0, 1e-5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MercuryAcceleratesAtPerihelionAndConservesKeplerEnergyAndAngularMomentum()
|
||||
{
|
||||
var orbit = Sol.Value.Planets.First(p => p.Id == "mercury").Orbit! with { MeanAnomalyAtEpochRadians = 0 };
|
||||
DVector3 Velocity(double t) => (orbit.Position(t + 1) - orbit.Position(t - 1)) * .5;
|
||||
var periSpeed = Velocity(0).Length;
|
||||
var apSpeed = Velocity(orbit.PeriodSeconds * .5).Length;
|
||||
Assert.InRange(periSpeed / apSpeed, 1.517, 1.519);
|
||||
var mu = System.Math.Pow(System.Math.Tau / orbit.PeriodSeconds, 2) * System.Math.Pow(orbit.SemiMajorAxisMeters, 3);
|
||||
var energy = -mu / (2 * orbit.SemiMajorAxisMeters);
|
||||
var h = DVector3.Cross(orbit.Position(0), Velocity(0)).Length;
|
||||
foreach (var fraction in new[] { .01, .17, .4, .8 })
|
||||
{
|
||||
var t = fraction * orbit.PeriodSeconds;
|
||||
var v = Velocity(t); var r = orbit.Position(t);
|
||||
Assert.InRange(System.Math.Abs((v.LengthSquared * .5 - mu / r.Length) / energy - 1), 0, 1e-7);
|
||||
Assert.InRange(System.Math.Abs(DVector3.Cross(r, v).Length / h - 1), 0, 1e-7);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EarthUsesSiderealDayAndYearAndMoonOrbitsTheMovingEarth()
|
||||
{
|
||||
var start = CelestialSimulation.Evaluate(Sol.Value, 0);
|
||||
var hour = CelestialSimulation.Evaluate(Sol.Value, 3600);
|
||||
var earth = start.Planets.First(p => p.Id == "earth");
|
||||
var later = hour.Planets[earth.Index];
|
||||
Assert.InRange(earth.Rotation!.PeriodSeconds, 86164, 86165);
|
||||
Assert.InRange(earth.Orbit!.PeriodSeconds / 86400, 365.25, 365.26);
|
||||
Assert.InRange((later.PositionMeters - earth.PositionMeters).Length, 106e6, 110e6);
|
||||
var moon = start.Planets.First(p => p.Id == "moon");
|
||||
var day = CelestialSimulation.Evaluate(Sol.Value, 86400);
|
||||
var relative = day.Planets[moon.Index].PositionMeters - day.Planets[earth.Index].PositionMeters;
|
||||
Assert.InRange(relative.Length, 363e6, 406e6);
|
||||
Assert.InRange((relative - moon.Orbit!.Position(86400)).Length, 0, .0001);
|
||||
foreach (var p in start.Planets)
|
||||
{
|
||||
Assert.NotNull(p.Orbit); Assert.NotNull(p.Rotation);
|
||||
Assert.Same(p.Terrain, hour.Planets[p.Index].Terrain);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("earth", true)]
|
||||
[InlineData("venus", false)]
|
||||
[InlineData("uranus", false)]
|
||||
public void SpinDirectionMatchesProgradeOrRetrogradeWithoutDoubleNegation(string id, bool prograde)
|
||||
{
|
||||
var p = Sol.Value.Planets.First(p => p.Id == id);
|
||||
var a = p.Rotation!.At(0).ToWorld(new(1, 0, 0));
|
||||
var b = p.Rotation.At(System.Math.Abs(p.Rotation.PeriodSeconds) / 100).ToWorld(new(1, 0, 0));
|
||||
var orbit = p.Orbit!;
|
||||
var angular = DVector3.Dot(DVector3.Cross(a, b), DVector3.Cross(orbit.U, orbit.V));
|
||||
Assert.Equal(prograde, angular > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StreamedSystemUsesSameTimeAfterUnloadingAndDoesNotAccumulateOrbitError()
|
||||
{
|
||||
var generator = new UniverseGenerator(42);
|
||||
var streamer = new UniverseStreamer(generator) { SimulationSeconds = 1234567 };
|
||||
var first = streamer.Load(new(0, 0, 0));
|
||||
streamer.Load(new(1000, 0, 0));
|
||||
var again = streamer.Load(new(0, 0, 0));
|
||||
Assert.Equal(first.ActiveSystem.Planets.Select(p => p.PositionMeters), again.ActiveSystem.Planets.Select(p => p.PositionMeters));
|
||||
var refreshed = streamer.Refresh(again);
|
||||
Assert.Equal(again.ActiveSystem.Planets.Select(p => p.PositionMeters), refreshed.ActiveSystem.Planets.Select(p => p.PositionMeters));
|
||||
streamer.SimulationSeconds += 1000;
|
||||
var moved = streamer.Refresh(again);
|
||||
Assert.NotEqual(again.ActiveSystem.Planets[0].PositionMeters, moved.ActiveSystem.Planets[0].PositionMeters);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GroundedPlayerFollowsOrbitAndTerrainRotationWithoutLosingClearance()
|
||||
{
|
||||
const double cell = 10000000;
|
||||
var planet = new PlanetData(0, "Test", DVector3.Zero, 10000, 150, 0, Vector3.One, Vector3.Zero, 1e6, 0, 0, 20)
|
||||
{
|
||||
Orbit = new(1e6, .1, 0, 86400, new(1, 0, 0), new(0, 0, 1)),
|
||||
Rotation = new(1000, 23.4, 30)
|
||||
};
|
||||
var basis = new StarSystemData(new(0, 0, 0), 1, [new(0, "Sun", DVector3.Zero, 1000, 1, Vector3.One, 1)], [planet]);
|
||||
var before = new UniverseNeighborhood(CelestialSimulation.Evaluate(basis, 0), []);
|
||||
var p = before.ActiveSystem.Planets[0];
|
||||
var center = UniversePosition.AtCellCenter(basis.Coordinate, cell);
|
||||
var direction = p.Orientation.ToWorld(new DVector3(.6, .8, 0));
|
||||
var body = center.Add(p.PositionMeters, cell);
|
||||
var player = new PlayerController(body.Add(direction * (TerrainHeight.Sample(p, direction) + 1.7), cell),
|
||||
p.Orientation.ToWorld(new(0, 0, 1)), direction, cell, before);
|
||||
player.Update(.01, 0, 0, false, false, before);
|
||||
Assert.True(player.Grounded);
|
||||
var local = p.Orientation.ToBody(player.Position.RelativeTo(body, cell));
|
||||
for (var i = 1; i <= 20; i++)
|
||||
{
|
||||
var current = new UniverseNeighborhood(CelestialSimulation.Evaluate(basis, i * 10), []);
|
||||
player.ApplyCelestialMotion(before, current);
|
||||
p = current.ActiveSystem.Planets[0];
|
||||
var offset = player.Position.RelativeTo(center.Add(p.PositionMeters, cell), cell);
|
||||
Assert.InRange((p.Orientation.ToBody(offset) - local).Length, 0, .0001);
|
||||
Assert.InRange(offset.Length - TerrainHeight.Sample(p, offset.Normalized()), 1.6999, 1.7001);
|
||||
Assert.InRange(DVector3.Dot(player.Up, offset.Normalized()), .999999, 1.000001);
|
||||
before = current;
|
||||
}
|
||||
player.Update(.01, 0, 0, true, false, before);
|
||||
Assert.False(player.Grounded);
|
||||
var jumpBody = center.Add(before.ActiveSystem.Planets[0].PositionMeters, cell);
|
||||
var jumpLocal = before.ActiveSystem.Planets[0].Orientation.ToBody(player.Position.RelativeTo(jumpBody, cell));
|
||||
var whileJumping = new UniverseNeighborhood(CelestialSimulation.Evaluate(basis, 210), []);
|
||||
player.ApplyCelestialMotion(before, whileJumping);
|
||||
p = whileJumping.ActiveSystem.Planets[0];
|
||||
var carriedJump = p.Orientation.ToBody(player.Position.RelativeTo(center.Add(p.PositionMeters, cell), cell));
|
||||
Assert.InRange((carriedJump - jumpLocal).Length, 0, .0001);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DebugTimeSliderSupportsPauseRealTimeAndMaximum()
|
||||
{
|
||||
Assert.Equal(0d, PauseMenu.TimeScaleHit(1010, 390, 1440, 900));
|
||||
Assert.Equal(100d, PauseMenu.TimeScaleHit(1330, 390, 1440, 900));
|
||||
Assert.Equal(1d, PauseMenu.TimeScaleHit(1129, 469, 1440, 900));
|
||||
Assert.Null(PauseMenu.TimeScaleHit(720, 450, 1440, 900));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FocusFlightTracksMovingEarthAtMaximumTimeScale()
|
||||
{
|
||||
var cell = UniverseScale.DefaultCellSizeMeters;
|
||||
var world = new UniverseNeighborhood(CelestialSimulation.Evaluate(Sol.Value, 0), []);
|
||||
var camera = new FlyCamera(UniversePosition.AtCellCenter(new(0, 0, 0), cell), cell);
|
||||
camera.SetInitialView(world.ActiveSystem, new StartupConfiguration { PlanetId = "earth", DistanceInRadii = 5 });
|
||||
camera.RequestFocus([world.ActiveSystem], "earth");
|
||||
var time = 0d;
|
||||
for (var i = 0; i < 60 && camera.IsFocusing; i++)
|
||||
{
|
||||
time += 10;
|
||||
var next = new UniverseNeighborhood(CelestialSimulation.Evaluate(Sol.Value, time), []);
|
||||
camera.ApplyCelestialMotion(world, next, time);
|
||||
camera.Update(.1, DVector3.Zero, next);
|
||||
world = next;
|
||||
}
|
||||
Assert.False(camera.IsFocusing);
|
||||
var earth = world.ActiveSystem.Planets.Single(p => p.Id == "earth");
|
||||
var body = UniversePosition.AtCellCenter(new(0, 0, 0), cell).Add(earth.PositionMeters, cell);
|
||||
var toward = body.RelativeTo(camera.Position, cell);
|
||||
Assert.InRange(toward.Length / earth.RadiusMeters, 1.1, 10);
|
||||
Assert.InRange(DVector3.Dot(toward.Normalized(), camera.Forward), .999, 1.001);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user