using XFEEDToolBox.Core.Galaxy;
using XFEEDToolBox.Core.Rendering;
using System.Numerics;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public class GalaxyAppearanceTests
{
[Test] public void SpectralColoursUseActualTypesAndUnknownTypesStayNeutral()
{
Check.Equal(0,StellarAppearance.ColourIndex("O (Blue-White) Star"));Check.Equal(5,StellarAppearance.ColourIndex("K"));
Check.Equal(6,StellarAppearance.ColourIndex("M (Red dwarf) Star"));Check.Equal(10,StellarAppearance.ColourIndex("DA"));
Check.Equal(11,StellarAppearance.ColourIndex("Neutron Star"));Check.Equal(13,StellarAppearance.ColourIndex(""));
Check.That(StellarAppearance.Palette[0].B>StellarAppearance.Palette[0].R);
Check.That(StellarAppearance.Palette[6].R>StellarAppearance.Palette[6].B);
Check.Equal(10,"OBAFGKMLTY".Select(c=>StellarAppearance.Palette[StellarAppearance.ColourIndex(c.ToString())]).Distinct().Count());
Check.Equal(6,StellarAppearance.ColourIndex("CJ"));Check.Equal(5,StellarAppearance.ColourIndex("TTS"));
Check.Equal(12,StellarAppearance.ColourIndex("Supermassive Black Hole"));
}
[Test] public void StellarSpritesAttenuateWithDistanceAndKeepSelectionSeparateFromThePhotosphere()
{
var nearby=StellarAppearance.Sprite(4,0,4);var distant=StellarAppearance.Sprite(4,400,4);
Check.That(nearby.CoreRadius>distant.CoreRadius&&nearby.Opacity>distant.Opacity);
Check.That(nearby.CoreRadius<2&&nearby.Radius<12,"Normal stars must remain compact luminous points at the default map scale.");
foreach(var importance in new[]{1,2,3})
{
var marked=StellarAppearance.Sprite(4,0,4,importance);
Check.Equal(nearby.CoreRadius,marked.CoreRadius);Check.Equal(nearby.Opacity,marked.Opacity);
if(importance>=2)Check.That(marked.Radius>=20,"Marker geometry must have enough space without enlarging the star.");
}
Check.That(StellarAppearance.Sprite(4,0,1000).CoreRadius>nearby.CoreRadius);
Check.That(StellarAppearance.Sprite(4,0,.001).CoreRadius<nearby.CoreRadius);
}
[Test] public void CompactObjectsAndBrownDwarfsHaveTheirOwnStellarMorphology()
{
Check.Equal(StellarMorphology.BrownDwarf,StellarAppearance.Sprite(7,0,4).Morphology);
Check.Equal(StellarMorphology.WhiteDwarf,StellarAppearance.Sprite(10,0,4).Morphology);
Check.Equal(StellarMorphology.NeutronStar,StellarAppearance.Sprite(11,0,4).Morphology);
Check.Equal(StellarMorphology.BlackHole,StellarAppearance.Sprite(12,0,4).Morphology);
Check.Equal(0f,StellarAppearance.Sprite(12,0,4).Corona);
Check.That(StellarAppearance.Sprite(9,0,4).Opacity<StellarAppearance.Sprite(4,0,4).Opacity);
var invalid=StellarAppearance.Sprite(-1,double.NaN,double.PositiveInfinity);
Check.That(float.IsFinite(invalid.Radius+invalid.CoreRadius+invalid.Opacity));
}
[Test] public void OriginalCompactSizeReplacesSpectralEstimatesAndPreservesItsRatios()
{
var blue=StellarAppearance.Sprite(0,0,4,originalSize:.7f);
var red=StellarAppearance.Sprite(6,0,4,originalSize:.7f);
Check.Equal(blue.CoreRadius,red.CoreRadius);
var small=StellarAppearance.Sprite(4,0,4,originalSize:.3f);
var large=StellarAppearance.Sprite(4,0,4,originalSize:1.2f);
Check.That(Math.Abs(large.CoreRadius/small.CoreRadius-4)<.0001f);
Check.That(StellarAppearance.Sprite(4,0,4,originalSize:.05f).CoreRadius<.2f);
var marked=StellarAppearance.Sprite(4,0,4,3,originalSize:.3f);
Check.Equal(small.CoreRadius,marked.CoreRadius);Check.That(marked.Radius>=26);
Check.Equal(StellarAppearance.Sprite(4,0,4),StellarAppearance.Sprite(4,0,4,originalSize:float.NaN));
Check.Equal(StellarAppearance.Sprite(4,0,4,realistic:false),StellarAppearance.Sprite(4,0,4,realistic:false,originalSize:1.2f));
}
[Test] public void TerritoryPointsUseOriginalBoundedCameraDistanceTerm()
{
foreach(var distance in new[]{-1d,0d,10d,25d})Check.Equal(1f,StellarAppearance.PowerTerritoryDistanceScale(distance));
Check.Equal(1.5f,StellarAppearance.PowerTerritoryDistanceScale(37.5));
Check.Equal(2f,StellarAppearance.PowerTerritoryDistanceScale(50));
Check.Equal(2f,StellarAppearance.PowerTerritoryDistanceScale(50000));
Check.Equal(1f,StellarAppearance.PowerTerritoryDistanceScale(double.NaN));
Check.Equal(1f,StellarAppearance.PowerTerritoryDistanceScale(double.PositiveInfinity));
}
[Test] public void OriginalPhysicalSpriteUsesNativeLuminosityOverrideAndSeparateCompactObjects()
{
const int regular=25<<10;
foreach(var pair in new[]{(0,0),(2,1),(7,2),(9,3),(12,4),(17,5),(40,7),(41,8),(42,8),(43,9),(46,10),(48,13),(63,13)})
Check.Equal(pair.Item2,StellarAppearance.OriginalSprite((ushort)(regular|pair.Item1)));
for(var luminosity=0;luminosity<=13;luminosity++)
Check.Equal(5,StellarAppearance.OriginalSprite((ushort)(luminosity<<10|2)));
Check.Equal(6,StellarAppearance.OriginalSprite((ushort)(24<<10|2)));
Check.Equal(8,StellarAppearance.OriginalSprite(41));
Check.Equal(8,StellarAppearance.OriginalSprite((ushort)(24<<10|42)));
}
[Test] public void OriginalStellarHalfExtentMatchesProjectedUnitQuadWithoutPixelMinimum()
{
var camera=GalaxyCamera.Create(new(0,0,0),1600,900,.3,.45,4,20,-10);
foreach(var depth in new[]{camera.Distance*.1,camera.Distance,camera.Distance*3})
foreach(var size in new[]{.05f,.3f,.7f,2f})
{
var centre=new XFEEDToolBox.Core.Models.Coordinates(camera.Eye.X+camera.Forward.X*depth,camera.Eye.Y+camera.Forward.Y*depth,camera.Eye.Z+camera.Forward.Z*depth);
var half=(float)(Half)size;
var corner=new XFEEDToolBox.Core.Models.Coordinates(centre.X+camera.Right.X*half,centre.Y+camera.Right.Y*half,centre.Z+camera.Right.Z*half);
var expected=camera.Project(corner).X-camera.Project(centre).X;
var actual=StellarAppearance.OriginalProjectedHalfExtent(size,camera.Project(centre).Scale);
Check.That(Math.Abs(actual-expected)<.0001);
}
Check.That(StellarAppearance.OriginalProjectedHalfExtent(.05f,.001)<.0001f);
Check.Equal(0f,StellarAppearance.OriginalProjectedHalfExtent(float.NaN,4));
Check.Equal(0f,StellarAppearance.OriginalProjectedHalfExtent(.7f,double.PositiveInfinity));
}
[Test] public void NebulaTransmissionStopsAtThePerspectiveCameraInsideTheCloud()
{
var volume=new NebulaVolume(4,Enumerable.Range(0,4*4*4*4).Select(i=>i%4==3?(byte)255:(byte)0).ToArray(),Enumerable.Repeat(new Vector4(0,0,0,1),256).ToArray());
var atStar=volume.Transmission(Vector3.Zero,Vector3.UnitZ,0);
var closeCamera=volume.Transmission(Vector3.Zero,Vector3.UnitZ,.25f);
var outsideCamera=volume.Transmission(Vector3.Zero,Vector3.UnitZ,3);
Check.Equal(1f,atStar);Check.That(atStar>closeCamera&&closeCamera>outsideCamera);
Check.That(Math.Abs(closeCamera-MathF.Exp(-.25f*1.8f))<.0001f);
Check.Equal(volume.Transmission(Vector3.Zero,Vector3.UnitZ),outsideCamera);
}
[Test] public void InfiniteGridReachesEveryViewportEdgeUnderRotationPanAndZoom()
{
Span<MapSegment> lines=stackalloc MapSegment[InfiniteGalaxyGrid.MaximumLines];
foreach(var (yaw,pitch,scale,panX,panY) in new[]{(.3,.45,4d,0d,0d),(2.8,-.7,.001,183247d,-95378d),(-2d,1.45,1000d,-93714d,924815d),(.8,.02,4d,9000d,-3000d)})
{
var count=InfiniteGalaxyGrid.Write(lines,3840,2088,yaw,pitch,scale,panX,panY,out var step);
Check.That(count is >0 and <=InfiniteGalaxyGrid.MaximumLines);Check.That(step>0);
var left=false;var right=false;var top=false;var bottom=false;
foreach(var line in lines[..count])
{
foreach(var (x,y) in new[]{(line.X1,line.Y1),(line.X2,line.Y2)})
{
Check.That(double.IsFinite(x+y)&&x>=-.001&&x<=3840.001&&y>=-.001&&y<=2088.001);
left|=Math.Abs(x)<.001;right|=Math.Abs(x-3840)<.001;top|=Math.Abs(y)<.001;bottom|=Math.Abs(y-2088)<.001;
}
}
Check.That(left&&right&&top&&bottom,"Projected infinite grid must cross all viewport edges.");
}
}
[Test] public void EdgeOnGridIsHiddenAndInvalidCameraDoesNotEmitGeometry()
{
Span<MapSegment> lines=stackalloc MapSegment[InfiniteGalaxyGrid.MaximumLines];
Check.Equal(0,InfiniteGalaxyGrid.Write(lines,1000,800,.3,0,4,0,0,out _));
Check.Equal(0,InfiniteGalaxyGrid.Write(lines,1000,800,.3,.4,0,0,0,out _));
Check.Equal(0,InfiniteGalaxyGrid.Write(lines,1000,800,.3,.4,4,double.NaN,0,out _));
}
}
using XFEEDToolBox.Core.Galaxy;
using XFEEDToolBox.Core.Rendering;
using System.Numerics;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public class GalaxyAppearanceTests
{
[Test] public void SpectralColoursUseActualTypesAndUnknownTypesStayNeutral()
{
Check.Equal(0,StellarAppearance.ColourIndex("O (Blue-White) Star"));Check.Equal(5,StellarAppearance.ColourIndex("K"));
Check.Equal(6,StellarAppearance.ColourIndex("M (Red dwarf) Star"));Check.Equal(10,StellarAppearance.ColourIndex("DA"));
Check.Equal(11,StellarAppearance.ColourIndex("Neutron Star"));Check.Equal(13,StellarAppearance.ColourIndex(""));
Check.That(StellarAppearance.Palette[0].B>StellarAppearance.Palette[0].R);
Check.That(StellarAppearance.Palette[6].R>StellarAppearance.Palette[6].B);
Check.Equal(10,"OBAFGKMLTY".Select(c=>StellarAppearance.Palette[StellarAppearance.ColourIndex(c.ToString())]).Distinct().Count());
Check.Equal(6,StellarAppearance.ColourIndex("CJ"));Check.Equal(5,StellarAppearance.ColourIndex("TTS"));
Check.Equal(12,StellarAppearance.ColourIndex("Supermassive Black Hole"));
}
[Test] public void StellarSpritesAttenuateWithDistanceAndKeepSelectionSeparateFromThePhotosphere()
{
var nearby=StellarAppearance.Sprite(4,0,4);var distant=StellarAppearance.Sprite(4,400,4);
Check.That(nearby.CoreRadius>distant.CoreRadius&&nearby.Opacity>distant.Opacity);
Check.That(nearby.CoreRadius<2&&nearby.Radius<12,"Normal stars must remain compact luminous points at the default map scale.");
foreach(var importance in new[]{1,2,3})
{
var marked=StellarAppearance.Sprite(4,0,4,importance);
Check.Equal(nearby.CoreRadius,marked.CoreRadius);Check.Equal(nearby.Opacity,marked.Opacity);
if(importance>=2)Check.That(marked.Radius>=20,"Marker geometry must have enough space without enlarging the star.");
}
Check.That(StellarAppearance.Sprite(4,0,1000).CoreRadius>nearby.CoreRadius);
Check.That(StellarAppearance.Sprite(4,0,.001).CoreRadius<nearby.CoreRadius);
}
[Test] public void CompactObjectsAndBrownDwarfsHaveTheirOwnStellarMorphology()
{
Check.Equal(StellarMorphology.BrownDwarf,StellarAppearance.Sprite(7,0,4).Morphology);
Check.Equal(StellarMorphology.WhiteDwarf,StellarAppearance.Sprite(10,0,4).Morphology);
Check.Equal(StellarMorphology.NeutronStar,StellarAppearance.Sprite(11,0,4).Morphology);
Check.Equal(StellarMorphology.BlackHole,StellarAppearance.Sprite(12,0,4).Morphology);
Check.Equal(0f,StellarAppearance.Sprite(12,0,4).Corona);
Check.That(StellarAppearance.Sprite(9,0,4).Opacity<StellarAppearance.Sprite(4,0,4).Opacity);
var invalid=StellarAppearance.Sprite(-1,double.NaN,double.PositiveInfinity);
Check.That(float.IsFinite(invalid.Radius+invalid.CoreRadius+invalid.Opacity));
}
[Test] public void OriginalCompactSizeReplacesSpectralEstimatesAndPreservesItsRatios()
{
var blue=StellarAppearance.Sprite(0,0,4,originalSize:.7f);
var red=StellarAppearance.Sprite(6,0,4,originalSize:.7f);
Check.Equal(blue.CoreRadius,red.CoreRadius);
var small=StellarAppearance.Sprite(4,0,4,originalSize:.3f);
var large=StellarAppearance.Sprite(4,0,4,originalSize:1.2f);
Check.That(Math.Abs(large.CoreRadius/small.CoreRadius-4)<.0001f);
Check.That(StellarAppearance.Sprite(4,0,4,originalSize:.05f).CoreRadius<.2f);
var marked=StellarAppearance.Sprite(4,0,4,3,originalSize:.3f);
Check.Equal(small.CoreRadius,marked.CoreRadius);Check.That(marked.Radius>=26);
Check.Equal(StellarAppearance.Sprite(4,0,4),StellarAppearance.Sprite(4,0,4,originalSize:float.NaN));
Check.Equal(StellarAppearance.Sprite(4,0,4,realistic:false),StellarAppearance.Sprite(4,0,4,realistic:false,originalSize:1.2f));
}
[Test] public void TerritoryPointsUseOriginalBoundedCameraDistanceTerm()
{
foreach(var distance in new[]{-1d,0d,10d,25d})Check.Equal(1f,StellarAppearance.PowerTerritoryDistanceScale(distance));
Check.Equal(1.5f,StellarAppearance.PowerTerritoryDistanceScale(37.5));
Check.Equal(2f,StellarAppearance.PowerTerritoryDistanceScale(50));
Check.Equal(2f,StellarAppearance.PowerTerritoryDistanceScale(50000));
Check.Equal(1f,StellarAppearance.PowerTerritoryDistanceScale(double.NaN));
Check.Equal(1f,StellarAppearance.PowerTerritoryDistanceScale(double.PositiveInfinity));
}
[Test] public void OriginalPhysicalSpriteUsesNativeLuminosityOverrideAndSeparateCompactObjects()
{
const int regular=25<<10;
foreach(var pair in new[]{(0,0),(2,1),(7,2),(9,3),(12,4),(17,5),(40,7),(41,8),(42,8),(43,9),(46,10),(48,13),(63,13)})
Check.Equal(pair.Item2,StellarAppearance.OriginalSprite((ushort)(regular|pair.Item1)));
for(var luminosity=0;luminosity<=13;luminosity++)
Check.Equal(5,StellarAppearance.OriginalSprite((ushort)(luminosity<<10|2)));
Check.Equal(6,StellarAppearance.OriginalSprite((ushort)(24<<10|2)));
Check.Equal(8,StellarAppearance.OriginalSprite(41));
Check.Equal(8,StellarAppearance.OriginalSprite((ushort)(24<<10|42)));
}
[Test] public void OriginalStellarHalfExtentMatchesProjectedUnitQuadWithoutPixelMinimum()
{
var camera=GalaxyCamera.Create(new(0,0,0),1600,900,.3,.45,4,20,-10);
foreach(var depth in new[]{camera.Distance*.1,camera.Distance,camera.Distance*3})
foreach(var size in new[]{.05f,.3f,.7f,2f})
{
var centre=new XFEEDToolBox.Core.Models.Coordinates(camera.Eye.X+camera.Forward.X*depth,camera.Eye.Y+camera.Forward.Y*depth,camera.Eye.Z+camera.Forward.Z*depth);
var half=(float)(Half)size;
var corner=new XFEEDToolBox.Core.Models.Coordinates(centre.X+camera.Right.X*half,centre.Y+camera.Right.Y*half,centre.Z+camera.Right.Z*half);
var expected=camera.Project(corner).X-camera.Project(centre).X;
var actual=StellarAppearance.OriginalProjectedHalfExtent(size,camera.Project(centre).Scale);
Check.That(Math.Abs(actual-expected)<.0001);
}
Check.That(StellarAppearance.OriginalProjectedHalfExtent(.05f,.001)<.0001f);
Check.Equal(0f,StellarAppearance.OriginalProjectedHalfExtent(float.NaN,4));
Check.Equal(0f,StellarAppearance.OriginalProjectedHalfExtent(.7f,double.PositiveInfinity));
}
[Test] public void NebulaTransmissionStopsAtThePerspectiveCameraInsideTheCloud()
{
var volume=new NebulaVolume(4,Enumerable.Range(0,4*4*4*4).Select(i=>i%4==3?(byte)255:(byte)0).ToArray(),Enumerable.Repeat(new Vector4(0,0,0,1),256).ToArray());
var atStar=volume.Transmission(Vector3.Zero,Vector3.UnitZ,0);
var closeCamera=volume.Transmission(Vector3.Zero,Vector3.UnitZ,.25f);
var outsideCamera=volume.Transmission(Vector3.Zero,Vector3.UnitZ,3);
Check.Equal(1f,atStar);Check.That(atStar>closeCamera&&closeCamera>outsideCamera);
Check.That(Math.Abs(closeCamera-MathF.Exp(-.25f*1.8f))<.0001f);
Check.Equal(volume.Transmission(Vector3.Zero,Vector3.UnitZ),outsideCamera);
}
[Test] public void InfiniteGridReachesEveryViewportEdgeUnderRotationPanAndZoom()
{
Span<MapSegment> lines=stackalloc MapSegment[InfiniteGalaxyGrid.MaximumLines];
foreach(var (yaw,pitch,scale,panX,panY) in new[]{(.3,.45,4d,0d,0d),(2.8,-.7,.001,183247d,-95378d),(-2d,1.45,1000d,-93714d,924815d),(.8,.02,4d,9000d,-3000d)})
{
var count=InfiniteGalaxyGrid.Write(lines,3840,2088,yaw,pitch,scale,panX,panY,out var step);
Check.That(count is >0 and <=InfiniteGalaxyGrid.MaximumLines);Check.That(step>0);
var left=false;var right=false;var top=false;var bottom=false;
foreach(var line in lines[..count])
{
foreach(var (x,y) in new[]{(line.X1,line.Y1),(line.X2,line.Y2)})
{
Check.That(double.IsFinite(x+y)&&x>=-.001&&x<=3840.001&&y>=-.001&&y<=2088.001);
left|=Math.Abs(x)<.001;right|=Math.Abs(x-3840)<.001;top|=Math.Abs(y)<.001;bottom|=Math.Abs(y-2088)<.001;
}
}
Check.That(left&&right&&top&&bottom,"Projected infinite grid must cross all viewport edges.");
}
}
[Test] public void EdgeOnGridIsHiddenAndInvalidCameraDoesNotEmitGeometry()
{
Span<MapSegment> lines=stackalloc MapSegment[InfiniteGalaxyGrid.MaximumLines];
Check.Equal(0,InfiniteGalaxyGrid.Write(lines,1000,800,.3,0,4,0,0,out _));
Check.Equal(0,InfiniteGalaxyGrid.Write(lines,1000,800,.3,.4,0,0,0,out _));
Check.Equal(0,InfiniteGalaxyGrid.Write(lines,1000,800,.3,.4,4,double.NaN,0,out _));
}
}