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:
parent
e2f0c8580e
commit
e12d255d2e
4 changed files with 182 additions and 84 deletions
|
|
@ -1764,49 +1764,41 @@ public sealed class GameWindow : IDisposable
|
|||
// 3. Fallback: 1.0m (conservative default for trees / small objects).
|
||||
foreach (var entity in lb.Entities)
|
||||
{
|
||||
float bestRadius = 0f;
|
||||
uint physicsGfxId = 0;
|
||||
// Register EACH physics-enabled part so multi-part Setups
|
||||
// (buildings, trees) have all their collision geometry registered.
|
||||
// Each part gets its own ShadowEntry with its world-space transform.
|
||||
var entityRoot =
|
||||
System.Numerics.Matrix4x4.CreateFromQuaternion(entity.Rotation) *
|
||||
System.Numerics.Matrix4x4.CreateTranslation(entity.Position);
|
||||
|
||||
uint sourceId = entity.SourceGfxObjOrSetupId;
|
||||
if ((sourceId & 0xFF000000u) == 0x01000000u)
|
||||
uint partIndex = 0;
|
||||
foreach (var meshRef in entity.MeshRefs)
|
||||
{
|
||||
// Direct GfxObj stab.
|
||||
var cached = _physicsDataCache.GetGfxObj(sourceId);
|
||||
if (cached?.BSP?.Root is not null)
|
||||
{
|
||||
physicsGfxId = sourceId;
|
||||
bestRadius = cached.BoundingSphere?.Radius ?? 1f;
|
||||
}
|
||||
}
|
||||
else if ((sourceId & 0xFF000000u) == 0x02000000u)
|
||||
{
|
||||
// Setup (multi-part building / creature proxy). Use the first
|
||||
// part that has a physics BSP; register using Setup.Radius for
|
||||
// the broad-phase sphere so the query covers the whole assembly.
|
||||
var setupCached = _physicsDataCache.GetSetup(sourceId);
|
||||
if (setupCached is not null && setupCached.Radius > 0f)
|
||||
bestRadius = setupCached.Radius;
|
||||
var partCached = _physicsDataCache.GetGfxObj(meshRef.GfxObjId);
|
||||
if (partCached?.BSP?.Root is null) { partIndex++; continue; }
|
||||
|
||||
foreach (var meshRef in entity.MeshRefs)
|
||||
{
|
||||
var partCached = _physicsDataCache.GetGfxObj(meshRef.GfxObjId);
|
||||
if (partCached?.BSP?.Root is not null)
|
||||
{
|
||||
physicsGfxId = meshRef.GfxObjId;
|
||||
if (bestRadius <= 0f)
|
||||
bestRadius = partCached.BoundingSphere?.Radius ?? 1f;
|
||||
break; // register just the first physics part for MVP
|
||||
}
|
||||
}
|
||||
}
|
||||
// Compute the part's world-space position from its transform.
|
||||
var partWorld = meshRef.PartTransform * entityRoot;
|
||||
var partPos = new System.Numerics.Vector3(partWorld.M41, partWorld.M42, partWorld.M43);
|
||||
|
||||
if (physicsGfxId != 0)
|
||||
{
|
||||
float reg_radius = bestRadius > 0f ? bestRadius : 1f;
|
||||
// Extract rotation from the world matrix.
|
||||
System.Numerics.Quaternion partRot;
|
||||
if (System.Numerics.Matrix4x4.Decompose(partWorld,
|
||||
out _, out partRot, out _))
|
||||
{ /* decompose succeeded */ }
|
||||
else
|
||||
partRot = entity.Rotation;
|
||||
|
||||
float partRadius = partCached.BoundingSphere?.Radius ?? 1f;
|
||||
|
||||
// Use a unique sub-ID per part: entity.Id * 256 + partIndex.
|
||||
uint partId = entity.Id * 256u + partIndex;
|
||||
_physicsEngine.ShadowObjects.Register(
|
||||
entity.Id, physicsGfxId,
|
||||
entity.Position, reg_radius,
|
||||
partId, meshRef.GfxObjId,
|
||||
partPos, partRot, partRadius,
|
||||
origin.X, origin.Y, lb.LandblockId);
|
||||
|
||||
partIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue