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}