fix(physics): always register CylSphere alongside BSP parts

Previously CylSphere was only registered when no BSP parts existed.
Retail tests BOTH: CylSphere as the broad collision volume (trunk)
plus BSP parts for polygon-level collision. This ensures the trunk
cylinder catches collisions that individual BSP parts might miss,
especially at the base of large trees.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-14 13:16:18 +02:00
parent 2ebbfc4864
commit bfe6a49d67

View file

@ -1801,10 +1801,10 @@ public sealed class GameWindow : IDisposable
partIndex++;
}
// If no BSP parts were registered, check for CylSphere collision
// from the Setup (trees, rocks, NPCs use cylinder collision).
if (partIndex == 0 || !entity.MeshRefs.Any(mr =>
_physicsDataCache.GetGfxObj(mr.GfxObjId)?.BSP?.Root is not null))
// ALWAYS register CylSphere when the Setup has one, even if BSP parts
// were also registered. Retail tests both: CylSphere as the broad
// collision volume (trunk) plus BSP parts for precise polygon collision.
// The CylSphere catches the base/trunk that BSP parts might miss.
{
var setup = _physicsDataCache.GetSetup(entity.SourceGfxObjOrSetupId);
if (setup is not null && setup.CylSpheres.Count > 0)
@ -1813,6 +1813,7 @@ public sealed class GameWindow : IDisposable
float cylRadius = cyl.Radius > 0 ? cyl.Radius : setup.Radius;
if (cylRadius > 0)
{
// Use entity.Id directly (not partId) for the CylSphere entry.
_physicsEngine.ShadowObjects.Register(
entity.Id, entity.SourceGfxObjOrSetupId,
entity.Position + new System.Numerics.Vector3(cyl.Origin.X, cyl.Origin.Y, cyl.Origin.Z),
@ -1821,8 +1822,9 @@ public sealed class GameWindow : IDisposable
AcDream.Core.Physics.ShadowCollisionType.Cylinder, cyl.Height);
}
}
else if (setup is not null && setup.Spheres.Count > 0)
else if (setup is not null && setup.Spheres.Count > 0 && partIndex == 0)
{
// Fallback: use bounding sphere only if no BSP and no CylSphere.
var sph = setup.Spheres[0];
if (sph.Radius > 0)
{