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; }
}