skog, mjuka skuggor, jämnare övergång vatten på av avstånd
This commit is contained in:
@@ -91,4 +91,62 @@ public sealed class OceanTests
|
||||
Assert.InRange(a.Sum(w => w.Amplitude), settings.WaveAmplitudeMeters, settings.WaveAmplitudeMeters * 3);
|
||||
Assert.All(a, w => Assert.InRange(w.Amplitude * w.WaveNumber, 0, .2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpectrumSpreadsEnergyAcrossDirectionsAndIndependentPhases()
|
||||
{
|
||||
var settings = new OceanSettings();
|
||||
var waves = OceanSurface.Spectrum(settings, 7);
|
||||
Assert.Equal(OceanSurface.WaveCount, waves.Length);
|
||||
Assert.All(waves, wave =>
|
||||
{
|
||||
Assert.Equal(1, wave.Direction.Length, 10);
|
||||
Assert.Equal(9.81 * wave.WaveNumber, wave.AngularFrequency * wave.AngularFrequency, 9);
|
||||
Assert.InRange(wave.Phase, 0, System.Math.Tau);
|
||||
});
|
||||
Assert.True(waves.Select(w => w.Phase).Distinct().Count() > 24);
|
||||
for (var band = 0; band < waves.Length / 4; band++)
|
||||
{
|
||||
var group = waves.Skip(band * 4).Take(4).ToArray();
|
||||
Assert.True(group.Select(w => w.WaveNumber).Distinct().Count() == 4);
|
||||
Assert.Contains(group, w => DVector3.Dot(group[0].Direction, w.Direction) < .8);
|
||||
}
|
||||
var still = OceanSurface.Spectrum(settings with { WaveSpeed = 0 }, 7);
|
||||
Assert.All(still, wave => Assert.Equal(0, wave.AngularFrequency));
|
||||
Assert.Equal(waves.Select(w => (w.Direction, w.Phase, w.Amplitude)), still.Select(w => (w.Direction, w.Phase, w.Amplitude)));
|
||||
var storm = OceanSurface.Spectrum(settings with { WaveAmplitudeMeters = 30, WaveLengthMeters = 10 }, 7);
|
||||
Assert.All(storm, wave => Assert.InRange(wave.Amplitude * wave.WaveNumber, 0, .18000001));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlanetJsonLoadsWaterDetailControlsAndRejectsInvalidValues()
|
||||
{
|
||||
var settings = new OceanSettings { WindSpreadDegrees = 75, RippleStrength = 1.2,
|
||||
WaveGeometryDistanceMeters = 6000, WhitecapThreshold = .3 };
|
||||
var path = Path.Combine(Path.GetTempPath(), $"universe-water-detail-{Guid.NewGuid():N}.json");
|
||||
OceanSettings Load(OceanSettings ocean)
|
||||
{
|
||||
var definition = new SolarSystemDocument { Systems = [new SolarSystemDefinition
|
||||
{
|
||||
Id = "water-system", Name = "Water test", Sector = new(0, 0, 0),
|
||||
Stars = [new StarDefinition { Id = "sun", Name = "Sun", RadiusMeters = 695700000 }],
|
||||
Planets = [new PlanetDefinition { Id = "ocean", Name = "Ocean", RadiusMeters = 6371000,
|
||||
Orbit = new() { SemiMajorAxisAu = 1 }, Ocean = ocean with { MaskFromSurfaceTexture = false } }]
|
||||
}] };
|
||||
File.WriteAllText(path, JsonSerializer.Serialize(definition));
|
||||
return KnownSolarSystemCatalog.Load(path).GetById("water-system").Planets.Single().Ocean!;
|
||||
}
|
||||
try
|
||||
{
|
||||
var parsed = Load(settings);
|
||||
Assert.Equal(settings.WindSpreadDegrees, parsed.WindSpreadDegrees);
|
||||
Assert.Equal(settings.RippleStrength, parsed.RippleStrength);
|
||||
Assert.Equal(settings.WaveGeometryDistanceMeters, parsed.WaveGeometryDistanceMeters);
|
||||
Assert.Equal(settings.WhitecapThreshold, parsed.WhitecapThreshold);
|
||||
foreach (var invalid in new[] { settings with { WindSpreadDegrees = 181 }, settings with { RippleStrength = -1 },
|
||||
settings with { WaveGeometryDistanceMeters = 0 }, settings with { WhitecapThreshold = .01 } })
|
||||
Assert.Throws<InvalidDataException>(() => Load(invalid));
|
||||
}
|
||||
finally { File.Delete(path); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,19 @@ public sealed class TerrainAndCollisionTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TerrainStreamingKeepsPerPatchAllocationsBelow64KiB()
|
||||
{
|
||||
var planet=new UniverseGenerator(55).Generate(new(0,0,0)).Planets[0];
|
||||
var key=new TerrainPatchKey(0,10,512,512);
|
||||
TerrainPatchBuilder.Build(planet,key);
|
||||
var started=GC.GetAllocatedBytesForCurrentThread();
|
||||
var patch=TerrainPatchBuilder.Build(planet,key);
|
||||
var allocated=GC.GetAllocatedBytesForCurrentThread()-started;
|
||||
Assert.InRange(allocated,1,64*1024);
|
||||
Assert.Equal(TerrainPatchBuilder.Segments*TerrainPatchBuilder.Segments*6+TerrainPatchBuilder.Segments*24,patch.Indices.Length);
|
||||
Assert.All(patch.Indices,index=>Assert.True(index<patch.Vertices.Length/9));
|
||||
}
|
||||
[Fact]
|
||||
public void MountainNormalsFollowReliefAndFaceOutward()
|
||||
{
|
||||
|
||||
246
tests/Universe.Tests/VegetationTests.cs
Normal file
246
tests/Universe.Tests/VegetationTests.cs
Normal file
@@ -0,0 +1,246 @@
|
||||
using System.Numerics;
|
||||
using Universe.Core.Generation;
|
||||
using Universe.Core.Math;
|
||||
using Universe.Core.Physics;
|
||||
using Universe.Core.World;
|
||||
using Universe.Core.Configuration;
|
||||
using Universe.App.Runtime;
|
||||
using Universe.App.Graphics;
|
||||
|
||||
namespace Universe.Tests;
|
||||
|
||||
public sealed class VegetationTests
|
||||
{
|
||||
[Fact]
|
||||
public void SoftShadowControlsRoundTripAndRejectInvalidQualitySettings()
|
||||
{
|
||||
var settings=new VegetationSettings {ShadowSoftness=.75,ShadowCascadeBlend=.25,ShadowBiasMeters=.045};
|
||||
var options=new System.Text.Json.JsonSerializerOptions {PropertyNamingPolicy=System.Text.Json.JsonNamingPolicy.CamelCase};
|
||||
var loaded=System.Text.Json.JsonSerializer.Deserialize<VegetationSettings>(System.Text.Json.JsonSerializer.Serialize(settings,options),options)!;
|
||||
loaded.Validate();
|
||||
Assert.Equal(settings.ShadowSoftness,loaded.ShadowSoftness);
|
||||
Assert.Equal(settings.ShadowCascadeBlend,loaded.ShadowCascadeBlend);
|
||||
Assert.Equal(settings.ShadowBiasMeters,loaded.ShadowBiasMeters);
|
||||
foreach(var invalid in new[]{settings with {ShadowSoftness=-.1},settings with {ShadowSoftness=1.1},settings with {ShadowSoftness=double.NaN},
|
||||
settings with {ShadowCascadeBlend=-.1},settings with {ShadowCascadeBlend=.5},settings with {ShadowBiasMeters=0}})
|
||||
Assert.Throws<InvalidDataException>(invalid.Validate);
|
||||
}
|
||||
private static BiomeField Field(byte[]? color=null,bool ocean=false,PlanetOrientation? orientation=null)
|
||||
{
|
||||
var rgba=color??[45,80,35,255];
|
||||
var planet=new PlanetData(0,"Test",DVector3.Zero,UniverseScale.EarthRadiusMeters,0,0,Vector3.One,Vector3.Zero,0,0,0,77)
|
||||
{Id="test",SurfaceTexture=new(2,2,rgba.Concat(rgba).Concat(rgba).Concat(rgba).ToArray()),
|
||||
Vegetation=new(){TreeDistanceMeters=500},Orientation=orientation??PlanetOrientation.Identity,OceanMask=ocean?new(2,2,[1,1,1,1]):null};
|
||||
var catalog=new VegetationCatalog
|
||||
{
|
||||
Biomes=[new("forest",[45,80,35],24,"forest"),new("dry",[145,150,80],18,"dry"),new("sand",[205,181,120],15,"sand"),new("snow",[240,242,245],12,"snow")],
|
||||
Plants=[new(){Id="tree",Model="tree.glb",Type=VegetationType.Tree,MatchColorRgb=[45,80,35],TrunkCollision=new()}]
|
||||
};
|
||||
return new(planet,catalog);
|
||||
}
|
||||
[Fact]
|
||||
public void RgbToleranceIsNormalizedAndHasASoftOuterEdge()
|
||||
{
|
||||
int[] color=[45,80,35];var target=new Vector3(45,80,35);var diagonal=255*System.Math.Sqrt(3);
|
||||
Assert.Equal(1,VegetationCatalog.Match(target,color,0));
|
||||
Assert.Equal(0,VegetationCatalog.Match(target+Vector3.One,color,0));
|
||||
Assert.InRange(VegetationCatalog.Match(target+new Vector3((float)(diagonal*.09),0,0),color,10),.4999,.5001);
|
||||
Assert.InRange(VegetationCatalog.Match(target+new Vector3((float)(diagonal*.1),0,0),color,10),0,.00001);
|
||||
}
|
||||
[Fact]
|
||||
public void TextureSamplingWrapsAndUsesTheSharedPlanetCoordinates()
|
||||
{
|
||||
var image=new RasterImage(4,2,[255,0,0,255,0,255,0,255,0,0,255,255,255,255,255,255,255,0,0,255,0,255,0,255,0,0,255,255,255,255,255,255]);
|
||||
var green=new DVector3(System.Math.Cos(System.Math.PI*.25),0,System.Math.Sin(System.Math.PI*.25));
|
||||
Assert.InRange(Vector3.Distance(image.SampleColor(green),new(0,255,0)),0,.0001);
|
||||
var a=image.SampleColor(new(-1,0,.00000001));var b=image.SampleColor(new(-1,0,-.00000001));
|
||||
Assert.InRange(Vector3.Distance(a,b),0,.0001);
|
||||
Assert.True(float.IsFinite(image.SampleColor(new(0,1,0)).X));
|
||||
}
|
||||
[Fact]
|
||||
public void BiomesDistinguishForestDrySandAndSnow()
|
||||
{
|
||||
var f=Field();
|
||||
Assert.InRange(f.Weights(new(45,80,35)).X,.99,1);
|
||||
Assert.True(f.Weights(new(145,150,80)).Y>.4);
|
||||
Assert.True(f.Weights(new(205,181,120)).Z>.4);
|
||||
Assert.Equal(1,f.Weights(new(240,242,245)).W);
|
||||
}
|
||||
[Fact]
|
||||
public void TilesAreDeterministicAndDoNotDependOnPlanetRotationOrTerrainLod()
|
||||
{
|
||||
var a=new VegetationTileGenerator(Field());var b=new VegetationTileGenerator(Field(orientation:new(23.4,179,45)));
|
||||
var key=VegetationTileKey.At(new(1,.3,.2),a.Level(0),0);
|
||||
var first=a.Generate(key);Assert.NotEmpty(first.Instances);
|
||||
Assert.Equal(first.Instances,a.Generate(key).Instances);Assert.Equal(first.Instances,b.Generate(key).Instances);
|
||||
Assert.All(first.Instances,i=>Assert.InRange(System.Math.Abs(i.BodyPosition.Length-a.Field.Planet.RadiusMeters+.22),0,.00001));
|
||||
}
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public void SnowAndOceanDoNotGrowTrees(bool ocean)
|
||||
{
|
||||
var g=new VegetationTileGenerator(Field(ocean?null:[240,242,245,255],ocean));
|
||||
var key=VegetationTileKey.At(new(1,.3,.2),g.Level(0),0);
|
||||
Assert.Empty(g.Generate(key).Instances);
|
||||
}
|
||||
[Fact]
|
||||
public void CubeEdgesAndPolesProduceValidStableTiles()
|
||||
{
|
||||
var g=new VegetationTileGenerator(Field());
|
||||
foreach(var d in new[]{new DVector3(1,1,0),new(0,1,0),new(-1,.2,-1)})
|
||||
{
|
||||
var key=VegetationTileKey.At(d,g.Level(0),0);
|
||||
var neighbors=new[]{key,key.Offset(1,0),key.Offset(-1,0),key.Offset(0,1)}.Distinct().SelectMany(k=>g.Generate(k).Instances).ToArray();
|
||||
Assert.Equal(neighbors.Length,neighbors.Select(i=>i.Id).Distinct().Count());
|
||||
Assert.All(neighbors,i=>Assert.True(double.IsFinite(i.BodyPosition.Length)));
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
public void AddingSpeciesDoesNotMultiplyLayerDensity()
|
||||
{
|
||||
var field=Field();var generator=new VegetationTileGenerator(field);var key=VegetationTileKey.At(new(1,.3,.2),generator.Level(0),0);
|
||||
var one=generator.Generate(key);
|
||||
var twoField=new BiomeField(field.Planet,field.Catalog with {Plants=[field.Catalog.Plants[0],field.Catalog.Plants[0] with {Id="second"}]});
|
||||
var two=new VegetationTileGenerator(twoField).Generate(key);
|
||||
Assert.Equal(one.Instances.Length,two.Instances.Length);
|
||||
Assert.Equal(one.Instances.Select(i=>i.Id),two.Instances.Select(i=>i.Id));
|
||||
}
|
||||
[Fact]
|
||||
public void SweptPlayerStopsAtTrunkAndSlidesAlongIt()
|
||||
{
|
||||
TrunkCapsule[] trunks=[new(DVector3.Zero,new(0,1,0),.36,10)];
|
||||
var stopped=VegetationCollision.Move(new(-1000,1.8,0),new(1000,1.8,0),new(0,1,0),1.8,trunks);
|
||||
Assert.InRange(stopped.X,-.642,-.639);
|
||||
var slid=VegetationCollision.Move(new(-2,1.8,0),new(2,1.8,3),new(0,1,0),1.8,trunks);
|
||||
Assert.True(slid.Z>2);
|
||||
var crouched=VegetationCollision.Move(new(-2,1,0),new(2,1,0),new(0,1,0),1,trunks);
|
||||
Assert.True(crouched.X<-.63);
|
||||
var above=VegetationCollision.Move(new(-2,12,0),new(2,12,0),new(0,1,0),1.8,trunks);
|
||||
Assert.Equal(2,above.X,6);
|
||||
var inside=VegetationCollision.Move(new(0,1.8,0),new(0,1.8,0),new(0,1,0),1.8,trunks);
|
||||
Assert.True(System.Math.Sqrt(inside.X*inside.X+inside.Z*inside.Z)>.64);
|
||||
}
|
||||
[Fact]
|
||||
public void ProceduralBiomesAndStreamingSettingsWorkWithoutATexture()
|
||||
{
|
||||
var field=Field();var procedural=new BiomeField(field.Planet with {SurfaceTexture=null},field.Catalog);
|
||||
Assert.Equal(procedural.Color(new(1,0,0)),new BiomeField(procedural.Planet,field.Catalog).Color(new(1,0,0)));
|
||||
Assert.True(procedural.Weights(procedural.Color(new(0,1,0))).W>.8);
|
||||
Assert.Throws<InvalidDataException>(()=>new VegetationSettings {UploadBudgetMilliseconds=0}.Validate());
|
||||
Assert.Throws<InvalidDataException>(()=>new VegetationSettings {TreeLodMeters=[100,50,200]}.Validate());
|
||||
}
|
||||
[Fact]
|
||||
public void PlanetIdentityAndPlacementFiltersAreRespected()
|
||||
{
|
||||
var field=Field();var generator=new VegetationTileGenerator(field);var key=VegetationTileKey.At(new(1,.3,.2),generator.Level(0),0);
|
||||
var first=generator.Generate(key).Instances;
|
||||
var other=new VegetationTileGenerator(new(field.Planet with {Id="another-planet"},field.Catalog)).Generate(key).Instances;
|
||||
Assert.NotEqual(first.Select(i=>i.Id),other.Select(i=>i.Id));
|
||||
foreach(var plant in new[]{field.Catalog.Plants[0] with {AltitudeMeters=[100,200]},field.Catalog.Plants[0] with {AbsoluteLatitudeDegrees=[70,90]}})
|
||||
Assert.Empty(new VegetationTileGenerator(new(field.Planet,field.Catalog with {Plants=[plant]})).Generate(key).Instances);
|
||||
Assert.NotEmpty(new VegetationTileGenerator(new(field.Planet,field.Catalog with {Plants=[field.Catalog.Plants[0] with {MaxSlopeDegrees=0}]})).Generate(key).Instances);
|
||||
}
|
||||
[Fact]
|
||||
public void MinimumSpacingAlsoHoldsAcrossCubeFaceAndChunkBoundaries()
|
||||
{
|
||||
var f=Field();var g=new VegetationTileGenerator(new(f.Planet,f.Catalog with {Plants=[f.Catalog.Plants[0] with {MinimumSpacingMeters=8}]}));
|
||||
var key=VegetationTileKey.At(new(1,.2,1),g.Level(0),0);var adjacent=key.Offset(-1,0);
|
||||
Assert.NotEqual(key.Face,adjacent.Face);
|
||||
var instances=new[]{key,adjacent,key.Offset(0,1)}.SelectMany(k=>g.Generate(k).Instances).ToArray();Assert.NotEmpty(instances);
|
||||
for(var i=0;i<instances.Length;i++)for(var j=i+1;j<instances.Length;j++)
|
||||
Assert.True((instances[i].BodyPosition-instances[j].BodyPosition).Length>=7.999,"Minimum spacing crossed a cube/chunk seam.");
|
||||
}
|
||||
[Fact]
|
||||
public async Task StreamingIsBoundedReloadableAndUsesWorkerResults()
|
||||
{
|
||||
var f=Field();using var stream=new VegetationStream(f);var camera=new DVector3(1,.3,.2).Normalized()*(f.Planet.RadiusMeters+1.8);
|
||||
stream.Update(camera);Assert.InRange(stream.PendingTiles,1,8);
|
||||
for(var i=0;i<100&&stream.InstanceCount==0;i++){await Task.Delay(10);stream.Update(camera);Assert.InRange(stream.PendingTiles,0,8);}
|
||||
Assert.True(stream.InstanceCount>0);var tile=stream.Tiles.First(t=>t.Instances.Length>0);
|
||||
Assert.Equal(tile.Instances,stream.Generator.Generate(tile.Key).Instances);
|
||||
stream.Clear();Assert.Equal(0,stream.InstanceCount);
|
||||
}
|
||||
[Fact]
|
||||
public async Task StandingStillRetainsTheWholeVegetationNeighbourhood()
|
||||
{
|
||||
var field=Field();using var stream=new VegetationStream(field);
|
||||
var camera=new DVector3(1,.3,.2).Normalized()*(field.Planet.RadiusMeters+1.8);
|
||||
var deadline=DateTime.UtcNow.AddSeconds(10);
|
||||
do
|
||||
{
|
||||
stream.Update(camera);
|
||||
Assert.InRange(stream.PendingTiles,0,8);
|
||||
Assert.InRange(stream.Tiles.Count()+stream.PendingTiles,0,field.Planet.Vegetation!.MaximumCachedTiles);
|
||||
if(stream.PendingTiles==0)break;
|
||||
await Task.Delay(2);
|
||||
}while(DateTime.UtcNow<deadline);
|
||||
Assert.Equal(0,stream.PendingTiles);
|
||||
var forward=DVector3.Cross(camera.Normalized(),new(0,1,0)).Normalized();
|
||||
var offsets=stream.Tiles.Where(t=>t.Key.Layer==0).Select(t=>t.Origin.Normalized()*camera.Length-camera).ToArray();
|
||||
Assert.Contains(offsets,d=>DVector3.Dot(d,forward)>200);
|
||||
Assert.Contains(offsets,d=>DVector3.Dot(d,forward)<-200);
|
||||
var revision=stream.Revision;var instances=stream.InstanceCount;
|
||||
// More than the former 120-frame expiry: every direction remains resident.
|
||||
for(var frame=0;frame<500;frame++)stream.Update(camera);
|
||||
Assert.Equal(revision,stream.Revision);Assert.Equal(instances,stream.InstanceCount);
|
||||
Assert.Equal(0,stream.PendingTiles);
|
||||
}
|
||||
[Fact]
|
||||
public void PaddedVegetationViewCoversSmallHeadTurnsIncludingRoll()
|
||||
{
|
||||
var cached=new VegetationView(new(0,0,1),new(1,0,0),new(0,1,0),.6,1.6);
|
||||
var random=new Random(317);
|
||||
for(var trial=0;trial<500;trial++)
|
||||
{
|
||||
var axis=new DVector3(random.NextDouble()-.5,random.NextDouble()-.5,random.NextDouble()-.5).Normalized();
|
||||
var angle=random.NextDouble()*3.99*System.Math.PI/180;
|
||||
DVector3 Rotate(DVector3 v)=>v*System.Math.Cos(angle)+DVector3.Cross(axis,v)*System.Math.Sin(angle)
|
||||
+axis*(DVector3.Dot(axis,v)*(1-System.Math.Cos(angle)));
|
||||
var current=cached with {Forward=Rotate(cached.Forward),Right=Rotate(cached.Right),Up=Rotate(cached.Up)};
|
||||
Assert.True(cached.CanReuse(current));
|
||||
foreach(var x in new[]{-1d,0,1})foreach(var y in new[]{-1d,0,1})
|
||||
{
|
||||
var offset=current.Forward*100+current.Right*(100*x*current.TanHalfFov*current.Aspect)+current.Up*(100*y*current.TanHalfFov);
|
||||
Assert.True(cached.Intersects(offset,0),"Reusing the buffer discarded a point inside the rotated view.");
|
||||
}
|
||||
}
|
||||
var a=5*System.Math.PI/180;
|
||||
Assert.False(cached.CanReuse(cached with {Right=new(System.Math.Cos(a),System.Math.Sin(a),0),Up=new(-System.Math.Sin(a),System.Math.Cos(a),0)}));
|
||||
Assert.False(cached.CanReuse(cached with {Aspect=2}));
|
||||
Assert.False(cached.CanReuse(cached with {TanHalfFov=.8}));
|
||||
Assert.False(cached.Intersects(new(0,0,-100),1));
|
||||
}
|
||||
[Fact]
|
||||
public void ForestBenchmarkLandsAtEyeHeightAndReturnsToEarthView()
|
||||
{
|
||||
var system=KnownSolarSystemCatalog.Load(Path.Combine(AppContext.BaseDirectory,"KnownSolarSystem","solar-systems.json")).GetById("sol");
|
||||
var earth=system.Planets.Single(p=>p.Id=="earth");var cell=UniverseScale.DefaultCellSizeMeters;
|
||||
var origin=UniversePosition.AtCellCenter(system.Coordinate,cell);var body=origin.Add(earth.PositionMeters,cell);
|
||||
var camera=new FlyCamera(body.Add(new(0,0,earth.RadiusMeters*4),cell),cell);var home=camera.Position;var forward=camera.Forward;var up=camera.Up;
|
||||
var catalog=VegetationCatalog.Load(Path.Combine(AppContext.BaseDirectory,"Content","Vegitations","vegetation.json"));
|
||||
var benchmark=new ForestBenchmark(system,earth,catalog,camera,home,forward,up);
|
||||
var nearest=double.PositiveInfinity;
|
||||
for(var i=0;i<961;i++)
|
||||
{
|
||||
benchmark.Update(.1,camera);benchmark.RecordFrame(1d/60);
|
||||
if(benchmark.ElapsedSeconds is >=40 and <80)
|
||||
{
|
||||
var relative=camera.Position.RelativeTo(body,cell);var height=relative.Length-TerrainHeight.Sample(earth,relative.Normalized());
|
||||
Assert.InRange(height,1.798,1.802); // AU-scale sector arithmetic retains millimetre precision here.
|
||||
nearest=System.Math.Min(nearest,height);
|
||||
}
|
||||
}
|
||||
Assert.True(benchmark.IsComplete);Assert.InRange(nearest,1.798,1.802);
|
||||
Assert.InRange(camera.Position.RelativeTo(home,cell).Length,0,.00001);Assert.Equal(forward,camera.Forward);
|
||||
Assert.InRange(benchmark.AverageFps,59.999,60.001);
|
||||
Assert.True(benchmark.PerformancePassed);
|
||||
}
|
||||
[Fact]
|
||||
public void TransparentFoliageMipsKeepEdgeColourInsteadOfBlackHalos()
|
||||
{
|
||||
var image=new RasterImage(2,2,[255,20,10,255,0,0,0,0,0,0,0,0,0,0,0,0]);
|
||||
var mip=PlantMipmaps.Get(image,false)[1];Assert.Equal(1,mip.Width);
|
||||
Assert.Equal(new byte[]{255,20,10,63},mip.Rgba);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user