fix(core): Phase B.3 — add centroid + radius bounds to PortalPlane crossing test
Portal planes are infinite — IsCrossing only checked if positions were on opposite sides of the plane, without verifying the crossing point was near the actual portal opening. Walking 50m from a building but crossing the same plane extension triggered false indoor transitions, sinking the player into underground cell floors. Fix: FromVertices now computes centroid (average of 3 vertices) and bounding radius (max vertex distance + 2 unit padding). IsCrossing rejects crossings where both positions are further than 2×radius from the centroid. Only nearby crossings (within doorway range) trigger a transition. Also fixes jump-not-landing: the false portal transitions were producing wrong resolvedGroundZ values during the jump arc, making the landing check (candidateZ <= resolvedGroundZ) never true. 283 tests still green. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ae06f9c0ff
commit
9dbb2cbd5c
1 changed files with 27 additions and 8 deletions
|
|
@ -11,11 +11,15 @@ public readonly record struct PortalPlane(
|
|||
float D,
|
||||
uint TargetCellId, // OtherCellId — the cell on the far side (0xFFFF = outdoor)
|
||||
uint OwnerCellId, // the EnvCell that owns this portal
|
||||
ushort Flags) // PortalFlags value
|
||||
ushort Flags, // PortalFlags value
|
||||
Vector3 Centroid, // center of the portal polygon in world space
|
||||
float Radius) // bounding radius of the portal polygon
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct a PortalPlane from three coplanar vertices (winding order
|
||||
/// determines the normal direction via cross product).
|
||||
/// determines the normal direction via cross product). Also computes
|
||||
/// the centroid and bounding radius from the vertices so
|
||||
/// <see cref="IsCrossing"/> can reject crossings far from the portal.
|
||||
/// </summary>
|
||||
public static PortalPlane FromVertices(
|
||||
Vector3 v0, Vector3 v1, Vector3 v2,
|
||||
|
|
@ -25,21 +29,36 @@ public readonly record struct PortalPlane(
|
|||
var edge2 = v2 - v0;
|
||||
var normal = Vector3.Normalize(Vector3.Cross(edge1, edge2));
|
||||
float d = -Vector3.Dot(normal, v0);
|
||||
return new PortalPlane(normal, d, targetCellId, ownerCellId, flags);
|
||||
|
||||
// Centroid = average of the three vertices.
|
||||
var centroid = (v0 + v1 + v2) / 3f;
|
||||
// Bounding radius = max distance from centroid to any vertex + padding.
|
||||
float r0 = Vector3.Distance(centroid, v0);
|
||||
float r1 = Vector3.Distance(centroid, v1);
|
||||
float r2 = Vector3.Distance(centroid, v2);
|
||||
float radius = MathF.Max(r0, MathF.Max(r1, r2)) + 2f; // 2 unit padding
|
||||
|
||||
return new PortalPlane(normal, d, targetCellId, ownerCellId, flags, centroid, radius);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true when the movement from <paramref name="oldPos"/> to
|
||||
/// <paramref name="newPos"/> crosses this plane (the two positions are on
|
||||
/// strictly opposite sides). A position exactly on the plane (distance = 0)
|
||||
/// does NOT count as a crossing.
|
||||
/// <paramref name="newPos"/> crosses this plane AND both positions are
|
||||
/// within the portal's bounding radius of its centroid. Without the
|
||||
/// radius check, infinite planes cause false transitions when walking
|
||||
/// far from a doorway but on the same plane extension.
|
||||
/// </summary>
|
||||
public bool IsCrossing(Vector3 oldPos, Vector3 newPos)
|
||||
{
|
||||
// Quick reject: if both positions are far from the portal, skip
|
||||
// the plane test entirely. Use the closer of the two positions.
|
||||
float distOld = Vector3.Distance(oldPos, Centroid);
|
||||
float distNew = Vector3.Distance(newPos, Centroid);
|
||||
float minDist = MathF.Min(distOld, distNew);
|
||||
if (minDist > Radius * 2f) return false;
|
||||
|
||||
float oldDist = Vector3.Dot(Normal, oldPos) + D;
|
||||
float newDist = Vector3.Dot(Normal, newPos) + D;
|
||||
// Strictly negative product → opposite signs → crossed the plane.
|
||||
// If either distance is exactly 0 the product is 0, not negative → no crossing.
|
||||
return oldDist * newDist < 0f;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue