tidssystem

This commit is contained in:
2026-11-01 16:52:32 +01:00
parent 9d44d14cc9
commit a7207086b2
24 changed files with 849 additions and 174 deletions

View File

@@ -34,11 +34,12 @@ internal sealed class AdaptivePlanetMesh : IDisposable
public AdaptivePlanetMesh(GL gl, PlanetData planet)
{
_gl = gl;
_planet = planet;
// Meshes live in a stable body frame. Spin is applied on the GPU, never by rebuilding terrain.
_planet = planet with { Orientation = PlanetOrientation.Identity };
for (var face = 0; face < 6; face++)
{
var key = new TerrainPatchKey(face, 0, 0, 0);
var data = TerrainPatchBuilder.Build(planet, key);
var data = TerrainPatchBuilder.Build(_planet, key);
_tiles[key] = new() { Data = data, Mesh = new(gl, 9, data.Vertices, data.Indices) };
}
}

View File

@@ -13,6 +13,50 @@ namespace Universe.App.Graphics;
/// <summary>Offscreen tests of the production shaders; no desktop capture or mouse control.</summary>
internal static class GraphicsReport
{
public static void RunSimulation(GL gl, UniverseRenderer renderer, UniverseNeighborhood initial, double cellSize, int width, int height)
{
var directory = Path.Combine(AppContext.BaseDirectory, "graphics-report"); Directory.CreateDirectory(directory);
var center = UniversePosition.AtCellCenter(initial.ActiveSystem.Coordinate, cellSize);
var camera = new FlyCamera(center, cellSize);
var basis = initial.ActiveSystem;
camera.SetInitialView(basis, new StartupConfiguration { PlanetId = "earth", DistanceInRadii = 3, LongitudeDegrees = 20, LatitudeDegrees = 20 });
var earth = basis.Planets.Single(p => p.Id == "earth");
var offset = camera.Position.RelativeTo(center.Add(earth.PositionMeters, cellSize), cellSize);
var forward = camera.Forward; var up = camera.Up;
float[] Frame(double seconds, string file)
{
var system = Universe.Core.Physics.CelestialSimulation.Evaluate(basis, basis.EpochSecondsSinceJ2000 + seconds);
var p = system.Planets.Single(p => p.Id == "earth");
camera.SetPlayerPose(center.Add(p.PositionMeters + offset, cellSize), forward, up, resetMotionHistory: true);
renderer.SimulationTimeSeconds = seconds;
for (var i = 0; i < 24; i++) renderer.Render(camera, initial with { ActiveSystem = system }, 0);
gl.Finish(); var pixels = Read(gl, width, height);
if (gl.GetError() != GLEnum.NoError || pixels.Any(v => !float.IsFinite(v)))
throw new InvalidOperationException("Simulation produced invalid OpenGL output.");
WritePng(Path.Combine(directory, file), width, height, pixels, false);
return pixels;
}
var first = Frame(0, "earth-time-0h.png");
var rotated = Frame(21600, "earth-time-6h.png");
var difference = first.Zip(rotated, (a,b) => System.Math.Abs(a-b)).Average();
if (difference < .002) throw new InvalidOperationException($"Earth spin did not visibly change the production rendering: {difference}.");
renderer.DrawMenu(2, true, 100, Universe.Core.Physics.SimulationClock.J2000.AddHours(6));
gl.Finish(); WritePng(Path.Combine(directory, "time-debug-menu.png"), width, height, Read(gl, width, height), false);
// Sample a detailed surface after spin: mesh generation, culling and displacement must use the body frame.
var after = Universe.Core.Physics.CelestialSimulation.Evaluate(basis, basis.EpochSecondsSinceJ2000 + 21600);
var planet = after.Planets.Single(p => p.Id == "earth");
var lat = 46.5 * System.Math.PI / 180; var lon = -7.5 * System.Math.PI / 180;
var radial = planet.Orientation.ToWorld(new(System.Math.Cos(lat)*System.Math.Cos(lon), System.Math.Sin(lat), System.Math.Cos(lat)*System.Math.Sin(lon)));
var tangent = DVector3.Cross(radial, planet.Orientation.ToWorld(new(0,1,0))).Normalized();
var radius = TerrainHeight.Sample(planet, radial);
camera.SetPlayerPose(center.Add(planet.PositionMeters + radial * (radius + 400), cellSize), (tangent - radial * .3).Normalized(), radial, true);
for (var i = 0; i < 72; i++) renderer.Render(camera, initial with { ActiveSystem = after }, 0);
gl.Finish(); var ground = Read(gl, width, height);
if (gl.GetError() != GLEnum.NoError) throw new InvalidOperationException("Rotating terrain GL error.");
WritePng(Path.Combine(directory, "earth-rotated-terrain.png"), width, height, ground, false);
Console.WriteLine($"Simulation GPU report passed: Earth 0/6h visual difference {difference:F5}, rotated terrain {renderer.TerrainTiles} tiles, LOD {renderer.TerrainDeepestLevel}; debug time menu. Images: {directory}");
}
public static void RunStarTrails(GL gl,StarMotionBlurSettings settings,int width,int height)
{
using var trails=new StarTrailRenderer(gl,settings with {Enabled=true});

View File

@@ -49,7 +49,18 @@ internal sealed class PauseMenu : IDisposable
return -1;
}
public void Draw(int width, int height, bool debugEnabled)
public static double? TimeScaleHit(double x, double y, int width, int height)
{
var (s, cx, cy) = Layout(width, height);
x = (x - cx) / s; y = (y - cy) / s;
if (x >= 290 && x <= 610 && y >= -80 && y <= -40) return System.Math.Round((x - 290) / 320 * 100);
if (y >= 0 && y <= 38)
for (var i = 0; i < 4; i++)
if (x >= 290 + i * 82 && x <= 364 + i * 82) return new double[] { 0, 1, 10, 100 }[i];
return null;
}
public void Draw(int width, int height, bool debugEnabled, double timeScale = 1, DateTimeOffset? simulationUtc = null)
{
var (s, cx, cy) = Layout(width, height);
var data = new List<float>();
@@ -84,6 +95,26 @@ internal sealed class PauseMenu : IDisposable
Text(label, cx, y + 12*s, (i >= 3 ? 3.35 : 4) * s, .92f, .95f, 1);
}
Text("P OR ESC TO RESUME", cx, cy + 250*s, 2*s, .6f, .7f, .8f);
if (debugEnabled)
{
Rect(cx + 250*s, cy - 180*s, 400*s, 340*s, .025f, .04f, .075f);
Text("WORLD TIME", cx + 450*s, cy - 150*s, 3.5*s, .9f, .95f, 1);
Text($"SPEED {timeScale:0.##} X", cx + 450*s, cy - 108*s, 2.8*s, .4f, .85f, 1);
Rect(cx + 290*s, cy - 64*s, 320*s, 8*s, .13f, .24f, .34f);
Rect(cx + 290*s, cy - 64*s, timeScale / 100 * 320*s, 8*s, .3f, .7f, .95f);
Rect(cx + (286 + timeScale / 100 * 320)*s, cy - 74*s, 8*s, 28*s, .8f, .95f, 1);
Text("0 X 100 X", cx + 450*s, cy - 30*s, 1.8*s, .7f, .8f, .9f);
var speeds = new[] { "0 X", "1 X", "10 X", "100 X" };
for (var i = 0; i < speeds.Length; i++)
{
Rect(cx + (290 + i * 82)*s, cy, 74*s, 38*s, .08f, .18f, .28f);
Text(speeds[i], cx + (327 + i * 82)*s, cy + 12*s, 2*s, .9f, .95f, 1);
}
Text("LEFT / RIGHT TO ADJUST", cx + 450*s, cy + 60*s, 2*s, .65f, .8f, .9f);
Text("PAUSED IN MENU", cx + 450*s, cy + 88*s, 2*s, .65f, .8f, .9f);
Text((simulationUtc ?? Universe.Core.Physics.SimulationClock.J2000).ToString("yyyy-MM-dd HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture),
cx + 450*s, cy + 121*s, 2*s, .7f, .85f, .95f);
}
_mesh.ReplaceVertices(data.ToArray()); _mesh.Draw(GLEnum.Triangles);
}
public void Dispose() => _mesh.Dispose();

View File

@@ -10,7 +10,8 @@ internal sealed class PerformanceOverlay : IDisposable
public PerformanceOverlay(GL gl) => _mesh = new(gl, 6, [], dynamic: true);
public void Draw(int width, int height, IReadOnlyList<KeyValuePair<string, double>> samples,
double cpuMilliseconds, double fps, int terrainTiles, int terrainLod, int pendingTiles, string? probePlanetId)
double cpuMilliseconds, double fps, int terrainTiles, int terrainLod, int pendingTiles, string? probePlanetId,
double timeScale = 1, DateTimeOffset? simulationUtc = null)
{
var values = samples.ToDictionary(p => p.Key, p => p.Value, StringComparer.Ordinal);
var rows = new List<(string Label, string Value, bool Emphasis)>();
@@ -44,6 +45,8 @@ internal sealed class PerformanceOverlay : IDisposable
rows.Add(("FPS", $"{Number(fps, "F1")}", false));
rows.Add(("TERRAIN TILES", terrainTiles.ToString(CultureInfo.InvariantCulture), false));
rows.Add(("LOD / PENDING", $"{terrainLod} / {pendingTiles}", false));
rows.Add(("TIME SCALE", $"{Number(timeScale, "F1")} X", true));
if (simulationUtc is { } date) rows.Add(("WORLD UTC", date.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), false));
var scale = System.Math.Clamp(System.Math.Min(width / 1440d, height / 900d), .58, 1.25);
var x = 14 * scale;

View File

@@ -8,6 +8,7 @@ uniform float uRadius;
uniform mat4 uViewProjection;
uniform float uFar;
uniform mat4 uWorldToBody;
uniform mat4 uBodyToWorld;
uniform vec3 uMaterialAnchor;
out vec3 vWorldPosition;
out vec3 vPlanetLocal;
@@ -15,10 +16,11 @@ out vec3 vNormal;
out float vFragDepth;
out vec3 vMaterialPosition;
void main() {
vWorldPosition=uChunkPosition+aPosition;
vPlanetLocal=uChunkOrigin+aPosition/uRadius;
vNormal=aNormal;
vMaterialPosition=mat3(uWorldToBody)*aPosition+uMaterialAnchor;
vec3 worldOffset=mat3(uBodyToWorld)*aPosition;
vWorldPosition=uChunkPosition+worldOffset;
vPlanetLocal=uChunkOrigin+worldOffset/uRadius;
vNormal=mat3(uBodyToWorld)*aNormal;
vMaterialPosition=aPosition+uMaterialAnchor;
vec4 clip=uViewProjection*vec4(vWorldPosition,1.0);
vFragDepth=clip.w+1.0;
gl_Position=clip;

View File

@@ -66,6 +66,7 @@ internal sealed class UniverseRenderer : IDisposable
private int _width;
private int _height;
private double _time;
public double? SimulationTimeSeconds { get; set; }
private int _probeFrameCounter;
private int _probeEffectIndex;
private string? _debugProbePlanetId;
@@ -260,7 +261,7 @@ internal sealed class UniverseRenderer : IDisposable
if (timer is null) return;
_gl.Finish(); FrameTimings[name] = timer.Elapsed.TotalMilliseconds; timer.Restart();
}
_time += System.Math.Min(deltaSeconds, 0.1);
_time = SimulationTimeSeconds ?? (_time + System.Math.Min(deltaSeconds, 0.1));
TerrainTiles = 0;
TerrainDeepestLevel = 0; TerrainPendingTiles = 0;
TerrainNearestLevel = 0; TerrainNearestDistance = double.PositiveInfinity;
@@ -355,20 +356,21 @@ internal sealed class UniverseRenderer : IDisposable
}
public int MenuHit(double x, double y, int width, int height) => _pauseMenu.Hit(x, y, width, height);
public void DrawMenu(int selection, bool debugEnabled)
public double? TimeScaleHit(double x, double y, int width, int height) => PauseMenu.TimeScaleHit(x, y, width, height);
public void DrawMenu(int selection, bool debugEnabled, double timeScale = 1, DateTimeOffset? simulationUtc = null)
{
_gl.Disable(GLEnum.DepthTest); _gl.Disable(GLEnum.Blend);
_pauseMenu.Selected = selection; _overlay.Use(); _pauseMenu.Draw(_width, _height, debugEnabled);
_pauseMenu.Selected = selection; _overlay.Use(); _pauseMenu.Draw(_width, _height, debugEnabled, timeScale, simulationUtc);
_gl.Enable(GLEnum.DepthTest);
}
public void DrawDebugOverlay(double fps)
public void DrawDebugOverlay(double fps, double timeScale = 1, DateTimeOffset? simulationUtc = null)
{
if (!DebugMode) return;
_gl.Disable(GLEnum.DepthTest); _gl.Disable(GLEnum.Blend); _gl.DepthMask(false);
_overlay.Use();
_performanceOverlay.Draw(_width, _height, DebugSamples(), CpuRenderMilliseconds, fps,
TerrainTiles, TerrainDeepestLevel, TerrainPendingTiles, _debugProbePlanetId);
TerrainTiles, TerrainDeepestLevel, TerrainPendingTiles, _debugProbePlanetId, timeScale, simulationUtc);
_gl.DepthMask(true); _gl.Enable(GLEnum.DepthTest);
}
@@ -490,14 +492,16 @@ internal sealed class UniverseRenderer : IDisposable
BindRingProfile(_surface, planet);
_surface.Set("uTerrainShadows", relative.Length < planet.RadiusMeters * 1.18 && probeEffect != PlanetProbeEffect.MountainShadows ? 1 : 0);
if (onlyPlanetId is null) _gpuProfiler.BeginPass($"PLANET {planet.Id.ToUpperInvariant()}");
mesh.Draw(-relative, camera.Forward, camera.Right, camera.Up, _height, (double)_width / _height, System.Math.Tan(camera.FieldOfViewRadians * 0.5), (origin, gpu) =>
_surface.SetMatrix("uBodyToWorld", planet.Orientation.BodyToWorld);
mesh.Draw(planet.Orientation.ToBody(-relative), planet.Orientation.ToBody(camera.Forward), planet.Orientation.ToBody(camera.Right), planet.Orientation.ToBody(camera.Up), _height, (double)_width / _height, System.Math.Tan(camera.FieldOfViewRadians * 0.5), (origin, gpu) =>
{
var anchor = planet.Orientation.ToBody(origin);
var anchor = origin;
var scale = materialSettings.ScaleMeters;
static double Mod(double value, double divisor) => value - System.Math.Floor(value / divisor) * divisor;
_surface.Set("uMaterialAnchor", new Vector3((float)Mod(anchor.X, scale), (float)Mod(anchor.Y, scale), (float)Mod(anchor.Z, scale)));
_surface.Set("uChunkPosition", (relative + origin).ToVector3());
_surface.Set("uChunkOrigin", (origin / planet.RadiusMeters).ToVector3());
var worldOrigin = planet.Orientation.ToWorld(origin);
_surface.Set("uChunkPosition", (relative + worldOrigin).ToVector3());
_surface.Set("uChunkOrigin", (worldOrigin / planet.RadiusMeters).ToVector3());
gpu.Draw();
});
if (onlyPlanetId is null)

View File

@@ -12,6 +12,8 @@ public sealed class FlyCamera
private DVector3 _up = new(0, 1, 0);
private FocusFlight? _flight;
private double _flightElapsed;
private StarSystemData? _flightSystem;
private PlanetData? _flightPlanet;
private double _defaultFieldOfView = 2 * System.Math.Atan(.67);
public double FieldOfViewRadians { get; private set; } = 2 * System.Math.Atan(.67);
public FlyCamera(UniversePosition initialPosition, double cellSizeMeters)
@@ -336,6 +338,58 @@ public sealed class FlyCamera
_flight = new(travelStart, body, startRadial, radial, approachRadius, finalRadius, Forward, Up, forward, up, speed,
duration, FieldOfViewRadians, fieldOfView ?? _defaultFieldOfView, departure);
_flightElapsed = 0;
_flightSystem = null; _flightPlanet = null;
}
public void ApplyCelestialMotion(UniverseNeighborhood previous, UniverseNeighborhood current, double secondsSinceJ2000)
{
var bodies = previous.NearbySystems.Append(previous.ActiveSystem)
.SelectMany(s => s.Planets.Select(p => (System: s, Planet: p, Body: World(s, p.PositionMeters)))).ToArray();
if (_flight is { } flight)
{
if (flight.Departure is { } departure)
{
var source = bodies.FirstOrDefault(b => b.Body.RelativeTo(departure.Body, CellSizeMeters).Length < 1);
if (source.Planet is { } sourcePlanet)
{
var movedSystem = CelestialSimulation.Evaluate(source.System, secondsSinceJ2000);
var movedPlanet = movedSystem.Planets.First(p => p.Id == sourcePlanet.Id);
DVector3 TurnDeparture(DVector3 v) => movedPlanet.Orientation.ToWorld(sourcePlanet.Orientation.ToBody(v));
var movedBody = World(movedSystem, movedPlanet.PositionMeters);
var movedDeparture = departure with { Body = movedBody, StartRadial = TurnDeparture(departure.StartRadial), TargetRadial = TurnDeparture(departure.TargetRadial) };
flight = flight with { Departure = movedDeparture,
Start = movedBody.Add(movedDeparture.TargetRadial * departure.ClearanceRadius, CellSizeMeters) };
}
}
if (_flightPlanet is null)
{
var target = bodies.FirstOrDefault(b => b.Body.RelativeTo(flight.Body, CellSizeMeters).Length < 1);
_flightSystem = target.System; _flightPlanet = target.Planet;
}
if (_flightSystem is not null && _flightPlanet is { } old)
{
var updated = CelestialSimulation.Evaluate(_flightSystem, secondsSinceJ2000).Planets.First(p => p.Id == old.Id);
DVector3 Turn(DVector3 v) => updated.Orientation.ToWorld(old.Orientation.ToBody(v));
_flight = flight with { Body = World(_flightSystem, updated.PositionMeters), TargetRadial = Turn(flight.TargetRadial),
TargetForward = Turn(flight.TargetForward), TargetUp = Turn(flight.TargetUp) };
_flightPlanet = updated;
}
else _flight = flight;
return;
}
var nearest = bodies.MinBy(b => b.Body.RelativeTo(Position, CellSizeMeters).Length / b.Planet.RadiusMeters);
if (nearest.Planet is null) return;
var offset = Position.RelativeTo(nearest.Body, CellSizeMeters);
if (offset.Length > nearest.Planet.RadiusMeters * 12) return;
var system = current.NearbySystems.Append(current.ActiveSystem).FirstOrDefault(s => s.Coordinate == nearest.System.Coordinate);
var planet = system?.Planets.FirstOrDefault(p => p.Id == nearest.Planet.Id);
if (planet is null) return;
if (offset.Length - TerrainHeight.Sample(nearest.Planet, offset.Normalized()) < 100)
{
DVector3 Turn(DVector3 v) => planet.Orientation.ToWorld(nearest.Planet.Orientation.ToBody(v));
offset = Turn(offset); _forward = Turn(_forward); _up = Turn(_up);
}
Position = World(system!, planet.PositionMeters).Add(offset, CellSizeMeters);
}
public void Update(double deltaSeconds, DVector3 movement, UniverseNeighborhood neighborhood)

View File

@@ -28,7 +28,7 @@ internal sealed class GalaxyJourneyBenchmark : IBenchmarkSession
public object? RenderEnvironment { get; set; }
public GalaxyJourneyBenchmark(GalaxyUniverse galaxies, UniverseGenerator generator, StarSystemData homeSystem, PlanetData earth,
UniversePosition home, DVector3 homeForward, DVector3 homeUp)
UniversePosition home, DVector3 homeForward, DVector3 homeUp, double simulationSeconds = 0)
{
_galaxies = galaxies; _home = home; _homeForward = homeForward; _homeUp = homeUp;
_homeBody = UniversePosition.AtCellCenter(homeSystem.Coordinate, galaxies.CellSize).Add(earth.PositionMeters, galaxies.CellSize);
@@ -37,7 +37,7 @@ internal sealed class GalaxyJourneyBenchmark : IBenchmarkSession
HomeGalaxy = galaxies.GalaxyAt(home);
var region = HomeGalaxy.Region;
TargetGalaxy = galaxies.Get(new(checked(region.X + 1), region.Y, region.Z));
TargetSystem = generator.Generate(galaxies.SelectSystem(TargetGalaxy));
TargetSystem = Universe.Core.Physics.CelestialSimulation.Evaluate(generator.Generate(galaxies.SelectSystem(TargetGalaxy)), simulationSeconds);
TargetPlanet = TargetSystem.Planets.Where(p => !p.IsGasPlanet && p.ParentPlanetIndex is null).OrderByDescending(p => p.RadiusMeters).First();
_targetBody = UniversePosition.AtCellCenter(TargetSystem.Coordinate, galaxies.CellSize).Add(TargetPlanet.PositionMeters, galaxies.CellSize);
// Exercise the illuminated terrain/shadow path, rather than accidentally

View File

@@ -39,6 +39,24 @@ public sealed class PlayerController
_pitch = System.Math.Clamp(_pitch - dy * .0022, -1.5, 1.5);
}
public void ApplyCelestialMotion(UniverseNeighborhood previous, UniverseNeighborhood current)
{
if (Nearest(previous) is not { } old) return;
var system = current.NearbySystems.Append(current.ActiveSystem).FirstOrDefault(s => s.Coordinate == old.System.Coordinate);
var planet = system?.Planets.FirstOrDefault(p => p.Id == old.Planet.Id);
if (planet is null) return;
var body = UniversePosition.AtCellCenter(system!.Coordinate, _cellSize).Add(planet.PositionMeters, _cellSize);
var relative = Position.RelativeTo(old.Body, _cellSize);
// Use the rotating local frame near the surface, including jumps: the player inherits the ground's
// rotational motion instead of the planet sliding hundreds of metres sideways during a jump.
if (Grounded || relative.Length - TerrainHeight.Sample(old.Planet, relative.Normalized()) <= System.Math.Max(2000, old.Planet.AtmosphereHeightMeters))
{
DVector3 Rotate(DVector3 v) => planet.Orientation.ToWorld(old.Planet.Orientation.ToBody(v));
relative = Rotate(relative); _heading = Rotate(_heading); Up = Rotate(Up); _velocity = Rotate(_velocity);
}
Position = body.Add(relative, _cellSize);
}
public void Update(double dt, double forward, double side, bool jump, bool crouch, UniverseNeighborhood neighborhood)
{
if (!double.IsFinite(dt) || dt <= 0) return;

View File

@@ -10,6 +10,7 @@ using Universe.Core.Generation;
using Universe.Core.Math;
using Universe.Core.World;
using Universe.Core.Configuration;
using Universe.Core.Physics;
namespace Universe.App;
@@ -20,6 +21,7 @@ public sealed class UniverseApplication : IDisposable
private readonly UniverseStreamer _streamer;
private readonly GalaxyUniverse _galaxies;
private readonly StarMotionBlurSettings _starMotionBlur;
private readonly SimulationClock _clock;
private readonly IReadOnlyList<string> _planetHotkeys;
private readonly StarSystemData _homeSystem;
private readonly PlanetData _homeEarth;
@@ -52,6 +54,7 @@ public sealed class UniverseApplication : IDisposable
{
var configPath = Path.Combine(AppContext.BaseDirectory, "config.json");
var configuration = EngineConfiguration.Load(configPath);
_clock = new(configuration.Simulation);
_starMotionBlur = configuration.StarMotionBlur;
_planetHotkeys = configuration.Startup.PlanetHotkeys;
var catalogPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(configPath)!, configuration.KnownSolarSystemsFile));
@@ -60,8 +63,9 @@ public sealed class UniverseApplication : IDisposable
Path.GetFullPath(Path.Combine(Path.GetDirectoryName(configPath)!, configuration.Galaxies.KnownGalaxiesFile)));
_generator = new(configuration.Seed, configuration.CellSizeMeters, catalog, configuration.BlackHoles, _galaxies);
_streamer = new(_generator);
_streamer.SimulationSeconds = _clock.SecondsSinceJ2000;
var initialSystem = configuration.ResolveStartup(catalog).System;
_homeSystem = _generator.Generate(catalog.GetById("sol").Coordinate);
_homeSystem = _streamer.AtCurrentTime(_generator.Generate(catalog.GetById("sol").Coordinate));
_homeEarth = _homeSystem.Planets.FirstOrDefault(p => p.Id.Equals("earth", StringComparison.OrdinalIgnoreCase))
?? throw new InvalidDataException("The known solar system must define an Earth planet with id 'earth'.");
var startCoordinate = initialSystem.Coordinate;
@@ -79,7 +83,7 @@ public sealed class UniverseApplication : IDisposable
var options = WindowOptions.Default with
{
Title = "Universe | loading",
IsVisible = !Environment.GetCommandLineArgs().Any(a => a is "--graphics-report" or "--scene-report" or "--ocean-report" or "--cloud-report" or "--terrain-report" or "--menu-report" or "--terrain-perf-report" or "--black-hole-report" or "--galaxy-journey-report" or "--star-trails-report"),
IsVisible = !Environment.GetCommandLineArgs().Any(a => a is "--simulation-report" or "--graphics-report" or "--scene-report" or "--ocean-report" or "--cloud-report" or "--terrain-report" or "--menu-report" or "--terrain-perf-report" or "--black-hole-report" or "--galaxy-journey-report" or "--star-trails-report"),
Size = new Vector2D<int>(1440, 900),
VSync = true,
PreferredDepthBufferBits = 24,
@@ -100,6 +104,11 @@ public sealed class UniverseApplication : IDisposable
var gl = _window.CreateOpenGL();
_input = _window.CreateInput();
_renderer = new(gl, _window.FramebufferSize.X, _window.FramebufferSize.Y, _galaxies, _starMotionBlur);
if (Environment.GetCommandLineArgs().Contains("--simulation-report"))
{
GraphicsReport.RunSimulation(gl, _renderer, _neighborhood, _generator.CellSizeMeters, _window.FramebufferSize.X, _window.FramebufferSize.Y);
_window.Close(); return;
}
if (Environment.GetCommandLineArgs().Contains("--star-trails-report"))
{
GraphicsReport.RunStarTrails(gl,_starMotionBlur,_window.FramebufferSize.X,_window.FramebufferSize.Y);
@@ -164,6 +173,16 @@ public sealed class UniverseApplication : IDisposable
if (_menuOpen) { _mouseDelta = Vector2.Zero; return; }
var previous = _neighborhood;
_clock.Advance(deltaSeconds);
_streamer.SimulationSeconds = _clock.SecondsSinceJ2000;
if (_clock.TimeScale > 0)
{
_neighborhood = _streamer.Refresh(previous);
if (_player is { } movingPlayer) movingPlayer.ApplyCelestialMotion(previous, _neighborhood);
else _camera.ApplyCelestialMotion(previous, _neighborhood, _clock.SecondsSinceJ2000);
}
if (_mouseCaptured)
{
if (_player is null) _camera.Rotate(_mouseDelta.X, _mouseDelta.Y);
@@ -216,12 +235,13 @@ public sealed class UniverseApplication : IDisposable
if (_renderer is not null)
{
_renderer.DebugMode = _debugMode;
_renderer.SimulationTimeSeconds = _clock.ElapsedSeconds;
_renderer.DebugProbeEnabled = _debugMode && !_menuOpen;
}
_renderer?.Render(_camera, _neighborhood, _menuOpen ? 0 : deltaSeconds);
_renderer?.DrawDebugOverlay(1 / _frameTime);
_renderer?.DrawDebugOverlay(1 / _frameTime, _clock.TimeScale, _clock.Utc);
if (_benchmark is { HasRun: true } benchmark) _renderer?.DrawBenchmarkOverlay(benchmark);
if (_menuOpen) _renderer?.DrawMenu(_menuSelection, _debugMode);
if (_menuOpen) _renderer?.DrawMenu(_menuSelection, _debugMode, _clock.TimeScale, _clock.Utc);
_benchmarkNeedsInitialFrame = false;
}
@@ -252,6 +272,8 @@ public sealed class UniverseApplication : IDisposable
if (_menuOpen)
{
if (key == Key.Escape) SetMenu(false);
else if (_debugMode && key is Key.Left or Key.Right)
_clock.TimeScale += (key == Key.Right ? 1 : -1) * (IsDown(Key.ShiftLeft) || IsDown(Key.ShiftRight) ? 10 : 1);
else if (key == Key.Up) _menuSelection = (_menuSelection + 6) % 7;
else if (key == Key.Down) _menuSelection = (_menuSelection + 1) % 7;
else if (key is Key.Enter or Key.KeypadEnter) ActivateMenu();
@@ -337,6 +359,9 @@ public sealed class UniverseApplication : IDisposable
{
if (_menuOpen)
{
if (_debugMode && mouse.IsButtonPressed(MouseButton.Left)
&& _renderer?.TimeScaleHit(position.X, position.Y, _window.Size.X, _window.Size.Y) is { } rate)
_clock.TimeScale = rate;
var hit = _renderer?.MenuHit(position.X, position.Y, _window.Size.X, _window.Size.Y) ?? -1;
if (hit >= 0) _menuSelection = hit;
return;
@@ -363,6 +388,8 @@ public sealed class UniverseApplication : IDisposable
{
if (!_menuOpen || button != MouseButton.Left) return;
var position = mouse.Position;
if (_debugMode && _renderer?.TimeScaleHit(position.X, position.Y, _window.Size.X, _window.Size.Y) is { } rate)
{ _clock.TimeScale = rate; return; }
var hit = _renderer?.MenuHit(position.X, position.Y, _window.Size.X, _window.Size.Y) ?? -1;
if (hit >= 0) { _menuSelection = hit; ActivateMenu(); }
}
@@ -390,7 +417,8 @@ public sealed class UniverseApplication : IDisposable
_debugMode = false; _player = null;
_benchmark = CreateGalaxyJourney();
_benchmarkNeedsInitialFrame = true;
_camera.SetPlayerPose(_homeCameraPosition, _homeCameraForward, _homeCameraUp, resetMotionHistory: true);
var home = CurrentHomeView();
_camera.SetPlayerPose(home.Position, home.Forward, home.Up, resetMotionHistory: true);
UpdateNeighborhood(); SetMenu(false); return;
}
if (_menuSelection is 3 or 4)
@@ -417,8 +445,9 @@ public sealed class UniverseApplication : IDisposable
private void StartBenchmark(bool unknownPlanet)
{
_camera.RestoreDefaultFieldOfView();
var system = _homeSystem;
var planet = _homeEarth;
var home = CurrentHomeView();
var system = home.System;
var planet = home.Earth;
if (unknownPlanet)
{
var candidate = _neighborhood.NearbySystems.Append(_neighborhood.ActiveSystem)
@@ -441,7 +470,7 @@ public sealed class UniverseApplication : IDisposable
_debugMode = false;
_player = null;
_benchmark = new BenchmarkRunner(system, planet, _generator.CellSizeMeters, _camera,
_homeCameraPosition, _homeCameraForward, _homeCameraUp);
home.Position, home.Forward, home.Up);
_benchmarkNeedsInitialFrame = true;
UpdateNeighborhood();
SetMenu(false);
@@ -450,14 +479,27 @@ public sealed class UniverseApplication : IDisposable
private GalaxyJourneyBenchmark CreateGalaxyJourney()
{
_camera.RestoreDefaultFieldOfView();
var journey = new GalaxyJourneyBenchmark(_galaxies, _generator, _homeSystem, _homeEarth,
_homeCameraPosition, _homeCameraForward, _homeCameraUp);
var home = CurrentHomeView();
var journey = new GalaxyJourneyBenchmark(_galaxies, _generator, home.System, home.Earth,
home.Position, home.Forward, home.Up, _clock.SecondsSinceJ2000);
journey.RenderEnvironment = new { Width = _window.FramebufferSize.X, Height = _window.FramebufferSize.Y, Gpu = _renderer?.HardwareName, Mode = "interactive-vsync", FieldOfViewDegrees = _camera.FieldOfViewRadians * 180 / System.Math.PI };
foreach (var planet in _homeSystem.Planets) _renderer?.PreparePlanet(_homeSystem, planet);
foreach (var planet in journey.TargetSystem.Planets) _renderer?.PreparePlanet(journey.TargetSystem, planet);
return journey;
}
private (StarSystemData System, PlanetData Earth, UniversePosition Position, DVector3 Forward, DVector3 Up) CurrentHomeView()
{
var system = _streamer.AtCurrentTime(_homeSystem);
var earth = system.Planets.First(p => p.Id == _homeEarth.Id);
var center = UniversePosition.AtCellCenter(system.Coordinate, _generator.CellSizeMeters);
var oldBody = center.Add(_homeEarth.PositionMeters, _generator.CellSizeMeters);
var body = center.Add(earth.PositionMeters, _generator.CellSizeMeters);
DVector3 Turn(DVector3 v) => earth.Orientation.ToWorld(_homeEarth.Orientation.ToBody(v));
return (system, earth, body.Add(Turn(_homeCameraPosition.RelativeTo(oldBody, _generator.CellSizeMeters)), _generator.CellSizeMeters),
Turn(_homeCameraForward), Turn(_homeCameraUp));
}
private void OnClosing()
{
_renderer?.Dispose();