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)