Indoor CellStruct PhysicsBSP collision for room walls/ceilings. Dual sphere (body+head) from Setup dimensions. StepUp attempts before sliding when hitting low obstacles. FindTimeOfCollision for exact parametric BSP contact time. Full 6-path BSP dispatcher wired into FindEnvCollisions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
121 lines
4.6 KiB
C#
121 lines
4.6 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Numerics;
|
|
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();
|
|
private readonly ConcurrentDictionary<uint, CellPhysics> _cellStruct = 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,
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <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; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public sealed class CellPhysics
|
|
{
|
|
public required PhysicsBSPTree BSP { get; init; }
|
|
public required Dictionary<ushort, Polygon> PhysicsPolygons { get; init; }
|
|
public required VertexArray Vertices { get; init; }
|
|
public Matrix4x4 WorldTransform { get; init; }
|
|
public Matrix4x4 InverseWorldTransform { get; init; }
|
|
}
|