Skip to main content

box3d_rust/world/
mod.rs

1//! Simulation world: create bodies, shapes, and joints; step; query and cast.
2//!
3//! Start here with [`World::new`] and [`World::step`]. Body/shape/joint creation
4//! lives in [`crate::body`], [`crate::shape`], and [`crate::joint`]. Use
5//! [`crate::types::default_world_def`] (and the other `default_*_def` helpers)
6//! for C-compatible defaults.
7//!
8//! ```rust
9//! use box3d_rust::types::default_world_def;
10//! use box3d_rust::world::World;
11//!
12//! let mut world = World::new(&default_world_def());
13//! world.step(1.0 / 60.0, 1);
14//! ```
15//!
16//! Port of `physics_world.h` / `physics_world.c` (plus `b3Profile` from types.h).
17//!
18//! Porting decisions (mirroring box2d-rust):
19//! - C keeps a global world registry. The Rust [`World`] is an owned object;
20//!   `world_id`/`generation` are kept so body/shape/joint ids remain
21//!   bit-compatible with C.
22//! - The arena allocator (`b3Stack`) is per-step scratch; the Rust solver
23//!   allocates scratch as `Vec`s in the step, so there is no stack field.
24//! - Manifold block allocators become `Vec`-owned manifolds on contacts for now;
25//!   a pooled allocator can return later if profiling warrants it.
26//! - Scheduler/task-system fields are deferred with the single-threaded port;
27//!   `worker_count` is kept and clamped like C.
28//! - Hull database is a content-keyed `Rc` store (verstable map in C).
29//! - Pre-solve and custom-filter callbacks keep the C shape as `Option<fn>` with
30//!   a `u64` context.
31//!
32//! SPDX-FileCopyrightText: 2025 Erin Catto
33//! SPDX-License-Identifier: MIT
34
35mod api;
36mod draw;
37mod dump;
38mod query;
39mod step;
40mod validate;
41
42pub use api::*;
43pub use draw::*;
44pub use dump::*;
45pub use query::*;
46
47use crate::bitset::BitSet;
48use crate::body::Body;
49use crate::broad_phase::BroadPhase;
50use crate::constants::CONTACT_MANIFOLD_COUNT_BUCKETS;
51use crate::constraint_graph::ConstraintGraph;
52use crate::contact::Contact;
53use crate::debug_draw::{
54    CreateDebugShapeCallback, DebugLine, DebugPoint, DestroyDebugShapeCallback,
55    DEBUG_LINE_CAPACITY, DEBUG_POINT_CAPACITY,
56};
57use crate::events::{
58    BodyMoveEvent, ContactBeginTouchEvent, ContactEndTouchEvent, ContactHitEvent, JointEvent,
59    SensorBeginTouchEvent, SensorEndTouchEvent,
60};
61use crate::hull::HullDatabase;
62use crate::id::ShapeId;
63use crate::id_pool::IdPool;
64use crate::island::Island;
65use crate::joint::Joint;
66use crate::math_functions::Vec3;
67use crate::name_cache::NameCache;
68use crate::sensor::{Sensor, SensorHit, SensorTaskContext};
69use crate::shape::Shape;
70use crate::solver_set::SolverSet;
71use crate::types::{Capacity, FrictionCallback, RestitutionCallback};
72
73/// Prototype for a contact filter callback. (b3CustomFilterFcn)
74pub type CustomFilterFcn = fn(ShapeId, ShapeId, u64) -> bool;
75
76/// Prototype for a pre-solve callback. (b3PreSolveFcn)
77pub type PreSolveFcn =
78    fn(ShapeId, ShapeId, crate::math_functions::Pos, crate::math_functions::Vec3, u64) -> bool;
79
80/// Profiling data. Times are in milliseconds. (b3Profile)
81#[derive(Debug, Clone, Copy, PartialEq, Default)]
82pub struct Profile {
83    pub step: f32,
84    pub pairs: f32,
85    pub collide: f32,
86    pub solve: f32,
87    pub solver_setup: f32,
88    pub constraints: f32,
89    pub prepare_constraints: f32,
90    pub integrate_velocities: f32,
91    pub warm_start: f32,
92    pub solve_impulses: f32,
93    pub integrate_positions: f32,
94    pub relax_impulses: f32,
95    pub apply_restitution: f32,
96    pub store_impulses: f32,
97    pub split_islands: f32,
98    pub transforms: f32,
99    pub sensor_hits: f32,
100    pub joint_events: f32,
101    pub hit_events: f32,
102    pub refit: f32,
103    pub bullets: f32,
104    pub sleep_islands: f32,
105    pub sensors: f32,
106}
107
108/// Per thread task storage. (b3TaskContext)
109///
110/// Arena, debug draw buffers, and false-sharing padding from C are omitted;
111/// the serial port owns step scratch locally.
112#[derive(Debug, Clone)]
113pub struct TaskContext {
114    /// Collect per thread sensor continuous hit events.
115    pub sensor_hits: Vec<SensorHit>,
116
117    /// Align with contact id capacity; signal a change in contact status.
118    pub contact_state_bit_set: BitSet,
119
120    /// Align with joint id capacity; signal a change in joint status.
121    pub joint_state_bit_set: BitSet,
122
123    /// Align with contact id capacity; signal a hit event.
124    pub hit_event_bit_set: BitSet,
125
126    /// Fast-path flag: true when this worker set at least one bit in
127    /// hit_event_bit_set this step.
128    pub has_hit_events: bool,
129
130    /// Track bodies with shapes that have enlarged AABBs.
131    pub enlarged_sim_bit_set: BitSet,
132
133    /// Used to put islands to sleep.
134    pub awake_island_bit_set: BitSet,
135
136    /// Per worker split island candidate.
137    pub split_sleep_time: f32,
138    pub split_island_id: i32,
139
140    /// Profiling
141    pub sat_call_count: i32,
142    pub sat_cache_hit_count: i32,
143    pub distance_iterations: i32,
144    pub push_back_iterations: i32,
145    pub root_iterations: i32,
146
147    /// Number of contacts recycled this step (collide pass).
148    pub recycled_contact_count: i32,
149
150    pub manifold_counts: [i32; CONTACT_MANIFOLD_COUNT_BUCKETS],
151
152    /// Solver debug points drawn by `world_draw`. (`b3TaskContext::points`)
153    pub points: [DebugPoint; DEBUG_POINT_CAPACITY],
154    pub point_count: usize,
155
156    /// Solver debug lines drawn by `world_draw`. (`b3TaskContext::lines`)
157    pub lines: [DebugLine; DEBUG_LINE_CAPACITY],
158    pub line_count: usize,
159}
160
161impl Default for TaskContext {
162    fn default() -> Self {
163        TaskContext {
164            sensor_hits: Vec::new(),
165            contact_state_bit_set: BitSet::default(),
166            joint_state_bit_set: BitSet::default(),
167            hit_event_bit_set: BitSet::default(),
168            has_hit_events: false,
169            enlarged_sim_bit_set: BitSet::default(),
170            awake_island_bit_set: BitSet::default(),
171            split_sleep_time: 0.0,
172            split_island_id: 0,
173            sat_call_count: 0,
174            sat_cache_hit_count: 0,
175            distance_iterations: 0,
176            push_back_iterations: 0,
177            root_iterations: 0,
178            recycled_contact_count: 0,
179            manifold_counts: [0; CONTACT_MANIFOLD_COUNT_BUCKETS],
180            points: [DebugPoint::default(); DEBUG_POINT_CAPACITY],
181            point_count: 0,
182            lines: [DebugLine::default(); DEBUG_LINE_CAPACITY],
183            line_count: 0,
184        }
185    }
186}
187
188/// The world struct manages all physics entities, dynamic simulation, and
189/// asynchronous queries. (b3World)
190#[derive(Debug)]
191pub struct World {
192    pub broad_phase: BroadPhase,
193    pub constraint_graph: ConstraintGraph,
194
195    /// The body id pool allocates and recycles body ids. Aligns with `bodies`.
196    pub body_id_pool: IdPool,
197
198    /// Sparse array mapping body ids to body data stored in solver sets.
199    pub bodies: Vec<Body>,
200
201    /// Provides free list for solver sets.
202    pub solver_set_id_pool: IdPool,
203
204    /// Solver sets store sims in contiguous arrays. Set 0 is static, set 1 is
205    /// disabled, set 2 is awake; the rest are sleeping islands.
206    pub solver_sets: Vec<SolverSet>,
207
208    /// Used to create stable ids for joints.
209    pub joint_id_pool: IdPool,
210
211    /// Sparse array mapping joint ids to joints in the constraint graph or
212    /// solver sets.
213    pub joints: Vec<Joint>,
214
215    /// Used to create stable ids for contacts.
216    pub contact_id_pool: IdPool,
217
218    /// Sparse array mapping contact ids to contacts.
219    pub contacts: Vec<Contact>,
220
221    /// Used to create stable ids for islands.
222    pub island_id_pool: IdPool,
223
224    /// Persistent islands.
225    pub islands: Vec<Island>,
226
227    pub shape_id_pool: IdPool,
228
229    /// Sparse array of shapes.
230    pub shapes: Vec<Shape>,
231
232    /// Content-keyed shared hull store. (world->hullDatabase)
233    pub hull_database: HullDatabase,
234
235    /// Name cache for shape and body names.
236    pub names: NameCache,
237
238    /// Dense array of sensor data.
239    pub sensors: Vec<Sensor>,
240
241    /// Per thread storage (one entry in the single-threaded port).
242    pub task_contexts: Vec<TaskContext>,
243    pub sensor_task_contexts: Vec<SensorTaskContext>,
244
245    pub body_move_events: Vec<BodyMoveEvent>,
246    pub sensor_begin_events: Vec<SensorBeginTouchEvent>,
247    pub contact_begin_events: Vec<ContactBeginTouchEvent>,
248
249    /// End events are double buffered so the user doesn't need to flush events.
250    pub sensor_end_events: [Vec<SensorEndTouchEvent>; 2],
251    pub contact_end_events: [Vec<ContactEndTouchEvent>; 2],
252    pub end_event_array_index: i32,
253
254    pub contact_hit_events: Vec<ContactHitEvent>,
255    pub joint_events: Vec<JointEvent>,
256
257    /// Used to track debug draw.
258    pub debug_body_set: BitSet,
259    pub debug_joint_set: BitSet,
260    pub debug_contact_set: BitSet,
261    pub debug_island_set: BitSet,
262
263    /// Id that is incremented every time step.
264    pub step_index: u64,
265
266    /// Identify islands for splitting.
267    pub split_island_id: i32,
268
269    pub gravity: Vec3,
270    pub hit_event_threshold: f32,
271    pub restitution_threshold: f32,
272    pub max_linear_speed: f32,
273    pub contact_speed: f32,
274    pub contact_hertz: f32,
275    pub contact_damping_ratio: f32,
276    pub contact_recycle_distance: f32,
277
278    pub friction_callback: Option<FrictionCallback>,
279    pub restitution_callback: Option<RestitutionCallback>,
280
281    pub generation: u16,
282
283    pub profile: Profile,
284    pub sat_call_count: i32,
285    pub sat_cache_hit_count: i32,
286    pub manifold_counts: [i32; CONTACT_MANIFOLD_COUNT_BUCKETS],
287
288    pub max_capacity: Capacity,
289
290    pub pre_solve_fcn: Option<PreSolveFcn>,
291    pub pre_solve_context: u64,
292
293    pub custom_filter_fcn: Option<CustomFilterFcn>,
294    pub custom_filter_context: u64,
295
296    /// Create GPU/user shapes for debug draw. (`createDebugShape`)
297    pub create_debug_shape: Option<CreateDebugShapeCallback>,
298    /// Destroy GPU/user shapes. (`destroyDebugShape`)
299    pub destroy_debug_shape: Option<DestroyDebugShapeCallback>,
300    /// Context for the debug-shape callbacks. (`userDebugShapeContext`)
301    pub user_debug_shape_context: u64,
302
303    pub worker_count: i32,
304
305    pub user_data: u64,
306
307    /// Inverse sub-step, remembered for reporting forces and torques.
308    pub inv_h: f32,
309
310    /// Inverse full-step.
311    pub inv_dt: f32,
312
313    pub world_id: u16,
314
315    pub enable_sleep: bool,
316    pub locked: bool,
317    pub enable_warm_starting: bool,
318    pub enable_continuous: bool,
319    pub enable_speculative: bool,
320    pub in_use: bool,
321
322    /// Non-null while a recording session is active. Set by
323    /// [`crate::recording::start_recording`], cleared by
324    /// [`crate::recording::stop_recording`]. (world->recording)
325    pub recording: Option<*mut crate::recording::Recording>,
326}
327
328/// Default friction mixing: `sqrt(frictionA * frictionB)`.
329/// (static b3DefaultFrictionCallback — from types.c / physics_world.c)
330pub fn default_friction_callback(
331    friction_a: f32,
332    _material_a: u64,
333    friction_b: f32,
334    _material_b: u64,
335) -> f32 {
336    (friction_a * friction_b).sqrt()
337}
338
339/// Default restitution mixing: `max(restitutionA, restitutionB)`.
340pub fn default_restitution_callback(
341    restitution_a: f32,
342    _material_a: u64,
343    restitution_b: f32,
344    _material_b: u64,
345) -> f32 {
346    crate::math_functions::max_float(restitution_a, restitution_b)
347}
348
349impl World {
350    /// Create a world. (b3CreateWorld)
351    ///
352    /// Differences from C, all documented in the module header: there is no
353    /// global world registry (the returned World is owned; `world_id` stays 0
354    /// unless the embedder assigns one), no arena stack / manifold block
355    /// allocator yet, and the serial task path is always used
356    /// (worker_count = 1 with one task context), which is the C fallback when
357    /// no task system is supplied.
358    pub fn new(def: &crate::types::WorldDef) -> World {
359        use crate::constants::{
360            contact_recycle_distance, linear_slop, mesh_rest_offset, speculative_distance,
361        };
362        use crate::core::{NULL_INDEX, SECRET_COOKIE};
363        use crate::math_functions::max_int;
364        use crate::solver_set::{AWAKE_SET, DISABLED_SET, STATIC_SET};
365
366        debug_assert!(def.internal_value == SECRET_COOKIE);
367        debug_assert!(linear_slop() <= mesh_rest_offset());
368        debug_assert!(mesh_rest_offset() < speculative_distance());
369
370        use crate::contact::initialize_contact_registers;
371        initialize_contact_registers();
372
373        let body_capacity = max_int(
374            16,
375            def.capacity.static_body_count + def.capacity.dynamic_body_count,
376        ) as usize;
377        let shape_capacity = max_int(
378            16,
379            def.capacity.static_shape_count + def.capacity.dynamic_shape_count,
380        ) as usize;
381        let contact_capacity = max_int(16, def.capacity.contact_count) as usize;
382
383        let mut solver_set_id_pool = IdPool::new();
384        let mut solver_sets: Vec<SolverSet> = Vec::with_capacity(8);
385
386        // add empty static, disabled, and awake body sets
387        // static set
388        let mut set = SolverSet {
389            set_index: solver_set_id_pool.alloc_id(),
390            ..Default::default()
391        };
392        set.body_sims
393            .reserve(max_int(16, def.capacity.static_body_count) as usize);
394        solver_sets.push(set);
395        debug_assert!(solver_sets[STATIC_SET as usize].set_index == STATIC_SET);
396
397        // disabled set
398        solver_sets.push(SolverSet {
399            set_index: solver_set_id_pool.alloc_id(),
400            ..Default::default()
401        });
402        debug_assert!(solver_sets[DISABLED_SET as usize].set_index == DISABLED_SET);
403
404        // awake set
405        let mut awake = SolverSet {
406            set_index: solver_set_id_pool.alloc_id(),
407            ..Default::default()
408        };
409        awake
410            .body_sims
411            .reserve(max_int(16, def.capacity.dynamic_body_count) as usize);
412        awake
413            .body_states
414            .reserve(max_int(16, def.capacity.dynamic_body_count) as usize);
415        awake.contact_indices.reserve(contact_capacity);
416        solver_sets.push(awake);
417        debug_assert!(solver_sets[AWAKE_SET as usize].set_index == AWAKE_SET);
418
419        World {
420            broad_phase: BroadPhase::new(&def.capacity),
421            constraint_graph: ConstraintGraph::new(16),
422            body_id_pool: IdPool::new(),
423            bodies: Vec::with_capacity(body_capacity),
424            solver_set_id_pool,
425            solver_sets,
426            joint_id_pool: IdPool::new(),
427            joints: Vec::with_capacity(16),
428            contact_id_pool: IdPool::new(),
429            contacts: Vec::with_capacity(contact_capacity),
430            island_id_pool: IdPool::new(),
431            islands: Vec::with_capacity(max_int(16, def.capacity.dynamic_body_count) as usize),
432            shape_id_pool: IdPool::new(),
433            shapes: Vec::with_capacity(shape_capacity),
434            hull_database: HullDatabase::new(),
435            names: NameCache::new(),
436            sensors: Vec::with_capacity(4),
437            // Serial fallback: one worker context. (b3CreateWorkerContexts)
438            task_contexts: vec![TaskContext::default()],
439            sensor_task_contexts: vec![SensorTaskContext::default()],
440            body_move_events: Vec::with_capacity(4),
441            sensor_begin_events: Vec::with_capacity(4),
442            contact_begin_events: Vec::with_capacity(4),
443            sensor_end_events: [Vec::with_capacity(4), Vec::with_capacity(4)],
444            contact_end_events: [Vec::with_capacity(4), Vec::with_capacity(4)],
445            end_event_array_index: 0,
446            contact_hit_events: Vec::with_capacity(4),
447            joint_events: Vec::with_capacity(4),
448            debug_body_set: BitSet::new(256),
449            debug_joint_set: BitSet::new(256),
450            debug_contact_set: BitSet::new(256),
451            debug_island_set: BitSet::new(256),
452            step_index: 0,
453            split_island_id: NULL_INDEX,
454            gravity: def.gravity,
455            hit_event_threshold: def.hit_event_threshold,
456            restitution_threshold: def.restitution_threshold,
457            max_linear_speed: def.maximum_linear_speed,
458            contact_speed: def.contact_speed,
459            contact_hertz: def.contact_hertz,
460            contact_damping_ratio: def.contact_damping_ratio,
461            contact_recycle_distance: contact_recycle_distance(),
462            friction_callback: Some(def.friction_callback.unwrap_or(default_friction_callback)),
463            restitution_callback: Some(
464                def.restitution_callback
465                    .unwrap_or(default_restitution_callback),
466            ),
467            generation: 0,
468            profile: Profile::default(),
469            sat_call_count: 0,
470            sat_cache_hit_count: 0,
471            manifold_counts: [0; CONTACT_MANIFOLD_COUNT_BUCKETS],
472            max_capacity: def.capacity,
473            pre_solve_fcn: None,
474            pre_solve_context: 0,
475            custom_filter_fcn: None,
476            custom_filter_context: 0,
477            create_debug_shape: def.create_debug_shape,
478            destroy_debug_shape: def.destroy_debug_shape,
479            user_debug_shape_context: def.user_debug_shape_context,
480            worker_count: 1,
481            user_data: def.user_data,
482            inv_h: 0.0,
483            inv_dt: 0.0,
484            world_id: 0,
485            enable_sleep: def.enable_sleep,
486            locked: false,
487            enable_warm_starting: true,
488            enable_continuous: def.enable_continuous,
489            enable_speculative: true,
490            in_use: true,
491            recording: None,
492        }
493    }
494
495    /// (b3World_EnableContinuous)
496    pub fn enable_continuous(&mut self, flag: bool) {
497        debug_assert!(!self.locked);
498        if self.locked {
499            return;
500        }
501        self.enable_continuous = flag;
502    }
503
504    /// (b3World_IsContinuousEnabled)
505    pub fn is_continuous_enabled(&self) -> bool {
506        self.enable_continuous
507    }
508
509    /// Sensor begin/end events from the previous step. End events come from
510    /// the previous double-buffer slot. (b3World_GetSensorEvents)
511    pub fn get_sensor_events(&self) -> crate::events::SensorEvents<'_> {
512        let end_event_array_index = 1 - self.end_event_array_index;
513        crate::events::SensorEvents {
514            begin_events: &self.sensor_begin_events,
515            end_events: &self.sensor_end_events[end_event_array_index as usize],
516        }
517    }
518}