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}");
}
}