Skip to main content

rapier_viewport_plugin/
plugin.rs

1use std::sync::{Arc, Mutex, MutexGuard};
2
3use rapier3d::dynamics::{RigidBody, RigidBodyHandle};
4use rapier3d::geometry::{Collider, ColliderHandle, CollisionEvent};
5use rapier3d::math::Vector;
6use viewport_lib::runtime::plugin::phase;
7use viewport_lib::{NodeId, RuntimePlugin, RuntimeStepContext};
8
9use crate::events::{EventCollector, RapierContactEvent};
10use crate::state::RapierState;
11
12/// Acquire the state mutex, transparently recovering from poisoning.
13///
14/// A poisoned mutex means a previous holder panicked. `RapierState` is
15/// plain Rust values (rapier's sets, two `IndexMap`s, integration params,
16/// etc.) with no unsafe interior mutation, so taking the underlying state
17/// and continuing is safer in practice than cascading the panic into
18/// every future plugin call.
19#[inline]
20fn lock_state(state: &Mutex<RapierState>) -> MutexGuard<'_, RapierState> {
21    state.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
22}
23
24// ---------------------------------------------------------------------------
25// RapierPlugin: user-facing handle
26// ---------------------------------------------------------------------------
27
28/// User-facing handle to a Rapier physics simulation running inside viewport-lib.
29///
30/// Holds an `Arc<Mutex<RapierState>>` shared with the two `RuntimePlugin`
31/// implementors returned by [`into_plugins`](Self::into_plugins). Cloning the
32/// handle is cheap.
33///
34/// All methods on this handle take the state mutex. They block while the
35/// simulate plugin is running; keep handle calls off the hot path or batch
36/// them between frames.
37///
38/// **Mutex poisoning:** if a previous holder of the lock panicked (most
39/// commonly: a closure passed to `with_body_mut` / `with_state_mut`
40/// panicked), every subsequent lock acquisition transparently recovers
41/// the underlying state and continues. The plugin never panics on a
42/// poisoned mutex.
43///
44/// # Usage
45///
46/// ```rust,ignore
47/// use rapier_viewport_plugin::{RapierPlugin, rapier3d::prelude::*};
48///
49/// let mut rapier = RapierPlugin::new();
50///
51/// // Build bodies and colliders using rapier's own builders.
52/// let floor_body = RigidBodyBuilder::fixed().build();
53/// let floor_collider = ColliderBuilder::halfspace(Vector::z_axis()).build();
54/// rapier.add_body(floor_id, floor_body, floor_collider);
55///
56/// let box_body = RigidBodyBuilder::dynamic()
57///     .translation(glam::Vec3::new(0.0, 0.0, 5.0))
58///     .build();
59/// let box_collider = ColliderBuilder::cuboid(0.5, 0.5, 0.5)
60///     .restitution(0.4)
61///     .active_events(ActiveEvents::COLLISION_EVENTS)
62///     .build();
63/// rapier.add_body(box_id, box_body, box_collider);
64///
65/// let (prepare, simulate) = rapier.clone().into_plugins();
66/// ```
67///
68/// For anything not in the curated API (joints, locked axes, sensor flags,
69/// collision groups, the query pipeline, etc.) use the escape hatches:
70/// [`with_body_mut`](Self::with_body_mut), [`with_collider_mut`](Self::with_collider_mut),
71/// [`with_state_mut`](Self::with_state_mut), and [`with_state`](Self::with_state).
72#[derive(Clone)]
73pub struct RapierPlugin {
74    state: Arc<Mutex<RapierState>>,
75}
76
77impl Default for RapierPlugin {
78    fn default() -> Self {
79        Self::new()
80    }
81}
82
83impl RapierPlugin {
84    /// Create with Earth-like gravity (-9.81 m/s^2 on the Z axis, Z-up world).
85    pub fn new() -> Self {
86        Self {
87            state: Arc::new(Mutex::new(RapierState::new(Vector::new(0.0, 0.0, -9.81)))),
88        }
89    }
90
91    pub fn with_gravity(self, g: glam::Vec3) -> Self {
92        lock_state(&self.state).gravity = g;
93        self
94    }
95
96    // ------------------------------------------------------------------
97    // Body lifecycle
98    // ------------------------------------------------------------------
99
100    /// Register a body and its collider, binding both to a scene node.
101    ///
102    /// Construct `body` and `collider` with rapier's `RigidBodyBuilder` and
103    /// `ColliderBuilder`. Initial transform, mass, restitution, friction,
104    /// collision groups, sensor flag, CCD, locked axes, and every other
105    /// rapier property are set on the builders.
106    pub fn add_body(&mut self, node_id: NodeId, body: RigidBody, collider: Collider) {
107        lock_state(&self.state).add_body(node_id, body, collider);
108    }
109
110    /// Remove the body bound to `node_id`. No-op if the node was never registered.
111    pub fn remove_body(&mut self, node_id: NodeId) {
112        lock_state(&self.state).remove_body(node_id);
113    }
114
115    /// Remove every registered body and collider.
116    pub fn clear_bodies(&mut self) {
117        lock_state(&self.state).clear_bodies();
118    }
119
120    // ------------------------------------------------------------------
121    // Handle accessors + escape hatches
122    // ------------------------------------------------------------------
123
124    /// Look up the rapier [`RigidBodyHandle`] for a registered body.
125    pub fn body_handle(&self, node_id: NodeId) -> Option<RigidBodyHandle> {
126        lock_state(&self.state).node_to_body.get(&node_id).copied()
127    }
128
129    /// Look up the rapier [`ColliderHandle`] for a registered body.
130    pub fn collider_handle(&self, node_id: NodeId) -> Option<ColliderHandle> {
131        lock_state(&self.state).node_to_collider.get(&node_id).copied()
132    }
133
134    /// Run a closure with mutable access to a registered body's [`RigidBody`].
135    /// Returns `None` if the node has no body.
136    ///
137    /// ```rust,ignore
138    /// plugin.with_body_mut(id, |b| {
139    ///     b.apply_impulse(glam::Vec3::Z * 5.0, true);
140    ///     b.lock_translations(true, true);
141    /// });
142    /// ```
143    pub fn with_body_mut<R>(
144        &mut self,
145        node_id: NodeId,
146        f: impl FnOnce(&mut RigidBody) -> R,
147    ) -> Option<R> {
148        let mut state = lock_state(&self.state);
149        let handle = state.node_to_body.get(&node_id).copied()?;
150        let body = state.bodies.get_mut(handle)?;
151        Some(f(body))
152    }
153
154    /// Run a closure with mutable access to a registered body's [`Collider`].
155    /// Returns `None` if the node has no collider.
156    pub fn with_collider_mut<R>(
157        &mut self,
158        node_id: NodeId,
159        f: impl FnOnce(&mut Collider) -> R,
160    ) -> Option<R> {
161        let mut state = lock_state(&self.state);
162        let handle = state.node_to_collider.get(&node_id).copied()?;
163        let collider = state.colliders.get_mut(handle)?;
164        Some(f(collider))
165    }
166
167    /// Run a closure with mutable access to the entire [`RapierState`]. Use
168    /// this for anything the curated API does not cover: joints, spatial
169    /// queries against the broad phase, character controllers, etc.
170    ///
171    /// Note: the internal mutex is held for the duration of `f`. Do not call
172    /// back into other [`RapierPlugin`] methods from inside the closure -- that
173    /// will deadlock.
174    pub fn with_state_mut<R>(&mut self, f: impl FnOnce(&mut RapierState) -> R) -> R {
175        f(&mut lock_state(&self.state))
176    }
177
178    /// Read-only counterpart to [`with_state_mut`](Self::with_state_mut).
179    pub fn with_state<R>(&self, f: impl FnOnce(&RapierState) -> R) -> R {
180        f(&lock_state(&self.state))
181    }
182
183    // ------------------------------------------------------------------
184    // Plugin extraction
185    // ------------------------------------------------------------------
186
187    /// Split into the two `RuntimePlugin` objects the runtime needs. Bodies
188    /// can still be added or removed at runtime through the cloned handle
189    /// after this is called.
190    pub fn into_plugins(self) -> (RapierPreparePlugin, RapierSimulatePlugin) {
191        (
192            RapierPreparePlugin {
193                state: Arc::clone(&self.state),
194            },
195            RapierSimulatePlugin {
196                state: Arc::clone(&self.state),
197            },
198        )
199    }
200}
201
202// ---------------------------------------------------------------------------
203// Prepare plugin: sync scene -> rapier for fixed and kinematic bodies
204// ---------------------------------------------------------------------------
205
206pub struct RapierPreparePlugin {
207    state: Arc<Mutex<RapierState>>,
208}
209
210impl RuntimePlugin for RapierPreparePlugin {
211    fn priority(&self) -> i32 {
212        phase::PREPARE
213    }
214
215    fn step(&mut self, ctx: &mut RuntimeStepContext<'_>) {
216        let mut state = lock_state(&self.state);
217
218        // Snapshot (node, handle, is_kinematic) up front so we can mutate
219        // bodies in the loop without aliasing the map.
220        let synced: Vec<(NodeId, RigidBodyHandle, bool)> = state
221            .node_to_body
222            .iter()
223            .filter_map(|(&node_id, &handle)| {
224                let body = state.bodies.get(handle)?;
225                if body.is_kinematic() {
226                    Some((node_id, handle, true))
227                } else if body.is_fixed() {
228                    Some((node_id, handle, false))
229                } else {
230                    None
231                }
232            })
233            .collect();
234
235        for (node_id, handle, is_kinematic) in synced {
236            let Some(node) = ctx.scene.node(node_id) else {
237                continue;
238            };
239            let m = node.world_transform();
240            let (_, rotation, translation) = m.to_scale_rotation_translation();
241            let pose = rapier3d::math::Pose { rotation, translation };
242            if let Some(body) = state.bodies.get_mut(handle) {
243                if is_kinematic {
244                    body.set_next_kinematic_position(pose);
245                } else {
246                    body.set_position(pose, true);
247                }
248            }
249        }
250    }
251}
252
253// ---------------------------------------------------------------------------
254// Simulate plugin: step rapier, write back dynamic body transforms
255// ---------------------------------------------------------------------------
256
257pub struct RapierSimulatePlugin {
258    state: Arc<Mutex<RapierState>>,
259}
260
261impl RuntimePlugin for RapierSimulatePlugin {
262    fn priority(&self) -> i32 {
263        phase::SIMULATE
264    }
265
266    fn step(&mut self, ctx: &mut RuntimeStepContext<'_>) {
267        let dt = ctx.dt;
268        let mut state = lock_state(&self.state);
269
270        state.integration_params.dt = dt;
271
272        let collector = EventCollector::default();
273
274        // Destructure to satisfy the borrow checker: pipeline.step needs
275        // &mut pipeline plus independent borrows of every other field.
276        let RapierState {
277            ref mut pipeline,
278            ref gravity,
279            ref integration_params,
280            ref mut islands,
281            ref mut broad_phase,
282            ref mut narrow_phase,
283            ref mut bodies,
284            ref mut colliders,
285            ref mut impulse_joints,
286            ref mut multibody_joints,
287            ref mut ccd_solver,
288            ..
289        } = *state;
290
291        pipeline.step(
292            *gravity,
293            integration_params,
294            islands,
295            broad_phase,
296            narrow_phase,
297            bodies,
298            colliders,
299            impulse_joints,
300            multibody_joints,
301            ccd_solver,
302            &(),
303            &collector,
304        );
305
306        // Write dynamic body transforms back to the scene.
307        let dynamic_bodies: Vec<(NodeId, glam::Affine3A)> = state
308            .node_to_body
309            .iter()
310            .filter_map(|(&node_id, &handle)| {
311                let body = state.bodies.get(handle)?;
312                if body.is_dynamic() {
313                    let pose = body.position();
314                    let transform =
315                        glam::Affine3A::from_rotation_translation(pose.rotation, pose.translation);
316                    Some((node_id, transform))
317                } else {
318                    None
319                }
320            })
321            .collect();
322
323        for (node_id, transform) in dynamic_bodies {
324            ctx.writeback.set_preserve_scale(node_id, transform);
325        }
326
327        // Translate raw collision events to NodeId-based events.
328        let collisions = collector
329            .collisions
330            .into_inner()
331            .unwrap_or_else(|poisoned| poisoned.into_inner());
332        for event in collisions {
333            let (h1, h2, started) = match event {
334                CollisionEvent::Started(h1, h2, _) => (h1, h2, true),
335                CollisionEvent::Stopped(h1, h2, _) => (h1, h2, false),
336            };
337            let node_a = state.node_for_collider(h1);
338            let node_b = state.node_for_collider(h2);
339            if let (Some(node_a), Some(node_b)) = (node_a, node_b) {
340                ctx.output.events.emit(RapierContactEvent {
341                    node_a,
342                    node_b,
343                    started,
344                });
345            }
346        }
347    }
348}