using System.Collections.Concurrent; using System.Numerics; using DatReaderWriter.DBObjs; using DatReaderWriter.Enums; using DatReaderWriter.Types; namespace AcDream.Core.Physics; /// /// 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. /// public sealed class PhysicsDataCache { private readonly ConcurrentDictionary _gfxObj = new(); private readonly ConcurrentDictionary _setup = new(); private readonly ConcurrentDictionary _cellStruct = new(); /// /// 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. /// 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, }; } /// /// Extract and cache the collision shape data from a Setup. /// No-ops if the id is already cached. /// 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, }; } /// /// Extract and cache the physics BSP + polygon data from a CellStruct /// (indoor room geometry). No-ops if the id is already cached or the /// CellStruct has no physics BSP. /// public void CacheCellStruct(uint envCellId, CellStruct cellStruct, Matrix4x4 worldTransform) { if (_cellStruct.ContainsKey(envCellId)) return; if (cellStruct.PhysicsBSP?.Root is null) return; Matrix4x4.Invert(worldTransform, out var inverseTransform); _cellStruct[envCellId] = new CellPhysics { BSP = cellStruct.PhysicsBSP, PhysicsPolygons = cellStruct.PhysicsPolygons, Vertices = cellStruct.VertexArray, WorldTransform = worldTransform, InverseWorldTransform = inverseTransform, }; } 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 CellPhysics? GetCellStruct(uint id) => _cellStruct.TryGetValue(id, out var p) ? p : null; public int GfxObjCount => _gfxObj.Count; public int SetupCount => _setup.Count; public int CellStructCount => _cellStruct.Count; } /// Cached physics data for a single GfxObj part. public sealed class GfxObjPhysics { public required PhysicsBSPTree BSP { get; init; } public required Dictionary PhysicsPolygons { get; init; } public Sphere? BoundingSphere { get; init; } public required VertexArray Vertices { get; init; } } /// Cached collision shape data for a Setup (character/creature capsule). public sealed class SetupPhysics { public List CylSpheres { get; init; } = new(); public List Spheres { get; init; } = new(); public float Height { get; init; } public float Radius { get; init; } public float StepUpHeight { get; init; } public float StepDownHeight { get; init; } } /// /// Cached physics data for an indoor cell's room geometry (CellStruct). /// Used for wall/floor/ceiling collision in EnvCells. /// ACE: EnvCell.find_env_collisions queries CellStructure.PhysicsBSP. /// public sealed class CellPhysics { public required PhysicsBSPTree BSP { get; init; } public required Dictionary PhysicsPolygons { get; init; } public required VertexArray Vertices { get; init; } public Matrix4x4 WorldTransform { get; init; } public Matrix4x4 InverseWorldTransform { get; init; } }