feat(physics): port full CTransition collision response from pseudocode

Replace simplified push-out with retail-faithful SlideSphere and
AdjustOffset from transition_pseudocode.md. Crease-projection between
collision normal and contact plane produces smooth wall-sliding.
Object collision uses proper rotation transform to object-local space.

SlideSphere (section 6): computes crease direction via cross product
of collision normal and contact plane normal, projects displacement
onto the crease, then applies the correction offset. Handles three
cases: crease exists, parallel same-direction, parallel opposing.

AdjustOffset (section 6): adds safety check to keep sphere above
contact plane by computing signed distance and pushing up along Z
when the sphere dips below.

FindObjCollisions: removes ad-hoc penetration push-out, now calls
SlideSphere after BSP hit detection for proper wall-slide behavior.

Also fixes: ShadowEntry gains Rotation field, tests updated to match
Register signature, unused variables removed from GameWindow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-14 11:17:45 +02:00
parent e2f0c8580e
commit e12d255d2e
4 changed files with 182 additions and 84 deletions

View file

@ -20,14 +20,12 @@ public sealed class ShadowObjectRegistry
/// <summary>
/// Register an entity into the cells it overlaps based on world position + radius.
/// </summary>
public void Register(uint entityId, uint gfxObjId, Vector3 worldPos, float radius,
float worldOffsetX, float worldOffsetY, uint landblockId)
public void Register(uint entityId, uint gfxObjId, Vector3 worldPos, Quaternion rotation,
float radius, float worldOffsetX, float worldOffsetY, uint landblockId)
{
// Deregister first if already registered (handles position updates)
Deregister(entityId);
// Compute which cells the entity's bounding sphere overlaps.
// Each cell is 24×24m within a 192m landblock.
float localX = worldPos.X - worldOffsetX;
float localY = worldPos.Y - worldOffsetY;
@ -36,7 +34,7 @@ public sealed class ShadowObjectRegistry
int minCy = Math.Max(0, (int)((localY - radius) / 24f));
int maxCy = Math.Min(7, (int)((localY + radius) / 24f));
var entry = new ShadowEntry(entityId, gfxObjId, worldPos, radius);
var entry = new ShadowEntry(entityId, gfxObjId, worldPos, rotation, radius);
var cellIds = new List<uint>();
uint lbPrefix = landblockId & 0xFFFF0000u;
@ -148,4 +146,9 @@ public sealed class ShadowObjectRegistry
public int TotalRegistered => _entityToCells.Count;
}
public readonly record struct ShadowEntry(uint EntityId, uint GfxObjId, Vector3 Position, float Radius);
public readonly record struct ShadowEntry(
uint EntityId,
uint GfxObjId,
Vector3 Position,
Quaternion Rotation,
float Radius);