Skip to main content

PhysicsWorld

Struct PhysicsWorld 

Source
pub struct PhysicsWorld {
Show 13 fields pub config: SolverConfig, pub bodies: Vec<RigidBody>, pub distance_constraints: Vec<DistanceConstraint>, pub contact_constraints: Vec<ContactConstraint>, pub sdf_colliders: Vec<SdfCollider>, pub sdf_collision_radius: Fix128, pub contact_cache: ContactCache, pub material_table: MaterialTable, pub body_materials: Vec<MaterialId>, pub joints: Vec<Joint>, pub force_fields: Vec<ForceFieldInstance>, pub events: EventCollector, pub islands: IslandManager, /* private fields */
}
Expand description

XPBD physics world with batched constraint solving

PhysicsWorld integrates all physics subsystems:

  • Rigid body dynamics with XPBD solver
  • Automatic collision detection (BVH broad-phase + sphere narrow-phase)
  • Joint constraints (Ball, Hinge, Fixed, Slider, Spring, D6, ConeTwist)
  • Force fields (wind, gravity wells, buoyancy, vortex, drag)
  • Collision filtering (layer/mask bitmasks)
  • Contact events (begin/persist/end)
  • Sleeping/island management

Fields§

§config: SolverConfig

Solver configuration

§bodies: Vec<RigidBody>

All rigid bodies in the world

§distance_constraints: Vec<DistanceConstraint>

Distance constraints between bodies

§contact_constraints: Vec<ContactConstraint>

Contact constraints from collision detection

§sdf_colliders: Vec<SdfCollider>

SDF colliders for implicit collision detection

§sdf_collision_radius: Fix128

Default collision radius for body-vs-SDF queries

§contact_cache: ContactCache

Contact manifold cache for warm starting

§material_table: MaterialTable

Material pair lookup table

§body_materials: Vec<MaterialId>

Per-body material IDs

§joints: Vec<Joint>

Joint constraints (solved each substep alongside distance/contact)

Self::add_joint checks that both body indices are in range. A joint pushed here directly with an index past the last body makes the next step panic in the joint solve (index out of bounds), except a joint whose two indices are equal: the solve skips it, and so do the island rebuilds.

§force_fields: Vec<ForceFieldInstance>

Force fields applied at the start of each step

§events: EventCollector

Contact and trigger event collector

§islands: IslandManager

Island manager for sleeping and connectivity tracking

Implementations§

Source§

impl PhysicsWorld

Source

pub fn emit_contact_audio(&self, generator: &mut AudioGenerator)

Feed every contact of the last step to generator (AudioGenerator::process_contact), in the order of contact_events, which is deterministic.

One call per touching pair (a pair is reported once per step): a Begin event is a new contact, a Persist event a continuing one, and an End event is skipped. The relative velocity passed is the event’s normal speed (ContactEvent::relative_velocity, measured when the contact was detected, before the solver removed it) along the event normal, plus the tangential part of the bodies’ current linear velocity difference v_a − v_b. The contact passed has the event’s point as point_a and point_a + normal·depth as point_b.

The generator’s events are appended to; call AudioGenerator::begin_frame first to start a new frame.

Source§

impl PhysicsWorld

Source

pub fn time_of_impact( &self, body: usize, velocity: Vec3Fix, dt: Fix128, filter: &RayFilter, ) -> Option<TOI>

The first time in a step at which body body, translating with velocity for dt, touches any other geometry the world collides with (body shapes, compound children, static colliders and, with std, SDF colliders), as a TOI.

  • Shape. The body is swept as the sphere of its collision radius about its position (crate::solver::PhysicsWorld::cast_sphere). For a body with only a collision radius that is its exact geometry. For a body with a crate::shape::Shape or a compound collider the radius is the bounding radius about its centre of mass, so the reported time is never later than the true shape’s first contact (it can be earlier). A body with no collision radius collides with nothing: None.
  • Time. TOI::t is the fraction of the step, 0 at the start and 1 at velocity · dt: the distance travelled to the contact divided by |velocity · dt|.
  • Point and normal. The contact point on the other geometry and its surface normal there, toward the moving body: the convention of sphere_plane_toi, and of sphere_sphere_toi with the obstacle as sphere A and the moving body as sphere B (moving by velocity · dt). A body that already overlaps something reports t = 0, its own centre as the point and −velocity (normalized) as the normal (see crate::world_shape_query).
  • What is seen. filter decides, except that the body itself (and any SDF collider attached to it) is always excluded: the filter’s RayFilter::exclude_body is replaced by body. Everything else is taken at its current pose and treated as still; pass a relative velocity for a moving target.

An index out of range, dt ≤ 0 or a zero displacement gives None.

Source§

impl PhysicsWorld

Source

pub fn debug_draw(&self, flags: &DebugDrawFlags, data: &mut DebugDrawData)

Draw this world into data (debug_draw_world); data is cleared first.

Source§

impl PhysicsWorld

Source

pub fn stress_heatmap(&self, dt: Fix128, config: &HeatmapConfig) -> Heatmap

The stress heatmap of the last step’s contacts: generate_stress_heatmap over the body positions and contact_forces(dt), where dt is the frame step passed to step (a dt of zero carries no force, so the map is all zero).

Source§

impl PhysicsWorld

Source

pub fn law_id(&self, semantics_id: &[u8; 32]) -> [u8; 32]

Identifier of the law this world steps by, hashed with semantics_id (pass PHYSICS_SEMANTICS_ID for this build).

§What it covers

The rules a step reads, not the state it acts on: the solver config, the contact cache settings, sdf_collision_radius, continuous collision, the sleep thresholds, the material table, the force fields and the registered participants. The encoding below lists every field.

Not covered:

  • the state: bodies, constraints and joints, including joint motors (their gains and targets live on the joint, so they are state);
  • the broadphase kind and the sleep skip, since every choice gives the same bits;
  • the caller-supplied code a world can hold: pre-solve hooks, contact modifiers, SDF colliders and an installed GPU solver bridge.

config.warm_start_factor is covered although the CPU step does not read it: it is passed to an installed GPU solver bridge, whose result may depend on it.

§Guarantee, and its limit

Two worlds with the same identifier, the same state and the same inputs step to the same bits, provided the hooks, modifiers, SDF colliders and bridge they hold also behave the same, since those are outside the identifier. A participant contributes only its kind, step rule and ports: two participants of one kind that differ in their coefficients get the same identifier until participants expose their coefficients. The converse does not hold either: worlds whose laws step identically may still differ here (for example two force fields declared in the other order), so this is not a deduplication key. One case of this: config.solver_backend is covered, but step_parallel, try_step_parallel and the GPU bridge paths run XPBD whatever it says, so on those paths two worlds that differ only in the backend have different identifiers and step to the same bits.

§Encoding

SHA-256 over the concatenation below, which an independent implementation can reproduce byte for byte (the layout of the law identifiers of alice-zip). Primitive encodings:

ValueBytes
length, count, usizeu64 big-endian
u324 bytes big-endian
MaterialId (u16)widened to u64, big-endian
boolone byte, 0 or 1
Fix128raw high word (i64) then low word (u64), big-endian
Vec3Fixx, y, z as Fix128
byte string, enum valuelength, then the bytes; an enum value is written as the tag given in the table
listcount, then the items
Option<T>bool (1 for Some), then T when present

Fields, in order:

#FieldEncoding
1LAW_ID_DOMAINbyte string
2WORLD_LAW_KINDbyte string
3semantics_idthe 32 bytes, no length
4config.substeps, config.iterationsu64, u64
5config.gravity, config.dampingVec3Fix, Fix128
6config.solver_backendtag xpbd or tgs
7config.warm_start_factorFix128
8contact cache warm_start_factor, max_stale_framesFix128, u32
9sdf_collision_radiusFix128
10continuous collision: enabled, motion thresholdbool, Fix128
11sleep linear_threshold, angular_threshold, frames_to_sleepFix128, Fix128, u32
12default friction and restitution combine rulestag average, min, max or multiply, twice
13materials, in id orderlist of: id (MaterialId), static_friction, dynamic_friction, restitution (Fix128 each), friction combine, restitution combine (tags)
14pair overrides, sorted by (mat_a, mat_b) with mat_a <= mat_blist of: mat_a, mat_b (MaterialId), friction, restitution (Fix128)
15force fields, in the order they were addedlist of: enabled (bool), the bodies it is limited to (Option of a list of u64, sorted ascending without duplicates), then the field
16participants, in registration orderlist of: kind (u32), step rule, ports

A field is its tag followed by its parameters in declaration order: directional (direction Vec3Fix, strength), point (center Vec3Fix, strength, repulsive bool, max_force), drag (coefficient), buoyancy (surface_y, density, drag), vortex (center Vec3Fix, axis Vec3Fix, strength, falloff_radius), explosion (center Vec3Fix, strength, radius, falloff_power), magnetic (position Vec3Fix, moment Vec3Fix, strength); every parameter not marked otherwise is a Fix128. A step rule is the tag follow-substep, fixed followed by its dt (Fix128), or subcycle. A port is its id (u32) followed by the tag read, write or read-committed.

Sorting the pair overrides and the body lists keeps the identifier independent of the order they were registered in, which the step does not depend on either. Force fields and participants keep their order, since the step applies them in that order.

Source§

impl PhysicsWorld

Source

pub fn sdf_ccd_hits( &self, dt: Fix128, config: &SdfCcdConfig, ) -> Vec<(usize, usize, TOI)>

Sweep the world’s moving bodies against its SDF colliders.

A body is swept as a sphere of PhysicsWorld::sdf_collision_radius (the radius PhysicsWorld::sdf_contacts gives a body without a shape) along velocity * dt; this is the continuous counterpart of the discrete push-out in PhysicsWorld::step, to be asked before a step to learn which fast bodies would tunnel through a thin SDF. Static bodies, sensors and bodies slower than config.velocity_threshold are skipped, and an SDF attached to the body itself is never tested against it. Hits come back as in batch_sphere_trace_sdf, earliest time of impact first. An SDF attached to a body is swept at the pose it was given when it was added or at the end of the last step (see PhysicsWorld::sdf_contacts).

dt is the frame step.

Source§

impl PhysicsWorld

Source

pub fn sdf_field(&self) -> WorldSdfField<'_>

Source

pub fn move_sdf_character( &self, ch: &mut SdfCharacter, dt: f32, control: [f32; 3], ) -> MoveOutcome

Advance ch one frame against the world’s SDF colliders: SdfCharacter::step on PhysicsWorld::sdf_field, with the same arguments and result. dt is passed through unchanged, as SdfCharacter::step takes it (dt = 0 moves by control only).

The character sees only the SDF colliders, not bodies or static colliders; for a capsule against everything the world collides with see PhysicsWorld::move_character.

Source§

impl PhysicsWorld

The sensors read through the world: each method passes the world to the sensor’s own method and returns what it returns, advancing the sensor’s state (noise stream, IMU reference velocity) the same way.

Source

pub fn lidar_scan(&self, lidar: &mut Lidar) -> LidarScan

One scan of lidar from its pose in the world (Lidar::scan).

Source

pub fn lidar_scan_from_body( &self, lidar: &mut Lidar, body: usize, ) -> Option<LidarScan>

One scan of lidar mounted on body, not seeing that body (Lidar::scan_from_body). None for an index that is not a body.

Source

pub fn read_contact_sensor( &self, sensor: &mut ContactSensor, dt: Fix128, ) -> ContactReading

The contact-sensor reading after a step of dt (ContactSensor::read).

Source

pub fn sample_imu(&self, imu: &mut Imu, dt: Fix128) -> Option<ImuReading>

The IMU reading after a step of dt (Imu::sample): None when the body is gone or dt ≤ 0.

Source§

impl PhysicsWorld

Source

pub fn ray_caster(&self, filter: RayFilter) -> WorldRayCaster<'_>

A ray caster over this world’s current state with filter; the body BVH is built here, once.

Source

pub fn cast_ray( &self, origin: Vec3Fix, direction: Vec3Fix, max_t: Fix128, filter: &RayFilter, ) -> Option<WorldRayHit>

The nearest hit of a ray with the world’s geometry (see crate::shape_raycast).

Source

pub fn cast_ray_all( &self, origin: Vec3Fix, direction: Vec3Fix, max_t: Fix128, filter: &RayFilter, ) -> Vec<WorldRayHit>

Every collider the ray hits, one hit each, nearest first.

Source

pub fn cast_ray_any( &self, origin: Vec3Fix, direction: Vec3Fix, max_t: Fix128, filter: &RayFilter, ) -> bool

Whether the ray hits anything (stops at the first hit found).

Source§

impl PhysicsWorld

Source

pub fn move_character( &self, ctrl: &mut CharacterController, displacement: Vec3Fix, ) -> MoveResult

Move ctrl by displacement, sliding along everything the world collides with (see crate::world_character), with the default RayFilter.

Updates the controller’s position, grounded state, ground body and platform velocity as CharacterController::move_and_slide does, and returns them.

Source

pub fn move_character_with_filter( &self, ctrl: &mut CharacterController, displacement: Vec3Fix, filter: &RayFilter, ) -> MoveResult

PhysicsWorld::move_character seeing only what filter sees (for instance RayFilter::excluding_body for the character’s own body).

Source§

impl PhysicsWorld

Source

pub fn cast_sphere( &self, center: Vec3Fix, radius: Fix128, direction: Vec3Fix, max_t: Fix128, filter: &RayFilter, ) -> Option<WorldShapeHit>

The nearest collider a sphere of radius touches when its centre moves from center along direction for at most max_t (see crate::world_shape_query for the conventions). Radius 0 is PhysicsWorld::cast_ray; a negative radius, a zero direction or max_t ≤ 0 gives None.

Source

pub fn cast_capsule( &self, a: Vec3Fix, b: Vec3Fix, radius: Fix128, direction: Vec3Fix, max_t: Fix128, filter: &RayFilter, ) -> Option<WorldShapeHit>

The nearest collider a capsule (segment a–b grown by radius) touches when it moves along direction for at most max_t. Coinciding ends are PhysicsWorld::cast_sphere; a negative radius, a zero direction or max_t ≤ 0 gives None.

Source

pub fn overlap_sphere( &self, center: Vec3Fix, radius: Fix128, filter: &RayFilter, ) -> Vec<RayTarget>

Every collider a sphere of radius about center overlaps, sorted by target. A negative radius gives an empty list; radius 0 finds the solids center is strictly inside.

Source

pub fn overlap_aabb(&self, aabb: &AABB, filter: &RayFilter) -> Vec<RayTarget>

Every collider aabb overlaps, sorted by target (see crate::world_shape_query). A box with min > max on any axis gives an empty list.

Source§

impl PhysicsWorld

Source

pub fn add_participant( &mut self, participant: Box<dyn Participant>, ) -> Result<usize, RegisterError>

Register participant; it runs in every substep from the next step on. Returns its registration index.

The participant’s ports (Participant::ports) are read once, here, and checked with the ports of the participants already registered and the world’s fields (ParticipantPlan::new). A StepRule::Fixed step is checked against the substep at every step (StepError::Rule), because the substep is only known then.

§Errors

RegisterError::NonPositiveStep for StepRule::Fixed(Δt ≤ 0), RegisterError::Order when the ports would form a loop, RegisterError::Field when they do not fit the fields. The world is unchanged.

Source

pub fn participant_count(&self) -> usize

Number of registered participants.

Source

pub fn participant_kinds(&self) -> Vec<ParticipantKind>

The kinds of the registered participants, in registration order.

Source

pub fn participant_state(&self, index: usize) -> Option<Vec<u8>>

The state of participant index as its Participant::write_state writes it (the payload a snapshot holds), or None for an index past the last participant.

Source

pub fn observe_participant( &self, index: usize, ) -> Option<Observed<ObservationSink>>

What participant index reports (Participant::observe): Observed::Undecided while a fault is recorded, None for an index past the last participant.

Source

pub const fn fault(&self) -> Option<WorldFault>

The first fault recorded, if any. While one is recorded, Self::try_step refuses to run (StepError::Faulted) and the checked observations are undecided. It is part of the snapshot.

Source

pub fn clear_fault(&mut self)

Forget the recorded fault so that steps run again. The world’s overflow flag (Self::overflow_detected) is not touched.

Source

pub fn observe_body_checked( &self, body_idx: usize, ) -> Option<Observed<BodyObservation>>

Self::observe_body, or Observed::Undecided while a fault is recorded or the overflow flag is up (the values may have left the range of Fix128); None for an index past the last body.

Source

pub const fn fields(&self) -> &FieldBoard

The world’s shared fields.

Source

pub fn declare_field( &mut self, id: PortId, layout: FieldLayout, mode: FieldMode, ) -> Result<(), DeclareFieldError>

