khora_infra/physics/rapier/
mod.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Rapier implementation of the physics provider.
16
17mod conversions;
18mod debug;
19mod events;
20
21use khora_core::math::{Quat, Vec3};
22use khora_core::physics::{
23    BodyType, CharacterControllerOptions, ColliderDesc, ColliderHandle, ColliderShape,
24    CollisionEvent, PhysicsProvider, Ray, RaycastHit, RigidBodyDesc, RigidBodyHandle,
25};
26use rapier3d::control::*;
27use rapier3d::prelude::*;
28use std::sync::{Arc, Mutex};
29
30use conversions::*;
31use debug::*;
32use events::*;
33
34/// Implementation of the `PhysicsProvider` trait using the Rapier3D physics engine.
35pub struct RapierPhysicsWorld {
36    rigid_body_set: RigidBodySet,
37    collider_set: ColliderSet,
38    gravity: Vector,
39    integration_parameters: IntegrationParameters,
40    physics_pipeline: PhysicsPipeline,
41    island_manager: IslandManager,
42    broad_phase: BroadPhaseBvh,
43    narrow_phase: NarrowPhase,
44    impulse_joint_set: ImpulseJointSet,
45    multibody_joint_set: MultibodyJointSet,
46    ccd_solver: CCDSolver,
47    events: Arc<Mutex<Vec<CollisionEvent>>>,
48}
49
50impl Default for RapierPhysicsWorld {
51    fn default() -> Self {
52        Self {
53            rigid_body_set: RigidBodySet::new(),
54            collider_set: ColliderSet::new(),
55            gravity: Vector::new(0.0, -9.81, 0.0),
56            integration_parameters: IntegrationParameters::default(),
57            physics_pipeline: PhysicsPipeline::new(),
58            island_manager: IslandManager::new(),
59            broad_phase: BroadPhaseBvh::new(),
60            narrow_phase: NarrowPhase::new(),
61            impulse_joint_set: ImpulseJointSet::new(),
62            multibody_joint_set: MultibodyJointSet::new(),
63            ccd_solver: CCDSolver::new(),
64            events: Arc::new(Mutex::new(Vec::new())),
65        }
66    }
67}
68
69impl PhysicsProvider for RapierPhysicsWorld {
70    fn step(&mut self, dt: f32) {
71        self.integration_parameters.dt = dt;
72        let event_handler = RapierEventHandler {
73            events: self.events.clone(),
74        };
75
76        self.physics_pipeline.step(
77            self.gravity,
78            &self.integration_parameters,
79            &mut self.island_manager,
80            &mut self.broad_phase,
81            &mut self.narrow_phase,
82            &mut self.rigid_body_set,
83            &mut self.collider_set,
84            &mut self.impulse_joint_set,
85            &mut self.multibody_joint_set,
86            &mut self.ccd_solver,
87            &(),
88            &event_handler,
89        );
90    }
91
92    fn set_gravity(&mut self, gravity: Vec3) {
93        self.gravity = to_rapier_vec(gravity);
94    }
95
96    fn add_body(&mut self, desc: RigidBodyDesc) -> RigidBodyHandle {
97        let rb_type = match desc.body_type {
98            BodyType::Dynamic => RigidBodyType::Dynamic,
99            BodyType::Static => RigidBodyType::Fixed,
100            BodyType::Kinematic => RigidBodyType::KinematicVelocityBased,
101        };
102
103        let rigid_body = RigidBodyBuilder::new(rb_type)
104            .translation(to_rapier_vec(desc.position))
105            .rotation(to_rapier_quat(desc.rotation).to_scaled_axis())
106            .linvel(to_rapier_vec(desc.linear_velocity))
107            .angvel(to_rapier_vec(desc.angular_velocity))
108            .additional_mass(desc.mass)
109            .ccd_enabled(desc.ccd_enabled)
110            .build();
111
112        let handle = self.rigid_body_set.insert(rigid_body);
113        RigidBodyHandle(handle.into_raw_parts().0 as u64)
114    }
115
116    fn remove_body(&mut self, handle: RigidBodyHandle) {
117        let rb_handle = to_rapier_rb_handle(handle);
118        self.rigid_body_set.remove(
119            rb_handle,
120            &mut self.island_manager,
121            &mut self.collider_set,
122            &mut self.impulse_joint_set,
123            &mut self.multibody_joint_set,
124            true,
125        );
126    }
127
128    fn add_collider(&mut self, desc: ColliderDesc) -> ColliderHandle {
129        let shape = match desc.shape {
130            ColliderShape::Box(half) => SharedShape::cuboid(half.x, half.y, half.z),
131            ColliderShape::Sphere(r) => SharedShape::ball(r),
132            ColliderShape::Capsule(h, r) => SharedShape::capsule_y(h, r),
133        };
134
135        let collider = ColliderBuilder::new(shape)
136            .translation(to_rapier_vec(desc.position))
137            .rotation(to_rapier_quat(desc.rotation).to_scaled_axis())
138            .active_events(if desc.active_events {
139                ActiveEvents::COLLISION_EVENTS
140            } else {
141                ActiveEvents::empty()
142            })
143            .friction(desc.friction)
144            .restitution(desc.restitution)
145            .build();
146
147        let handle = if let Some(parent_handle) = desc.parent_body {
148            let rb_handle = to_rapier_rb_handle(parent_handle);
149            self.collider_set
150                .insert_with_parent(collider, rb_handle, &mut self.rigid_body_set)
151        } else {
152            self.collider_set.insert(collider)
153        };
154
155        ColliderHandle(handle.into_raw_parts().0 as u64)
156    }
157
158    fn remove_collider(&mut self, handle: ColliderHandle) {
159        let cl_handle = to_rapier_cl_handle(handle);
160        self.collider_set.remove(
161            cl_handle,
162            &mut self.island_manager,
163            &mut self.rigid_body_set,
164            true,
165        );
166    }
167
168    fn get_body_transform(&self, handle: RigidBodyHandle) -> (Vec3, Quat) {
169        let rb_handle = to_rapier_rb_handle(handle);
170        if let Some(rb) = self.rigid_body_set.get(rb_handle) {
171            let t = rb.translation();
172            let r = rb.rotation();
173            (from_rapier_vec(t), from_rapier_quat(*r))
174        } else {
175            (Vec3::ZERO, Quat::IDENTITY)
176        }
177    }
178
179    fn set_body_transform(&mut self, handle: RigidBodyHandle, pos: Vec3, rot: Quat) {
180        let rb_handle = to_rapier_rb_handle(handle);
181        if let Some(rb) = self.rigid_body_set.get_mut(rb_handle) {
182            rb.set_translation(to_rapier_vec(pos), true);
183            rb.set_rotation(to_rapier_quat(rot), true);
184        }
185    }
186
187    fn get_all_bodies(&self) -> Vec<RigidBodyHandle> {
188        self.rigid_body_set
189            .iter()
190            .map(|(handle, _)| RigidBodyHandle(handle.into_raw_parts().0 as u64))
191            .collect()
192    }
193
194    fn get_all_colliders(&self) -> Vec<ColliderHandle> {
195        self.collider_set
196            .iter()
197            .map(|(handle, _)| ColliderHandle(handle.into_raw_parts().0 as u64))
198            .collect()
199    }
200
201    fn update_body_properties(&mut self, handle: RigidBodyHandle, desc: RigidBodyDesc) {
202        let rb_handle = to_rapier_rb_handle(handle);
203        if let Some(rb) = self.rigid_body_set.get_mut(rb_handle) {
204            let rb_type = match desc.body_type {
205                BodyType::Dynamic => RigidBodyType::Dynamic,
206                BodyType::Static => RigidBodyType::Fixed,
207                BodyType::Kinematic => RigidBodyType::KinematicVelocityBased,
208            };
209            rb.set_body_type(rb_type, true);
210            rb.set_additional_mass(desc.mass, true);
211            rb.set_linvel(to_rapier_vec(desc.linear_velocity), true);
212            rb.set_angvel(to_rapier_vec(desc.angular_velocity), true);
213            rb.enable_ccd(desc.ccd_enabled);
214        }
215    }
216
217    fn update_collider_properties(&mut self, handle: ColliderHandle, desc: ColliderDesc) {
218        let cl_handle = to_rapier_cl_handle(handle);
219        if let Some(cl) = self.collider_set.get_mut(cl_handle) {
220            cl.set_translation(to_rapier_vec(desc.position));
221            cl.set_rotation(to_rapier_quat(desc.rotation));
222            cl.set_active_events(if desc.active_events {
223                ActiveEvents::COLLISION_EVENTS
224            } else {
225                ActiveEvents::empty()
226            });
227            cl.set_friction(desc.friction);
228            cl.set_restitution(desc.restitution);
229        }
230    }
231
232    fn get_debug_render_data(&self) -> (Vec<Vec3>, Vec<[u32; 2]>) {
233        let mut backend = RapierDebugBackend::default();
234        let mut pipeline = DebugRenderPipeline::default();
235        pipeline.render(
236            &mut backend,
237            &self.rigid_body_set,
238            &self.collider_set,
239            &self.impulse_joint_set,
240            &self.multibody_joint_set,
241            &self.narrow_phase,
242        );
243        (backend.vertices, backend.indices)
244    }
245
246    fn cast_ray(&self, ray: &Ray, max_toi: f32, solid: bool) -> Option<RaycastHit> {
247        let rapier_ray =
248            rapier3d::geometry::Ray::new(to_rapier_vec(ray.origin), to_rapier_vec(ray.direction));
249
250        let query_pipeline = self.broad_phase.as_query_pipeline(
251            self.narrow_phase.query_dispatcher(),
252            &self.rigid_body_set,
253            &self.collider_set,
254            QueryFilter::default(),
255        );
256
257        let (handle, intersection) =
258            query_pipeline.cast_ray_and_get_normal(&rapier_ray, max_toi, solid)?;
259
260        let hit_pos = rapier_ray.point_at(intersection.time_of_impact);
261
262        Some(RaycastHit {
263            collider: ColliderHandle(handle.into_raw_parts().0 as u64),
264            distance: intersection.time_of_impact,
265            normal: from_rapier_vec(intersection.normal),
266            position: from_rapier_vec(hit_pos),
267        })
268    }
269
270    fn get_collision_events(&self) -> Vec<CollisionEvent> {
271        let mut events = self.events.lock().unwrap();
272        std::mem::take(&mut *events)
273    }
274
275    fn move_character(
276        &self,
277        collider: ColliderHandle,
278        desired_translation: Vec3,
279        options: &CharacterControllerOptions,
280    ) -> (Vec3, bool) {
281        let cl_handle = to_rapier_cl_handle(collider);
282        if let Some(cl) = self.collider_set.get(cl_handle) {
283            let kcc = KinematicCharacterController {
284                offset: CharacterLength::Absolute(options.offset),
285                max_slope_climb_angle: options.max_slope_climb_angle,
286                min_slope_slide_angle: options.min_slope_slide_angle,
287                autostep: if options.autostep_enabled {
288                    Some(CharacterAutostep {
289                        max_height: CharacterLength::Absolute(options.autostep_height),
290                        min_width: CharacterLength::Absolute(options.autostep_min_width),
291                        include_dynamic_bodies: true,
292                    })
293                } else {
294                    None
295                },
296                ..Default::default()
297            };
298
299            let query_pipeline = self.broad_phase.as_query_pipeline(
300                self.narrow_phase.query_dispatcher(),
301                &self.rigid_body_set,
302                &self.collider_set,
303                QueryFilter::default().exclude_collider(cl_handle),
304            );
305
306            let result = kcc.move_shape(
307                self.integration_parameters.dt,
308                &query_pipeline,
309                cl.shape(),
310                cl.position(),
311                to_rapier_vec(desired_translation),
312                |_| {},
313            );
314
315            (from_rapier_vec(result.translation), result.grounded)
316        } else {
317            (Vec3::ZERO, false)
318        }
319    }
320}
321
322// --- Internal Helpers ---
323
324fn to_rapier_rb_handle(handle: RigidBodyHandle) -> rapier3d::dynamics::RigidBodyHandle {
325    rapier3d::dynamics::RigidBodyHandle::from_raw_parts(handle.0 as u32, 0)
326}
327
328fn to_rapier_cl_handle(handle: ColliderHandle) -> rapier3d::geometry::ColliderHandle {
329    rapier3d::geometry::ColliderHandle::from_raw_parts(handle.0 as u32, 0)
330}