rapier2d/pipeline/physics_pipeline/mod.rs
1//! Physics pipeline structures.
2
3use crate::alloc_prelude::*;
4
5use crate::counters::Counters;
6use crate::dynamics::{
7 CCDSolver, ImpulseJointSet, IntegrationParameters, IslandManager, MultibodyJointSet,
8 RigidBodySet, SoftBodySet,
9};
10use crate::geometry::{
11 BroadPhaseBvh, BroadPhasePairEvent, ColliderHandle, ColliderSet, ContactManifoldIndex,
12 NarrowPhase,
13};
14use crate::math::Vector;
15use crate::pipeline::{EventHandler, PhysicsHooks};
16
17mod quarantine;
18pub use quarantine::Quarantine;
19mod solve;
20mod substep;
21#[cfg(test)]
22mod test;
23#[cfg(test)]
24mod test_staged;
25
26/// The main physics simulation engine that runs your physics world forward in time.
27///
28/// Think of this as the "game loop" for your physics simulation. Each frame, you call
29/// [`PhysicsPipeline::step`] to advance the simulation by one timestep. This structure
30/// handles all the complex physics calculations: detecting collisions between objects,
31/// resolving contacts so objects don't overlap, and updating positions and velocities.
32///
33/// ## Performance note
34/// This structure only contains temporary working memory (workspace buffers). You can create
35/// a new one anytime, but it's more efficient to reuse the same instance across frames
36/// since Rapier can reuse allocated memory.
37///
38/// ## How it works (simplified)
39/// Rapier uses a time-stepping approach where each step involves:
40/// 1. **Collision detection**: Find which objects are touching or overlapping
41/// 2. **Constraint solving**: Calculate forces to prevent overlaps and enforce joint constraints
42/// 3. **Integration**: Update object positions and velocities based on forces and gravity
43/// 4. **Position correction**: Fix any remaining overlaps that might have occurred
44// NOTE: this contains only workspace data, so there is no point in making this serializable.
45pub struct PhysicsPipeline {
46 /// Counters used for benchmarking only.
47 pub counters: Counters,
48 joint_constraint_indices: Vec<ContactManifoldIndex>,
49 /// Whether [`Self::joint_constraint_indices`] has been filled by this pipeline yet.
50 /// The joint set memoizes its selection against the buffer the caller keeps, so a
51 /// pipeline that just came into existence must invalidate that memo before its first
52 /// selection — otherwise it reuses a buffer it never filled.
53 joint_selection_primed: bool,
54 broad_phase_events: Vec<BroadPhasePairEvent>,
55 /// Colliders moved by the last `advance_to_final_positions` with their fresh broad-phase
56 /// AABBs, fed to the broad-phase update without the user-modification tracking. AABBs are
57 /// computed inside the advance loop while body/collider are in cache.
58 end_step_collider_aabbs: Vec<(ColliderHandle, crate::geometry::Aabb)>,
59 /// Colliders the end-of-step soft-body sync deformed or moved to their cluster proxy's fresh
60 /// pose, whose broad-phase AABBs follow them.
61 soft_synced_colliders: Vec<ColliderHandle>,
62 /// Non-finite state detected and neutralized during the last step.
63 quarantine: Quarantine,
64 /// Workspace buffer holding the active body handles (parallel body update).
65 #[cfg(feature = "parallel")]
66 active_body_handles: Vec<crate::dynamics::RigidBodyHandle>,
67 /// Workspace: per-active-body sleep observations `(persistent island id, eligible)`,
68 /// run-length compressed by the fused traversal, consumed by `IslandManager::
69 /// update_islands`'s whole-island sleep decision — which never re-touches the body arena.
70 sleep_observations: Vec<(u32, bool)>,
71 /// The single, unified solver: the awake island is solved by its colored,
72 /// staged workers. On a non-parallel (or wasm) build it runs with one worker
73 /// inline on the calling thread.
74 staged_solver: crate::dynamics::StagedIslandSolver,
75 /// Handle on the BVH optimization pass running concurrently with the narrow
76 /// phase and solver (the `Mutex` only exists to keep the pipeline `Sync`; it is
77 /// never contended).
78 #[cfg(feature = "parallel")]
79 deferred_bvh:
80 std::sync::Mutex<Option<std::sync::mpsc::Receiver<crate::geometry::DeferredBvhOptimize>>>,
81 /// Deferred BVH optimization that had no spare worker to run on (single-threaded
82 /// pool, or `parallel` off): run inline by `join_deferred_bvh_optimize`, i.e. at
83 /// the same point of the step where the concurrent one is joined.
84 deferred_bvh_inline: Option<crate::geometry::DeferredBvhOptimize>,
85 /// Pool running the parallel parts of the step (see [`Self::configure_thread_pool`]).
86 /// `None` uses whichever pool the calling thread is in.
87 #[cfg(all(feature = "parallel", not(feature = "unsync-callbacks")))]
88 thread_pool: Option<std::sync::Arc<rayon::ThreadPool>>,
89}
90
91impl Default for PhysicsPipeline {
92 fn default() -> Self {
93 PhysicsPipeline::new()
94 }
95}
96
97#[allow(dead_code)]
98fn check_pipeline_send_sync() {
99 fn do_test<T: Sync>() {}
100 do_test::<PhysicsPipeline>();
101}
102
103impl PhysicsPipeline {
104 /// Creates a new physics pipeline.
105 ///
106 /// Call this once when setting up your physics world. The pipeline can be reused
107 /// across multiple frames for better performance.
108 pub fn new() -> PhysicsPipeline {
109 PhysicsPipeline {
110 counters: Counters::new(true),
111 #[cfg(feature = "parallel")]
112 active_body_handles: vec![],
113 sleep_observations: Vec::new(),
114 staged_solver: crate::dynamics::StagedIslandSolver::new(),
115 #[cfg(feature = "parallel")]
116 deferred_bvh: std::sync::Mutex::new(None),
117 deferred_bvh_inline: None,
118 #[cfg(all(feature = "parallel", not(feature = "unsync-callbacks")))]
119 thread_pool: None,
120 joint_constraint_indices: vec![],
121 joint_selection_primed: false,
122 broad_phase_events: vec![],
123 end_step_collider_aabbs: vec![],
124 soft_synced_colliders: vec![],
125 quarantine: Quarantine::default(),
126 }
127 }
128
129 /// Completes the BVH optimization pass deferred by the last broad-phase update (if
130 /// any) and puts the optimized tree back into the broad-phase. Must be called before
131 /// anything uses the broad-phase tree again.
132 ///
133 /// Waits for the concurrent pass when one was spawned; otherwise runs it here. Both
134 /// paths leave the same tree behind, so the build and the pool size don't change what
135 /// the rest of the step sees.
136 fn join_deferred_bvh_optimize(&mut self, broad_phase: &mut BroadPhaseBvh) {
137 #[cfg(feature = "parallel")]
138 if let Some(rx) = self.deferred_bvh.get_mut().unwrap().take() {
139 let task = rx.recv().expect("the deferred BVH optimization task died");
140 broad_phase.finish_deferred_optimize(task);
141 return;
142 }
143
144 if let Some(mut task) = self.deferred_bvh_inline.take() {
145 task.run();
146 broad_phase.finish_deferred_optimize(task);
147 }
148 }
149
150 /// Advances the physics simulation by one timestep.
151 ///
152 /// This is the main function you'll call every frame in your game loop. It performs all
153 /// physics calculations: collision detection, constraint solving, and updating object positions.
154 ///
155 /// # Parameters
156 ///
157 /// * `gravity` - The gravity vector applied to all dynamic bodies (e.g., `vector![0.0, -9.81, 0.0]` for Earth gravity pointing down)
158 /// * `integration_parameters` - Controls the simulation quality and timestep size (typically 60 Hz = 1/60 second per step)
159 /// * `islands` - Internal system that groups connected objects together for efficient solving (automatically managed)
160 /// * `broad_phase` - Fast collision detection phase that filters out distant object pairs (automatically managed)
161 /// * `narrow_phase` - Precise collision detection that computes exact contact points (automatically managed)
162 /// * `bodies` - Your collection of rigid bodies (the physical objects that move and collide)
163 /// * `colliders` - The collision shapes attached to your bodies (boxes, spheres, meshes, etc.)
164 /// * `impulse_joints` - Regular joints connecting bodies (hinges, sliders, etc.)
165 /// * `multibody_joints` - Articulated joints for robot-like structures (optional, can be empty)
166 /// * `soft_bodies` - Deformable particle bodies (optional, can be empty)
167 /// * `ccd_solver` - Continuous collision detection to prevent fast objects from tunneling through thin walls
168 /// * `hooks` - Optional callbacks to customize collision filtering and contact modification
169 /// * `events` - Optional handler to receive collision events (when objects start/stop touching)
170 ///
171 /// A step with a zero [`IntegrationParameters::dt`] (e.g. the first frame of a variable
172 /// timestep) doesn't simulate anything: it only applies the user changes and updates the
173 /// collision detection.
174 ///
175 /// # Example
176 ///
177 /// ```
178 /// # use rapier3d::prelude::*;
179 /// # let mut bodies = RigidBodySet::new();
180 /// # let mut colliders = ColliderSet::new();
181 /// # let mut impulse_joints = ImpulseJointSet::new();
182 /// # let mut multibody_joints = MultibodyJointSet::new();
183 /// # let mut soft_bodies = SoftBodySet::new();
184 /// # let mut islands = IslandManager::new();
185 /// # let mut broad_phase = BroadPhaseBvh::new();
186 /// # let mut narrow_phase = NarrowPhase::new();
187 /// # let mut ccd_solver = CCDSolver::new();
188 /// # let mut physics_pipeline = PhysicsPipeline::new();
189 /// # let integration_parameters = IntegrationParameters::default();
190 /// // In your game loop:
191 /// physics_pipeline.step(
192 /// Vector::new(0.0, -9.81, 0.0), // Gravity pointing down
193 /// &integration_parameters,
194 /// &mut islands,
195 /// &mut broad_phase,
196 /// &mut narrow_phase,
197 /// &mut bodies,
198 /// &mut colliders,
199 /// &mut impulse_joints,
200 /// &mut multibody_joints,
201 /// &mut soft_bodies,
202 /// &mut ccd_solver,
203 /// &(), // No custom hooks
204 /// &(), // No event handler
205 /// );
206 /// ```
207 pub fn step(
208 &mut self,
209 gravity: Vector,
210 integration_parameters: &IntegrationParameters,
211 islands: &mut IslandManager,
212 broad_phase: &mut BroadPhaseBvh,
213 narrow_phase: &mut NarrowPhase,
214 bodies: &mut RigidBodySet,
215 colliders: &mut ColliderSet,
216 impulse_joints: &mut ImpulseJointSet,
217 multibody_joints: &mut MultibodyJointSet,
218 soft_bodies: &mut SoftBodySet,
219 ccd_solver: &mut CCDSolver,
220 hooks: &dyn PhysicsHooks,
221 events: &dyn EventHandler,
222 ) {
223 // With a dedicated pool configured, run the whole step inside it.
224 #[cfg(all(feature = "parallel", not(feature = "unsync-callbacks")))]
225 if let Some(pool) = self.thread_pool.clone() {
226 return pool.install(|| {
227 self.step_inner(
228 gravity,
229 integration_parameters,
230 islands,
231 broad_phase,
232 narrow_phase,
233 bodies,
234 colliders,
235 impulse_joints,
236 multibody_joints,
237 soft_bodies,
238 ccd_solver,
239 hooks,
240 events,
241 )
242 });
243 }
244
245 self.step_inner(
246 gravity,
247 integration_parameters,
248 islands,
249 broad_phase,
250 narrow_phase,
251 bodies,
252 colliders,
253 impulse_joints,
254 multibody_joints,
255 soft_bodies,
256 ccd_solver,
257 hooks,
258 events,
259 )
260 }
261}
262
263#[cfg(all(feature = "parallel", not(feature = "unsync-callbacks")))]
264impl PhysicsPipeline {
265 /// Configures a dedicated thread pool for this pipeline's parallel work (default:
266 /// whichever pool the calling thread is in — usually the global one or the one
267 /// setup with `ThreadPool::install`.
268 pub fn configure_thread_pool(
269 &mut self,
270 num_threads: usize,
271 ) -> Result<(), rayon::ThreadPoolBuildError> {
272 let builder = rayon::ThreadPoolBuilder::new()
273 .num_threads(num_threads)
274 .thread_name(|i| alloc::format!("rapier-worker-{i}"));
275
276 self.thread_pool = Some(std::sync::Arc::new(builder.build()?));
277 Ok(())
278 }
279
280 /// The thread-pool used by this physics pipeline, if it was configured.
281 pub fn thread_pool(&self) -> Option<std::sync::Arc<rayon::ThreadPool>> {
282 self.thread_pool.clone()
283 }
284
285 /// Sets (or clears) the thread pool running this pipeline's parallel work.
286 ///
287 /// Unlike [`Self::configure_thread_pool`], this takes an existing pool.
288 pub fn set_thread_pool(&mut self, pool: Option<std::sync::Arc<rayon::ThreadPool>>) {
289 self.thread_pool = pool;
290 }
291
292 /// Removes the dedicated thread pool: the parallel parts of the step run on whichever
293 /// pool the calling thread is in again.
294 pub fn clear_thread_pool(&mut self) {
295 self.thread_pool = None;
296 }
297
298 /// The number of workers this pipeline's parallel work runs on: the size of its
299 /// dedicated thread pool, or of the pool the calling thread is in if it has none.
300 pub fn num_threads(&self) -> Option<usize> {
301 self.thread_pool
302 .as_ref()
303 .map(|pool| pool.current_num_threads())
304 }
305}