Skip to main content

nightshade/plugins/physics/
resources.rs

1use super::events::{CollisionEvent, CollisionEventCollector, ContactForceEvent};
2#[cfg(feature = "debug_render")]
3use crate::ecs::lines::components::Line;
4use nightshade_ecs::Entity;
5use rapier3d::prelude::*;
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum JointType {
10    Fixed,
11    Revolute,
12    Prismatic,
13    Spherical,
14    Rope,
15    Spring,
16}
17
18pub struct JointInfo {
19    pub parent_entity: Entity,
20    pub child_entity: Entity,
21    pub joint_type: JointType,
22    pub collisions_enabled: bool,
23    pub spring_rest_length: Option<f32>,
24    pub spring_stiffness: Option<f32>,
25    pub spring_damping: Option<f32>,
26}
27
28pub struct PendingJoint {
29    pub parent_entity: Entity,
30    pub child_entity: Entity,
31    pub(crate) joint: GenericJoint,
32    pub joint_type: JointType,
33    pub collisions_enabled: bool,
34    pub spring_rest_length: Option<f32>,
35    pub spring_stiffness: Option<f32>,
36    pub spring_damping: Option<f32>,
37}
38
39pub struct PhysicsWorld {
40    pub gravity: nalgebra_glm::Vec3,
41    pub(crate) integration_parameters: IntegrationParameters,
42    pub(crate) physics_pipeline: PhysicsPipeline,
43    pub(crate) island_manager: IslandManager,
44    pub(crate) broad_phase: BroadPhaseBvh,
45    pub(crate) narrow_phase: NarrowPhase,
46    pub(crate) impulse_joint_set: ImpulseJointSet,
47    pub(crate) multibody_joint_set: MultibodyJointSet,
48    pub(crate) ccd_solver: CCDSolver,
49    pub(crate) physics_hooks: (),
50    pub(crate) event_collector: CollisionEventCollector,
51    pub collision_events: Vec<CollisionEvent>,
52    pub contact_force_events: Vec<ContactForceEvent>,
53    pub(crate) rigid_body_set: RigidBodySet,
54    pub(crate) collider_set: ColliderSet,
55    pub fixed_timestep: f32,
56    pub accumulator: f32,
57    pub max_substeps: u32,
58    pub interpolation_alpha: f32,
59    pub debug_draw: bool,
60    pub debug_entity: Option<nightshade_ecs::Entity>,
61    pub debug_version: u64,
62    #[cfg(feature = "debug_render")]
63    pub(crate) debug_static_lines_cache: HashMap<ColliderHandle, Vec<Line>>,
64    pub debug_last_version: u64,
65    pub pending_joints: Vec<PendingJoint>,
66    pub(crate) joint_registry: HashMap<ImpulseJointHandle, JointInfo>,
67    pub(crate) handle_to_entity: HashMap<RigidBodyHandle, Entity>,
68    pub(crate) entity_to_handle: HashMap<Entity, RigidBodyHandle>,
69    pub enabled: bool,
70    /// Index of the physics member world in `world.ecs.worlds`, set the first
71    /// time a physics component is written. `None` until then.
72    pub component_world: Option<usize>,
73}
74
75impl Default for PhysicsWorld {
76    fn default() -> Self {
77        let integration_parameters = IntegrationParameters {
78            dt: 1.0 / 60.0,
79            ..Default::default()
80        };
81
82        Self {
83            gravity: nalgebra_glm::vec3(0.0, -9.81, 0.0),
84            integration_parameters,
85            physics_pipeline: PhysicsPipeline::new(),
86            island_manager: IslandManager::new(),
87            broad_phase: BroadPhaseBvh::new(),
88            narrow_phase: NarrowPhase::new(),
89            impulse_joint_set: ImpulseJointSet::new(),
90            multibody_joint_set: MultibodyJointSet::new(),
91            ccd_solver: CCDSolver::new(),
92            physics_hooks: (),
93            event_collector: CollisionEventCollector::new(),
94            collision_events: Vec::new(),
95            contact_force_events: Vec::new(),
96            rigid_body_set: RigidBodySet::new(),
97            collider_set: ColliderSet::new(),
98            fixed_timestep: 1.0 / 60.0,
99            accumulator: 0.0,
100            max_substeps: 4,
101            interpolation_alpha: 0.0,
102            debug_draw: false,
103            debug_entity: None,
104            debug_version: 0,
105            #[cfg(feature = "debug_render")]
106            debug_static_lines_cache: HashMap::new(),
107            debug_last_version: u64::MAX,
108            pending_joints: Vec::new(),
109            joint_registry: HashMap::new(),
110            handle_to_entity: HashMap::new(),
111            entity_to_handle: HashMap::new(),
112            enabled: false,
113            component_world: None,
114        }
115    }
116}
117
118pub fn physics_world_step(physics: &mut PhysicsWorld) {
119    let _span =
120        tracing::info_span!("physics_step", colliders = physics.collider_set.len()).entered();
121
122    physics.physics_pipeline.step(
123        &physics.gravity,
124        &physics.integration_parameters,
125        &mut physics.island_manager,
126        &mut physics.broad_phase,
127        &mut physics.narrow_phase,
128        &mut physics.rigid_body_set,
129        &mut physics.collider_set,
130        &mut physics.impulse_joint_set,
131        &mut physics.multibody_joint_set,
132        &mut physics.ccd_solver,
133        &physics.physics_hooks,
134        &physics.event_collector,
135    );
136}
137
138pub fn physics_world_collision_events(physics: &PhysicsWorld) -> &[CollisionEvent] {
139    &physics.collision_events
140}
141
142pub fn physics_world_contact_force_events(physics: &PhysicsWorld) -> &[ContactForceEvent] {
143    &physics.contact_force_events
144}
145
146pub fn physics_world_set_collision_events(physics: &mut PhysicsWorld, events: Vec<CollisionEvent>) {
147    physics.collision_events = events;
148}
149
150pub fn physics_world_set_contact_force_events(
151    physics: &mut PhysicsWorld,
152    events: Vec<ContactForceEvent>,
153) {
154    physics.contact_force_events = events;
155}
156
157pub fn physics_world_clear_events(physics: &mut PhysicsWorld) {
158    physics.collision_events.clear();
159    physics.contact_force_events.clear();
160}
161
162pub(crate) fn physics_world_query_pipeline(physics: &PhysicsWorld) -> QueryPipeline<'_> {
163    physics.broad_phase.as_query_pipeline(
164        physics.narrow_phase.query_dispatcher(),
165        &physics.rigid_body_set,
166        &physics.collider_set,
167        QueryFilter::default(),
168    )
169}
170
171pub fn physics_world_get_interpolation_alpha(physics: &PhysicsWorld) -> f32 {
172    physics.interpolation_alpha
173}
174
175pub(crate) fn physics_world_add_rigid_body(
176    physics: &mut PhysicsWorld,
177    rigid_body: RigidBody,
178) -> RigidBodyHandle {
179    physics.rigid_body_set.insert(rigid_body)
180}
181
182pub(crate) fn physics_world_add_collider(
183    physics: &mut PhysicsWorld,
184    collider: Collider,
185    parent_handle: RigidBodyHandle,
186) -> ColliderHandle {
187    physics.debug_version = physics.debug_version.wrapping_add(1);
188    physics
189        .collider_set
190        .insert_with_parent(collider, parent_handle, &mut physics.rigid_body_set)
191}
192
193pub(crate) fn physics_world_remove_rigid_body(physics: &mut PhysicsWorld, handle: RigidBodyHandle) {
194    physics.debug_version = physics.debug_version.wrapping_add(1);
195    if let Some(entity) = physics.handle_to_entity.remove(&handle) {
196        physics.entity_to_handle.remove(&entity);
197    }
198    physics.rigid_body_set.remove(
199        handle,
200        &mut physics.island_manager,
201        &mut physics.collider_set,
202        &mut physics.impulse_joint_set,
203        &mut physics.multibody_joint_set,
204        true,
205    );
206}
207
208pub(crate) fn physics_world_get_rigid_body_mut(
209    physics: &mut PhysicsWorld,
210    handle: RigidBodyHandle,
211) -> Option<&mut RigidBody> {
212    physics.rigid_body_set.get_mut(handle)
213}
214
215pub fn physics_world_cast_ray(
216    physics: &PhysicsWorld,
217    origin: nalgebra_glm::Vec3,
218    direction: nalgebra_glm::Vec3,
219    max_distance: f32,
220    exclude: Option<Entity>,
221) -> Option<super::types::RaycastHit> {
222    let mut filter = QueryFilter::default();
223    if let Some(entity) = exclude
224        && let Some(handle) = physics.entity_to_handle.get(&entity)
225    {
226        filter = filter.exclude_rigid_body(*handle);
227    }
228
229    let pipeline = physics.broad_phase.as_query_pipeline(
230        physics.narrow_phase.query_dispatcher(),
231        &physics.rigid_body_set,
232        &physics.collider_set,
233        filter,
234    );
235
236    let ray = Ray::new(
237        point![origin.x, origin.y, origin.z],
238        vector![direction.x, direction.y, direction.z],
239    );
240
241    let (collider_handle, intersection) =
242        pipeline.cast_ray_and_get_normal(&ray, max_distance, true)?;
243
244    let collider = physics.collider_set.get(collider_handle)?;
245    let parent_handle = collider.parent()?;
246    let entity = *physics.handle_to_entity.get(&parent_handle)?;
247
248    let point_world = ray.point_at(intersection.time_of_impact);
249    Some(super::types::RaycastHit {
250        entity,
251        distance: intersection.time_of_impact,
252        point: nalgebra_glm::vec3(point_world.x, point_world.y, point_world.z),
253        normal: nalgebra_glm::vec3(
254            intersection.normal.x,
255            intersection.normal.y,
256            intersection.normal.z,
257        ),
258    })
259}
260
261pub fn physics_world_overlap_sphere(
262    physics: &PhysicsWorld,
263    center: nalgebra_glm::Vec3,
264    radius: f32,
265    exclude: Option<Entity>,
266) -> Vec<Entity> {
267    let mut filter = QueryFilter::default();
268    if let Some(entity) = exclude
269        && let Some(handle) = physics.entity_to_handle.get(&entity)
270    {
271        filter = filter.exclude_rigid_body(*handle);
272    }
273
274    let pipeline = physics.broad_phase.as_query_pipeline(
275        physics.narrow_phase.query_dispatcher(),
276        &physics.rigid_body_set,
277        &physics.collider_set,
278        filter,
279    );
280
281    let shape = Ball::new(radius);
282    let pose = rapier3d::na::Isometry3::translation(center.x, center.y, center.z);
283
284    let mut hits = Vec::new();
285    for (collider_handle, _collider) in pipeline.intersect_shape(pose, &shape) {
286        if let Some(collider) = physics.collider_set.get(collider_handle)
287            && let Some(parent_handle) = collider.parent()
288            && let Some(&entity) = physics.handle_to_entity.get(&parent_handle)
289        {
290            hits.push(entity);
291        }
292    }
293    hits
294}
295
296/// Exact penetration contacts between a vertical capsule and the world.
297/// Pass `half_height` zero to query a sphere.
298pub fn physics_world_capsule_contacts(
299    physics: &PhysicsWorld,
300    center: nalgebra_glm::Vec3,
301    half_height: f32,
302    radius: f32,
303    exclude: Option<Entity>,
304) -> Vec<super::types::ShapeContact> {
305    let mut filter = QueryFilter::default();
306    if let Some(entity) = exclude
307        && let Some(handle) = physics.entity_to_handle.get(&entity)
308    {
309        filter = filter.exclude_rigid_body(*handle);
310    }
311
312    let pipeline = physics.broad_phase.as_query_pipeline(
313        physics.narrow_phase.query_dispatcher(),
314        &physics.rigid_body_set,
315        &physics.collider_set,
316        filter,
317    );
318
319    let shape = Capsule::new_y(half_height, radius);
320    let pose = rapier3d::na::Isometry3::translation(center.x, center.y, center.z);
321
322    let mut contacts = Vec::new();
323    for (_collider_handle, collider) in pipeline.intersect_shape(pose, &shape) {
324        let Ok(Some(contact)) = rapier3d::parry::query::contact(
325            &pose,
326            &shape,
327            collider.position(),
328            collider.shape(),
329            0.0,
330        ) else {
331            continue;
332        };
333        if contact.dist >= 0.0 {
334            continue;
335        }
336        let Some(parent_handle) = collider.parent() else {
337            continue;
338        };
339        let Some(&entity) = physics.handle_to_entity.get(&parent_handle) else {
340            continue;
341        };
342        contacts.push(super::types::ShapeContact {
343            entity,
344            point: nalgebra_glm::vec3(contact.point1.x, contact.point1.y, contact.point1.z),
345            normal: nalgebra_glm::vec3(-contact.normal1.x, -contact.normal1.y, -contact.normal1.z),
346            depth: -contact.dist,
347        });
348    }
349    contacts
350}
351
352pub fn physics_world_apply_impulse(
353    physics: &mut PhysicsWorld,
354    entity: Entity,
355    impulse: nalgebra_glm::Vec3,
356) {
357    let Some(handle) = physics.entity_to_handle.get(&entity).copied() else {
358        return;
359    };
360    let Some(rb) = physics.rigid_body_set.get_mut(handle) else {
361        return;
362    };
363    rb.apply_impulse(vector![impulse.x, impulse.y, impulse.z], true);
364}
365
366pub fn physics_world_apply_impulse_at_point(
367    physics: &mut PhysicsWorld,
368    entity: Entity,
369    impulse: nalgebra_glm::Vec3,
370    point: nalgebra_glm::Vec3,
371) {
372    let Some(handle) = physics.entity_to_handle.get(&entity).copied() else {
373        return;
374    };
375    let Some(rb) = physics.rigid_body_set.get_mut(handle) else {
376        return;
377    };
378    rb.apply_impulse_at_point(
379        vector![impulse.x, impulse.y, impulse.z],
380        point![point.x, point.y, point.z],
381        true,
382    );
383}
384
385pub fn physics_world_apply_force(
386    physics: &mut PhysicsWorld,
387    entity: Entity,
388    force: nalgebra_glm::Vec3,
389) {
390    let Some(handle) = physics.entity_to_handle.get(&entity).copied() else {
391        return;
392    };
393    let Some(rb) = physics.rigid_body_set.get_mut(handle) else {
394        return;
395    };
396    rb.add_force(vector![force.x, force.y, force.z], true);
397}
398
399pub fn physics_world_apply_torque(
400    physics: &mut PhysicsWorld,
401    entity: Entity,
402    torque: nalgebra_glm::Vec3,
403) {
404    let Some(handle) = physics.entity_to_handle.get(&entity).copied() else {
405        return;
406    };
407    let Some(rb) = physics.rigid_body_set.get_mut(handle) else {
408        return;
409    };
410    rb.add_torque(vector![torque.x, torque.y, torque.z], true);
411}
412
413pub fn physics_world_linear_velocity(
414    physics: &PhysicsWorld,
415    entity: Entity,
416) -> Option<nalgebra_glm::Vec3> {
417    let handle = physics.entity_to_handle.get(&entity)?;
418    let rb = physics.rigid_body_set.get(*handle)?;
419    let velocity = rb.linvel();
420    Some(nalgebra_glm::vec3(velocity.x, velocity.y, velocity.z))
421}
422
423pub fn physics_world_set_linear_velocity(
424    physics: &mut PhysicsWorld,
425    entity: Entity,
426    velocity: nalgebra_glm::Vec3,
427) {
428    let Some(handle) = physics.entity_to_handle.get(&entity).copied() else {
429        return;
430    };
431    let Some(rb) = physics.rigid_body_set.get_mut(handle) else {
432        return;
433    };
434    rb.set_linvel(vector![velocity.x, velocity.y, velocity.z], true);
435}
436
437/// Wakes every rigid body. Bodies created while the simulation was paused sit
438/// outside the active set until a step touches them, so the transform sync skips
439/// them and they appear frozen. Call this when the simulation resumes so every
440/// body integrates and writes back from the first frame.
441pub fn physics_world_wake_all(physics: &mut PhysicsWorld) {
442    for (_, rigid_body) in physics.rigid_body_set.iter_mut() {
443        rigid_body.wake_up(true);
444    }
445}
446
447pub fn physics_world_angular_velocity(
448    physics: &PhysicsWorld,
449    entity: Entity,
450) -> Option<nalgebra_glm::Vec3> {
451    let handle = physics.entity_to_handle.get(&entity)?;
452    let rb = physics.rigid_body_set.get(*handle)?;
453    let velocity = rb.angvel();
454    Some(nalgebra_glm::vec3(velocity.x, velocity.y, velocity.z))
455}
456
457pub fn physics_world_set_angular_velocity(
458    physics: &mut PhysicsWorld,
459    entity: Entity,
460    velocity: nalgebra_glm::Vec3,
461) {
462    let Some(handle) = physics.entity_to_handle.get(&entity).copied() else {
463        return;
464    };
465    let Some(rb) = physics.rigid_body_set.get_mut(handle) else {
466        return;
467    };
468    rb.set_angvel(vector![velocity.x, velocity.y, velocity.z], true);
469}
470
471pub fn physics_world_set_linear_damping(physics: &mut PhysicsWorld, entity: Entity, damping: f32) {
472    let Some(handle) = physics.entity_to_handle.get(&entity).copied() else {
473        return;
474    };
475    if let Some(rb) = physics.rigid_body_set.get_mut(handle) {
476        rb.set_linear_damping(damping);
477    }
478}
479
480pub fn physics_world_set_angular_damping(physics: &mut PhysicsWorld, entity: Entity, damping: f32) {
481    let Some(handle) = physics.entity_to_handle.get(&entity).copied() else {
482        return;
483    };
484    if let Some(rb) = physics.rigid_body_set.get_mut(handle) {
485        rb.set_angular_damping(damping);
486    }
487}
488
489pub fn physics_world_set_gravity_scale(physics: &mut PhysicsWorld, entity: Entity, scale: f32) {
490    let Some(handle) = physics.entity_to_handle.get(&entity).copied() else {
491        return;
492    };
493    if let Some(rb) = physics.rigid_body_set.get_mut(handle) {
494        rb.set_gravity_scale(scale, true);
495    }
496}
497
498pub fn physics_world_set_additional_mass(physics: &mut PhysicsWorld, entity: Entity, mass: f32) {
499    let Some(handle) = physics.entity_to_handle.get(&entity).copied() else {
500        return;
501    };
502    if let Some(rb) = physics.rigid_body_set.get_mut(handle) {
503        rb.set_additional_mass(mass, true);
504    }
505}
506
507pub fn physics_world_set_friction(physics: &mut PhysicsWorld, entity: Entity, friction: f32) {
508    let Some(handle) = physics.entity_to_handle.get(&entity).copied() else {
509        return;
510    };
511    let collider_handles: Vec<ColliderHandle> = physics
512        .rigid_body_set
513        .get(handle)
514        .map(|rb| rb.colliders().to_vec())
515        .unwrap_or_default();
516    for collider_handle in collider_handles {
517        if let Some(collider) = physics.collider_set.get_mut(collider_handle) {
518            collider.set_friction(friction);
519        }
520    }
521}
522
523pub fn physics_world_set_restitution(physics: &mut PhysicsWorld, entity: Entity, restitution: f32) {
524    let Some(handle) = physics.entity_to_handle.get(&entity).copied() else {
525        return;
526    };
527    let collider_handles: Vec<ColliderHandle> = physics
528        .rigid_body_set
529        .get(handle)
530        .map(|rb| rb.colliders().to_vec())
531        .unwrap_or_default();
532    for collider_handle in collider_handles {
533        if let Some(collider) = physics.collider_set.get_mut(collider_handle) {
534            collider.set_restitution(restitution);
535        }
536    }
537}
538
539pub fn physics_world_body_translation(
540    physics: &PhysicsWorld,
541    entity: Entity,
542) -> Option<nalgebra_glm::Vec3> {
543    let handle = physics.entity_to_handle.get(&entity)?;
544    let rb = physics.rigid_body_set.get(*handle)?;
545    let translation = rb.translation();
546    Some(nalgebra_glm::vec3(
547        translation.x,
548        translation.y,
549        translation.z,
550    ))
551}
552
553pub fn physics_world_body_rotation(
554    physics: &PhysicsWorld,
555    entity: Entity,
556) -> Option<nalgebra_glm::Quat> {
557    let handle = physics.entity_to_handle.get(&entity)?;
558    let rb = physics.rigid_body_set.get(*handle)?;
559    let rotation = rb.rotation();
560    Some(nalgebra_glm::quat(
561        rotation.i, rotation.j, rotation.k, rotation.w,
562    ))
563}