using XFEEDToolBox.Core.Navigation;
using XFEEDToolBox.Core.Rendering;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public sealed class LocalSystemSceneTests
{
private static SystemLocation Body(int key,Vector3d? position,SystemLocationCategory category=SystemLocationCategory.Planet)
=>new(key,(ulong)key,"Body "+key,"",3,category,null,position,null,false,"identity-"+key);
private static CurrentSystemSnapshot Snapshot(params SystemLocation[] rows)=>new(1,"Actual coordinates",1,1,DateTimeOffset.UtcNow,rows);
[Test]public void PerspectiveHasIndependentKnownAxisDirectionsAndDepthScaling()
{
var camera=new LocalSystemCamera(0,0,10,new(0,0,0));
var centre=camera.Project(new(0,0,0),1000,600)!.Value;
var right=camera.Project(new(1,0,0),1000,600)!.Value;
var above=camera.Project(new(0,1,0),1000,600)!.Value;
var nearer=camera.Project(new(1,0,5),1000,600)!.Value;
Check.Equal(500d,centre.X);Check.Equal(300d,centre.Y);Check.That(right.X>centre.X&&above.Y<centre.Y);
Check.That(Math.Abs((nearer.X-centre.X)-2*(right.X-centre.X))<1e-9);
}
[Test]public void UnmeasuredLocationsAreNotInventedAndRemainCounted()
{
var scene=new LocalSystemScene(Snapshot(Body(1,new(0,0,0),SystemLocationCategory.Star),Body(2,null),Body(3,new(double.NaN,0,0))));
Check.Equal(1,scene.Nodes.Count);Check.Equal(2,scene.UnpositionedCount);
}
[Test]public void LogDisplayPreservesDirectionAndRadialOrderAcrossLargeDistances()
{
var scene=new LocalSystemScene(Snapshot(Body(1,new(0,0,0),SystemLocationCategory.Star),Body(2,new(1e10,0,1e10)),Body(3,new(1e14,0,1e14))));
Check.That(scene.Nodes[1].Position.Length<scene.Nodes[2].Position.Length);
Check.That(Math.Abs(scene.Nodes[1].Position.X-scene.Nodes[1].Position.Z)<1e-12);
Check.That(Math.Abs(scene.Nodes[2].Position.Length-4.4)<1e-9);
}
[Test]public void FilteringDoesNotChangeTheProjectionOfRemainingPhysicalCoordinates()
{
var snapshot=Snapshot(Body(1,new(0,0,0),SystemLocationCategory.Star),Body(2,new(1e10,3e9,0)),Body(3,new(1e14,0,0)));
var full=new LocalSystemScene(snapshot);var filtered=new LocalSystemScene(snapshot,[snapshot.Locations[1]]);
Check.Equal(full.Nodes[1].Position,filtered.Nodes[0].Position);
}
[Test]public void FocusAndRotationAreRealCameraOperations()
{
var camera=LocalSystemCamera.Overview;var point=new Vector3d(2,.3,1);
var focused=camera.Focus(point).Project(point,1000,600)!.Value;
Check.Equal(500d,focused.X);Check.Equal(300d,focused.Y);
var before=camera.Project(point,1000,600)!.Value;var after=camera.Rotate(100,35).Project(point,1000,600)!.Value;
Check.That(Math.Abs(before.X-after.X)>1&&Math.Abs(before.Y-after.Y)>1);
}
[Test]public void HitTestingUsesDrawDepthAndOriginalDestinationIdentity()
{
var first=Body(1,new(0,0,0));var second=Body(2,new(0,0,3));
var near=new LocalSystemProjection(new(second,new(0,0,3)),200,100,3,20,new(0,0,1),false);
var far=new LocalSystemProjection(new(first,new(0,0,0)),200,100,8,40,new(0,0,1),false);
var hit=LocalSystemScene.HitTest([far,near],200,100);Check.Equal("identity-2",hit[0].Node.Location.Identity);
Check.Equal(0,LocalSystemScene.HitTest([far,near],500,500).Count);
}
[Test]public void BehindCameraIsNotSelectableAndZoomRemainsBounded()
{
var camera=new LocalSystemCamera(0,0,1,new(0,0,0));
Check.That(camera.Project(new(0,0,2),1000,600) is null);
Check.Equal(.5,camera.Zoom(10000).Distance);Check.Equal(35d,camera.Zoom(-10000).Distance);
}
[Test]public void NonFiniteCameraAndOverflowingCoordinatesDoNotReachTheRenderer()
{
var camera=LocalSystemCamera.Overview;
Check.That((camera with{Yaw=double.NaN}).Project(new(0,0,0),1000,600) is null);
Check.That(camera.Project(new(0,0,0),double.PositiveInfinity,600) is null);
var scene=new LocalSystemScene(Snapshot(Body(1,new(0,0,0),SystemLocationCategory.Star),Body(2,new(1e300,1e300,0))));
Check.Equal(1,scene.Nodes.Count);Check.Equal(1,scene.UnpositionedCount);Check.That(double.IsFinite(scene.ExtentMetres));
}
[Test]public void CoincidentSignalsDoNotRequireAnInventedNonzeroLightingDirection()
{
var scene=new LocalSystemScene(Snapshot(Body(1,new(0,0,0),SystemLocationCategory.Star),Body(2,new(0,0,0),SystemLocationCategory.Signal)));
var projected=scene.Project(LocalSystemCamera.Overview,1000,600,null);
Check.Equal(2,projected.Count);Check.That(projected.All(point=>point.Light.Finite&&point.Light.Length>0));
}
[Test]public void OverviewKeepsTheWholeCatalogueInsideTheViewportWithSymbolMargins()
{
// Independently sample the full display sphere rather than one favourable system.
foreach(var size in new[]{(W:300d,H:500d),(W:750d,H:310d),(W:600d,H:600d),(W:400d,H:140d)})
for(var latitude=-90;latitude<=90;latitude+=10)
for(var longitude=0;longitude<360;longitude+=10)
{
var a=latitude*Math.PI/180;var b=longitude*Math.PI/180;
var point=new Vector3d(4.4*Math.Cos(a)*Math.Cos(b),4.4*Math.Sin(a),4.4*Math.Cos(a)*Math.Sin(b));
var at=LocalSystemCamera.OverviewFor(size.W,size.H).Project(point,size.W,size.H)!.Value;
var starMargin=36*7/at.Depth*1.8;
Check.That(at.X-starMargin>=0&&at.X+starMargin<=size.W&&at.Y-starMargin>=0&&at.Y+starMargin<=size.H);
}
}
}
using XFEEDToolBox.Core.Navigation;
using XFEEDToolBox.Core.Rendering;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public sealed class LocalSystemSceneTests
{
private static SystemLocation Body(int key,Vector3d? position,SystemLocationCategory category=SystemLocationCategory.Planet)
=>new(key,(ulong)key,"Body "+key,"",3,category,null,position,null,false,"identity-"+key);
private static CurrentSystemSnapshot Snapshot(params SystemLocation[] rows)=>new(1,"Actual coordinates",1,1,DateTimeOffset.UtcNow,rows);
[Test]public void PerspectiveHasIndependentKnownAxisDirectionsAndDepthScaling()
{
var camera=new LocalSystemCamera(0,0,10,new(0,0,0));
var centre=camera.Project(new(0,0,0),1000,600)!.Value;
var right=camera.Project(new(1,0,0),1000,600)!.Value;
var above=camera.Project(new(0,1,0),1000,600)!.Value;
var nearer=camera.Project(new(1,0,5),1000,600)!.Value;
Check.Equal(500d,centre.X);Check.Equal(300d,centre.Y);Check.That(right.X>centre.X&&above.Y<centre.Y);
Check.That(Math.Abs((nearer.X-centre.X)-2*(right.X-centre.X))<1e-9);
}
[Test]public void UnmeasuredLocationsAreNotInventedAndRemainCounted()
{
var scene=new LocalSystemScene(Snapshot(Body(1,new(0,0,0),SystemLocationCategory.Star),Body(2,null),Body(3,new(double.NaN,0,0))));
Check.Equal(1,scene.Nodes.Count);Check.Equal(2,scene.UnpositionedCount);
}
[Test]public void LogDisplayPreservesDirectionAndRadialOrderAcrossLargeDistances()
{
var scene=new LocalSystemScene(Snapshot(Body(1,new(0,0,0),SystemLocationCategory.Star),Body(2,new(1e10,0,1e10)),Body(3,new(1e14,0,1e14))));
Check.That(scene.Nodes[1].Position.Length<scene.Nodes[2].Position.Length);
Check.That(Math.Abs(scene.Nodes[1].Position.X-scene.Nodes[1].Position.Z)<1e-12);
Check.That(Math.Abs(scene.Nodes[2].Position.Length-4.4)<1e-9);
}
[Test]public void FilteringDoesNotChangeTheProjectionOfRemainingPhysicalCoordinates()
{
var snapshot=Snapshot(Body(1,new(0,0,0),SystemLocationCategory.Star),Body(2,new(1e10,3e9,0)),Body(3,new(1e14,0,0)));
var full=new LocalSystemScene(snapshot);var filtered=new LocalSystemScene(snapshot,[snapshot.Locations[1]]);
Check.Equal(full.Nodes[1].Position,filtered.Nodes[0].Position);
}
[Test]public void FocusAndRotationAreRealCameraOperations()
{
var camera=LocalSystemCamera.Overview;var point=new Vector3d(2,.3,1);
var focused=camera.Focus(point).Project(point,1000,600)!.Value;
Check.Equal(500d,focused.X);Check.Equal(300d,focused.Y);
var before=camera.Project(point,1000,600)!.Value;var after=camera.Rotate(100,35).Project(point,1000,600)!.Value;
Check.That(Math.Abs(before.X-after.X)>1&&Math.Abs(before.Y-after.Y)>1);
}
[Test]public void HitTestingUsesDrawDepthAndOriginalDestinationIdentity()
{
var first=Body(1,new(0,0,0));var second=Body(2,new(0,0,3));
var near=new LocalSystemProjection(new(second,new(0,0,3)),200,100,3,20,new(0,0,1),false);
var far=new LocalSystemProjection(new(first,new(0,0,0)),200,100,8,40,new(0,0,1),false);
var hit=LocalSystemScene.HitTest([far,near],200,100);Check.Equal("identity-2",hit[0].Node.Location.Identity);
Check.Equal(0,LocalSystemScene.HitTest([far,near],500,500).Count);
}
[Test]public void BehindCameraIsNotSelectableAndZoomRemainsBounded()
{
var camera=new LocalSystemCamera(0,0,1,new(0,0,0));
Check.That(camera.Project(new(0,0,2),1000,600) is null);
Check.Equal(.5,camera.Zoom(10000).Distance);Check.Equal(35d,camera.Zoom(-10000).Distance);
}
[Test]public void NonFiniteCameraAndOverflowingCoordinatesDoNotReachTheRenderer()
{
var camera=LocalSystemCamera.Overview;
Check.That((camera with{Yaw=double.NaN}).Project(new(0,0,0),1000,600) is null);
Check.That(camera.Project(new(0,0,0),double.PositiveInfinity,600) is null);
var scene=new LocalSystemScene(Snapshot(Body(1,new(0,0,0),SystemLocationCategory.Star),Body(2,new(1e300,1e300,0))));
Check.Equal(1,scene.Nodes.Count);Check.Equal(1,scene.UnpositionedCount);Check.That(double.IsFinite(scene.ExtentMetres));
}
[Test]public void CoincidentSignalsDoNotRequireAnInventedNonzeroLightingDirection()
{
var scene=new LocalSystemScene(Snapshot(Body(1,new(0,0,0),SystemLocationCategory.Star),Body(2,new(0,0,0),SystemLocationCategory.Signal)));
var projected=scene.Project(LocalSystemCamera.Overview,1000,600,null);
Check.Equal(2,projected.Count);Check.That(projected.All(point=>point.Light.Finite&&point.Light.Length>0));
}
[Test]public void OverviewKeepsTheWholeCatalogueInsideTheViewportWithSymbolMargins()
{
// Independently sample the full display sphere rather than one favourable system.
foreach(var size in new[]{(W:300d,H:500d),(W:750d,H:310d),(W:600d,H:600d),(W:400d,H:140d)})
for(var latitude=-90;latitude<=90;latitude+=10)
for(var longitude=0;longitude<360;longitude+=10)
{
var a=latitude*Math.PI/180;var b=longitude*Math.PI/180;
var point=new Vector3d(4.4*Math.Cos(a)*Math.Cos(b),4.4*Math.Sin(a),4.4*Math.Cos(a)*Math.Sin(b));
var at=LocalSystemCamera.OverviewFor(size.W,size.H).Project(point,size.W,size.H)!.Value;
var starMargin=36*7/at.Depth*1.8;
Check.That(at.X-starMargin>=0&&at.X+starMargin<=size.W&&at.Y-starMargin>=0&&at.Y+starMargin<=size.H);
}
}
}