From bfe6a49d674d32ef2c6f4480702168b17d34a0ba Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 14 Apr 2026 13:16:18 +0200 Subject: [PATCH] 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) --- src/AcDream.App/Rendering/GameWindow.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 8b867e0..1831713 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -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) {