feat(physics): cell-based ShadowObject collision
Register static entities into terrain cells during streaming. Transition system queries nearby objects and runs BSP collision. Player can no longer walk through trees and buildings. - ShadowObjectRegistry: 24m×24m cell index, Register/Deregister/ RemoveLandblock/GetNearbyObjects matching retail AC's approach - PhysicsEngine: ShadowObjects property + DataCache wiring point; RemoveLandblock now also clears shadow objects; TryGetLandblockContext helper lets Transition resolve landblock id+offset for a world pos - Transition.FindObjCollisions: queries registry, broad-phase sphere test, narrow-phase BSPQuery.SphereIntersectsPoly in object-local space, returns Slid on hit to redirect movement along the surface - GameWindow.ApplyLoadedTerrainLocked: registers each static entity after physics BSP data is cached; selects radius from BSP bounding sphere or Setup.Radius; wires PhysicsDataCache into engine on OnLoad - 16 new ShadowObjectRegistry unit tests, all 361 tests green Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
246713e2cc
commit
e2f0c8580e
5 changed files with 541 additions and 2 deletions
|
|
@ -24,6 +24,19 @@ public sealed class PhysicsEngine
|
|||
/// <summary>Number of registered landblocks (diagnostic).</summary>
|
||||
public int LandblockCount => _landblocks.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Cell-based spatial index for static object collision.
|
||||
/// Populated during landblock streaming; queried by the Transition system.
|
||||
/// </summary>
|
||||
public ShadowObjectRegistry ShadowObjects { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Physics BSP cache shared with the streaming loader. Set once by the
|
||||
/// host (GameWindow) immediately after construction. The Transition system
|
||||
/// reads this during FindObjCollisions to perform narrow-phase BSP tests.
|
||||
/// </summary>
|
||||
public PhysicsDataCache? DataCache { get; set; }
|
||||
|
||||
private sealed record LandblockPhysics(
|
||||
TerrainSurface Terrain,
|
||||
IReadOnlyList<CellSurface> Cells,
|
||||
|
|
@ -43,9 +56,41 @@ public sealed class PhysicsEngine
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a previously registered landblock.
|
||||
/// Remove a previously registered landblock, including its shadow objects.
|
||||
/// </summary>
|
||||
public void RemoveLandblock(uint landblockId) => _landblocks.Remove(landblockId);
|
||||
public void RemoveLandblock(uint landblockId)
|
||||
{
|
||||
_landblocks.Remove(landblockId);
|
||||
ShadowObjects.RemoveLandblock(landblockId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the landblock that contains the given world-space XY position and
|
||||
/// return its ID plus world-space origin offsets. Returns false when no
|
||||
/// registered landblock covers the position.
|
||||
/// Used by Transition.FindObjCollisions to build the shadow-object query.
|
||||
/// </summary>
|
||||
public bool TryGetLandblockContext(float worldX, float worldY,
|
||||
out uint landblockId, out float worldOffsetX, out float worldOffsetY)
|
||||
{
|
||||
foreach (var kvp in _landblocks)
|
||||
{
|
||||
var lb = kvp.Value;
|
||||
float localX = worldX - lb.WorldOffsetX;
|
||||
float localY = worldY - lb.WorldOffsetY;
|
||||
if (localX >= 0f && localX < 192f && localY >= 0f && localY < 192f)
|
||||
{
|
||||
landblockId = kvp.Key;
|
||||
worldOffsetX = lb.WorldOffsetX;
|
||||
worldOffsetY = lb.WorldOffsetY;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
landblockId = 0;
|
||||
worldOffsetX = 0f;
|
||||
worldOffsetY = 0f;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sample the outdoor terrain Z at the given world-space XY position.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue