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)
|
||||
|
||||
Reference in New Issue
Block a user