Skip to main content

rapier_viewport_plugin/
state.rs

1use indexmap::IndexMap;
2
3use rapier3d::dynamics::{
4    CCDSolver, ImpulseJointSet, IntegrationParameters, IslandManager, MultibodyJointSet, RigidBody,
5    RigidBodyHandle, RigidBodySet,
6};
7use rapier3d::geometry::{
8    BroadPhaseBvh, Collider, ColliderHandle, ColliderSet, NarrowPhase,
9};
10use rapier3d::math::Vector;
11use rapier3d::pipeline::PhysicsPipeline;
12use viewport_lib::NodeId;
13
14/// Shared world state: every rapier set the simulation needs, plus the
15/// `NodeId`-to-handle maps that keep scene identity in sync with rapier's
16/// handle namespace.
17///
18/// The four maps are [`IndexMap`]s (insertion-ordered) rather than
19/// [`HashMap`](std::collections::HashMap) so iteration order is the same
20/// on every run. The simulate plugin walks `node_to_body` to drive its
21/// writeback and event-translation passes, so this gives deterministic
22/// writeback and `RapierContactEvent` order within a single process.
23///
24/// For cross-platform floating-point determinism (different CPUs
25/// producing the same bits), enable rapier's `enhanced-determinism` Cargo
26/// feature on the `rapier3d` dependency. That is a compile-time feature,
27/// not a runtime switch.
28///
29/// Fields are public; reach in via [`crate::RapierPlugin::with_state_mut`]
30/// for anything not covered by the curated handle API.
31pub struct RapierState {
32    pub integration_params: IntegrationParameters,
33    pub pipeline: PhysicsPipeline,
34    pub islands: IslandManager,
35    pub broad_phase: BroadPhaseBvh,
36    pub narrow_phase: NarrowPhase,
37    pub bodies: RigidBodySet,
38    pub colliders: ColliderSet,
39    pub impulse_joints: ImpulseJointSet,
40    pub multibody_joints: MultibodyJointSet,
41    pub ccd_solver: CCDSolver,
42    pub gravity: Vector,
43
44    pub node_to_body: IndexMap<NodeId, RigidBodyHandle>,
45    pub body_to_node: IndexMap<RigidBodyHandle, NodeId>,
46    pub node_to_collider: IndexMap<NodeId, ColliderHandle>,
47    pub collider_to_node: IndexMap<ColliderHandle, NodeId>,
48}
49
50impl RapierState {
51    pub fn new(gravity: Vector) -> Self {
52        Self {
53            integration_params: IntegrationParameters::default(),
54            pipeline: PhysicsPipeline::new(),
55            islands: IslandManager::new(),
56            broad_phase: BroadPhaseBvh::new(),
57            narrow_phase: NarrowPhase::new(),
58            bodies: RigidBodySet::new(),
59            colliders: ColliderSet::new(),
60            impulse_joints: ImpulseJointSet::new(),
61            multibody_joints: MultibodyJointSet::new(),
62            ccd_solver: CCDSolver::new(),
63            gravity,
64            node_to_body: IndexMap::new(),
65            body_to_node: IndexMap::new(),
66            node_to_collider: IndexMap::new(),
67            collider_to_node: IndexMap::new(),
68        }
69    }
70
71    /// Insert a rigid body + collider pair and bind them to a scene node. The
72    /// body and collider should already be configured (use rapier's
73    /// `RigidBodyBuilder` / `ColliderBuilder`).
74    pub fn add_body(&mut self, node_id: NodeId, body: RigidBody, collider: Collider) {
75        let body_handle = self.bodies.insert(body);
76        let collider_handle =
77            self.colliders
78                .insert_with_parent(collider, body_handle, &mut self.bodies);
79        self.node_to_body.insert(node_id, body_handle);
80        self.body_to_node.insert(body_handle, node_id);
81        self.node_to_collider.insert(node_id, collider_handle);
82        self.collider_to_node.insert(collider_handle, node_id);
83    }
84
85    /// Remove the body bound to `node_id` (and its collider). No-op if the
86    /// node was never registered.
87    ///
88    /// Uses `shift_remove` on every map so the insertion order of the
89    /// remaining entries is preserved; this is what gives the simulate
90    /// plugin's writeback a deterministic order across runs.
91    pub fn remove_body(&mut self, node_id: NodeId) {
92        let Some(body_handle) = self.node_to_body.shift_remove(&node_id) else {
93            return;
94        };
95        self.body_to_node.shift_remove(&body_handle);
96
97        if let Some(collider_handle) = self.node_to_collider.shift_remove(&node_id) {
98            self.collider_to_node.shift_remove(&collider_handle);
99        }
100
101        self.bodies.remove(
102            body_handle,
103            &mut self.islands,
104            &mut self.colliders,
105            &mut self.impulse_joints,
106            &mut self.multibody_joints,
107            true,
108        );
109    }
110
111    /// Remove every registered body.
112    pub fn clear_bodies(&mut self) {
113        let ids: Vec<NodeId> = self.node_to_body.keys().copied().collect();
114        for id in ids {
115            self.remove_body(id);
116        }
117    }
118
119    /// `NodeId` bound to a collider handle, if any.
120    pub fn node_for_collider(&self, handle: ColliderHandle) -> Option<NodeId> {
121        self.collider_to_node.get(&handle).copied()
122    }
123
124    /// `NodeId` bound to a rigid body handle, if any.
125    pub fn node_for_body(&self, handle: RigidBodyHandle) -> Option<NodeId> {
126        self.body_to_node.get(&handle).copied()
127    }
128}