tidssystem
This commit is contained in:
@@ -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) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user