using System.Numerics;
using DatReaderWriter.Enums;
using DatReaderWriter.Types;
using AcDream.Core.Physics;
using Xunit;
namespace AcDream.Core.Tests.Physics;
///
/// Unit tests for .
///
/// Real BSP data requires dat files (integration-test territory), so these
/// tests use manually constructed BSP nodes and polygon/vertex data that
/// match the structure the dat reader would produce.
///
public class BSPQueryTests
{
// -----------------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------------
///
/// Build a with four vertices forming a unit
/// square in the XY-plane (Z = 0), ids 0-3.
///
private static VertexArray UnitSquareVertexArray()
{
var va = new VertexArray();
var positions = new[]
{
new Vector3(0f, 0f, 0f),
new Vector3(1f, 0f, 0f),
new Vector3(1f, 1f, 0f),
new Vector3(0f, 1f, 0f),
};
for (ushort i = 0; i < positions.Length; i++)
{
var sv = new SWVertex { Origin = positions[i], Normal = Vector3.UnitZ };
va.Vertices[i] = sv;
}
return va;
}
///
/// Build a referencing vertex ids 0-3 in order.
///
private static Polygon UnitSquarePolygon() => new Polygon
{
SidesType = DatReaderWriter.Enums.CullMode.None,
VertexIds = new List { 0, 1, 2, 3 },
};
///
/// Build a leaf containing one polygon (id 0)
/// with a bounding sphere that covers the unit square.
///
private static PhysicsBSPNode LeafNode(Sphere bounds)
{
var node = new PhysicsBSPNode
{
Type = BSPNodeType.Leaf,
BoundingSphere = bounds,
};
node.Polygons.Add(0);
return node;
}
// -----------------------------------------------------------------------
// Test 1: null node returns false without throwing
// -----------------------------------------------------------------------
[Fact]
public void SphereIntersectsPoly_NullNode_ReturnsFalse()
{
var polygons = new Dictionary();
var vertices = new VertexArray();
bool hit = BSPQuery.SphereIntersectsPoly(
null, polygons, vertices,
new Vector3(0.5f, 0.5f, 0.1f), 0.2f,
out _, out _);
Assert.False(hit);
}
// -----------------------------------------------------------------------
// Test 2: sphere far outside the bounding sphere is fast-rejected
// -----------------------------------------------------------------------
[Fact]
public void SphereIntersectsPoly_MissesBoundingSphere_ReturnsFalse()
{
// Leaf node centred at origin with radius 1.
var bounds = new Sphere { Origin = Vector3.Zero, Radius = 1f };
var node = LeafNode(bounds);
var polygons = new Dictionary { [0] = UnitSquarePolygon() };
var vertices = UnitSquareVertexArray();
// Sphere is 100 units away — broad phase must reject.
bool hit = BSPQuery.SphereIntersectsPoly(
node, polygons, vertices,
new Vector3(100f, 100f, 100f), 0.5f,
out _, out _);
Assert.False(hit);
}
// -----------------------------------------------------------------------
// Test 3: sphere resting just above the unit-square floor polygon hits
// -----------------------------------------------------------------------
[Fact]
public void SphereIntersectsPoly_HitsLeafPolygon()
{
// Bounding sphere covers the 1×1 unit-square leaf.
var bounds = new Sphere { Origin = new Vector3(0.5f, 0.5f, 0f), Radius = 2f };
var node = LeafNode(bounds);
var polygons = new Dictionary { [0] = UnitSquarePolygon() };
var vertices = UnitSquareVertexArray();
// Sphere centred at (0.5, 0.5, 0.3) with radius 0.5 should touch Z=0 plane.
bool hit = BSPQuery.SphereIntersectsPoly(
node, polygons, vertices,
new Vector3(0.5f, 0.5f, 0.3f), 0.5f,
out ushort polyId, out Vector3 normal);
Assert.True(hit);
Assert.Equal(0, polyId);
// Normal should point roughly upward (+Z).
Assert.True(normal.Z > 0.9f, $"Expected Z-up normal, got {normal}");
}
// -----------------------------------------------------------------------
// Test 4: sphere entirely above the polygon (no contact) returns false
// -----------------------------------------------------------------------
[Fact]
public void SphereIntersectsPoly_SphereTooHigh_ReturnsFalse()
{
var bounds = new Sphere { Origin = new Vector3(0.5f, 0.5f, 0f), Radius = 2f };
var node = LeafNode(bounds);
var polygons = new Dictionary { [0] = UnitSquarePolygon() };
var vertices = UnitSquareVertexArray();
// Sphere centred 5 units above the floor with radius 0.3 → no contact.
bool hit = BSPQuery.SphereIntersectsPoly(
node, polygons, vertices,
new Vector3(0.5f, 0.5f, 5f), 0.3f,
out _, out _);
Assert.False(hit);
}
// -----------------------------------------------------------------------
// Test 5: internal node — sphere on positive side recurses pos subtree
// -----------------------------------------------------------------------
[Fact]
public void SphereIntersectsPoly_InternalNode_PosSubtreeHit()
{
// Leaf on the positive side (Z > 0 half-space) contains the floor poly.
var leafBounds = new Sphere { Origin = new Vector3(0.5f, 0.5f, 0f), Radius = 2f };
var leafNode = LeafNode(leafBounds);
var polygons = new Dictionary { [0] = UnitSquarePolygon() };
var vertices = UnitSquareVertexArray();
// Splitting plane: Z = 0, normal = +Z, D = 0.
// Sphere at Z = 0.3 is on the positive side.
var internalBounds = new Sphere { Origin = new Vector3(0.5f, 0.5f, 0f), Radius = 5f };
var internalNode = new PhysicsBSPNode
{
Type = BSPNodeType.BPnn, // has PosNode only (BPnn = pos + null-neg)
SplittingPlane = new Plane(Vector3.UnitZ, 0f),
BoundingSphere = internalBounds,
PosNode = leafNode,
NegNode = null,
};
bool hit = BSPQuery.SphereIntersectsPoly(
internalNode, polygons, vertices,
new Vector3(0.5f, 0.5f, 0.3f), 0.5f,
out ushort polyId, out _);
Assert.True(hit);
Assert.Equal(0, polyId);
}
// -----------------------------------------------------------------------
// Test 6: PhysicsDataCache — caches GfxObj with physics data
// -----------------------------------------------------------------------
[Fact]
public void PhysicsDataCache_CachesGfxObjWithPhysics()
{
// We can't easily construct a real GfxObj (field-based dat type),
// so this test verifies the SetupPhysics cache path which is more
// easily instantiated.
var cache = new PhysicsDataCache();
Assert.Equal(0, cache.GfxObjCount);
Assert.Equal(0, cache.SetupCount);
// GetGfxObj for an unknown id should return null safely.
Assert.Null(cache.GetGfxObj(0x01000001u));
Assert.Null(cache.GetSetup(0x02000001u));
}
}