Snyggare moln

This commit is contained in:
2026-11-03 22:56:33 +01:00
parent 6ea397a9e5
commit 490da8e97f
34 changed files with 1270 additions and 72 deletions

View File

@@ -0,0 +1,58 @@
using System.Numerics;
using Universe.App.Runtime;
using Universe.Core.Generation;
using Universe.Core.Math;
using Universe.Core.World;
namespace Universe.Tests;
public sealed class RingTemporalHistoryTests
{
private const float Far=1e12f;
private static PlanetData Ring(PlanetOrientation orientation)=>new(0,"Ring",DVector3.Zero,2_500_000,0,220_000,
Vector3.One,Vector3.UnitZ,0,0,0,110){Id="ring-world",RingWorld=new(),Orientation=orientation};
private static FlyCamera Camera()=>new(UniversePosition.AtCellCenter(new(0,0,0),1e10),1e10);
[Fact]
public void JitteredGeometryAndRayReconstructionUseTheSameSubpixelPosition()
{
var camera=Camera();camera.SetPlayerPose(camera.Position,new(0,0,-1),new(0,1,0));
var ring=Ring(PlanetOrientation.Identity);var history=new RingTemporalHistory();
const int width=2560,height=1440;
for(var frame=0;frame<16;frame++)
{
var vp=history.Advance(camera,ring,new(2_499_998,0,0),width,height,Far);
var point=new Vector4(1.4f,-.7f,-12,1);var clip=Vector4.Transform(point,vp);
var uv=new Vector2(clip.X/clip.W,clip.Y/clip.W)*.5f+new Vector2(.5f);
var xy=(uv-new Vector2(history.Jitter.X/width,history.Jitter.Y/height))*2-Vector2.One;
var tan=(float)System.Math.Tan(camera.FieldOfViewRadians*.5);
var ray=Vector3.Normalize(new(xy.X*tan*width/height,xy.Y*tan,-1));
Assert.InRange(Vector3.Distance(ray,Vector3.Normalize(new(point.X,point.Y,point.Z))),0,1e-6);
}
}
[Fact]
public void ReprojectionTracksRingRotationAndTranslationAtUniverseScale()
{
var camera=Camera();var history=new RingTemporalHistory();
var prior=Ring(new(35,73));var oldCamera=new DVector3(2_499_998,12345,0);
camera.SetPlayerPose(camera.Position,prior.Orientation.ToWorld(new(0,0,1)),prior.Orientation.ToWorld(new(-1,0,0)));
history.Advance(camera,prior,oldCamera,2560,1440,Far);
var next=Ring(new(35,73.001));var newCamera=oldCamera+new DVector3(0,.1,.2);
camera.SetPlayerPose(camera.Position.Add(new(100000,-4000,200),camera.CellSizeMeters),
next.Orientation.ToWorld(new(0,0,1)),next.Orientation.ToWorld(new(-1,0,0)));
history.Advance(camera,next,newCamera,2560,1440,Far);
Assert.True(history.Valid);
var bodyPoint=newCamera+new DVector3(-.2,3,12);
var current=next.Orientation.ToWorld(bodyPoint-newCamera).ToVector3();
var reprojected=Vector3.TransformNormal(current,history.CurrentToPrevious)+history.PreviousCameraOffset;
var expected=prior.Orientation.ToWorld(bodyPoint-oldCamera).ToVector3();
Assert.InRange(Vector3.Distance(reprojected,expected),0,1e-5);
camera.SetPlayerPose(camera.Position,camera.Forward,camera.Up,true);
history.Advance(camera,next,newCamera,2560,1440,Far);Assert.False(history.Valid);
history.Advance(camera,next,newCamera,2560,1440,Far);Assert.True(history.Valid);
history.Advance(camera,next,newCamera,1440,900,Far);Assert.False(history.Valid);
history.Advance(camera,next,newCamera+new DVector3(0,1000,0),1440,900,Far);Assert.False(history.Valid);
history.Advance(camera,null,newCamera,1440,900,Far);Assert.Equal(Vector2.Zero,history.Jitter);
}
}

View File

@@ -88,6 +88,9 @@ public sealed class RingWorldTests
Assert.Throws<InvalidDataException>(() => settings.Validate(1, 220_000, terrain, null));
Assert.Throws<InvalidDataException>(() => settings.Validate(2_500_000, 2_500_000, terrain, null));
Assert.Throws<InvalidDataException>(() => settings.Validate(2_500_000, 220_000, terrain, new() { DepthMeters = 60_000 }));
Assert.Throws<InvalidDataException>(() => (settings with {CloudDetail=new(){NearBaseAltitudeMeters=219000,NearThicknessMeters=2000}}).Validate(2_500_000,220_000,terrain,null));
Assert.Throws<InvalidDataException>(() => (settings with {CloudDetail=new(){NearDistanceMeters=double.NaN}}).Validate(2_500_000,220_000,terrain,null));
Assert.Throws<InvalidDataException>(() => (settings with {CloudDetail=new(){WeatherVariation=2}}).Validate(2_500_000,220_000,terrain,null));
}
[Fact]
@@ -216,6 +219,33 @@ public sealed class RingWorldTests
Assert.True(camera.Position.RelativeTo(body, Cell).Length > planet.MaximumTerrainRadiusMeters * 2);
}
[Fact]
public void MeadowWrapsWithTheRingAndExcludesWaterAndBareBiomes()
{
var green=new RasterImage(2,2,Enumerable.Repeat(new byte[]{45,80,35,255},4).SelectMany(p=>p).ToArray());
var planet=Ring() with {SurfaceTexture=green};var settings=new RingMeadowSettings();
foreach(var column in new[]{0,RingMeadowTile.Columns(planet)-1})
{
var tile=RingMeadowTile.Build(planet,column,0,settings);
Assert.NotEmpty(tile.Instances);
Assert.Equal(tile.Instances,RingMeadowTile.Build(planet,column,0,settings).Instances);
Assert.All(tile.Instances,n=>Assert.True(float.IsFinite(n)));
for(var i=0;i<tile.Instances.Length;i+=9)
{
var point=tile.Origin+new DVector3(tile.Instances[i],tile.Instances[i+1],tile.Instances[i+2]);
var normal=new DVector3(tile.Instances[i+3],tile.Instances[i+4],tile.Instances[i+5]);
Assert.InRange(RingWorldGeometry.Clearance(planet,point),-.026,-.024);
Assert.True(DVector3.Dot(normal,-RingWorldGeometry.Radial(point))>.9999);
}
}
var wet=planet with {OceanMask=new(2,2,[1,1,1,1])};
Assert.Empty(RingMeadowTile.Build(wet,0,0,settings).Instances);
var sand=planet with {SurfaceTexture=new(1,1,[205,181,120,255])};
Assert.Empty(RingMeadowTile.Build(sand,0,0,settings).Instances);
Assert.Throws<InvalidDataException>(()=>(settings with {SpacingMeters=double.NaN}).Validate());
Assert.Throws<InvalidDataException>(()=>(settings with {DistanceMeters=1000}).Validate());
}
[Fact]
public void VegetationIsDeterministicFacesInwardAndDoesNotGrowInWater()
{