using System; using System.Numerics; using AcDream.Core.Physics; using Xunit; namespace AcDream.Core.Tests.Physics; /// /// Conformance tests for . /// /// /// All anchor cases are geometrically verifiable independently of the /// implementation — they are derived from first-principles geometry, not /// from the method under test, to avoid a circular test. /// /// /// /// Retail oracle: CSphere::intersects_sphere @ 0x00537A80 (named-retail /// decomp) + ACE.Server/Physics/Sphere.cs::FindTimeOfCollision (C# port). /// /// public class SphereIntersectsSphereConformanceTests { // ----------------------------------------------------------------------- // Geometry anchors — verified by hand before the implementation existed // ----------------------------------------------------------------------- /// /// Two unit spheres (r=1 each) 5 units apart on the X axis. /// Mover at origin, target at (5, 0, 0). /// Sweep: move 5 units in +X. /// Combined radius = 2. /// Expected first contact at x = 3 from start = t = 3/5 = 0.6. /// [Fact] public void HeadOn_HitsAtExpectedTime() { bool hit = CollisionPrimitives.SweptSphereHitsSphere( moverCenter: Vector3.Zero, moverRadius: 1f, sweepDelta: new Vector3(5f, 0f, 0f), targetCenter: new Vector3(5f, 0f, 0f), targetRadius: 1f, out float t); Assert.True(hit, "Head-on sweep should hit"); // t = 3/5 = 0.6 — surface contact when mover centre is at x=3, // target centre at x=5, gap = 2 = combined radius. Within ±1e-4. Assert.True(MathF.Abs(t - 0.6f) < 1e-4f, $"Expected t≈0.6, got {t:G6}"); } /// /// Two unit spheres. Mover sweeps purely in +Y, target is offset 3 units /// in +X. The sweep never reaches within combined radius (2) of the target. /// [Fact] public void PerpendicularSweep_TooFar_Misses() { // Mover at origin, target at (3, 0, 0). Sweep in +Y by 10 units. // Closest approach = 3 units (> combined radius 2). bool hit = CollisionPrimitives.SweptSphereHitsSphere( moverCenter: Vector3.Zero, moverRadius: 1f, sweepDelta: new Vector3(0f, 10f, 0f), targetCenter: new Vector3(3f, 0f, 0f), targetRadius: 1f, out float _); Assert.False(hit, "Perpendicular sweep at distance 3 > combinedR 2 should miss"); } /// /// A sweep that grazes the target sphere (closest approach = exactly /// combined radius). Geometrically this is a tangent hit and should /// return true with t in (0, 1]. /// Mover at origin, sweep in +Y by 6. Target at (2, 3, 0). /// Combined radius = 2 (r1=r2=1). Closest approach = 2 (tangent). /// [Fact] public void TangentSweep_Hits() { // Mover sweeps from (0,0,0) to (0,6,0). // Target at (2, 3, 0). At t=0.5 mover centre is at (0,3,0). // Distance at closest = 2, exactly combinedR → tangent touch. bool hit = CollisionPrimitives.SweptSphereHitsSphere( moverCenter: Vector3.Zero, moverRadius: 1f, sweepDelta: new Vector3(0f, 6f, 0f), targetCenter: new Vector3(2f, 3f, 0f), targetRadius: 1f, out float t); Assert.True(hit, "Tangent sweep (distance = combinedR) should register as a hit"); Assert.True(t > 0f && t <= 1f, $"t={t:G6} should be in (0,1]"); } /// /// Sweep that completely passes by: target is beside the path, offset /// 2.1 units (> combined radius 2). Should miss. /// [Fact] public void OffAxisSweep_JustOutside_Misses() { bool hit = CollisionPrimitives.SweptSphereHitsSphere( moverCenter: Vector3.Zero, moverRadius: 1f, sweepDelta: new Vector3(0f, 6f, 0f), targetCenter: new Vector3(2.1f, 3f, 0f), targetRadius: 1f, out float _); Assert.False(hit, "Lateral offset 2.1 > combinedR 2 — should miss"); } /// /// Sweep away from the target — degenerate "wrong direction". /// Mover at (0,0,0) sweeps in −X while target is at (5,0,0). /// The sweep is directly away; no forward contact. /// [Fact] public void SweepAwayFromTarget_Misses() { bool hit = CollisionPrimitives.SweptSphereHitsSphere( moverCenter: Vector3.Zero, moverRadius: 1f, sweepDelta: new Vector3(-5f, 0f, 0f), targetCenter: new Vector3(5f, 0f, 0f), targetRadius: 1f, out float _); Assert.False(hit, "Sweep directly away from target should not hit"); } /// /// Sweep within the step but the target is too far for the step to reach. /// Target is 10 units away, sweep is only 3 units — t would be >1. /// [Fact] public void TargetBeyondStep_Misses() { // combinedR = 2; target centre 10 away; contact at t = (10-2)/3 ≈ 2.67 > 1. bool hit = CollisionPrimitives.SweptSphereHitsSphere( moverCenter: Vector3.Zero, moverRadius: 1f, sweepDelta: new Vector3(3f, 0f, 0f), targetCenter: new Vector3(10f, 0f, 0f), targetRadius: 1f, out float _); Assert.False(hit, "Target 10 away with only a 3-unit sweep should miss (t>1)"); } /// /// Zero-length sweep is degenerate — should not hit regardless of position. /// [Fact] public void DegenerateSweep_Misses() { bool hit = CollisionPrimitives.SweptSphereHitsSphere( moverCenter: Vector3.Zero, moverRadius: 1f, sweepDelta: Vector3.Zero, targetCenter: new Vector3(0.5f, 0f, 0f), targetRadius: 0.1f, out float _); Assert.False(hit, "Zero-length sweep should return false (degenerate)"); } /// /// Already-overlapping spheres: gap < 0 — the static overlap case. /// Retail returns -1 (no forward collision time) for already-overlapping /// spheres; returns /// false (caller handles static overlap separately). /// [Fact] public void AlreadyOverlapping_ReturnsFalse() { // Centres 0.5 apart, combined radius 2 — deeply overlapping. bool hit = CollisionPrimitives.SweptSphereHitsSphere( moverCenter: Vector3.Zero, moverRadius: 1f, sweepDelta: new Vector3(1f, 0f, 0f), targetCenter: new Vector3(0.5f, 0f, 0f), targetRadius: 1f, out float _); Assert.False(hit, "Already-overlapping spheres: retail FindTimeOfCollision returns -1 (no forward t); SweptSphereHitsSphere should return false"); } // ----------------------------------------------------------------------- // 3-D geometry — sphere primitive must use full 3-D distance // ----------------------------------------------------------------------- /// /// Pure Z-axis sweep: verifies the primitive uses 3-D distance (not XY-only). /// A purely vertical sweep toward a sphere directly below should hit. /// [Fact] public void VerticalSweep_HitsTargetBelow() { // Mover at (0,0,5), sweeps down -Z by 5 to (0,0,0). // Target at (0,0,0) with radius 1. Combined radius = 2. // First contact when mover centre Z = 2 → t = (5-2)/5 = 0.6. bool hit = CollisionPrimitives.SweptSphereHitsSphere( moverCenter: new Vector3(0f, 0f, 5f), moverRadius: 1f, sweepDelta: new Vector3(0f, 0f, -5f), targetCenter: Vector3.Zero, targetRadius: 1f, out float t); Assert.True(hit, "Vertical sweep toward sphere below should hit"); Assert.True(MathF.Abs(t - 0.6f) < 1e-4f, $"Expected t≈0.6, got {t:G6}"); } }