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
|
|
@ -1,7 +1,13 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace AcDream.Core.Physics;
|
||||
|
||||
public readonly record struct TerrainSurfacePolygon(
|
||||
float Z,
|
||||
Vector3 Normal,
|
||||
Vector3[] Vertices);
|
||||
|
||||
/// <summary>
|
||||
/// Outdoor terrain height resolver for a single landblock. Performs
|
||||
/// per-triangle barycentric Z interpolation matching the visual terrain
|
||||
|
|
@ -250,6 +256,72 @@ public sealed class TerrainSurface
|
|||
return (z, normal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sample the terrain triangle at (localX, localY), including the three
|
||||
/// local-space vertices that bound the sampled point. Edge-slide needs
|
||||
/// these vertices so the retail crossed-edge test can identify which edge
|
||||
/// the sphere left when a step-down probe fails.
|
||||
/// </summary>
|
||||
public TerrainSurfacePolygon SampleSurfacePolygon(float localX, float localY)
|
||||
{
|
||||
float fx = Math.Clamp(localX / CellSize, 0f, CellsPerSide - 0.001f);
|
||||
float fy = Math.Clamp(localY / CellSize, 0f, CellsPerSide - 0.001f);
|
||||
int cx = Math.Clamp((int)fx, 0, CellsPerSide - 1);
|
||||
int cy = Math.Clamp((int)fy, 0, CellsPerSide - 1);
|
||||
|
||||
float tx = fx - cx;
|
||||
float ty = fy - cy;
|
||||
|
||||
float hBL = _z[cx, cy ];
|
||||
float hBR = _z[cx + 1, cy ];
|
||||
float hTR = _z[cx + 1, cy + 1];
|
||||
float hTL = _z[cx, cy + 1];
|
||||
|
||||
bool splitSWtoNE = IsSplitSWtoNE(_landblockX, (uint)cx, _landblockY, (uint)cy);
|
||||
|
||||
Vector3 bl = new(cx * CellSize, cy * CellSize, hBL);
|
||||
Vector3 br = new((cx + 1) * CellSize, cy * CellSize, hBR);
|
||||
Vector3 tr = new((cx + 1) * CellSize, (cy + 1) * CellSize, hTR);
|
||||
Vector3 tl = new(cx * CellSize, (cy + 1) * CellSize, hTL);
|
||||
|
||||
float z;
|
||||
Vector3[] vertices;
|
||||
|
||||
if (splitSWtoNE)
|
||||
{
|
||||
if (tx > ty)
|
||||
{
|
||||
z = hBL + (hBR - hBL) * tx + (hTR - hBR) * ty;
|
||||
vertices = new[] { bl, br, tr };
|
||||
}
|
||||
else
|
||||
{
|
||||
z = hBL + (hTR - hTL) * tx + (hTL - hBL) * ty;
|
||||
vertices = new[] { bl, tr, tl };
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tx + ty <= 1f)
|
||||
{
|
||||
z = hBL + (hBR - hBL) * tx + (hTL - hBL) * ty;
|
||||
vertices = new[] { bl, br, tl };
|
||||
}
|
||||
else
|
||||
{
|
||||
z = hTR + (hTL - hTR) * (1f - tx) + (hBR - hTR) * (1f - ty);
|
||||
vertices = new[] { br, tr, tl };
|
||||
}
|
||||
}
|
||||
|
||||
var normal = Vector3.Normalize(
|
||||
Vector3.Cross(vertices[1] - vertices[0], vertices[2] - vertices[0]));
|
||||
if (normal.Z < 0f)
|
||||
normal = -normal;
|
||||
|
||||
return new TerrainSurfacePolygon(z, normal, vertices);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retail per-point water depth in meters — the amount the character's
|
||||
/// feet are allowed to sink below the contact plane before the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue