fix(physics): #32 L.2c precipice edge-slide context
Port the first retail precipice-slide slice from named retail/ACE: terrain and BSP walkable hits now preserve polygon vertices, failed step-down edges back-probe to rediscover the walkable polygon, and edge-slide can run precipice/cliff slide instead of only hard-stopping. Adds pseudocode anchors plus regression coverage for terrain polygon context and loaded-terrain boundary edge-slide. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
1ec40f2a4f
commit
261322b48e
10 changed files with 559 additions and 60 deletions
|
|
@ -377,30 +377,33 @@ public static class BSPQuery
|
|||
///
|
||||
/// <para>ACE: Polygon.cs find_crossed_edge.</para>
|
||||
/// </summary>
|
||||
private static bool FindCrossedEdge(
|
||||
ResolvedPolygon poly,
|
||||
CollisionSphere sphere,
|
||||
Vector3 up,
|
||||
ref Vector3 normal)
|
||||
internal static bool FindCrossedEdge(
|
||||
Plane polyPlane,
|
||||
ReadOnlySpan<Vector3> verts,
|
||||
Vector3 sphereCenter,
|
||||
Vector3 up,
|
||||
out Vector3 normal)
|
||||
{
|
||||
float angleUp = Vector3.Dot(poly.Plane.Normal, up);
|
||||
normal = Vector3.Zero;
|
||||
|
||||
float angleUp = Vector3.Dot(polyPlane.Normal, up);
|
||||
if (MathF.Abs(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(polyPlane.Normal, sphereCenter) + polyPlane.D) / angleUp;
|
||||
var center = sphereCenter - up * angle;
|
||||
|
||||
int n = poly.Vertices.Length;
|
||||
int n = verts.Length;
|
||||
int prevIdx = n - 1;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
var v = poly.Vertices[i];
|
||||
var lv = poly.Vertices[prevIdx];
|
||||
var v = verts[i];
|
||||
var lv = verts[prevIdx];
|
||||
prevIdx = i;
|
||||
|
||||
var edge = v - lv;
|
||||
var disp = center - lv;
|
||||
var cross = Vector3.Cross(poly.Plane.Normal, edge);
|
||||
var cross = Vector3.Cross(polyPlane.Normal, edge);
|
||||
|
||||
if (Vector3.Dot(disp, cross) < 0f)
|
||||
{
|
||||
|
|
@ -412,6 +415,47 @@ public static class BSPQuery
|
|||
return false;
|
||||
}
|
||||
|
||||
private static bool FindCrossedEdge(
|
||||
ResolvedPolygon poly,
|
||||
CollisionSphere sphere,
|
||||
Vector3 up,
|
||||
ref Vector3 normal)
|
||||
{
|
||||
if (!FindCrossedEdge(poly.Plane, poly.Vertices, sphere.Center, up, out var crossedNormal))
|
||||
return false;
|
||||
|
||||
normal = crossedNormal;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Vector3 TransformNormal(Vector3 normal, Quaternion localToWorld)
|
||||
{
|
||||
var worldNormal = Vector3.Transform(normal, localToWorld);
|
||||
return worldNormal.LengthSquared() > PhysicsGlobals.EpsilonSq
|
||||
? Vector3.Normalize(worldNormal)
|
||||
: Vector3.UnitZ;
|
||||
}
|
||||
|
||||
private static Vector3[] TransformVertices(
|
||||
ReadOnlySpan<Vector3> vertices,
|
||||
Quaternion localToWorld,
|
||||
float scale,
|
||||
Vector3 worldOrigin)
|
||||
{
|
||||
var result = new Vector3[vertices.Length];
|
||||
for (int i = 0; i < vertices.Length; i++)
|
||||
result[i] = Vector3.Transform(vertices[i] * scale, localToWorld) + worldOrigin;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Plane BuildWorldPlane(Vector3 worldNormal, ReadOnlySpan<Vector3> worldVertices)
|
||||
{
|
||||
float d = worldVertices.Length > 0
|
||||
? -Vector3.Dot(worldNormal, worldVertices[0])
|
||||
: 0f;
|
||||
return new Plane(worldNormal, d);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// adjust_to_placement_poly
|
||||
// ACE: Polygon.cs adjust_to_placement_poly
|
||||
|
|
@ -1037,7 +1081,8 @@ public static class BSPQuery
|
|||
CollisionSphere checkPos,
|
||||
Vector3 up,
|
||||
float scale,
|
||||
Quaternion localToWorld = default)
|
||||
Quaternion localToWorld = default,
|
||||
Vector3 worldOrigin = default)
|
||||
{
|
||||
if (localToWorld == default) localToWorld = Quaternion.Identity;
|
||||
|
||||
|
|
@ -1061,14 +1106,12 @@ public static class BSPQuery
|
|||
var offset = Vector3.Transform(adjusted, localToWorld) * scale;
|
||||
path.AddOffsetToCheckPos(offset);
|
||||
|
||||
var worldNormal = Vector3.Transform(polyHit.Plane.Normal, localToWorld);
|
||||
collisions.SetContactPlane(
|
||||
new Plane(worldNormal, polyHit.Plane.D * scale),
|
||||
path.CheckCellId, false);
|
||||
var worldNormal = TransformNormal(polyHit.Plane.Normal, localToWorld);
|
||||
var worldVertices = TransformVertices(polyHit.Vertices, localToWorld, scale, worldOrigin);
|
||||
var worldPlane = BuildWorldPlane(worldNormal, worldVertices);
|
||||
collisions.SetContactPlane(worldPlane, path.CheckCellId, false);
|
||||
|
||||
path.WalkableValid = true;
|
||||
path.WalkablePlane = new Plane(worldNormal, polyHit.Plane.D * scale);
|
||||
path.WalkableAllowance = PhysicsGlobals.FloorZ;
|
||||
path.SetWalkable(worldPlane, worldVertices, Vector3.UnitZ);
|
||||
|
||||
return TransitionState.Adjusted;
|
||||
}
|
||||
|
|
@ -1359,7 +1402,8 @@ public static class BSPQuery
|
|||
Vector3 localSpaceZ,
|
||||
float scale,
|
||||
Quaternion localToWorld = default,
|
||||
PhysicsEngine? engine = null)
|
||||
PhysicsEngine? engine = null,
|
||||
Vector3 worldOrigin = default)
|
||||
{
|
||||
if (root is null) return TransitionState.OK;
|
||||
// Default quaternion (0,0,0,0) → treat as identity
|
||||
|
|
@ -1410,7 +1454,7 @@ public static class BSPQuery
|
|||
// ----------------------------------------------------------------
|
||||
if (path.StepDown)
|
||||
{
|
||||
return StepSphereDown(root, resolved, transition, sphere0, localSpaceZ, scale, localToWorld);
|
||||
return StepSphereDown(root, resolved, transition, sphere0, localSpaceZ, scale, localToWorld, worldOrigin);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
|
@ -1433,14 +1477,12 @@ public static class BSPQuery
|
|||
var worldOffset = L2W(localOffset) * scale;
|
||||
path.AddOffsetToCheckPos(worldOffset);
|
||||
|
||||
var worldNormal = L2W(hitPoly.Plane.Normal);
|
||||
collisions.SetContactPlane(
|
||||
new Plane(worldNormal, hitPoly.Plane.D * scale),
|
||||
path.CheckCellId, false);
|
||||
var worldNormal = TransformNormal(hitPoly.Plane.Normal, localToWorld);
|
||||
var worldVertices = TransformVertices(hitPoly.Vertices, localToWorld, scale, worldOrigin);
|
||||
var worldPlane = BuildWorldPlane(worldNormal, worldVertices);
|
||||
collisions.SetContactPlane(worldPlane, path.CheckCellId, false);
|
||||
|
||||
path.WalkableValid = true;
|
||||
path.WalkablePlane = new Plane(worldNormal, hitPoly.Plane.D * scale);
|
||||
path.WalkableAllowance = PhysicsGlobals.FloorZ;
|
||||
path.SetWalkable(worldPlane, worldVertices, Vector3.UnitZ);
|
||||
|
||||
return TransitionState.Adjusted;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue