56 lines
4 KiB
Markdown
56 lines
4 KiB
Markdown
# Headless SoA world
|
|
|
|
A headless 3D Box3D world with a structure-of-arrays state view, for Lodur and Bad Day Simulator.
|
|
Box3D decides every physical outcome; Ymir creates bodies, steps the world and exposes state.
|
|
|
|
## Why it is not the retained session
|
|
|
|
`ymir_box3d_session_*` (`ymir_box3d.c`) is the planar Aetheria session: gravity is zero, Y is locked,
|
|
only yaw rotates (`motionLocks.linearY/angularX/angularZ`), and state leaves by `copy_bodies` into an
|
|
array of structs. A body cannot fall or tumble there, and every read is a copy. The SoA world is a
|
|
separate native object (`ymir_box3d_soa.c`, ABI 1) so the Aetheria session's contract is untouched.
|
|
|
|
## Contract
|
|
|
|
- Native (ABI 2): `ymir_box3d_soa_create(capacity, joint_capacity, gravity) / add_body / add_joint / step /
|
|
get_view / destroy`. `add_body` takes one descriptor (sphere, capsule, box, static box; position,
|
|
rotation, velocity, size, density, friction, restitution, rolling resistance, parent, limb). Capacities are fixed at creation,
|
|
so lane pointers never move.
|
|
- Body lanes (`ymir_box3d_soa_view`): `stable_id`, `kind`, `parent` and `limb` (set at creation, not read by
|
|
Box3D: the limb tree for the consumer), position xyz, rotation quaternion xyzw, linear and angular
|
|
velocity xyz. One contiguous array per component, indexed by body.
|
|
- Write lanes: per-body world `force` (at the centre of mass) and `torque`, xyz, mutable through the view.
|
|
`step` applies them to the dynamic bodies and clears them, so a lane holds one step's intent. A
|
|
non-finite value refuses the whole step before any body is touched.
|
|
- Joints: `add_joint` takes two body indices (B is the child), a world anchor and axis, and a kind
|
|
(revolute, spherical, weld). The joint frame has its z axis on the axis (the hinge axis); the creation
|
|
pose is the joint's zero. Optional limits: revolute angle, spherical swing cone and twist. Joint lanes:
|
|
`joint_kind`, `joint_body_a/b`, `joint_angle` (Box3D's revolute angle or spherical twist), `joint_frame`
|
|
(world orientation of frame A, quaternion), `joint_rotation` (frame B relative to frame A, quaternion,
|
|
w >= 0) and `joint_angular_velocity` (B minus A, in frame A). A consumer can compute a joint torque from
|
|
these and write it as equal and opposite torques on the two body lanes.
|
|
- State lanes change only inside `step`. Between steps a caller may read any state lane in place.
|
|
- Managed (`src/Ymir.Box3D/Box3DSoaWorld.cs`, internal): `Box3DSoaView` exposes each lane as a span over
|
|
native memory (`ReadOnlySpan` for state, `Span` for write lanes). Spans are invalid after `Dispose`.
|
|
`Count` and `JointCount` are as of when the view was taken, so take it after the last add.
|
|
- Determinism: one Box3D worker, bodies stepped in insertion order, same inputs give identical lane
|
|
bytes on the same build. It is not claimed across platforms or CPUs.
|
|
- Concurrency: creating Box3D worlds from several threads at once aborts the process (seen in tests);
|
|
create worlds one at a time.
|
|
|
|
## Tests
|
|
|
|
`tests/Ymir.Box3D.Soa.Tests`, run serially: gravity and rest read through one held view, address stability
|
|
across steps, two runs byte-equal at six checkpoints (25 spheres colliding on a slab), a one-ulp input
|
|
difference diverging, argument refusal, dispose; and for joints: hinge angle and the relative-rotation
|
|
lanes agreeing, an off-axis hinge, a limit and a weld, a spherical joint leaving the swing plane, force and
|
|
torque lanes (exact acceleration, cleared after the step, static bodies ignoring them, NaN refusing the
|
|
step), capsule and box rest heights with parent and limb lanes, refusals, and bit-identical repeat runs of
|
|
a torque-driven jointed body.
|
|
|
|
## Not done
|
|
|
|
Contact facts for the SoA world (Begin/Hit/End), body removal with a stable ordering rule, compound bodies
|
|
and per-shape material, joint motors and springs (Lodur drives joints from torque lanes instead), and a
|
|
public typed surface in `Ymir.Core` (the wrapper is internal, visible to the test assembly). Windows build
|
|
and Unity consumption are unrun; cross-platform bit-identity is unclaimed.
|