using System.Numerics;
using XFEEDToolBox.Core.Rendering;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public sealed class ShipModelTests
{
[Test] public void ObjPreservesFacesAndSupportsRelativeIndices()
{
var mesh = ShipMeshReader.ReadObj("v 0 0 0\nv 1 0 0\nv 1 1 0\nv 0 1 0\nf -4 -3 -2 -1\n");
Check.Equal(6, mesh.Length); Check.That(mesh.All(v => v.Normal.Z > .99f));
Check.Throws<InvalidDataException>(() => ShipMeshReader.ReadObj("v 0 0 0\nf 1 2 3"));
Check.Throws<InvalidDataException>(() => ShipMeshReader.ReadObj("v NaN 0 0\nv 1 0 0\nv 0 1 0\nf 1 2 3"));
}
[Test] public void BinaryStlWithSolidHeaderIsStillReadAsBinary()
{
var bytes = new byte[134]; "solid binary"u8.CopyTo(bytes); BitConverter.GetBytes(1u).CopyTo(bytes, 80);
BitConverter.GetBytes(1f).CopyTo(bytes, 108); BitConverter.GetBytes(1f).CopyTo(bytes, 124);
var triangles = ShipMeshReader.ReadStl(bytes); Check.Equal(3, triangles.Length);
Check.That(Vector3.Cross(triangles[1].Position - triangles[0].Position, triangles[2].Position - triangles[0].Position).Z > .99f);
}
[Test] public void PositionDecompressionUsesPerVertexExponentFromGameShader()
{
var bytes = Convert.FromHexString("0DA9168FA9F0E95B7E3CEB04");
var small = EliteLocalModels.DecodePosition(bytes);
Check.That(Math.Abs(small.X - 6.6607004f) < .0001f && Math.Abs(small.Y + 10.701213f) < .0001f && Math.Abs(small.Z - 6.978805f) < .0001f);
bytes[11] = 0x84; Check.Equal(small, EliteLocalModels.DecodePosition(bytes));
bytes[11] = 5; var large = EliteLocalModels.DecodePosition(bytes);
Check.That(Vector3.Distance(small, large) > 1 && float.IsFinite(large.X));
bytes[11] = 63; Check.Throws<InvalidDataException>(() => EliteLocalModels.DecodePosition(bytes));
Check.Throws<InvalidDataException>(() => EliteLocalModels.DecodePosition(new byte[4]));
}
[Test] public void EveryCurrentShipHasItsOwnVerifiedLocalMeshReference()
{
var models = EliteLocalModels.Reference.Models;
Check.Equal(49, models.Length); Check.Equal(49, models.Select(x => x.ShipType).Distinct().Count());
Check.That(models.All(m => m.SourceSha256.Length == 64 && m.ResourceHash.Length == 40 && m.Meshes.All(d => d.IndexCount % 3 == 0)));
Check.That(models.Single(m => m.ShipType == "anaconda").ResourceName == "anaconda_loadingscreen");
}
[Test] public void HologramKeepsRotatingHullAndPedestalInsideEveryAspectRatio()
{
var mesh = ShipMeshReader.Create([
new(new(-.3f, -.2f, -1), Vector3.UnitY), new(new(.3f, -.2f, -1), Vector3.UnitY), new(new(0, .2f, 1), Vector3.UnitY)], "fixture", "");
var scene = new HologramScene(mesh);
foreach (var aspect in new[] { .25f, .5f, 1, 1.5f, 2.5f, 5 })
{
var (camera, _) = scene.Camera(aspect);
for (var seconds = 0; seconds <= 120; seconds += 5)
{
var hull = mesh.Vertices.Select(v => Vector3.Transform(v.Position, HologramScene.HullWorld(seconds)));
foreach (var p in hull.Concat(scene.Pedestal.Select(v => v.Position)))
{
var clip = Vector4.Transform(new Vector4(p, 1), camera);
Check.That(clip.W > 0 && Math.Abs(clip.X / clip.W) < .94f && Math.Abs(clip.Y / clip.W) < .94f && clip.Z / clip.W is > 0 and < 1);
}
}
}
Check.That(scene.BaseY < mesh.Vertices.Min(v => v.Position.Y) - .2f);
Check.Throws<ArgumentOutOfRangeException>(() => scene.Camera(0));
}
[Test] public void FittedHullUsesViewportAndStaysInsideFrustumWhileRotating()
{
var points=(from x in new[]{-.3f,.3f} from y in new[]{-.12f,.12f} from z in new[]{-1f,1f} select new MeshVertex(new(x,y,z),Vector3.UnitY)).ToArray();
var mesh=ShipMeshReader.Create(points.Concat(points.Take(1)).ToArray(),"fixture","");var scene=new HologramScene(mesh,false,false);
foreach(var aspect in new[]{.4f,1f,1.7f,3f,5f})for(var seconds=0;seconds<120;seconds+=5)
{
var world=HologramScene.HullWorld(seconds);var(camera,_)=scene.FittedCamera(aspect,world);var extent=0f;
foreach(var point in points)
{
var clip=Vector4.Transform(new Vector4(Vector3.Transform(point.Position,world),1),camera);var x=Math.Abs(clip.X/clip.W);var y=Math.Abs(clip.Y/clip.W);
Check.That(clip.W>0&&x<.901f&&y<.901f&&clip.Z/clip.W is >0 and <1);extent=Math.Max(extent,Math.Max(x,y));
}
Check.That(extent>.899f,"Hull bounds did not use the viewport.");
}
}
[Test] public void HologramEdgesExcludeCoplanarTriangulationDiagonals()
{
var mesh = ShipMeshReader.Create(ShipMeshReader.ReadObj("v 0 0 0\nv 1 0 0\nv 1 1 0\nv 0 1 0\nf 1 2 3\nf 1 3 4"), "fixture", "");
var scene = new HologramScene(mesh);
Check.Equal(8, scene.FeatureLines.Length);
Check.That(scene.PedestalLines.Length > 96 && scene.Pedestal.Length > 96);
}
[Test] public void OppositeWindingDoesNotAddAnInternalPanelDiagonal()
{
var mesh=ShipMeshReader.Create(ShipMeshReader.ReadObj("v 0 0 0\nv 1 0 0\nv 1 1 0\nv 0 1 0\nf 1 2 3\nf 4 3 1"),"fixture","");
Check.Equal(8,new HologramScene(mesh).FeatureLines.Length);
}
[Test] public void SegmentedHullEdgesStayContinuousWhileTinyPanelDetailsAreRemoved()
{
var triangles=new List<MeshVertex>();
void Quad(float x,float y,float width,float height,float z)
{
var a=new Vector3(x,y,z);var b=new Vector3(x+width,y,z);var c=new Vector3(x+width,y+height,z);var d=new Vector3(x,y+height,z);
foreach(var p in new[]{a,b,c,a,c,d})triangles.Add(new(p,Vector3.UnitZ));
}
for(var i=0;i<100;i++)Quad(i*.01f,0,.01f,.6f,0);
for(var i=0;i<20;i++)Quad(.1f+i*.035f,.3f,.003f,.003f,.002f);
var scene=new HologramScene(ShipMeshReader.Create(triangles.ToArray(),"fixture",""));
Check.Equal(8,scene.FeatureLines.Length);
Check.That(scene.FeatureLines.All(v=>v.Normal.Length()>1));
}
}
using System.Numerics;
using XFEEDToolBox.Core.Rendering;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public sealed class ShipModelTests
{
[Test] public void ObjPreservesFacesAndSupportsRelativeIndices()
{
var mesh = ShipMeshReader.ReadObj("v 0 0 0\nv 1 0 0\nv 1 1 0\nv 0 1 0\nf -4 -3 -2 -1\n");
Check.Equal(6, mesh.Length); Check.That(mesh.All(v => v.Normal.Z > .99f));
Check.Throws<InvalidDataException>(() => ShipMeshReader.ReadObj("v 0 0 0\nf 1 2 3"));
Check.Throws<InvalidDataException>(() => ShipMeshReader.ReadObj("v NaN 0 0\nv 1 0 0\nv 0 1 0\nf 1 2 3"));
}
[Test] public void BinaryStlWithSolidHeaderIsStillReadAsBinary()
{
var bytes = new byte[134]; "solid binary"u8.CopyTo(bytes); BitConverter.GetBytes(1u).CopyTo(bytes, 80);
BitConverter.GetBytes(1f).CopyTo(bytes, 108); BitConverter.GetBytes(1f).CopyTo(bytes, 124);
var triangles = ShipMeshReader.ReadStl(bytes); Check.Equal(3, triangles.Length);
Check.That(Vector3.Cross(triangles[1].Position - triangles[0].Position, triangles[2].Position - triangles[0].Position).Z > .99f);
}
[Test] public void PositionDecompressionUsesPerVertexExponentFromGameShader()
{
var bytes = Convert.FromHexString("0DA9168FA9F0E95B7E3CEB04");
var small = EliteLocalModels.DecodePosition(bytes);
Check.That(Math.Abs(small.X - 6.6607004f) < .0001f && Math.Abs(small.Y + 10.701213f) < .0001f && Math.Abs(small.Z - 6.978805f) < .0001f);
bytes[11] = 0x84; Check.Equal(small, EliteLocalModels.DecodePosition(bytes));
bytes[11] = 5; var large = EliteLocalModels.DecodePosition(bytes);
Check.That(Vector3.Distance(small, large) > 1 && float.IsFinite(large.X));
bytes[11] = 63; Check.Throws<InvalidDataException>(() => EliteLocalModels.DecodePosition(bytes));
Check.Throws<InvalidDataException>(() => EliteLocalModels.DecodePosition(new byte[4]));
}
[Test] public void EveryCurrentShipHasItsOwnVerifiedLocalMeshReference()
{
var models = EliteLocalModels.Reference.Models;
Check.Equal(49, models.Length); Check.Equal(49, models.Select(x => x.ShipType).Distinct().Count());
Check.That(models.All(m => m.SourceSha256.Length == 64 && m.ResourceHash.Length == 40 && m.Meshes.All(d => d.IndexCount % 3 == 0)));
Check.That(models.Single(m => m.ShipType == "anaconda").ResourceName == "anaconda_loadingscreen");
}
[Test] public void HologramKeepsRotatingHullAndPedestalInsideEveryAspectRatio()
{
var mesh = ShipMeshReader.Create([
new(new(-.3f, -.2f, -1), Vector3.UnitY), new(new(.3f, -.2f, -1), Vector3.UnitY), new(new(0, .2f, 1), Vector3.UnitY)], "fixture", "");
var scene = new HologramScene(mesh);
foreach (var aspect in new[] { .25f, .5f, 1, 1.5f, 2.5f, 5 })
{
var (camera, _) = scene.Camera(aspect);
for (var seconds = 0; seconds <= 120; seconds += 5)
{
var hull = mesh.Vertices.Select(v => Vector3.Transform(v.Position, HologramScene.HullWorld(seconds)));
foreach (var p in hull.Concat(scene.Pedestal.Select(v => v.Position)))
{
var clip = Vector4.Transform(new Vector4(p, 1), camera);
Check.That(clip.W > 0 && Math.Abs(clip.X / clip.W) < .94f && Math.Abs(clip.Y / clip.W) < .94f && clip.Z / clip.W is > 0 and < 1);
}
}
}
Check.That(scene.BaseY < mesh.Vertices.Min(v => v.Position.Y) - .2f);
Check.Throws<ArgumentOutOfRangeException>(() => scene.Camera(0));
}
[Test] public void FittedHullUsesViewportAndStaysInsideFrustumWhileRotating()
{
var points=(from x in new[]{-.3f,.3f} from y in new[]{-.12f,.12f} from z in new[]{-1f,1f} select new MeshVertex(new(x,y,z),Vector3.UnitY)).ToArray();
var mesh=ShipMeshReader.Create(points.Concat(points.Take(1)).ToArray(),"fixture","");var scene=new HologramScene(mesh,false,false);
foreach(var aspect in new[]{.4f,1f,1.7f,3f,5f})for(var seconds=0;seconds<120;seconds+=5)
{
var world=HologramScene.HullWorld(seconds);var(camera,_)=scene.FittedCamera(aspect,world);var extent=0f;
foreach(var point in points)
{
var clip=Vector4.Transform(new Vector4(Vector3.Transform(point.Position,world),1),camera);var x=Math.Abs(clip.X/clip.W);var y=Math.Abs(clip.Y/clip.W);
Check.That(clip.W>0&&x<.901f&&y<.901f&&clip.Z/clip.W is >0 and <1);extent=Math.Max(extent,Math.Max(x,y));
}
Check.That(extent>.899f,"Hull bounds did not use the viewport.");
}
}
[Test] public void HologramEdgesExcludeCoplanarTriangulationDiagonals()
{
var mesh = ShipMeshReader.Create(ShipMeshReader.ReadObj("v 0 0 0\nv 1 0 0\nv 1 1 0\nv 0 1 0\nf 1 2 3\nf 1 3 4"), "fixture", "");
var scene = new HologramScene(mesh);
Check.Equal(8, scene.FeatureLines.Length);
Check.That(scene.PedestalLines.Length > 96 && scene.Pedestal.Length > 96);
}
[Test] public void OppositeWindingDoesNotAddAnInternalPanelDiagonal()
{
var mesh=ShipMeshReader.Create(ShipMeshReader.ReadObj("v 0 0 0\nv 1 0 0\nv 1 1 0\nv 0 1 0\nf 1 2 3\nf 4 3 1"),"fixture","");
Check.Equal(8,new HologramScene(mesh).FeatureLines.Length);
}
[Test] public void SegmentedHullEdgesStayContinuousWhileTinyPanelDetailsAreRemoved()
{
var triangles=new List<MeshVertex>();
void Quad(float x,float y,float width,float height,float z)
{
var a=new Vector3(x,y,z);var b=new Vector3(x+width,y,z);var c=new Vector3(x+width,y+height,z);var d=new Vector3(x,y+height,z);
foreach(var p in new[]{a,b,c,a,c,d})triangles.Add(new(p,Vector3.UnitZ));
}
for(var i=0;i<100;i++)Quad(i*.01f,0,.01f,.6f,0);
for(var i=0;i<20;i++)Quad(.1f+i*.035f,.3f,.003f,.003f,.002f);
var scene=new HologramScene(ShipMeshReader.Create(triangles.ToArray(),"fixture",""));
Check.Equal(8,scene.FeatureLines.Length);
Check.That(scene.FeatureLines.All(v=>v.Normal.Length()>1));
}
}