feat(physics): complete retail collision — indoor BSP, dual sphere, step-up, swept-sphere, 6-path dispatcher
Indoor CellStruct PhysicsBSP collision for room walls/ceilings. Dual sphere (body+head) from Setup dimensions. StepUp attempts before sliding when hitting low obstacles. FindTimeOfCollision for exact parametric BSP contact time. Full 6-path BSP dispatcher wired into FindEnvCollisions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1f30fbd2f5
commit
cadc72ed08
4 changed files with 362 additions and 100 deletions
|
|
@ -1142,4 +1142,164 @@ public static class BSPQuery
|
|||
return SphereIntersectsPoly(node.NegNode, polygons, vertices,
|
||||
sphereCenter, sphereRadius, movement, out hitPolyId, out hitNormal);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 14. SphereIntersectsPolyWithTime — swept-sphere BSP query using
|
||||
// FindTimeOfCollision for exact parametric contact time.
|
||||
// Fix 4: replaces static overlap + ad-hoc t computation.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Movement-aware sphere-BSP intersection that uses
|
||||
/// <see cref="CollisionPrimitives.FindTimeOfCollision"/> to compute the
|
||||
/// exact parametric time of first contact. Returns the earliest collision
|
||||
/// across all polygons in the BSP tree.
|
||||
///
|
||||
/// <para>
|
||||
/// Unlike <see cref="SphereIntersectsPoly(PhysicsBSPNode?, Dictionary{ushort, Polygon},
|
||||
/// VertexArray, Vector3, float, Vector3, out ushort, out Vector3)"/> which
|
||||
/// tests static overlap at start and end positions, this method finds the
|
||||
/// precise contact time via swept-sphere analysis.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static bool SphereIntersectsPolyWithTime(
|
||||
PhysicsBSPNode? node,
|
||||
Dictionary<ushort, Polygon> polygons,
|
||||
VertexArray vertices,
|
||||
Vector3 sphereCenter,
|
||||
float sphereRadius,
|
||||
Vector3 movement,
|
||||
out ushort hitPolyId,
|
||||
out Vector3 hitNormal,
|
||||
out float hitTime)
|
||||
{
|
||||
hitPolyId = 0;
|
||||
hitNormal = Vector3.Zero;
|
||||
hitTime = float.MaxValue;
|
||||
|
||||
if (node is null) return false;
|
||||
|
||||
SphereIntersectsPolyWithTimeRecurse(
|
||||
node, polygons, vertices,
|
||||
sphereCenter, sphereRadius, movement,
|
||||
ref hitPolyId, ref hitNormal, ref hitTime);
|
||||
|
||||
return hitTime < float.MaxValue;
|
||||
}
|
||||
|
||||
private static void SphereIntersectsPolyWithTimeRecurse(
|
||||
PhysicsBSPNode? node,
|
||||
Dictionary<ushort, Polygon> polygons,
|
||||
VertexArray vertices,
|
||||
Vector3 sphereCenter,
|
||||
float sphereRadius,
|
||||
Vector3 movement,
|
||||
ref ushort hitPolyId,
|
||||
ref Vector3 hitNormal,
|
||||
ref float bestTime)
|
||||
{
|
||||
if (node is null) return;
|
||||
|
||||
// Broad phase: bounding sphere + movement extent
|
||||
float dist = Vector3.Distance(sphereCenter, node.BoundingSphere.Origin);
|
||||
if (dist > sphereRadius + node.BoundingSphere.Radius + movement.Length() + 0.1f)
|
||||
return;
|
||||
|
||||
// Leaf node: test each polygon with FindTimeOfCollision
|
||||
if (node.Type == BSPNodeType.Leaf)
|
||||
{
|
||||
foreach (var polyIdx in node.Polygons)
|
||||
{
|
||||
if (!polygons.TryGetValue(polyIdx, out var poly)) continue;
|
||||
if (!TryGetPolyPlane(poly, vertices, out var polyPlane, out var polyVerts))
|
||||
continue;
|
||||
|
||||
// Front-face culling: only collide if moving toward this face.
|
||||
if (Vector3.Dot(movement, polyPlane.Normal) >= 0f)
|
||||
continue;
|
||||
|
||||
// Use FindTimeOfCollision for exact parametric contact time.
|
||||
if (CollisionPrimitives.FindTimeOfCollision(
|
||||
polyPlane, polyVerts,
|
||||
sphereCenter, sphereRadius,
|
||||
movement, out float t))
|
||||
{
|
||||
// FindTimeOfCollision returns t such that contact = origin - movement*t.
|
||||
// For our purposes, a positive t means the sphere reaches the polygon
|
||||
// when travelling along 'movement'. We want the absolute value as
|
||||
// our parametric time (0=start, 1=end of movement).
|
||||
float absT = MathF.Abs(t);
|
||||
if (absT < bestTime)
|
||||
{
|
||||
bestTime = absT;
|
||||
hitPolyId = polyIdx;
|
||||
hitNormal = polyPlane.Normal;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: static overlap test at start and end positions.
|
||||
if (CollisionPrimitives.SphereIntersectsPoly(
|
||||
polyPlane, polyVerts, sphereCenter, sphereRadius, out _))
|
||||
{
|
||||
if (0f < bestTime)
|
||||
{
|
||||
bestTime = 0f;
|
||||
hitPolyId = polyIdx;
|
||||
hitNormal = polyPlane.Normal;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector3 endCenter = sphereCenter + movement;
|
||||
if (CollisionPrimitives.SphereIntersectsPoly(
|
||||
polyPlane, polyVerts, endCenter, sphereRadius, out _))
|
||||
{
|
||||
if (1f < bestTime)
|
||||
{
|
||||
bestTime = 1f;
|
||||
hitPolyId = polyIdx;
|
||||
hitNormal = polyPlane.Normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Internal node: classify against splitting plane
|
||||
float splitDist = Vector3.Dot(node.SplittingPlane.Normal, sphereCenter)
|
||||
+ node.SplittingPlane.D;
|
||||
float reach = sphereRadius + movement.Length();
|
||||
|
||||
if (splitDist >= reach)
|
||||
{
|
||||
SphereIntersectsPolyWithTimeRecurse(
|
||||
node.PosNode, polygons, vertices,
|
||||
sphereCenter, sphereRadius, movement,
|
||||
ref hitPolyId, ref hitNormal, ref bestTime);
|
||||
return;
|
||||
}
|
||||
|
||||
if (splitDist <= -reach)
|
||||
{
|
||||
SphereIntersectsPolyWithTimeRecurse(
|
||||
node.NegNode, polygons, vertices,
|
||||
sphereCenter, sphereRadius, movement,
|
||||
ref hitPolyId, ref hitNormal, ref bestTime);
|
||||
return;
|
||||
}
|
||||
|
||||
// Straddles: check both sides to find the earliest collision.
|
||||
SphereIntersectsPolyWithTimeRecurse(
|
||||
node.PosNode, polygons, vertices,
|
||||
sphereCenter, sphereRadius, movement,
|
||||
ref hitPolyId, ref hitNormal, ref bestTime);
|
||||
|
||||
SphereIntersectsPolyWithTimeRecurse(
|
||||
node.NegNode, polygons, vertices,
|
||||
sphereCenter, sphereRadius, movement,
|
||||
ref hitPolyId, ref hitNormal, ref bestTime);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue