using System.Numerics; namespace AcDream.Core.Physics; /// /// One collision-bearing shape attached to a logical PhysicsObj. /// The set is a DISPATCH, not a union (AP-152): a BSP door emits one shape /// per physics-BSP part and no primitive; a primitive-only object emits its /// CylSpheres, else its Spheres; a simple item zero. Positions and rotations are /// LOCAL to the entity's origin so /// can re-transform them when the entity moves. /// /// /// Retail anchor: CPhysicsPart + CPhysicsPart::find_obj_collisions /// at acclient_2013_pseudo_c.txt:275045-275055. Each part stores its /// own transform and dispatches per-part collision to its GfxObj. /// /// /// /// Center of the shape's bounding sphere IN THE SHAPE'S OWN LOCAL FRAME — /// the frame rotates out of and /// is measured in — already multiplied by the entity /// scale, like and . /// Zero for Cylinder/Sphere shapes, whose /// already IS their center. /// /// /// BSP shapes need it because a GfxObj's physics BSP is authored in the /// GfxObj's own coordinates and its root bounding sphere is frequently NOT /// centered on that origin (376 of the 973 physics-BSP parts in the /// installed client_portal.dat sit further from it than half their /// own radius; worst 20.762 m on a 27.708 m sphere, gfx 0x010036DD). /// 's flood needs the /// sphere's real position, not the part origin's. /// /// /// /// Retail anchor: CGfxObj::physics_sphere ([gfxobj+0x74]) is /// assigned BSPTREE::GetSphere(physics_bsp) @0x005397e0 /// (mov eax,[ecx]; add eax,4 — the root BSPNODE's /// CSphere sphere, past its 4-byte vftable), so retail's per-part /// flood sphere IS the BSP root bounding sphere, origin included. /// CEnvCell::find_transit_cells @0x0052cae0 — the part-array overload /// reached from CPhysicsObj::find_bbox_cell_list @0x00510fc0 via /// CPartArray::calc_cross_cells_static @0x00518160's /// [vtbl+0x7c] dispatch — loads that sphere at /// 0x0052cb36 mov esi,[ecx+0x74], transforms its CENTER through the /// part's own Position at [part+0x30] /// (0x0052cb4c add eax,0x30 / 0x0052cb5a call Position::localtolocal), /// and only then reads the radius at 0x0052cb65 fadd [esi+0xc]. /// /// public readonly record struct ShadowShape( uint GfxObjId, Vector3 LocalPosition, Quaternion LocalRotation, float Scale, ShadowCollisionType CollisionType, float Radius, float CylHeight, Vector3 BoundsCenter = default);