using XFEEDToolBox.Core.Navigation;
using XFEEDToolBox.Core.Rendering;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public sealed class LocalSystemOrbitGeometryTests
{
private static (CurrentSystemSnapshot Snapshot,SystemOrbit Orbit) KnownOrbit()
{
var parent=new SystemLocation(1,11,"Parent star","",0,SystemLocationCategory.Star,null,new(1e11,2e10,0),null,false,"parent-star");
var child=new SystemLocation(2,22,"Child planet","",0,SystemLocationCategory.Planet,null,new(1.6e11,2e10,0),null,false,"child-planet");
var orbit=new SystemOrbit{SystemAddress=123,ChildWorldAddress=22,ChildIdentity=child.Identity,ParentWorldAddress=11,ParentIdentity=parent.Identity,
SemiMajorAxisMetres=1e11,Eccentricity=.4,PeriapsisDirection=new(1,0,0),TransverseDirection=new(0,0,1),
Quality=SystemOrbitQuality.NativeVerified,Source="Explicit geometry fixture",UnavailableReason=""};
return(new(123,"Geometry fixture",1,1,DateTimeOffset.UtcNow,[parent,child]){Orbits=[orbit]},orbit);
}
[Test]public void ParentIsTheFocusRatherThanTheCentreAndPolylineClosesExactly()
{
var parent=new Vector3d(100,200,300);
Check.That(LocalSystemOrbitGeometry.TrySampleEllipse(parent,1000,.6,new(1,0,0),new(0,1,0),out var points,out _));
Check.Equal(129,points.Count);Check.Equal(points[0],points[^1]);
Check.That((points[0]-(parent+new Vector3d(400,0,0))).Length<1e-9);
Check.That((points[64]-(parent+new Vector3d(-1600,0,0))).Length<1e-9);
// At eccentric anomaly pi/2, x=-a*e and y=a*sqrt(1-e^2).
Check.That((points[32]-(parent+new Vector3d(-600,800,0))).Length<1e-9);
}
[Test]public void RealThreeDimensionalBasisIsPreservedWithoutChoosingAScreenPlane()
{
var parent=new Vector3d(9e9,-2e9,7e9);var x=new Vector3d(0,0,1);var y=new Vector3d(1,0,0);
Check.That(LocalSystemOrbitGeometry.TrySampleEllipse(parent,1e8,0,x,y,out var points,out _));
Check.That((points[0]-parent-x*1e8).Length<1e-6);
Check.That((points[32]-parent-y*1e8).Length<1e-6);
Check.That(points.All(point=>Math.Abs((point-parent).Length-1e8)<1e-5&&point.Y==parent.Y));
}
[Test]public void MissingNonOrthogonalAndNonUnitPlanesAreRejectedRatherThanInvented()
{
foreach(var axes in new[]{(X:Vector3d.Zero,Y:new Vector3d(0,1,0)),(X:new Vector3d(1,0,0),Y:new Vector3d(1,0,0)),
(X:new Vector3d(2,0,0),Y:new Vector3d(0,1,0)),(X:new Vector3d(double.NaN,0,0),Y:new Vector3d(0,1,0))})
{Check.That(!LocalSystemOrbitGeometry.TrySampleEllipse(Vector3d.Zero,10,.1,axes.X,axes.Y,out var points,out _));Check.Equal(0,points.Count);}
}
[Test]public void NonEllipticAndOverflowingParametersCannotReachTheRenderer()
{
foreach(var value in new[]{(A:0d,E:0d),(A:-1d,E:.5),(A:1d,E:-.1),(A:1d,E:1d),(A:1d,E:double.NaN),
(A:double.PositiveInfinity,E:0d),(A:double.MaxValue,E:.9)})
{Check.That(!LocalSystemOrbitGeometry.TrySampleEllipse(Vector3d.Zero,value.A,value.E,new(1,0,0),new(0,1,0),out var points,out _));Check.Equal(0,points.Count);}
}
[Test]public void DistanceCompressionIsAppliedAfterSamplingAroundAnOffsetParent()
{
var star=new SystemLocation(1,1,"Star","",0,SystemLocationCategory.Star,null,new(0,0,0),null,false,"star");
var parent=new SystemLocation(2,2,"Planet","",0,SystemLocationCategory.Planet,null,new(2e11,0,0),null,false,"parent");
var scene=new LocalSystemScene(new(1,"System",1,1,DateTimeOffset.UtcNow,[star,parent]));
Check.That(LocalSystemOrbitGeometry.TrySampleEllipse(parent.Position!.Value,1e11,.4,new(1,0,0),new(0,0,1),out var actual,out _));
var transformed=actual.Select(scene.Transform).ToArray();
var compressedParent=scene.Transform(parent.Position.Value);
// A circle drawn around the already compressed parent would wrongly
// give equal radial offsets; the actual radial log transform does not.
Check.That(Math.Abs((transformed[0]-compressedParent).Length-(transformed[64]-compressedParent).Length)>.01);
Check.That(transformed.All(point=>point.Finite));
}
[Test]public void SceneUsesOneTransformForBodyOrbitAndParentFocus()
{
var (snapshot,_)=KnownOrbit();var scene=new LocalSystemScene(snapshot);var path=scene.Orbits.Single();
Check.Equal(1,scene.VerifiedOrbitCount);Check.Equal(0,scene.UnavailableOrbitCount);
Check.Equal(scene.Nodes.Single(node=>node.Location.Identity==path.Child.Identity).Position,path.Points[0]);
// The display scale is affine within this parent family, rather than
// applying a radial log that bends offset ellipses into crescents.
var centre=(path.Points[0]+path.Points[64])/2;var x=(path.Points[0]-path.Points[64])/2;var z=(path.Points[32]-path.Points[96])/2;
Check.That(path.Points.All(point=>Math.Abs(Math.Pow(Vector3d.Dot(point-centre,x)/Vector3d.Dot(x,x),2)
+Math.Pow(Vector3d.Dot(point-centre,z)/Vector3d.Dot(z,z),2)-1)<1e-9));
Check.That(path.Points.All(point=>point.Length<=4.4+1e-9));
var camera=LocalSystemCamera.Overview;var before=camera.Project(path.Points[32],900,600)!.Value;
var after=camera.Rotate(80,20).Project(path.Points[32],900,600)!.Value;
Check.That(Math.Abs(before.X-after.X)>1||Math.Abs(before.Y-after.Y)>1);
}
[Test]public void UnknownPlanesWrongSystemsAndAmbiguousParentsNeverBecomeOrbits()
{
var (snapshot,orbit)=KnownOrbit();
foreach(var invalid in new[]{orbit with{Quality=SystemOrbitQuality.ElementsOnly},orbit with{SystemAddress=124},
orbit with{ParentIdentity="wrong"},orbit with{ParentWorldAddress=99},orbit with{ChildIdentity="wrong"},
orbit with{PeriapsisDirection=null},orbit with{Source=""}})
{var scene=new LocalSystemScene(snapshot with{Orbits=[invalid]});Check.Equal(0,scene.Orbits.Count);Check.Equal(1,scene.UnavailableOrbitCount);}
var ambiguous=new LocalSystemScene(snapshot with{Locations=[..snapshot.Locations,snapshot.Locations[0]]});
Check.Equal(0,ambiguous.Orbits.Count);Check.Equal(1,ambiguous.UnavailableOrbitCount);
}
[Test]public void FilteringKeepsOrbitTransformAndCanHideParentWithoutChangingItsFocus()
{
var (snapshot,_)=KnownOrbit();var all=new LocalSystemScene(snapshot);var childOnly=new LocalSystemScene(snapshot,[snapshot.Locations[1]]);
Check.Equal(1,childOnly.Orbits.Count);Check.Equal(all.ExtentMetres,childOnly.ExtentMetres);
Check.That(all.Orbits[0].Points.SequenceEqual(childOnly.Orbits[0].Points));
Check.Equal("parent-star",childOnly.Orbits[0].ParentIdentity);
var parentOnly=new LocalSystemScene(snapshot,[snapshot.Locations[0]]);
Check.Equal(0,parentOnly.Orbits.Count);Check.Equal(1,parentOnly.VerifiedOrbitCount);
}
[Test]public void DuplicateChildOrbitRecordsAreReportedInsteadOfPickingOne()
{
var (snapshot,orbit)=KnownOrbit();var scene=new LocalSystemScene(snapshot with{Orbits=[orbit,orbit with{Eccentricity=.2}]});
Check.Equal(0,scene.Orbits.Count);Check.Equal(0,scene.VerifiedOrbitCount);Check.Equal(2,scene.UnavailableOrbitCount);
Check.That(scene.OrbitUnavailableReasons.Any(reason=>reason.Contains("多个",StringComparison.Ordinal)));
}
[Test]public void VerifiedNativeBarycentreCanBeAFocusWithoutInventingACatalogueNode()
{
var (snapshot,orbit)=KnownOrbit();var focus=snapshot.Locations[0].Position;
var scene=new LocalSystemScene(snapshot with{Locations=[snapshot.Locations[1]],Orbits=[orbit with{ParentPosition=focus,ParentName="Actual native barycentre",ParentIsBarycentre=true}]});
Check.Equal(1,scene.Nodes.Count);Check.Equal(1,scene.Orbits.Count);Check.That(scene.Orbits[0].Parent is null);
Check.Equal(focus!.Value,scene.Orbits[0].FocusPosition);Check.Equal("parent-star",scene.Orbits[0].ParentIdentity);
var unknown=new LocalSystemScene(snapshot with{Locations=[snapshot.Locations[1]],Orbits=[orbit with{ParentPosition=focus,Quality=SystemOrbitQuality.ElementsOnly}]});
Check.Equal(0,unknown.Orbits.Count);
var conflict=new LocalSystemScene(snapshot with{Orbits=[orbit with{ParentPosition=new(8e15,0,0)}]});
Check.Equal(0,conflict.Orbits.Count);
}
[Test]public void SharedWorldCarriersAndPoiDoNotConflictWithAVerifiedPhysicalFocus()
{
var (snapshot,orbit)=KnownOrbit();var parent=snapshot.Locations[0];
var carrier=new SystemLocation(3,parent.WorldAddress,"Carrier","",0,SystemLocationCategory.Carrier,123,new(1e11,1e8,0),null,false,"shared-world-carrier");
var poi=carrier with{Key=4,Name="POI",Category=SystemLocationCategory.PointOfInterest,Identity="shared-world-poi",MarketId=null};
var signal=poi with{Key=5,Name="Signal",Category=SystemLocationCategory.Signal,Identity="shared-world-signal"};
// The native focus is real even when it has no selectable physical row.
var recorded=snapshot with{Locations=[snapshot.Locations[1],carrier,poi,signal],Orbits=[orbit with{ParentPosition=parent.Position}]};
var scene=new LocalSystemScene(recorded);Check.Equal(1,scene.Orbits.Count);Check.Equal(0,scene.UnavailableOrbitCount);
Check.Equal(parent.Position!.Value,scene.Orbits[0].FocusPosition);Check.That(scene.Orbits[0].Parent is null);
// Another physical body claiming that world still is a real conflict.
var wrongParent=parent with{Identity="different-physical-parent"};
Check.Equal(0,new LocalSystemScene(recorded with{Locations=[..recorded.Locations,wrongParent]}).Orbits.Count);
// A carrier may share IDs but cannot become an orbiting physical child.
var wrongChild=carrier with{WorldAddress=orbit.ChildWorldAddress,Identity=orbit.ChildIdentity};
Check.Equal(0,new LocalSystemScene(recorded with{Locations=[wrongChild,poi,signal]}).Orbits.Count);
}
}
using XFEEDToolBox.Core.Navigation;
using XFEEDToolBox.Core.Rendering;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public sealed class LocalSystemOrbitGeometryTests
{
private static (CurrentSystemSnapshot Snapshot,SystemOrbit Orbit) KnownOrbit()
{
var parent=new SystemLocation(1,11,"Parent star","",0,SystemLocationCategory.Star,null,new(1e11,2e10,0),null,false,"parent-star");
var child=new SystemLocation(2,22,"Child planet","",0,SystemLocationCategory.Planet,null,new(1.6e11,2e10,0),null,false,"child-planet");
var orbit=new SystemOrbit{SystemAddress=123,ChildWorldAddress=22,ChildIdentity=child.Identity,ParentWorldAddress=11,ParentIdentity=parent.Identity,
SemiMajorAxisMetres=1e11,Eccentricity=.4,PeriapsisDirection=new(1,0,0),TransverseDirection=new(0,0,1),
Quality=SystemOrbitQuality.NativeVerified,Source="Explicit geometry fixture",UnavailableReason=""};
return(new(123,"Geometry fixture",1,1,DateTimeOffset.UtcNow,[parent,child]){Orbits=[orbit]},orbit);
}
[Test]public void ParentIsTheFocusRatherThanTheCentreAndPolylineClosesExactly()
{
var parent=new Vector3d(100,200,300);
Check.That(LocalSystemOrbitGeometry.TrySampleEllipse(parent,1000,.6,new(1,0,0),new(0,1,0),out var points,out _));
Check.Equal(129,points.Count);Check.Equal(points[0],points[^1]);
Check.That((points[0]-(parent+new Vector3d(400,0,0))).Length<1e-9);
Check.That((points[64]-(parent+new Vector3d(-1600,0,0))).Length<1e-9);
// At eccentric anomaly pi/2, x=-a*e and y=a*sqrt(1-e^2).
Check.That((points[32]-(parent+new Vector3d(-600,800,0))).Length<1e-9);
}
[Test]public void RealThreeDimensionalBasisIsPreservedWithoutChoosingAScreenPlane()
{
var parent=new Vector3d(9e9,-2e9,7e9);var x=new Vector3d(0,0,1);var y=new Vector3d(1,0,0);
Check.That(LocalSystemOrbitGeometry.TrySampleEllipse(parent,1e8,0,x,y,out var points,out _));
Check.That((points[0]-parent-x*1e8).Length<1e-6);
Check.That((points[32]-parent-y*1e8).Length<1e-6);
Check.That(points.All(point=>Math.Abs((point-parent).Length-1e8)<1e-5&&point.Y==parent.Y));
}
[Test]public void MissingNonOrthogonalAndNonUnitPlanesAreRejectedRatherThanInvented()
{
foreach(var axes in new[]{(X:Vector3d.Zero,Y:new Vector3d(0,1,0)),(X:new Vector3d(1,0,0),Y:new Vector3d(1,0,0)),
(X:new Vector3d(2,0,0),Y:new Vector3d(0,1,0)),(X:new Vector3d(double.NaN,0,0),Y:new Vector3d(0,1,0))})
{Check.That(!LocalSystemOrbitGeometry.TrySampleEllipse(Vector3d.Zero,10,.1,axes.X,axes.Y,out var points,out _));Check.Equal(0,points.Count);}
}
[Test]public void NonEllipticAndOverflowingParametersCannotReachTheRenderer()
{
foreach(var value in new[]{(A:0d,E:0d),(A:-1d,E:.5),(A:1d,E:-.1),(A:1d,E:1d),(A:1d,E:double.NaN),
(A:double.PositiveInfinity,E:0d),(A:double.MaxValue,E:.9)})
{Check.That(!LocalSystemOrbitGeometry.TrySampleEllipse(Vector3d.Zero,value.A,value.E,new(1,0,0),new(0,1,0),out var points,out _));Check.Equal(0,points.Count);}
}
[Test]public void DistanceCompressionIsAppliedAfterSamplingAroundAnOffsetParent()
{
var star=new SystemLocation(1,1,"Star","",0,SystemLocationCategory.Star,null,new(0,0,0),null,false,"star");
var parent=new SystemLocation(2,2,"Planet","",0,SystemLocationCategory.Planet,null,new(2e11,0,0),null,false,"parent");
var scene=new LocalSystemScene(new(1,"System",1,1,DateTimeOffset.UtcNow,[star,parent]));
Check.That(LocalSystemOrbitGeometry.TrySampleEllipse(parent.Position!.Value,1e11,.4,new(1,0,0),new(0,0,1),out var actual,out _));
var transformed=actual.Select(scene.Transform).ToArray();
var compressedParent=scene.Transform(parent.Position.Value);
// A circle drawn around the already compressed parent would wrongly
// give equal radial offsets; the actual radial log transform does not.
Check.That(Math.Abs((transformed[0]-compressedParent).Length-(transformed[64]-compressedParent).Length)>.01);
Check.That(transformed.All(point=>point.Finite));
}
[Test]public void SceneUsesOneTransformForBodyOrbitAndParentFocus()
{
var (snapshot,_)=KnownOrbit();var scene=new LocalSystemScene(snapshot);var path=scene.Orbits.Single();
Check.Equal(1,scene.VerifiedOrbitCount);Check.Equal(0,scene.UnavailableOrbitCount);
Check.Equal(scene.Nodes.Single(node=>node.Location.Identity==path.Child.Identity).Position,path.Points[0]);
// The display scale is affine within this parent family, rather than
// applying a radial log that bends offset ellipses into crescents.
var centre=(path.Points[0]+path.Points[64])/2;var x=(path.Points[0]-path.Points[64])/2;var z=(path.Points[32]-path.Points[96])/2;
Check.That(path.Points.All(point=>Math.Abs(Math.Pow(Vector3d.Dot(point-centre,x)/Vector3d.Dot(x,x),2)
+Math.Pow(Vector3d.Dot(point-centre,z)/Vector3d.Dot(z,z),2)-1)<1e-9));
Check.That(path.Points.All(point=>point.Length<=4.4+1e-9));
var camera=LocalSystemCamera.Overview;var before=camera.Project(path.Points[32],900,600)!.Value;
var after=camera.Rotate(80,20).Project(path.Points[32],900,600)!.Value;
Check.That(Math.Abs(before.X-after.X)>1||Math.Abs(before.Y-after.Y)>1);
}
[Test]public void UnknownPlanesWrongSystemsAndAmbiguousParentsNeverBecomeOrbits()
{
var (snapshot,orbit)=KnownOrbit();
foreach(var invalid in new[]{orbit with{Quality=SystemOrbitQuality.ElementsOnly},orbit with{SystemAddress=124},
orbit with{ParentIdentity="wrong"},orbit with{ParentWorldAddress=99},orbit with{ChildIdentity="wrong"},
orbit with{PeriapsisDirection=null},orbit with{Source=""}})
{var scene=new LocalSystemScene(snapshot with{Orbits=[invalid]});Check.Equal(0,scene.Orbits.Count);Check.Equal(1,scene.UnavailableOrbitCount);}
var ambiguous=new LocalSystemScene(snapshot with{Locations=[..snapshot.Locations,snapshot.Locations[0]]});
Check.Equal(0,ambiguous.Orbits.Count);Check.Equal(1,ambiguous.UnavailableOrbitCount);
}
[Test]public void FilteringKeepsOrbitTransformAndCanHideParentWithoutChangingItsFocus()
{
var (snapshot,_)=KnownOrbit();var all=new LocalSystemScene(snapshot);var childOnly=new LocalSystemScene(snapshot,[snapshot.Locations[1]]);
Check.Equal(1,childOnly.Orbits.Count);Check.Equal(all.ExtentMetres,childOnly.ExtentMetres);
Check.That(all.Orbits[0].Points.SequenceEqual(childOnly.Orbits[0].Points));
Check.Equal("parent-star",childOnly.Orbits[0].ParentIdentity);
var parentOnly=new LocalSystemScene(snapshot,[snapshot.Locations[0]]);
Check.Equal(0,parentOnly.Orbits.Count);Check.Equal(1,parentOnly.VerifiedOrbitCount);
}
[Test]public void DuplicateChildOrbitRecordsAreReportedInsteadOfPickingOne()
{
var (snapshot,orbit)=KnownOrbit();var scene=new LocalSystemScene(snapshot with{Orbits=[orbit,orbit with{Eccentricity=.2}]});
Check.Equal(0,scene.Orbits.Count);Check.Equal(0,scene.VerifiedOrbitCount);Check.Equal(2,scene.UnavailableOrbitCount);
Check.That(scene.OrbitUnavailableReasons.Any(reason=>reason.Contains("多个",StringComparison.Ordinal)));
}
[Test]public void VerifiedNativeBarycentreCanBeAFocusWithoutInventingACatalogueNode()
{
var (snapshot,orbit)=KnownOrbit();var focus=snapshot.Locations[0].Position;
var scene=new LocalSystemScene(snapshot with{Locations=[snapshot.Locations[1]],Orbits=[orbit with{ParentPosition=focus,ParentName="Actual native barycentre",ParentIsBarycentre=true}]});
Check.Equal(1,scene.Nodes.Count);Check.Equal(1,scene.Orbits.Count);Check.That(scene.Orbits[0].Parent is null);
Check.Equal(focus!.Value,scene.Orbits[0].FocusPosition);Check.Equal("parent-star",scene.Orbits[0].ParentIdentity);
var unknown=new LocalSystemScene(snapshot with{Locations=[snapshot.Locations[1]],Orbits=[orbit with{ParentPosition=focus,Quality=SystemOrbitQuality.ElementsOnly}]});
Check.Equal(0,unknown.Orbits.Count);
var conflict=new LocalSystemScene(snapshot with{Orbits=[orbit with{ParentPosition=new(8e15,0,0)}]});
Check.Equal(0,conflict.Orbits.Count);
}
[Test]public void SharedWorldCarriersAndPoiDoNotConflictWithAVerifiedPhysicalFocus()
{
var (snapshot,orbit)=KnownOrbit();var parent=snapshot.Locations[0];
var carrier=new SystemLocation(3,parent.WorldAddress,"Carrier","",0,SystemLocationCategory.Carrier,123,new(1e11,1e8,0),null,false,"shared-world-carrier");
var poi=carrier with{Key=4,Name="POI",Category=SystemLocationCategory.PointOfInterest,Identity="shared-world-poi",MarketId=null};
var signal=poi with{Key=5,Name="Signal",Category=SystemLocationCategory.Signal,Identity="shared-world-signal"};
// The native focus is real even when it has no selectable physical row.
var recorded=snapshot with{Locations=[snapshot.Locations[1],carrier,poi,signal],Orbits=[orbit with{ParentPosition=parent.Position}]};
var scene=new LocalSystemScene(recorded);Check.Equal(1,scene.Orbits.Count);Check.Equal(0,scene.UnavailableOrbitCount);
Check.Equal(parent.Position!.Value,scene.Orbits[0].FocusPosition);Check.That(scene.Orbits[0].Parent is null);
// Another physical body claiming that world still is a real conflict.
var wrongParent=parent with{Identity="different-physical-parent"};
Check.Equal(0,new LocalSystemScene(recorded with{Locations=[..recorded.Locations,wrongParent]}).Orbits.Count);
// A carrier may share IDs but cannot become an orbiting physical child.
var wrongChild=carrier with{WorldAddress=orbit.ChildWorldAddress,Identity=orbit.ChildIdentity};
Check.Equal(0,new LocalSystemScene(recorded with{Locations=[wrongChild,poi,signal]}).Orbits.Count);
}
}