Declare shared field id, all samples zero. Declare a field before the participants that use it.

§Errors

DeclareFieldError::Field when the board refuses it, DeclareFieldError::Ports when the registered participants’ ports no longer fit with it. The world is unchanged.

Source

pub fn set_field( &mut self, id: PortId, values: &[Fix128], ) -> Result<(), FieldError>

Set the committed value of field id (an initial condition, between steps).

§Errors

FieldError::Unknown or FieldError::Length; the field is unchanged.

Source

pub fn try_step(&mut self, dt: Fix128) -> Result<(), StepError>

One step of dt, refusing to run when it cannot run normally.

In order: while a fault is recorded the step is refused (StepError::Faulted) and the world is unchanged; a non-positive dt changes nothing and is Ok; every participant’s step rule is checked against the substep width h the participants are handed (dt / substeps, or the TGS width under super::SolverBackend::Tgs, see the module documentation and try_step_parallel (feature parallel); StepError::Rule) and every FieldLayout::PerBody field against the body count (StepError::BodyCount), refused unchanged. Then the step runs (see the module documentation of crate::world_participant and Self::step); when a fault was recorded during it the step still ran to the end and StepError::FaultRaised reports it.

With no participant registered this is Self::step as it always was, bit for bit.

§Errors

See above.

Source

pub fn try_step_parallel(&mut self, dt: Fix128) -> Result<(), StepError>

Self::try_step on the batched (parallel) path of Self::step_parallel. Participants are called one after the other, as in Self::try_step. This path runs the XPBD substep loop whatever super::SolverBackend is configured (with super::SolverBackend::Tgs it gives the same bits as with Xpbd; a batched TGS path does not exist yet), so the participants are handed h = dt / substeps and step rules are checked against that width, also under super::SolverBackend::Tgs.

§Errors

As Self::try_step.

Source§

impl PhysicsWorld

Source

pub fn set_continuous_collision(&mut self, config: WorldCcdConfig)

Set continuous collision for the world step. Off by default.

Source

pub fn continuous_collision(&self) -> WorldCcdConfig

The continuous collision setting of the world step.

Source§

impl PhysicsWorld

Source

pub const WORLD_SNAPSHOT_MAGIC: [u8; 4]

Magic of a Self::snapshot_world blob (b"APWS", distinct from Self::STATE_MAGIC of the rollback blob).

Source

pub const WORLD_SNAPSHOT_VERSION: u16 = 4

Format version of a Self::snapshot_world blob. Version 1 blobs (no participants, fault or fields section) are still read, as a world without participants, fault or fields, version 1 and 2 blobs (no continuous_collision section) as a world with continuous collision off, and version 1 to 3 blobs (no semantics_id / law_id in the header) without the semantics check, reported as LawCheck::Unpinned by Self::restore_world_checked; a blob of any other version is rejected with WorldSnapshotError::UnsupportedVersion.

Source

pub fn snapshot_world(&self) -> Vec<u8> ⓘ

Write every piece of state Self::step reads into one versioned blob with a checksum.

Restore it with Self::from_world_snapshot (into a new world) or Self::restore_world (into an existing one, keeping its SDF fields and callbacks). Every later step of the restored world is bit-identical to the original’s.

§Examples
use alice_physics::{Fix128, PhysicsConfig, PhysicsWorld, RigidBody, Vec3Fix};

let mut world = PhysicsWorld::new(PhysicsConfig::default());
world.add_body(RigidBody::new_dynamic(Vec3Fix::from_int(0, 5, 0), Fix128::ONE));
let dt = Fix128::from_ratio(1, 60);
world.step_n(10, dt);

let blob = world.snapshot_world();
let mut branch = PhysicsWorld::from_world_snapshot(&blob).unwrap();
world.step_n(30, dt);
branch.step_n(30, dt);
assert_eq!(world.snapshot_world(), branch.snapshot_world());
§Field coverage

Every field of PhysicsWorld, classified. “Saved” fields are written byte-exact; “rebuilt” fields are derived on restore from saved ones; “not covered” fields cannot be written as data, and restore instead requires the target world to already hold the same number of them.

fieldclasshow / why
configsavedall of SolverConfig
bodiessavedevery RigidBody field, including prev_*, damping, kinematic_target
distance_constraintssavedincluding cached_lambda
contact_constraintssavedincluding cached_lambda
sdf_colliders (pose)savedposition / rotation / scale / body_index and the cached inv_rotation / scale_f32 / inv_scale_f32 as stored (not recomputed)
sdf_colliders (field)not coveredBox<dyn SdfField> is code; the target world keeps its own fields, and the count must match (WorldSnapshotError::SdfFieldCountMismatch)
sdf_collision_radiussaved
static_colliderssavedplane / height field / triangle mesh including its BVH nodes as stored
constraint_batches / batches_dirty / batch_static_bodiessavedas stored, so a stale-but-clean batch set is reproduced rather than recomputed
contact_cache (manifolds, max_stale_frames, warm_start_factor)saved
contact_cache (pair_index, std)rebuiltfrom the manifolds in order, the same way ContactCache::end_frame rebuilds it
material_table / body_materialssavedmaterials, pair overrides, default combine rules
pre_solve_hooks / contact_modifiers (std)not coveredclosures / trait objects; the count is recorded and must match (WorldSnapshotError::CallbackCountMismatch)
gpu_solver_bridge (feature gpu-solver-bridge)not coveredtrait object; “installed or not” is recorded and must match
jointssavedall seven kinds, every field
joint_motors / joint_motors_3dnot coveredthe restore target keeps its own motors (add them with PhysicsWorld::add_joint_motor / PhysicsWorld::add_joint_motor_3d before restoring), as the format predates them; a motor’s gains and targets that change between frames are saved next to the snapshot
force_fieldssavedfield, affected_bodies, enabled
eventssavedthis frame’s events and the previous / current pair and trigger sets that decide begin / persist / end
islands (sleep_data, config)saved
islands (union-find parent / rank)rebuiltIslandManager::new plus a union of every joint’s bodies, the same as the start of every step
body_collision_radii / body_filterssavedas stored, including their own lengths
broadphase / broadphase_tree / broadphase_proxiessavedthe persistent tree node by node (pair order follows the tree layout)
broadphase_hybridrebuiltempty; its pairs are a pure function of the bodies staged each substep, so a fresh one gives the same pairs
body_colliderssavedshape or compound, including the compound’s cached AABB and dirty flag
overflow_detectedsavedsticky flag
kinematic_substeps_leftrebuiltalways 0 outside step (reset at the end of every step), and a snapshot is only taken between steps
tgs_impulse_cache (std)savedentries sorted by id, hit / miss counters; the live set is rebuilt empty (the sweep at the end of every step empties it)
participants (std)saved / not coveredeach participant’s kind and its crate::world_participant::Participant::write_state payload are saved; the participant itself is code, so the target world must hold the same kinds in the same order (WorldSnapshotError::ParticipantMismatch), and every payload is checked before any is read (WorldSnapshotError::ParticipantState)
participant_plan (std)not coveredderived from the target world’s own participants and fields
faultsavedthe recorded fault, so a restored branch is still faulted
fieldssavedthe committed values once (crate::world_participant::FieldBoard::write_values); the target world must declare the same fields (WorldSnapshotError::FieldState)
ccdsavedPhysicsWorld::continuous_collision (version 3)

Not in the table because they are not PhysicsWorld fields: motors the caller applies itself with crate::motor::apply_motors and crate::character::CharacterController are owned by the caller and are applied from outside step, and cloth / fluid / FEM / vehicle state lives in its own types. Save them next to the world snapshot.

Known difference: the union-find of islands is rebuilt from the joints. After a remove_joint between two steps, the original still carries that union until the next step resets it, while the restored world does not; the two differ only for PhysicsWorld::wake_body called before that next step. The structure is private to crate::sleeping and has no non-mutating accessor, so it cannot be copied from &self.

§Format (version 4)
rangecontent
[0..4)magic PhysicsWorld::WORLD_SNAPSHOT_MAGIC (b"APWS")
[4..6)version u16 = PhysicsWorld::WORLD_SNAPSHOT_VERSION
[6..8)reserved u16 = 0
[8..16)payload length u64
[16..48)semantics_id: crate::PHYSICS_SEMANTICS_ID of the writing binary
[48..80)law_id: PhysicsWorld::law_id of this world under that semantics_id
[80..80+len)payload (the sections in the table above, little endian)
last 8FNV-1a 64 of every preceding byte (header included)

Version 1 to 3 blobs have the 16-byte header only (no semantics_id or law_id) and their payload starts at byte 16; the payload itself is the same in version 3 and 4. Version 4 grows the header rather than using the reserved bytes, which stay 0.

The header law_id is the rule of the world when it was written. It is not guaranteed to equal PhysicsWorld::law_id of a world after restore: the restored world keeps what the blob does not hold (for example its own participants), so whatever PhysicsWorld::law_id reads from those comes from the target world. The reader does not recompute it; Self::restore_world_checked compares the header value with the one the caller expects.

Version 2 appends three sections to the version 1 payload, after the TGS cache, in this order:

sectioncontent
participantscount: u64, then per participant kind: u32, payload_len: u64, payload
faultu8 code: 0 none, 1 participant (index: u64, kind: u32, fault tag u8), 2 rigid overflow, 3 force out of range (body: u64), 4 field out of range (field: u32, index: u64)
fieldscrate::world_participant::FieldBoard::write_values

Version 3 appends one section after fields:

sectioncontent
continuous_collisionenabled: u8 (0 / 1), motion_threshold (PhysicsWorld::continuous_collision)

The version is raised for it because the reader rejects trailing bytes: a section appended to a version 2 blob could not be told apart from a corrupt one.

Source

pub fn from_world_snapshot(data: &[u8]) -> Result<Self, WorldSnapshotError>

Build a new world from a Self::snapshot_world blob.

The new world holds no SDF fields, hooks, contact modifiers or GPU bridge, so a snapshot taken with any of those is rejected (WorldSnapshotError::SdfFieldCountMismatch / WorldSnapshotError::CallbackCountMismatch); use Self::restore_world on a world that holds them instead.

Rotations are restored as stored, as in Self::restore_world.

§Errors

See WorldSnapshotError for which input gives which error.

Source

pub fn restore_world(&mut self, data: &[u8]) -> Result<(), WorldSnapshotError>

Replace this world’s state with a Self::snapshot_world blob.

Everything in the blob replaces the current state, including the SolverConfig and the body population. What is not data stays: the SDF fields of self.sdf_colliders (paired with the blob’s poses in order), the pre-solve hooks, contact modifiers and the installed GPU bridge; their counts must equal the snapshot’s.

Body and SDF collider rotations are restored as stored and brought to unit length by the next step, so a non-unit rotation in data scales what it rotates by |q|^2 in the queries made before that step (see RigidBody::rotation). The next step also rebuilds each collider’s cached inverse rotation and scale from its restored fields (crate::sdf_collider::SdfCollider::update_cache).

§Errors

See WorldSnapshotError for which input gives which error. On error self is unchanged.

Source

pub fn restore_world_checked( &mut self, data: &[u8], expected_law_id: Option<&[u8; 32]>, ) -> Result<LawCheck, WorldSnapshotError>

Self::restore_world, also comparing the blob’s rule identifier with expected_law_id and reporting what could be verified.

blobexpected_law_idresult
version 4, header semantics_id is not crate::PHYSICS_SEMANTICS_IDanyWorldSnapshotError::SemanticsMismatch
version 4Some(id), header law_id == idLawCheck::Verified
version 4Some(id), header law_id != idWorldSnapshotError::LawIdMismatch
version 4NoneLawCheck::SemanticsVerified
version 1 to 3any (not compared)LawCheck::Unpinned

expected_law_id is compared with the header as written, not with Self::law_id of the restored world (see the format notes on Self::snapshot_world). To accept only verified blobs, refuse anything but LawCheck::Verified.

§Intended use

Pass the target world’s own self.law_id(&PHYSICS_SEMANTICS_ID), computed before the restore, as expected_law_id: the blob is then accepted only if it was written under the rule this world already runs. Passing the blob header’s own law_id compares the blob with itself and always gives LawCheck::Verified, which checks nothing. The target’s participants are kept by the restore (they are code, not data), so a target whose participant has the same kind but another step rule is refused this way, while a plain Self::restore_world accepts it.

LawCheck::Verified then means that the target was already built with the same rules as the blob before the restore. The restore overwrites the configuration, but the target’s own law_id is taken before that and covers the whole configuration, so a target that differs only in configuration (gravity, materials, force fields, and so on) gets WorldSnapshotError::LawIdMismatch: the check is strict and never accepts a blob written under other rules.

§Examples
use alice_physics::{
    Fix128, LawCheck, PhysicsConfig, PhysicsWorld, RigidBody, Vec3Fix,
    WorldSnapshotError, PHYSICS_SEMANTICS_ID,
};

let mut world = PhysicsWorld::new(PhysicsConfig::default());
world.add_body(RigidBody::new_dynamic(Vec3Fix::from_int(0, 5, 0), Fix128::ONE));
let blob = world.snapshot_world();

// A target running the same rule: its own law id, taken before the restore
let mut same = PhysicsWorld::new(PhysicsConfig::default());
let expected = same.law_id(&PHYSICS_SEMANTICS_ID);
assert_eq!(same.restore_world_checked(&blob, Some(&expected)), Ok(LawCheck::Verified));

// A target running another rule refuses the blob and keeps its state
let mut other = PhysicsWorld::new(PhysicsConfig {
    gravity: Vec3Fix::from_int(0, -3, 0),
    ..PhysicsConfig::default()
});
let expected = other.law_id(&PHYSICS_SEMANTICS_ID);
let before = other.snapshot_world();
assert!(matches!(
    other.restore_world_checked(&blob, Some(&expected)),
    Err(WorldSnapshotError::LawIdMismatch { .. })
));
assert_eq!(other.snapshot_world(), before);

// Without an expected id only the stepping semantics is checked
assert_eq!(other.restore_world_checked(&blob, None), Ok(LawCheck::SemanticsVerified));
§Errors

Every error of Self::restore_world, and WorldSnapshotError::LawIdMismatch. On error self is unchanged.

Source§

impl PhysicsWorld

Source

pub const STATE_MAGIC: [u8; 4]

Self::serialize_state blob の magic

⚠️ 旧 format は先頭が body 数だったので、magic を検査しないと 新実装が旧 blob を誤って解釈する

Source

pub const STATE_VERSION: u16 = 3

Self::serialize_state blob の format version

v1 = header 12 + body ごと 208 + body ごと sleep 5 v2 = v1 + world ごと overflow flag 1 byte (doctrine B-12) v3 = v2 + world ごと population fingerprint 8 byte (Self::population_fingerprint、rollback の body 数不一致検査強化)

被覆を足す時はここを上げ、Self::deserialize_state で分岐する (不一致を silent に読み替えない — 旧 version の blob は false で拒否、 v1 / v2 の blob は v3 実装に対して version 不一致で拒否される、 既存の v1→v2 bump と同じ方針)

Source

pub fn new(config: SolverConfig) -> Self

Create a new, empty physics world with the given solver configuration.

§Examples
use alice_physics::{Fix128, PhysicsConfig, PhysicsWorld, RigidBody, Vec3Fix};

let config = PhysicsConfig::default();
let mut world = PhysicsWorld::new(config);

// World starts with no bodies
assert_eq!(world.bodies.len(), 0);

// Add a body and verify it is tracked
let body = RigidBody::new_dynamic(Vec3Fix::from_int(0, 5, 0), Fix128::ONE);
let id = world.add_body(body);
assert_eq!(id, 0);
assert_eq!(world.bodies.len(), 1);
Source

pub fn reset_world(&mut self)

Reset the world to the same empty state Self::new produces, keeping the current SolverConfig.

§World Auditor の reset() 契約 (WM-07)

「同じ初期状態から N step を 2 回実行すると bit 一致する」を成立させる ための入口 *self = Self::new(self.config) と等価 (config は Copy) — bodies / joints / force_fields / islands / overflow_detected を含む 全 field が new と同じ既定値に戻る (installed hook / GPU bridge も含む、呼び出し側が明示的に残したい 状態があれば reset_world の前に退避すること)

Self::deserialize_state による rollback とは異なる経路 — rollback は「population を caller が replay で合わせてから特定 frame の連続状態を 復元する」契約だが、reset_world は「population ごと空にする」契約 両者は 「population を目標状態まで合わせてから連続状態を復元する」 という同じ形の特殊例 (reset_world は目標 population が空の場合)

§Examples
use alice_physics::{Fix128, PhysicsConfig, PhysicsWorld, RigidBody, Vec3Fix};

