fix(physics): enforce retail step-down support radius (#273)
This commit is contained in:
parent
4dd40ad8fe
commit
c24bc571cf
9 changed files with 2117 additions and 24 deletions
|
|
@ -284,28 +284,46 @@ public static class BSPQuery
|
|||
CollisionSphere sphere,
|
||||
Vector3 up,
|
||||
bool small)
|
||||
=> CheckWalkableSupport(
|
||||
poly.Plane,
|
||||
poly.Vertices,
|
||||
sphere.Center,
|
||||
small ? sphere.Radius * 0.5f : sphere.Radius,
|
||||
up);
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>CPolygon::check_walkable</c> against an already resolved
|
||||
/// polygon. The caller supplies the effective support radius; retail's
|
||||
/// <c>SPHEREPATH::check_walkables</c> halves its saved sphere before
|
||||
/// entering this routine.
|
||||
/// </summary>
|
||||
internal static bool CheckWalkableSupport(
|
||||
Plane plane,
|
||||
ReadOnlySpan<Vector3> vertices,
|
||||
Vector3 center,
|
||||
float supportRadius,
|
||||
Vector3 up)
|
||||
{
|
||||
float angleUp = Vector3.Dot(poly.Plane.Normal, up);
|
||||
float angleUp = Vector3.Dot(plane.Normal, up);
|
||||
if (angleUp < PhysicsGlobals.EPSILON) return false;
|
||||
|
||||
float angle = (Vector3.Dot(poly.Plane.Normal, sphere.Center) + poly.Plane.D) / angleUp;
|
||||
var center = sphere.Center - up * angle;
|
||||
float angle = (Vector3.Dot(plane.Normal, center) + plane.D) / angleUp;
|
||||
center -= up * angle;
|
||||
|
||||
float radsum = sphere.Radius * sphere.Radius;
|
||||
if (small) radsum *= 0.25f;
|
||||
float radsum = supportRadius * supportRadius;
|
||||
|
||||
int n = poly.Vertices.Length;
|
||||
int n = vertices.Length;
|
||||
int prevIdx = n - 1;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
var v = poly.Vertices[i];
|
||||
var lv = poly.Vertices[prevIdx];
|
||||
var v = vertices[i];
|
||||
var lv = vertices[prevIdx];
|
||||
prevIdx = i;
|
||||
|
||||
var edge = v - lv;
|
||||
var disp = center - lv;
|
||||
var cross = Vector3.Cross(poly.Plane.Normal, edge);
|
||||
var cross = Vector3.Cross(plane.Normal, edge);
|
||||
float diff = Vector3.Dot(disp, cross);
|
||||
|
||||
if (diff < 0f)
|
||||
|
|
|
|||
|
|
@ -850,6 +850,27 @@ public sealed class SpherePath
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>SPHEREPATH::check_walkables</c> (0x0050C3E0). A missing
|
||||
/// remembered polygon passes; otherwise the foot sphere is tested against
|
||||
/// that polygon with half its normal radius. Retail mutates an embedded
|
||||
/// scratch sphere before the test. Our world-space representation can pass
|
||||
/// the equivalent effective radius without mutating canonical sphere state.
|
||||
/// </summary>
|
||||
internal bool CheckWalkables()
|
||||
{
|
||||
if (!HasWalkablePolygon || WalkableVertices is null)
|
||||
return true;
|
||||
|
||||
var footSphere = GlobalSphere[0];
|
||||
return BSPQuery.CheckWalkableSupport(
|
||||
WalkablePlane,
|
||||
WalkableVertices,
|
||||
footSphere.Origin,
|
||||
footSphere.Radius * 0.5f,
|
||||
WalkableUp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>SPHEREPATH::init</c> reset for a retained transition record.
|
||||
/// Every logical value is restored; only the two private exact-length
|
||||
|
|
@ -5211,6 +5232,22 @@ public sealed class Transition
|
|||
&& CollisionInfo.ContactPlaneValid
|
||||
&& CollisionInfo.ContactPlane.Normal.Z >= walkableZ)
|
||||
{
|
||||
// Retail CTransition::step_down (0x0050B2A0) validates the
|
||||
// candidate's actual support before placement whenever an
|
||||
// already-grounded mover is not performing a step-up. The first
|
||||
// check uses SPHEREPATH::check_walkables' half-radius foot sphere;
|
||||
// when that fails, DoCheckWalkable performs the downward BSP
|
||||
// probe with the same small-support rule. Omitting this gate let a
|
||||
// wall/post slide leave most of the player sphere beyond a floor
|
||||
// edge while the full-radius step-down overlap still counted as
|
||||
// grounded (issue #273).
|
||||
if (ObjectInfo.EdgeSlide
|
||||
&& !sp.StepUp
|
||||
&& !DoCheckWalkable(walkableZ, engine))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// L.2.3h (2026-04-29): Placement validation is for the
|
||||
// DoStepUp use case (prevents climbing through walls by
|
||||
// stepping up onto ground beyond a tall wall). For the
|
||||
|
|
@ -5461,8 +5498,10 @@ public sealed class Transition
|
|||
if ((oi.State & ObjectInfoState.OnWalkable) == 0)
|
||||
return true;
|
||||
|
||||
// If the current walkable entry is still valid, skip the probe.
|
||||
if (sp.WalkableValid)
|
||||
// Retail first validates the remembered polygon with a half-radius
|
||||
// support sphere. Merely having a polygon pointer is insufficient:
|
||||
// the candidate may already hang too far beyond its edge.
|
||||
if (sp.CheckWalkables())
|
||||
return true;
|
||||
|
||||
sp.SaveCheckPos();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue