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