feat(physics): PhysicsDataCache + BSP sphere query
Load PhysicsBSP and PhysicsPolygons from GfxObj dats during streaming. BSPQuery.SphereIntersectsPoly traverses the tree for collision detection. Ported from decompiled FUN_00539270, cross-ref ACE BSPNode.sphere_intersects_poly. - PhysicsDataCache: thread-safe ConcurrentDictionary-backed cache of GfxObjPhysics (BSP tree + polygon dict + vertex array) and SetupPhysics (capsule dimensions). CacheGfxObj/CacheSetup are idempotent — safe to call at every dat load site. - BSPQuery.SphereIntersectsPoly: recursive BSP descent with bounding-sphere broad phase, leaf polygon test via existing CollisionPrimitives.SphereIntersectsPoly (FUN_00539500), and splitting-plane classification for internal nodes. - GameWindow: _physicsDataCache populated at all GfxObj/Setup dat load sites (streaming worker path, live-spawn path, ApplyLoadedTerrain render-thread path). - 6 new unit tests covering null node, bounding-sphere miss, leaf hit, no-contact, internal node recursion, and empty cache behaviour. All 447 tests green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0bec5d5296
commit
874d267117
4 changed files with 462 additions and 0 deletions
80
src/AcDream.Core/Physics/PhysicsDataCache.cs
Normal file
80
src/AcDream.Core/Physics/PhysicsDataCache.cs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
using System.Collections.Concurrent;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Enums;
|
||||
using DatReaderWriter.Types;
|
||||
|
||||
namespace AcDream.Core.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// Thread-safe cache of physics-relevant data extracted from GfxObj and Setup
|
||||
/// dat objects during streaming. Populated by the streaming worker thread;
|
||||
/// read by the physics engine on the game/render thread. ConcurrentDictionary
|
||||
/// makes cross-thread access safe without a global lock.
|
||||
/// </summary>
|
||||
public sealed class PhysicsDataCache
|
||||
{
|
||||
private readonly ConcurrentDictionary<uint, GfxObjPhysics> _gfxObj = new();
|
||||
private readonly ConcurrentDictionary<uint, SetupPhysics> _setup = new();
|
||||
|
||||
/// <summary>
|
||||
/// Extract and cache the physics BSP + polygon data from a GfxObj.
|
||||
/// No-ops if the id is already cached or the GfxObj has no physics data.
|
||||
/// </summary>
|
||||
public void CacheGfxObj(uint gfxObjId, GfxObj gfxObj)
|
||||
{
|
||||
if (_gfxObj.ContainsKey(gfxObjId)) return;
|
||||
if (!gfxObj.Flags.HasFlag(GfxObjFlags.HasPhysics)) return;
|
||||
if (gfxObj.PhysicsBSP?.Root is null) return;
|
||||
|
||||
_gfxObj[gfxObjId] = new GfxObjPhysics
|
||||
{
|
||||
BSP = gfxObj.PhysicsBSP,
|
||||
PhysicsPolygons = gfxObj.PhysicsPolygons,
|
||||
BoundingSphere = gfxObj.PhysicsBSP.Root.BoundingSphere,
|
||||
Vertices = gfxObj.VertexArray,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract and cache the collision shape data from a Setup.
|
||||
/// No-ops if the id is already cached.
|
||||
/// </summary>
|
||||
public void CacheSetup(uint setupId, Setup setup)
|
||||
{
|
||||
if (_setup.ContainsKey(setupId)) return;
|
||||
_setup[setupId] = new SetupPhysics
|
||||
{
|
||||
CylSpheres = setup.CylSpheres ?? new(),
|
||||
Spheres = setup.Spheres ?? new(),
|
||||
Height = setup.Height,
|
||||
Radius = setup.Radius,
|
||||
StepUpHeight = setup.StepUpHeight,
|
||||
StepDownHeight = setup.StepDownHeight,
|
||||
};
|
||||
}
|
||||
|
||||
public GfxObjPhysics? GetGfxObj(uint id) => _gfxObj.TryGetValue(id, out var p) ? p : null;
|
||||
public SetupPhysics? GetSetup(uint id) => _setup.TryGetValue(id, out var p) ? p : null;
|
||||
public int GfxObjCount => _gfxObj.Count;
|
||||
public int SetupCount => _setup.Count;
|
||||
}
|
||||
|
||||
/// <summary>Cached physics data for a single GfxObj part.</summary>
|
||||
public sealed class GfxObjPhysics
|
||||
{
|
||||
public required PhysicsBSPTree BSP { get; init; }
|
||||
public required Dictionary<ushort, Polygon> PhysicsPolygons { get; init; }
|
||||
public Sphere? BoundingSphere { get; init; }
|
||||
public required VertexArray Vertices { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Cached collision shape data for a Setup (character/creature capsule).</summary>
|
||||
public sealed class SetupPhysics
|
||||
{
|
||||
public List<CylSphere> CylSpheres { get; init; } = new();
|
||||
public List<Sphere> Spheres { get; init; } = new();
|
||||
public float Height { get; init; }
|
||||
public float Radius { get; init; }
|
||||
public float StepUpHeight { get; init; }
|
||||
public float StepDownHeight { get; init; }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue