Skip to main content

box2d_rust/world/
mod.rs

1// Port of the world data model from box2d-cpp-reference/src/physics_world.h
2// (b2World, b2TaskContext) plus b2Profile from types.h. Logic from
3// physics_world.c lands across the remaining bring-up commits.
4//
5// Porting decisions:
6// - C keeps a global world registry (b2_worlds[B2_MAX_WORLDS], ids into it).
7//   The Rust `World` is an owned object instead; `world_id`/`generation` are
8//   kept so body/shape/joint ids remain bit-compatible with C. A registry
9//   would force global mutable state through every call; ownership is the
10//   sound Rust equivalent with identical observable behavior inside a world.
11// - The arena allocator (b2Stack) is per-step scratch for performance; the
12//   Rust solver allocates its scratch as Vecs in the step, so there is no
13//   stack field.
14// - Scheduler/task-system fields (enqueueTask, finishTask, userTaskContext,
15//   userTreeTask, scheduler, activeTaskCount, taskCount) are deferred with the
16//   single-threaded port; worker_count is kept and clamped like C.
17// - b2Recording is the snapshot/replay subsystem, ported later; no field yet.
18// - Pre-solve and custom-filter callbacks keep the C shape as Option<fn> with
19//   a u64 context.
20//
21// SPDX-FileCopyrightText: 2023 Erin Catto
22// SPDX-License-Identifier: MIT
23
24use crate::bitset::BitSet;
25use crate::body::Body;
26use crate::broad_phase::BroadPhase;
27use crate::constraint_graph::ConstraintGraph;
28use crate::contact::Contact;
29use crate::events::{
30    BodyMoveEvent, ContactBeginTouchEvent, ContactEndTouchEvent, ContactHitEvent, JointEvent,
31    SensorBeginTouchEvent, SensorEndTouchEvent,
32};
33use crate::id::ShapeId;
34use crate::id_pool::IdPool;
35use crate::island::Island;
36use crate::joint::Joint;
37use crate::math_functions::{Pos, Vec2};
38use crate::sensor::{Sensor, SensorHit, SensorTaskContext};
39use crate::shape::{ChainShape, Shape};
40use crate::solver_set::SolverSet;
41use crate::types::{Capacity, FrictionCallback, RestitutionCallback};
42
43/// Prototype for a contact filter callback. Called when a contact pair is
44/// considered for collision, if one of the two shapes has custom filtering
45/// enabled. Return false to disable the collision. (b2CustomFilterFcn)
46pub type CustomFilterFcn = fn(ShapeId, ShapeId, u64) -> bool;
47
48/// Prototype for a pre-solve callback. Called after a contact is updated, only
49/// for awake dynamic bodies with pre-solve events enabled. Return false to
50/// disable the contact this step. (b2PreSolveFcn)
51pub type PreSolveFcn = fn(ShapeId, ShapeId, Pos, Vec2, u64) -> bool;
52
53/// Profiling data. Times are in milliseconds. (b2Profile)
54#[derive(Debug, Clone, Copy, PartialEq, Default)]
55pub struct Profile {
56    pub step: f32,
57    pub pairs: f32,
58    pub collide: f32,
59    pub solve: f32,
60    pub solver_setup: f32,
61    pub constraints: f32,
62    pub prepare_constraints: f32,
63    pub integrate_velocities: f32,
64    pub warm_start: f32,
65    pub solve_impulses: f32,
66    pub integrate_positions: f32,
67    pub relax_impulses: f32,
68    pub apply_restitution: f32,
69    pub store_impulses: f32,
70    pub split_islands: f32,
71    pub transforms: f32,
72    pub sensor_hits: f32,
73    pub joint_events: f32,
74    pub hit_events: f32,
75    pub refit: f32,
76    pub bullets: f32,
77    pub sleep_islands: f32,
78    pub sensors: f32,
79}
80
81/// Per thread task storage. (b2TaskContext)
82#[derive(Debug, Clone, Default)]
83pub struct TaskContext {
84    /// Collect per thread sensor continuous hit events.
85    pub sensor_hits: Vec<SensorHit>,
86
87    /// These bits align with the contact id capacity and signal a change in
88    /// contact status.
89    pub contact_state_bit_set: BitSet,
90
91    /// These bits align with the contact id capacity and signal a hit event.
92    pub hit_event_bit_set: BitSet,
93
94    /// Fast-path flag: true when this worker set at least one bit in
95    /// hit_event_bit_set this step.
96    pub has_hit_events: bool,
97
98    /// These bits align with the joint id capacity and signal a change in
99    /// joint status.
100    pub joint_state_bit_set: BitSet,
101
102    /// Used to track bodies with shapes that have enlarged AABBs. This avoids
103    /// a bit array that is very large when there are many static shapes.
104    pub enlarged_sim_bit_set: BitSet,
105
106    /// Used to put islands to sleep.
107    pub awake_island_bit_set: BitSet,
108
109    /// Per worker split island candidate.
110    pub split_sleep_time: f32,
111    pub split_island_id: i32,
112
113    /// Number of contacts recycled this step (collide pass).
114    pub recycled_contact_count: i32,
115}
116
117/// The world struct manages all physics entities, dynamic simulation, and
118/// asynchronous queries. (b2World)
119#[derive(Debug)]
120pub struct World {
121    pub broad_phase: BroadPhase,
122    pub constraint_graph: ConstraintGraph,
123
124    /// The body id pool allocates and recycles body ids. Body ids provide a
125    /// stable identifier for users. Aligns with `bodies`.
126    pub body_id_pool: IdPool,
127
128    /// Sparse array mapping body ids to the body data stored in solver sets.
129    pub bodies: Vec<Body>,
130
131    /// Provides free list for solver sets.
132    pub solver_set_id_pool: IdPool,
133
134    /// Solver sets store sims in contiguous arrays. Set 0 is static, set 1 is
135    /// disabled, set 2 is awake; the rest are sleeping islands.
136    pub solver_sets: Vec<SolverSet>,
137
138    /// Used to create stable ids for joints.
139    pub joint_id_pool: IdPool,
140
141    /// Sparse array mapping joint ids to joints in the constraint graph or
142    /// solver sets.
143    pub joints: Vec<Joint>,
144
145    /// Used to create stable ids for contacts.
146    pub contact_id_pool: IdPool,
147
148    /// Sparse array mapping contact ids to contacts in the constraint graph
149    /// or solver sets.
150    pub contacts: Vec<Contact>,
151
152    /// Used to create stable ids for islands.
153    pub island_id_pool: IdPool,
154
155    /// Persistent islands.
156    pub islands: Vec<Island>,
157
158    pub shape_id_pool: IdPool,
159    pub chain_id_pool: IdPool,
160
161    /// Sparse arrays that point into the pools above.
162    pub shapes: Vec<Shape>,
163    pub chain_shapes: Vec<ChainShape>,
164
165    /// Dense array of sensor data.
166    pub sensors: Vec<Sensor>,
167
168    /// Per thread storage (one entry in the single-threaded port).
169    pub task_contexts: Vec<TaskContext>,
170    pub sensor_task_contexts: Vec<SensorTaskContext>,
171
172    pub body_move_events: Vec<BodyMoveEvent>,
173    pub sensor_begin_events: Vec<SensorBeginTouchEvent>,
174    pub contact_begin_events: Vec<ContactBeginTouchEvent>,
175
176    /// End events are double buffered so that the user doesn't need to flush
177    /// events.
178    pub sensor_end_events: [Vec<SensorEndTouchEvent>; 2],
179    pub contact_end_events: [Vec<ContactEndTouchEvent>; 2],
180    pub end_event_array_index: i32,
181
182    pub contact_hit_events: Vec<ContactHitEvent>,
183    pub joint_events: Vec<JointEvent>,
184
185    /// Used to track debug draw.
186    pub debug_body_set: BitSet,
187    pub debug_joint_set: BitSet,
188    pub debug_contact_set: BitSet,
189    pub debug_island_set: BitSet,
190
191    /// Id that is incremented every time step.
192    pub step_index: u64,
193
194    /// Identify islands for splitting.
195    pub split_island_id: i32,
196
197    pub gravity: Vec2,
198    pub hit_event_threshold: f32,
199    pub restitution_threshold: f32,
200    pub max_linear_speed: f32,
201    pub contact_speed: f32,
202    pub contact_hertz: f32,
203    pub contact_damping_ratio: f32,
204    pub contact_recycle_distance: f32,
205
206    pub friction_callback: Option<FrictionCallback>,
207    pub restitution_callback: Option<RestitutionCallback>,
208
209    pub generation: u16,
210
211    pub profile: Profile,
212
213    pub max_capacity: Capacity,
214
215    pub pre_solve_fcn: Option<PreSolveFcn>,
216    pub pre_solve_context: u64,
217
218    pub custom_filter_fcn: Option<CustomFilterFcn>,
219    pub custom_filter_context: u64,
220
221    pub worker_count: i32,
222
223    pub user_data: u64,
224
225    /// Inverse sub-step, remembered for reporting forces and torques.
226    pub inv_h: f32,
227
228    /// Inverse full-step.
229    pub inv_dt: f32,
230
231    pub world_id: u16,
232
233    pub enable_sleep: bool,
234    pub locked: bool,
235    pub enable_warm_starting: bool,
236    pub enable_contact_softening: bool,
237    pub enable_continuous: bool,
238    pub enable_speculative: bool,
239    pub in_use: bool,
240
241    /// Active recording session; owned by the world between
242    /// world_start_recording and world_stop_recording. (C: b2Recording*)
243    pub recording: Option<crate::recording::Recording>,
244}
245
246/// Default friction mixing: `sqrt(frictionA * frictionB)`.
247/// (static b2DefaultFrictionCallback)
248pub fn default_friction_callback(
249    friction_a: f32,
250    _material_a: u64,
251    friction_b: f32,
252    _material_b: u64,
253) -> f32 {
254    (friction_a * friction_b).sqrt()
255}
256
257/// Default restitution mixing: `max(restitutionA, restitutionB)`.
258/// (static b2DefaultRestitutionCallback)
259pub fn default_restitution_callback(
260    restitution_a: f32,
261    _material_a: u64,
262    restitution_b: f32,
263    _material_b: u64,
264) -> f32 {
265    crate::math_functions::max_float(restitution_a, restitution_b)
266}
267
268impl World {
269    /// Create a world. (b2CreateWorld)
270    ///
271    /// Differences from C, all documented in the module header: there is no
272    /// global world registry (the returned World is owned; `world_id` stays 0
273    /// unless the embedder assigns one), no arena stack, and the serial task
274    /// path is always used (worker_count = 1 with one task context), which is
275    /// the C fallback when no task system is supplied.
276    pub fn new(def: &crate::types::WorldDef) -> World {
277        use crate::constants::contact_recycle_distance;
278        use crate::constraint_graph::ConstraintGraph;
279        use crate::math_functions::max_int;
280
281        debug_assert!(def.internal_value == crate::core::SECRET_COOKIE);
282
283        let body_capacity = max_int(
284            16,
285            def.capacity.static_body_count + def.capacity.dynamic_body_count,
286        ) as usize;
287        let shape_capacity = max_int(
288            16,
289            def.capacity.static_shape_count + def.capacity.dynamic_shape_count,
290        ) as usize;
291        let contact_capacity = max_int(16, def.capacity.contact_count) as usize;
292
293        let mut solver_set_id_pool = IdPool::new();
294        let mut solver_sets: Vec<SolverSet> = Vec::with_capacity(8);
295
296        // add empty static, disabled, and awake body sets
297        // static set
298        let mut set = SolverSet {
299            set_index: solver_set_id_pool.alloc_id(),
300            ..Default::default()
301        };
302        set.body_sims
303            .reserve(max_int(16, def.capacity.static_body_count) as usize);
304        solver_sets.push(set);
305        debug_assert!(
306            solver_sets[crate::solver_set::STATIC_SET as usize].set_index
307                == crate::solver_set::STATIC_SET
308        );
309
310        // disabled set
311        solver_sets.push(SolverSet {
312            set_index: solver_set_id_pool.alloc_id(),
313            ..Default::default()
314        });
315        debug_assert!(
316            solver_sets[crate::solver_set::DISABLED_SET as usize].set_index
317                == crate::solver_set::DISABLED_SET
318        );
319
320        // awake set
321        let mut awake = SolverSet {
322            set_index: solver_set_id_pool.alloc_id(),
323            ..Default::default()
324        };
325        awake
326            .body_sims
327            .reserve(max_int(16, def.capacity.dynamic_body_count) as usize);
328        awake
329            .body_states
330            .reserve(max_int(16, def.capacity.dynamic_body_count) as usize);
331        awake.contact_sims.reserve(contact_capacity);
332        solver_sets.push(awake);
333        debug_assert!(
334            solver_sets[crate::solver_set::AWAKE_SET as usize].set_index
335                == crate::solver_set::AWAKE_SET
336        );
337
338        World {
339            broad_phase: BroadPhase::new(&def.capacity),
340            constraint_graph: ConstraintGraph::new(&def.capacity),
341            body_id_pool: IdPool::new(),
342            bodies: Vec::with_capacity(body_capacity),
343            solver_set_id_pool,
344            solver_sets,
345            joint_id_pool: IdPool::new(),
346            joints: Vec::with_capacity(16),
347            contact_id_pool: IdPool::new(),
348            contacts: Vec::with_capacity(contact_capacity),
349            island_id_pool: IdPool::new(),
350            islands: Vec::with_capacity(max_int(16, def.capacity.dynamic_body_count) as usize),
351            shape_id_pool: IdPool::new(),
352            chain_id_pool: IdPool::new(),
353            shapes: Vec::with_capacity(shape_capacity),
354            chain_shapes: Vec::with_capacity(4),
355            sensors: Vec::with_capacity(4),
356            // Serial fallback: one worker context. (b2CreateWorkerContexts)
357            task_contexts: vec![TaskContext::default()],
358            sensor_task_contexts: vec![SensorTaskContext::default()],
359            body_move_events: Vec::with_capacity(4),
360            sensor_begin_events: Vec::with_capacity(4),
361            contact_begin_events: Vec::with_capacity(4),
362            sensor_end_events: [Vec::with_capacity(4), Vec::with_capacity(4)],
363            contact_end_events: [Vec::with_capacity(4), Vec::with_capacity(4)],
364            end_event_array_index: 0,
365            contact_hit_events: Vec::with_capacity(4),
366            joint_events: Vec::with_capacity(4),
367            debug_body_set: BitSet::new(256),
368            debug_joint_set: BitSet::new(256),
369            debug_contact_set: BitSet::new(256),
370            debug_island_set: BitSet::new(256),
371            step_index: 0,
372            split_island_id: crate::core::NULL_INDEX,
373            gravity: def.gravity,
374            hit_event_threshold: def.hit_event_threshold,
375            restitution_threshold: def.restitution_threshold,
376            max_linear_speed: def.maximum_linear_speed,
377            contact_speed: def.contact_speed,
378            contact_hertz: def.contact_hertz,
379            contact_damping_ratio: def.contact_damping_ratio,
380            contact_recycle_distance: contact_recycle_distance(),
381            friction_callback: Some(def.friction_callback.unwrap_or(default_friction_callback)),
382            restitution_callback: Some(
383                def.restitution_callback
384                    .unwrap_or(default_restitution_callback),
385            ),
386            generation: 0,
387            profile: Profile::default(),
388            max_capacity: def.capacity,
389            pre_solve_fcn: None,
390            pre_solve_context: 0,
391            custom_filter_fcn: None,
392            custom_filter_context: 0,
393            worker_count: 1,
394            user_data: def.user_data,
395            inv_h: 0.0,
396            inv_dt: 0.0,
397            world_id: 0,
398            enable_sleep: def.enable_sleep,
399            locked: false,
400            enable_warm_starting: true,
401            enable_contact_softening: def.enable_contact_softening,
402            enable_continuous: def.enable_continuous,
403            enable_speculative: true,
404            in_use: true,
405            recording: None,
406        }
407    }
408
409    /// Validate the solver-set bookkeeping. (b2ValidateSolverSets)
410    ///
411    /// bring-up: the C version (physics_world.c, compiled only with
412    /// B2_ENABLE_VALIDATION) also cross-checks contacts, joints, and graph
413    /// colors; those checks are added as their slices land. This subset
414    /// validates the body <-> sim <-> set <-> island mapping.
415    pub fn validate_solver_sets(&self) {
416        // B2_VALIDATE: compiled out in release like the C reference
417        if !cfg!(debug_assertions) {
418            return;
419        }
420
421        use crate::core::NULL_INDEX;
422
423        let mut active_body_count = 0;
424        for (set_index, set) in self.solver_sets.iter().enumerate() {
425            if set.set_index == NULL_INDEX {
426                // free slot
427                debug_assert!(set.body_sims.is_empty());
428                debug_assert!(set.body_states.is_empty());
429                debug_assert!(set.island_sims.is_empty());
430                continue;
431            }
432
433            debug_assert!(set.set_index == set_index as i32);
434
435            if set_index == crate::solver_set::AWAKE_SET as usize {
436                debug_assert!(set.body_sims.len() == set.body_states.len());
437            } else {
438                debug_assert!(set.body_states.is_empty());
439            }
440
441            for (local_index, sim) in set.body_sims.iter().enumerate() {
442                let body = &self.bodies[sim.body_id as usize];
443                debug_assert!(body.set_index == set_index as i32);
444                debug_assert!(body.local_index == local_index as i32);
445                debug_assert!(body.id == sim.body_id);
446                let _ = (body, local_index);
447            }
448            active_body_count += set.body_sims.len() as i32;
449
450            for (local_index, island_sim) in set.island_sims.iter().enumerate() {
451                let island = &self.islands[island_sim.island_id as usize];
452                debug_assert!(island.set_index == set_index as i32);
453                debug_assert!(island.local_index == local_index as i32);
454                let _ = (island, local_index);
455            }
456        }
457
458        debug_assert!(active_body_count == self.body_id_pool.id_count());
459        let _ = active_body_count;
460    }
461}
462
463mod api;
464mod collide;
465mod draw;
466mod query;
467mod step;
468
469pub use api::*;
470pub use draw::*;
471pub use query::*;
472pub use step::*;