let config = PhysicsConfig::default();
let mut world = PhysicsWorld::new(config);
world.add_body(RigidBody::new_dynamic(Vec3Fix::from_int(0, 5, 0), Fix128::ONE));
assert_eq!(world.bodies.len(), 1);

world.reset_world();
assert_eq!(world.bodies.len(), 0);
assert!(!world.overflow_detected());
Source

pub fn set_gpu_solver_bridge( &mut self, bridge: Option<Box<dyn GpuSolverBridge + Send + Sync>>, )

v0.11.0: install a GPU solver bridge for automatic contact-solve routing. Subsequent calls to Self::step / the substep loop route contact-solve through this bridge instead of the CPU solver. Pass Some(bridge) to install; use Self::take_gpu_solver_bridge to remove.

The bridge is stored as Box<dyn GpuSolverBridge + Send + Sync>, so implementers must satisfy Send + Sync. For ALICE-TRT v3.0.0+ this is guaranteed because TrtSolverAdapter uses Arc<GpuDevice> and holds only Send + Sync fields.

Callers who want explicit control without installing the bridge on the world (game-engine wrapper hosts, hot-swap harnesses) can continue to use Self::step_with_bridge / Self::substep_with_bridge / Self::solve_contact_constraints_with_bridge with an externally-owned bridge — those methods ignore the installed bridge and take a &mut B parameter.

Source

pub fn take_gpu_solver_bridge( &mut self, ) -> Option<Box<dyn GpuSolverBridge + Send + Sync>>

v0.11.0: remove and return the currently installed GPU solver bridge, if any. Subsequent calls to Self::step / the substep loop revert to the CPU contact solver.

Source

pub const fn gpu_solver_bridge_installed(&self) -> bool

v0.11.0: whether a GPU solver bridge is currently installed on this world. Cheap; does not touch the bridge.

Source

pub fn add_body(&mut self, body: RigidBody) -> usize

Add rigid body, returns index

The body’s rotations are stored as unit quaternions (see RigidBody::set_rotation).

Source

pub fn add_body_with_radius(&mut self, body: RigidBody, radius: Fix128) -> usize

Add rigid body with a collision sphere radius for automatic detection.

Bodies with a collision radius participate in BVH broad-phase and sphere-sphere narrow-phase collision detection during step().

The body’s rotations are stored as unit quaternions, as in Self::add_body.

Source

pub fn add_shaped_body( &mut self, shape: &Shape, density: Fix128, position: Vec3Fix, ) -> Result<usize, ShapeError>

Add a rigid body shaped like shape, made of material of density, with its centre of mass at position.

The body gets the shape’s mass (density × volume), the shape’s principal inertia about the centre of mass (not the unit-sphere inertia RigidBody::new assigns), and the shape’s bounding sphere about the centre of mass as its collision radius, so it takes part in the sphere broad- and narrow-phase like any body added with Self::add_body_with_radius. See crate::shape for the frames.

§Errors

ShapeError when the density is not positive, a dimension is not, or the mass or inertia does not fit Fix128. Nothing is added in that case.

Source

pub fn set_body_shape(&mut self, body_idx: usize, shape: &Shape) -> bool

Give an existing body a convex shape as its collider: its collision radius becomes the shape’s bounding radius about the centre of mass, and a pair of bodies of which either carries a shape is decided by the shape (crate::collider::contact; against a body without one, by its collision sphere) instead of by the bounding spheres. Returns false for an out-of-range index.

The body’s position is taken as the shape’s centre of mass (see crate::shape); its mass and inertia are not changed — use Self::add_shaped_body for a body whose mass properties come from the shape.

Source

pub fn colliders_overlap(&self, a: usize, b: usize) -> bool

Whether the colliders of two bodies overlap, the same way the contact generation decides it: GJK on their shapes when both carry one (Self::set_body_shape, Self::add_shaped_body), GJK on the shape and the other body’s collision sphere when one does, otherwise the test of their collision spheres. false for an index that is not a body or a body without a collider. Touching exactly counts as overlapping when a shape is involved (GJK’s convention) and as clear for two spheres (the contact generation’s).

Source

pub fn add_compound_body( &mut self, compound: &CompoundShape, density: Fix128, position: Vec3Fix, ) -> Result<usize, ShapeError>

Add a rigid body made of a CompoundShape: material of density, the compound’s centre of mass at position.

The mass is the children’s total and the inertia is their summed tensor about the common centre of mass, which for an asymmetric compound has products of inertia; the body stores the principal moments, so its local frame is the principal frame, and its initial rotation is the rotation from that frame to the compound’s as authored — the compound appears in the world exactly as it was built, translated so its centre of mass is at position. The body collides as its children (body_collider, internal): a gap between two children is not part of the body, and its collision radius is the sphere about the centre of mass that contains them all.

§Errors

ShapeError: NonPositiveDensity; DegenerateShape for an empty compound or one with no volume; MassNotRepresentable when the mass or a principal moment is not positive or the sizes are too large for Fix128. Nothing is added in those cases.

Source

pub fn remove_body(&mut self, idx: usize) -> Option<RigidBody>

Remove a body by index (swap-remove).

The last body is moved to fill the gap. All constraints and joints referencing the old last index are remapped. Returns the removed body, or None if the index is out of bounds.

Source

pub fn body_count(&self) -> usize

Number of bodies in the world

Source

pub fn active_body_count(&self) -> usize

Number of active (non-sleeping) bodies

Source

pub fn set_body_material(&mut self, body_idx: usize, material_id: MaterialId)

Set a body’s material ID

Source

pub fn add_pre_solve_hook(&mut self, hook: PreSolveHook)

Add a pre-solve contact hook

The hook is called with (body_a_index, body_b_index, &contact). Return false to skip the contact (e.g., for one-way platforms).

Source

pub fn clear_pre_solve_hooks(&mut self)

Clear all pre-solve hooks

Source

pub fn add_contact_modifier(&mut self, modifier: Box<dyn ContactModifier>)

Add a contact modifier

Contact modifiers can mutate contact properties (normal, depth, friction, restitution) before solving. Return false from modify_contact to discard.

Source

pub fn clear_contact_modifiers(&mut self)

Clear all contact modifiers

Source

pub fn begin_frame(&mut self)

Begin a new simulation frame (updates contact cache lifecycle)

Source

pub fn end_frame(&mut self)

End a simulation frame (prune old contacts from cache)

Source

pub fn add_joint(&mut self, joint: Joint) -> usize

Add a joint constraint, returns joint index.

§Panics

Panics if either body index in the joint is out of bounds.

Source

pub fn remove_joint(&mut self, idx: usize) -> Option<Joint>

Remove a joint by index (swap-remove), returns the removed joint

Motors follow their joint: a motor on the removed joint is removed with it, and a motor on the last joint (moved into idx) is re-pointed to idx.

Source

pub fn add_joint_motor( &mut self, joint_index: usize, controller: PdController, ) -> usize

Attach a one-axis PD motor to joint joint_index, returns the motor index.

The motor is applied by Self::step every substep (see crate::motor::apply_motors for the generalised coordinate per joint type: the twist angle for a hinge, the centre distance otherwise). The controller is used as given, so a controller still in crate::motor::MotorMode::Off does nothing until a target is set (Self::set_joint_motor_velocity_target / Self::joint_motor_mut). Motors follow their joint through Self::remove_joint and Self::remove_body; a motor whose index no longer names a joint (after editing the public joints field directly) does nothing.

Source

pub fn joint_motor_mut(&mut self, motor: usize) -> Option<&mut JointMotor>

The motor added as motor by Self::add_joint_motor, for changing its gains or targets between steps.

Source

pub fn set_joint_motor_velocity_target( &mut self, motor: usize, target: Fix128, ) -> bool

Switch motor motor to velocity mode with target (rad/s for a hinge, m/s along the centre line otherwise). Returns false if there is no such motor.

Source

pub fn disable_joint_motor(&mut self, motor: usize) -> bool

Turn motor motor off (it applies no force until a target is set again). Returns false if there is no such motor.

Source

pub fn add_joint_motor_3d( &mut self, joint_index: usize, kp: Vec3Fix, kd: Vec3Fix, max_torque: Fix128, ) -> usize

Attach a three-axis rotation motor with per-axis gains kp / kd and torque cap max_torque to joint joint_index, returns its index.

The motor starts Off; Self::set_joint_motor_3d_rotation_target drives the joint’s relative rotation rotation_b * rotation_a⁻¹ to a target (see crate::motor::PdController3D::compute_torque). It follows its joint through removals like Self::add_joint_motor.

Source

pub fn joint_motor_3d_mut( &mut self, motor: usize, ) -> Option<&mut PdController3D>

The controller of rotation motor motor (Self::add_joint_motor_3d).

Source

pub fn set_joint_motor_3d_rotation_target( &mut self, motor: usize, target: QuatFix, ) -> bool

Drive rotation motor motor to the relative rotation target (position mode). Returns false if there is no such motor.

Source

pub fn joint_count(&self) -> usize

Number of joints

Source

pub fn add_force_field(&mut self, field: ForceFieldInstance) -> usize

Add a force field, returns field index

Source

pub fn remove_force_field(&mut self, idx: usize) -> Option<ForceFieldInstance>

Remove a force field by index

Source

pub fn set_body_collision_radius(&mut self, body_idx: usize, radius: Fix128)

Set collision radius for a body (enables automatic sphere collision detection)

Source

pub fn clear_body_collision_radius(&mut self, body_idx: usize)

Remove collision radius (disable automatic collision detection for this body)

Source

pub fn set_body_filter(&mut self, body_idx: usize, filter: CollisionFilter)

Set collision filter for a body

Source

pub fn body_filter(&self, body_idx: usize) -> CollisionFilter

Get collision filter for a body

Source

pub fn is_sleeping(&self, body_idx: usize) -> bool

Check if a body is sleeping

Source

pub fn wake_body(&mut self, body_idx: usize)

Wake up a body and all connected bodies in its island

Source

pub fn set_sleep_config(&mut self, config: SleepConfig)

Set the sleep configuration

Source

pub fn set_sleep_skip(&mut self, enabled: bool)

Turn the sleep skip of Self::step on (the default) or off.

With it on, a sleeping body at rest that no joint or distance constraint references is left out of every stage of the step, and its contacts are found through a persistent tree of such bodies instead of rebuilding the broad-phase over it (see StageWork). The simulation is bit-identical either way: the skip only drops work whose result is already known (a body at rest stays where it is, with the velocity it already has). Turning it off drops the tree; it is rebuilt on the next step with the skip on.

Only step with the XPBD backend and Broadphase::Bvh skips; the other entry points and broad-phases (Broadphase::DynamicTree, Broadphase::Hybrid) visit every body as before.

Source

pub const fn sleep_skip(&self) -> bool

Whether the sleep skip of Self::step is on (see Self::set_sleep_skip).

Source

pub const fn stage_work(&self) -> StageWork

Work counters of the last Self::step (see StageWork).

Source

pub fn observe_body(&self, body_idx: usize) -> Option<BodyObservation>

Observe a single body’s law-relevant state.

Returns None if body_idx is out of range (there is no body at that index — note that Self::remove_body uses swap_remove, so body_idx is only stable within the frame it was observed).

⚠️ grounded は意図的に持たない — 一般 RigidBody に地面接触の 定義は存在せず (character.rs の CharacterController::detect_ground は特化アルゴリズムで、全 body に適用できる基準ではない) Law / goal 述語が「接地」を必要とする場合は in_contact + 接触法線 (EventCollector::contact_events の crate::event::ContactEvent::normal) から 自前で構成する

Source

pub fn observe_bodies(&self) -> Vec<BodyObservation>

Observe every body in the world, in body index order.

Source

pub fn contact_events(&self) -> &[ContactEvent]

Get contact events from the last step

Source

pub fn trigger_events(&self) -> &[TriggerEvent]

Get trigger events from the last step

Source

pub fn drain_contact_events(&mut self) -> Vec<ContactEvent>

Drain (consume) contact events from the last step

Source

pub fn drain_trigger_events(&mut self) -> Vec<TriggerEvent>

Drain (consume) trigger events from the last step

Source

pub fn raycast( &self, origin: Vec3Fix, direction: Vec3Fix, max_distance: Fix128, ) -> Option<(usize, Fix128)>

Cast a ray against all body collision spheres, returns (body_index, distance).

Uses a BVH broad-phase to cull bodies outside the ray’s bounding box, then performs exact ray-sphere intersection on candidates. Returns None if direction is zero or no body is hit.

Every body is tested as its bounding sphere (its collision radius), even when it carries a shape or a compound, and static colliders and SDF colliders are not tested. This differs from the contacts, which a shaped body makes with its shape: a ray can hit the bounding sphere of a body that nothing touches there. Self::cast_ray tests the actual geometry (see crate::shape_raycast).

Source

pub fn combined_material( &self, body_a: usize, body_b: usize, ) -> CombinedMaterial

Get combined material properties for a body pair

§Claims
  • A body whose material is crate::material::DEFAULT_MATERIAL contributes its own RigidBody::friction and RigidBody::restitution (the default material is “no material assigned”); a body with any other material contributes that material’s dynamic_friction / restitution and its own fields are not read.
  • Bodies built with the defaults (friction 0.3, restitution 0.5) give the same result as before, since the default material holds the same values.
  • The two contributions are combined with the materials’ combine rules; a pair override registered for the two materials takes precedence over both.
  • An index outside the body list counts as the default material with the material’s own values.
Source

pub fn add_distance_constraint(&mut self, constraint: DistanceConstraint)

Add distance constraint

Source

pub fn add_contact(&mut self, contact: ContactConstraint)

Add contact constraint

Source

pub fn add_contact_with_material( &mut self, body_a: usize, body_b: usize, contact: Contact, )

Add contact constraint with material lookup

Automatically looks up friction/restitution from the material table.

Source

pub fn clear_contacts(&mut self)

Clear all contact constraints. step() does this at the start of every substep (1.2.0) before detect_collisions, so contacts added manually between frames are not seen by step(); drive the substep API yourself if you generate contacts outside the built-in sphere / SDF detection.

Source

pub fn rebuild_batches(&mut self)

Rebuild constraint batches using greedy graph coloring

Each batch contains constraints that share no bodies, allowing safe parallel modification.

Source

pub fn num_batches(&self) -> usize

Get number of constraint batches (colors used)

Source

pub fn step(&mut self, dt: Fix128)

Step simulation by dt (in fixed-point seconds)

Integrated pipeline:

  1. Begin event frame
  2. Apply force fields to body velocities
  3. Detect collisions (BVH broad-phase + sphere narrow-phase)
  4. Substep loop (joint motors, integrate, solve constraints + joints, update velocities)
  5. Update sleeping states
  6. End event frame (generates end-of-contact events)

Deterministic: same inputs always produce same outputs.

§Limitation: joints
  • step solves the joints once per substep, after and outside the iterations loop that solves distance and contact constraints.
  • Joint compliance is therefore exact XPBD with respect to substeps: each substep’s single solve starts from λ = 0, so a compliant joint under a constant load F settles at the stretch F · compliance whatever iterations is.
  • Convergence of a rigid joint chain depends on substeps only, not on iterations.
§Participants

step is Self::try_step with the result dropped: while a fault is recorded (Self::fault) it leaves the world unchanged. Without registered participants no fault is ever recorded and step runs as it always has.

Source

pub fn step_n(&mut self, n: usize, dt: Fix128)

Run Self::step n times with the same dt.

The native counterpart of the Python step_n and WASM stepN bindings, which call this: the result is bit-identical to calling step(dt) n times in a loop, n = 0 leaves the world unchanged, and a non-positive dt leaves it unchanged for every n (as step does).

§Examples
use alice_physics::{Fix128, PhysicsConfig, PhysicsWorld, RigidBody, Vec3Fix};

let mut a = PhysicsWorld::new(PhysicsConfig::default());
a.add_body(RigidBody::new_dynamic(Vec3Fix::from_int(0, 5, 0), Fix128::ONE));
let mut b = PhysicsWorld::from_world_snapshot(&a.snapshot_world()).unwrap();

let dt = Fix128::from_ratio(1, 60);
a.step_n(3, dt);
for _ in 0..3 {
    b.step(dt);
}
assert_eq!(a.bodies[0].position, b.bodies[0].position);
Source

pub fn tgs_cache_stats(&self) -> TgsCacheStats

Warm-start hit-rate diagnostics for the SolverBackend::Tgs path’s per-contact impulse cache (see Self::reset_tgs_cache_stats to isolate a specific measurement window, e.g. after warm-up frames).

Always hits: 0, misses: 0 while config.solver_backend is Xpbd — the internal step_tgs (the only writer of this cache) is never called on that path.

Source

pub fn reset_tgs_cache_stats(&mut self)

Clears the Self::tgs_cache_stats hit/miss counters without touching the cached impulses themselves — the same contract as the internal ImpulseCache::reset_stats. Useful when a host wants to measure hit rate over a specific window instead of the cache’s entire lifetime (e.g. after a scene’s warm-up frames).

Source

pub fn step_parallel(&mut self, dt: Fix128)

Step with batched constraint solving

When parallel feature is enabled, processes independent constraint batches in parallel using Rayon. Includes the same integrated pipeline as step(): force fields, collision detection, joints, events, sleeping.

§Determinism contract

step_parallel is deterministic — the same world stepped twice, on any number of threads and on every platform, produces the same bits (graph colouring is deterministic and constraints inside a batch touch disjoint bodies). It is not bit-identical to Self::step once two constraints share a body: the batches are a different Gauss–Seidel ordering than constraint index order, and Gauss–Seidel is order dependent. Lockstep / rollback peers must therefore all use the same path (fuzz/fuzz_targets/fuzz_step_parity.rs checks both properties).

§Solver backend

config.solver_backend is not consulted: this path always runs the XPBD substep loop, so with SolverBackend::Tgs it gives the same bits as with SolverBackend::Xpbd. A batched TGS path does not exist yet; use Self::step for TGS.

§Participants

step_parallel is Self::try_step_parallel with the result dropped.

Source

pub fn solve_contact_constraints_with_bridge<B: GpuSolverBridge + ?Sized>( &mut self, bridge: &mut B, )

v0.10.0 opt-in: run one PGS contact-solve iteration via a caller-supplied GpuSolverBridge instead of the CPU-side solve_contact_constraints hot loop.

§Claims
  • Each call runs the hooks and modifiers once, writes the result back to the constraints, then runs one bridge pass over the survivors.
  • substep_with_bridge runs the hooks and modifiers once per substep and then only the pass per iteration, so a relative modifier does not compound with iterations.
§Byte-exact CPU parity

The bridge-routed path applies Stage A filters (sensor skip, pre-solve hooks, contact modifiers) on the CPU side upfront exactly as solve_contact_constraints does inline. Because the closures the hooks and modifiers evaluate depend only on their function inputs — never on live body state that changes during the loop — a batched upfront application is byte-exact equivalent to the CPU’s per-constraint application. Stage B (depth ≤ 0 skip, w_sum < ε skip, warm-start biased lambda, cached_lambda write, position correction) is routed through the bridge, which runs the same sequential Gauss-Seidel semantics because bridge backends dispatch a single-workgroup single-thread compute kernel.

§Constraint contract
  • The bridge receives a filtered Vec<ContactConstraint> containing only the constraints that survived Stage A (sensor-free, hook-approved, modifier-approved). The contact / friction / restitution fields may be mutated from the original slot values if a contact modifier ran.
  • After readback the method writes each updated cached_lambda back into self.contact_constraints via an index map, mirroring the CPU write that lands at self.contact_constraints[i].cached_lambda = lambda at the corresponding slot.
  • Body positions are written back to self.bodies[i].position.
§Panics

Panics if the bridge implementation does not override the v0.9.0 contact-solve methods (send_contact_constraints, send_body_state, dispatch_contact_solve_iteration, recv_contact_constraints, recv_body_positions).

Source

pub fn step_with_bridge<B: GpuSolverBridge + ?Sized>( &mut self, bridge: &mut B, dt: Fix128, )

v0.10.0 opt-in: run one full simulation step with the contact-solve stage routed through a caller-supplied GpuSolverBridge. Semantically equivalent to Self::step with substep(_) replaced by Self::substep_with_bridge inside the substep loop.

§Byte-exact CPU parity

When the bridge implementation is byte-exact vs the CPU solve_contact_constraints, a full step_with_bridge produces self.bodies and self.contact_constraints state byte-identical to what a CPU-only step(dt) call produces from the same initial state and configuration.

§Solver backend

config.solver_backend is not consulted: the bridge replaces the contact solve of the XPBD substep, so with SolverBackend::Tgs this gives the same bits as with SolverBackend::Xpbd. A bridge path for TGS does not exist yet; use Self::step for TGS.

Source

pub fn substep_with_bridge<B: GpuSolverBridge + ?Sized>( &mut self, bridge: &mut B, dt: Fix128, )

v0.10.0 opt-in: run one substep with the contact-solve stage routed through a caller-supplied GpuSolverBridge. The integrate + distance-projection + joint + velocity-update stages stay on the CPU; only the inner PGS contact-solve iterations are dispatched through the bridge.

§Byte-exact CPU parity

When the bridge implementation is byte-exact vs the CPU solve_contact_constraints (which every ALICE-TRT TrtSolverAdapter release since v2.6.0 asserts on the 3-platform CI matrix), a full substep_with_bridge sequence produces self.bodies and self.contact_constraints state byte-identical to what a CPU-only substep(dt) call produces from the same initial state and configuration.

Called on its own, this runs no participant: the participants of crate::world_participant are called by the step loops (Self::step_with_bridge, Self::try_step) around each substep.

§Solver backend

config.solver_backend is not consulted: the bridge replaces the contact solve of the XPBD substep, so with SolverBackend::Tgs this gives the same bits as with SolverBackend::Xpbd. A bridge path for TGS does not exist yet; use Self::step for TGS.

Source

pub fn solve_joints_with_bridge<B: GpuSolverBridge + ?Sized>( &mut self, bridge: &mut B, dt: Fix128, )

v0.12.0: run one joint-solve pass with the joint stage routed through a caller-supplied GpuSolverBridge. Extracts body positions, rotations and inverse masses into the shape the bridge expects, uploads them via send_joints, send_body_state and send_body_rotations, dispatches dispatch_joint_solve_iteration(dt), reads back the post-solve positions via recv_body_positions, and writes them back into self.bodies[i].position.

§Contract
  • The bridge must implement the v0.12.0 joint-solve methods (send_joints, send_body_rotations, dispatch_joint_solve_iteration); the default panic! implementations surface backends that do not.
  • dt is the substep length used to compute compliance / (dt * dt) inside the kernel.
  • A joint whose two ends are the same body is not uploaded (the CPU solve skips it too, the body stays free); the remaining joints keep their order.
  • When no joint remains (self.joints is empty or holds only such joints), this method is a no-op and returns without touching the bridge — useful for callers that unconditionally route through a bridge and let this method decide whether there’s work to do.
§Byte-exact CPU parity

The CPU-side solve_joints runs each joint independently in index order; the bridge is required to reproduce that exact order and per-joint arithmetic. ALICE-TRT v3.1.0 satisfies this by dispatching a single-workgroup single-thread kernel that walks the uploaded joint list top to bottom.

Source

pub fn add_sdf_collider(&mut self, collider: SdfCollider) -> usize

Add an SDF collider to the world

A collider attached to a body (SdfCollider::new_dynamic) is placed at that body’s current pose when the body exists, and follows the body from then on: every step copies the body’s pose into it before SDF overlap is resolved and again at the end of the step.

The collider’s cached values are recomputed from its fields (SdfCollider::update_cache): a rotation or scale written to the collider directly before the call takes effect, and the rotation is stored as a unit quaternion.

Source

pub fn last_step_sdf_contacts(&self) -> &[SdfContact]

The SDF contacts resolved by the last step, every substep of it, in the order they were applied: by substep, then by body index, then by collider index.

Each entry is one push-out of a body out of an SDF collider (SdfContact): where, along which normal, how deep, and how fast the body was approaching, and the collider’s frame at that substep (a collider attached to a body moves with it during the step, so convert point with the recorded frame, not with the collider’s pose after the step). The record is emptied at the head of each step (Self::step, Self::try_step, step_parallel, step_with_bridge); a step that does not run (a recorded fault, a non-positive dt) leaves it as it was. substep_with_bridge called on its own appends to it. Recording changes nothing in the step: a world whose record is never read steps to the same bits. The buffer is reused from step to step, so it holds its largest size so far (one entry per body per collider at most).

Self::sdf_contacts is a different query: what the bodies overlap now, between steps, without moving anything.

The participants of crate::world_participant read the contacts recorded so far in the current step through SubstepCtx::sdf_contacts.

Source

pub fn remove_sdf_collider(&mut self, idx: usize) -> Option<SdfCollider>

Remove an SDF collider by index

Source

pub fn set_sdf_collision_radius(&mut self, radius: Fix128)

Set the collision radius for body-vs-SDF queries

Source

pub fn sdf_contacts(&self) -> Vec<(usize, Contact)>

The contacts of the bodies with the SDF colliders, as (body index, contact): what step would push each body out of, without moving anything. A body with a shape or a compound is tested as that collider, any other body as a sphere of sdf_collision_radius; static bodies, sensors and an SDF attached to the body itself are skipped. An SDF attached to a body is queried at the pose it was given when it was added or at the end of the last step; after moving a body by hand, call crate::sdf_collider::sync_dynamic_sdf_colliders on sdf_colliders first.

Source

pub fn contact_forces(&self, dt: Fix128) -> Vec<(Vec3Fix, Vec3Fix, Fix128)>

The normal force each contact of the last step carries, as (contact point, normal, force) — the input crate::contact_viz draws.

dt is the frame step passed to step. The solver applies a separation λ (cached_lambda) along the normal in the last substep, of length h = dt / substeps, which for bodies of inverse masses wₐ, w_b is the displacement of a force F over h: λ = (wₐ + w_b)·F·h², so F = λ / ((wₐ + w_b)·h²). A body resting under gravity then carries its weight: m·g. The normal points from B to A and the force acts on A. Contacts between two immovable bodies carry none and are left out. A body that has gone to sleep (about a minute of rest) is not solved, so its contacts are not there to report.

Source

pub fn contact_arrows(&self, dt: Fix128) -> Vec<ContactArrow>

Arrows for the normal forces of the last step’s contacts (contact_forces through crate::contact_viz::generate_contact_arrows).

Source

pub fn contact_friction_arrows(&self, dt: Fix128) -> Vec<ContactArrow>

Two tangential arrows of magnitude μ·F for each contact of the last step, with the friction coefficient of that contact (they differ between material pairs).

Source

pub fn contact_friction_cones(&self, dt: Fix128) -> Vec<FrictionCone>

A friction cone (half-angle atan μ, height the normal force) for each contact of the last step, with that contact’s friction coefficient.

Source

pub fn add_static_collider(&mut self, collider: StaticCollider) -> usize

Add an immovable collision surface — a plane, a height field or a triangle mesh — and return its index. See crate::static_collider.

Source

pub fn remove_static_collider(&mut self, idx: usize) -> Option<StaticCollider>

Remove a static collider by index, returning it, or None when the index is out of range. Later colliders shift down by one.

Source

pub fn static_collider_count(&self) -> usize

Number of static colliders.

Source

pub fn set_broadphase(&mut self, kind: Broadphase)

Select the broad-phase. Every kind gives bit-identical simulations (see Broadphase); switching drops the persistent tree and the Broadphase::Hybrid layers, which are rebuilt on the next step if that kind is chosen.

Source

pub fn broadphase(&self) -> Broadphase

The broad-phase in use.

Source

pub fn broadphase_stats(&self) -> BroadphaseStats

What the Broadphase::DynamicTree tree holds: how many bodies have a proxy and how tall the tree is. Both are 0 until a step has run with that broad-phase.

Source

pub fn broadphase_proxy_aabb(&self, body_idx: usize) -> Option<AABB>

The fattened box the Broadphase::DynamicTree tree keeps for a body, or None when the body has no proxy (no collision radius, or no step yet).

Source

pub fn get_body(&self, idx: usize) -> Option<&RigidBody>

Get body by index

Source

pub fn get_body_mut(&mut self, idx: usize) -> Option<&mut RigidBody>

Get mutable body by index

A rotation written through the returned reference is not normalized until the next step: a non-unit quaternion q scales what it rotates by |q|^2 in the queries made before then (see RigidBody::rotation).

Source

pub const fn overflow_detected(&self) -> bool

範囲外の積が起きた step があったか (sticky、WM-01 / doctrine B-12)

true は この world の軌道が信用できない ことを意味する doctrine §3 の 3 値で言えば undecided — 探索では「この枝は未決定」

§なぜ値ベースの検出器では足りないか

Fix128::mul の積が範囲外で 2 の冪だと hi が厳密に 0 になり (2⁴⁰ × 2⁴⁰)、position += velocity*dt が 0 加算 → update_velocities が速度も 0 に上書き → 「原点で完全に静止した自己整合な状態」 に落ちる ⚠️ 静止は物理的に妥当なのでどの不変条件でも red にならない

§sticky である理由

1 度立ったら step を重ねても落ちない 落ちると探索が 「未決定だった枝」を後の step で決定済と誤認する

⚠️ flag は状態の一部なので Self::serialize_state の被覆に 入れる必要がある (入れないと巻き戻した先で undecided が消える、 doctrine B-12 の指摘) — format v2 で収録済 (world ごと 1 byte、 Self::STATE_VERSION 参照)

Source

pub fn serialize_state(&self) -> Vec<u8> ⓘ

Serialize world state (for rollback netcode).

§被覆 (v1、2026-09-30)
範囲内容
[0..4)magic Self::STATE_MAGIC (b"APHY")
[4..6)version u16 = Self::STATE_VERSION
[6..8)reserved u16 = 0 (将来の flag 用、8 byte 境界揃え)
[8..12)body 数 u32
[12..)body ごとに 208 byte = position 48 + velocity 48 + rotation 64 + angular_velocity 48
続きbody ごとに 5 byte = SleepState u8 + idle_frames u32
続きworld ごと overflow flag 1 byte (v2)
続きworld ごと population fingerprint u64 (v3、Self::population_fingerprint)

⚠️ SleepState + idle_frames を v1 で被覆に入れた (WM-08) 旧 format は body の運動状態だけを持ち、deserialize_state 末尾の IslandManager::new が sleep 状態を 0 に戻していたため、 巻き戻した先で眠るタイミングがずれて軌道が割れた (tests/wm08_state_coverage.rs が対照実験付きで red を実測してから実装)

⚠️ magic と version を入れた理由: 旧 blob は先頭が body 数なので、 検査しないと新実装が旧 blob を誤って解釈する magic 不一致 / version 不一致は deserialize_state が false を返して明示的に拒否する

依然として保存 (= 復元) しないもの: constraints / joints / force fields / collision radii / filters / materials (rollback netcode では game state から毎 frame 再構築される前提) ⚠️ v3 の population fingerprint はこれらの値を検査にのみ使う (復元はしない) ので 「保存しない」契約自体は変わらない

⚠️ crate::netcode::SimulationChecksum は 本 blob の全 byte から 導出される (被覆の単一源化、B-13) ので、ここに state を足すと checksum も自動で追従する

Source

pub fn population_fingerprint(&self) -> u64

FNV-1a 64-bit hash of per-body quantities Self::serialize_state does not restore (shape/mass/inertia/collider/filter/material), in body index order.

§なぜこれが要るか (rollback body 数不一致、gap #3)

Self::deserialize_state は body 数の一致しか見ていない remove_body は swap_remove なので、count が元に戻っても index↔body の対応 (= population) が変わっていることがある — その場合 count 一致だけでは検出できず、別の body に誤った状態を 書き込んで silent に通る (fail-fast になっていない)

⚠️ 位置 / 速度 / 回転 / sleep を含めない — それらは blob 自身が 復元する量なので、ここに混ぜると「自分で組んだ量で自分を検査する」 循環になり population 不一致を検出できなくなる

Source

pub fn deserialize_state(&mut self, data: &[u8]) -> bool

Deserialize world state (for rollback netcode).

Restores per-body transforms. The rotations are restored as stored and brought to unit length by the next step, so a non-unit rotation in data scales what it rotates by |q|^2 in the queries made before that step (see RigidBody::rotation). Parallel arrays (collision radii, filters, materials, island manager) are resized to match the body count, preserving existing entries and zero-filling new ones.

§Rollback の契約 (gap #3、2026-10-02)

body 数が一致していても population (どの body がどんな inv_mass / inv_inertia / collider / filter / material を持つか) が一致していなければ拒否する (Self::population_fingerprint、v3) caller は body の生成・破棄 (add_body / remove_body) と joints / force fields / filters / collider radii / materials の変更を、目標 frame まで決定論的に replay してから本関数を呼ぶこと ⚠️ joints の再構築は本関数の呼び出し前に行う — 本関数の末尾で island を joints から作り直すため、順序が逆だと sleep の島が 異なる形になる

不一致 (false) の場合、self は一切変更されない (すべての検査は body 状態を書き込む前に完了する)

Trait Implementations§

Source§

impl Debug for PhysicsWorld

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.