Keep external-container visibility tied to the signed surface gap between the player and container physics cylinders, matching gmExternalContainerUI's authored UseRadius range watcher. This prevents a valid ViewContents response from being closed at ACE's natural approach endpoint. Port Position::cylinder_distance and ACCWeenieObject::ObjectsInRange into Core with conformance coverage for 3-D separation, overlap, mode precedence, and inclusive boundaries. Release build succeeds and all 5,895 tests pass with five intentional skips. Co-authored-by: OpenAI Codex <codex@openai.com>
90 lines
3.3 KiB
C#
90 lines
3.3 KiB
C#
using System.Numerics;
|
|
using AcDream.Core.Physics;
|
|
using AcDream.Core.Physics.Motion;
|
|
using Xunit;
|
|
|
|
namespace AcDream.Core.Tests.Physics.Motion;
|
|
|
|
/// <summary>
|
|
/// R4-V1 — <c>Position::cylinder_distance</c>, the pure-math shape per
|
|
/// r4-moveto-decomp.md §5a (<c>MoveToManager::GetCurrentDistance</c>,
|
|
/// <c>005291b0</c>): edge-to-edge distance between two vertical cylinders
|
|
/// (own radius/height, target radius/height, both positions). Object moves
|
|
/// (use_spheres set on the wire) use this; position moves use plain
|
|
/// Euclidean <c>Position::distance</c> (§5a: "position moves use center
|
|
/// distance" — <see cref="MoveToMath.CylinderDistance"/> is the object-move
|
|
/// variant only; center distance is <c>Vector3.Distance</c>, already
|
|
/// available, not re-ported here).
|
|
///
|
|
/// <para>
|
|
/// The exact branch structure is visible at named-retail
|
|
/// <c>Position::cylinder_distance @ 0x005A97F0</c> and independently matches
|
|
/// ACE <c>Physics.Common.Position.CylinderDistance</c>. It uses full 3D center
|
|
/// distance, both radii, both heights, and returns signed overlap.
|
|
/// </para>
|
|
/// </summary>
|
|
public sealed class MoveToMathCylinderDistanceTests
|
|
{
|
|
[Fact]
|
|
public void TwoCylinders_HorizontallySeparated_SubtractsBothRadii()
|
|
{
|
|
// centers 10 units apart on X, radii 1 and 2 → edge distance = 10-1-2=7
|
|
float d = MoveToMath.CylinderDistance(
|
|
ownRadius: 1f, ownHeight: 2f, ownPos: Vector3.Zero,
|
|
targetRadius: 2f, targetHeight: 2f, targetPos: new Vector3(10f, 0f, 0f));
|
|
|
|
Assert.Equal(7f, d, 3);
|
|
}
|
|
|
|
[Fact]
|
|
public void TwoCylinders_Overlapping_ReturnsSignedThreeDimensionalOverlap()
|
|
{
|
|
// radial gap = 1 - 10 = -9; vertical gap = 0 - 2 = -2.
|
|
float d = MoveToMath.CylinderDistance(
|
|
ownRadius: 5f, ownHeight: 2f, ownPos: Vector3.Zero,
|
|
targetRadius: 5f, targetHeight: 2f, targetPos: new Vector3(1f, 0f, 0f));
|
|
|
|
Assert.Equal(-MathF.Sqrt(85f), d, 3);
|
|
}
|
|
|
|
[Fact]
|
|
public void TwoCylinders_ZeroRadii_ReducesToCenterDistance()
|
|
{
|
|
float d = MoveToMath.CylinderDistance(
|
|
ownRadius: 0f, ownHeight: 2f, ownPos: Vector3.Zero,
|
|
targetRadius: 0f, targetHeight: 2f, targetPos: new Vector3(3f, 4f, 0f));
|
|
|
|
Assert.Equal(5f, d, 3); // 3-4-5 triangle
|
|
}
|
|
|
|
[Fact]
|
|
public void TwoCylinders_VerticallyAndRadiallySeparated_CombinesBothGaps()
|
|
{
|
|
// full center gap = 5, radial gap = 3; vertical gap = 4 - 2 = 2.
|
|
float d = MoveToMath.CylinderDistance(
|
|
ownRadius: 1f, ownHeight: 2f, ownPos: Vector3.Zero,
|
|
targetRadius: 1f, targetHeight: 2f, targetPos: new Vector3(3f, 0f, 4f));
|
|
|
|
Assert.Equal(MathF.Sqrt(13f), d, 3);
|
|
}
|
|
|
|
[Fact]
|
|
public void SamePosition_ReturnsSignedOverlap()
|
|
{
|
|
float d = MoveToMath.CylinderDistance(
|
|
ownRadius: 0.5f, ownHeight: 2f, ownPos: Vector3.Zero,
|
|
targetRadius: 0.5f, targetHeight: 2f, targetPos: Vector3.Zero);
|
|
|
|
Assert.Equal(-MathF.Sqrt(5f), d, 3);
|
|
}
|
|
|
|
[Fact]
|
|
public void OppositeGapSigns_ReturnsRadialGapVerbatim()
|
|
{
|
|
float d = MoveToMath.CylinderDistance(
|
|
ownRadius: 5f, ownHeight: 1f, ownPos: Vector3.Zero,
|
|
targetRadius: 5f, targetHeight: 1f, targetPos: new Vector3(0f, 0f, 3f));
|
|
|
|
Assert.Equal(-7f, d, 3);
|
|
}
|
|
}
|