feat(physics): add BSPQuery.FindWalkableSphere wrapper
Thin public wrapper over the existing retail-faithful FindWalkableInternal (BSPNODE::find_walkable + BSPLEAF::find_walkable port). Probes downward by probeDistance along up, returns the closest walkable polygon the sphere would rest on plus the adjusted center. Will replace Transition.TryFindIndoorWalkablePlane's linear first-match scan (next commit). The wrapper is callable from any "stand here, find my floor" use case; current intent is indoor walkable-plane synthesis. 4 unit tests covering: two-floors-foot-between (sphere overlapping lower floor), only-upper-floor-foot-above (sphere overlapping upper floor), no-walkable-in-probe-range (sphere out of overlap distance for all polygons), steep-poly-rejected-by-WalkableAllowance. Note: find_walkable requires sphere to overlap the polygon plane (|dist| <= radius); the tests use geometry that exercises this correctly, unlike the spec's illustrative values which assumed a "nearest below" scan. Spec: docs/superpowers/specs/2026-05-19-indoor-walkable-plane-bsp-port-design.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ff548b962c
commit
7f55e14cd7
2 changed files with 290 additions and 0 deletions
|
|
@ -1130,6 +1130,89 @@ public static class BSPQuery
|
|||
return TransitionState.OK;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// find_walkable_sphere — "stand here, find my contact plane"
|
||||
// Indoor walkable-plane synthesis entry point (Phase 2 follow-up 2026-05-19).
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// "Stand here, find my contact plane" entry point over the BSPNode/BSPLeaf
|
||||
/// find_walkable BSP traversal. Probes downward by <paramref name="probeDistance"/>
|
||||
/// along <paramref name="up"/> and returns the closest walkable polygon the
|
||||
/// sphere would rest on, with the sphere's center adjusted to lie on that plane.
|
||||
///
|
||||
/// <para>
|
||||
/// Wraps the existing private <see cref="FindWalkableInternal"/> — which already
|
||||
/// implements the retail-faithful walkable-finder
|
||||
/// (BSPNODE::find_walkable + BSPLEAF::find_walkable +
|
||||
/// CPolygon::walkable_hits_sphere + CPolygon::adjust_sphere_to_plane,
|
||||
/// acclient_2013_pseudo_c.txt:326211, :326793, :323006, :322032).
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Intended call site: indoor walkable-plane synthesis in
|
||||
/// <c>Transition.TryFindIndoorWalkablePlane</c> when the indoor cell-BSP
|
||||
/// collision returns OK (no wall hit) and the resolver still needs a
|
||||
/// ContactPlane to feed ValidateWalkable. Outdoor terrain has its own path
|
||||
/// (<see cref="PhysicsEngine.SampleTerrainWalkable"/>) and does not use this.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// The caller is responsible for setting <c>transition.SpherePath.WalkableAllowance</c>
|
||||
/// to the desired walkability threshold (typically <see cref="PhysicsGlobals.FloorZ"/>)
|
||||
/// before calling, and restoring it after.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="root">Root of the cell's PhysicsBSP.</param>
|
||||
/// <param name="resolved">Pre-resolved polygon dictionary from PhysicsDataCache.</param>
|
||||
/// <param name="transition">Current transition (read for WalkableAllowance / walk_interp).</param>
|
||||
/// <param name="sphere">Foot sphere in cell-local space.</param>
|
||||
/// <param name="probeDistance">Downward probe distance in meters. Typical: 0.5f.</param>
|
||||
/// <param name="up">Up vector in cell-local space (typically Vector3.UnitZ).</param>
|
||||
/// <param name="hitPoly">Output: the walkable polygon found, or null on miss.</param>
|
||||
/// <param name="hitPolyId">Output: polygon id (dictionary key) of the hit. Zero on miss.</param>
|
||||
/// <param name="adjustedCenter">
|
||||
/// Output: sphere center adjusted onto the polygon plane. Equal to input
|
||||
/// <c>sphere.Origin</c> on miss.
|
||||
/// </param>
|
||||
/// <returns>True if a walkable polygon was found; false otherwise.</returns>
|
||||
public static bool FindWalkableSphere(
|
||||
PhysicsBSPNode? root,
|
||||
Dictionary<ushort, ResolvedPolygon> resolved,
|
||||
Transition transition,
|
||||
Sphere sphere,
|
||||
float probeDistance,
|
||||
Vector3 up,
|
||||
out ResolvedPolygon? hitPoly,
|
||||
out ushort hitPolyId,
|
||||
out Vector3 adjustedCenter)
|
||||
{
|
||||
adjustedCenter = sphere.Origin;
|
||||
hitPoly = null;
|
||||
hitPolyId = 0;
|
||||
|
||||
if (root is null) return false;
|
||||
|
||||
var validPos = new CollisionSphere(sphere.Origin, sphere.Radius);
|
||||
var movement = -up * probeDistance;
|
||||
bool changed = false;
|
||||
ushort polyId = 0;
|
||||
ResolvedPolygon? poly = null;
|
||||
|
||||
FindWalkableInternal(root, resolved, transition.SpherePath, validPos,
|
||||
movement, up, ref poly, ref polyId, ref changed);
|
||||
|
||||
if (changed && poly is not null)
|
||||
{
|
||||
hitPoly = poly;
|
||||
hitPolyId = polyId;
|
||||
adjustedCenter = validPos.Center;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// step_sphere_up — BSPTree level
|
||||
// ACE: BSPTree.cs step_sphere_up
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue