Skip to main content

pfx_physics/rigid/
world.rs

1use rapier3d::parry::mass_properties::MassProperties;
2use rapier3d::prelude::{
3    ActiveCollisionTypes, ActiveEvents, ActiveHooks, BroadPhaseBvh, ColliderBuilder,
4    ColliderHandle, CollisionEvent, CollisionEventFlags, ContactModificationContext, LockedAxes,
5    PhysicsHooks, PhysicsWorld, RigidBodyBuilder, RigidBodyHandle,
6};
7
8use super::convert::{
9    Iso, Quat, Vec3, body_handle, body_id, collider_handle, collider_id, iso, lerp_iso, pose, vec,
10};
11use super::event::Collector;
12use super::gravity::{SourceId, acceleration};
13use super::{
14    BodyDesc, BodyId, BodyState, ColliderDesc, ColliderId, Combine, Drive, Event, Friction, Kind,
15    Locks, Pose, Source,
16};
17
18#[derive(Clone, Copy, Debug, PartialEq)]
19pub struct WorldDesc {
20    pub rate: f32,
21    pub substeps: u32,
22    pub iterations: u32,
23    pub gravity: [f32; 3],
24    pub ccd: bool,
25}
26
27impl Default for WorldDesc {
28    fn default() -> Self {
29        Self {
30            rate: 60.0,
31            substeps: 1,
32            iterations: 4,
33            gravity: [0.0, -9.81, 0.0],
34            ccd: true,
35        }
36    }
37}
38
39struct Extra {
40    generation: u32,
41    kind: Kind,
42    locks: Locks,
43    anchor: Iso,
44    force: Vec3,
45    torque: Vec3,
46    drive: Option<Drive>,
47    target: Option<Iso>,
48    mass_given: bool,
49}
50
51#[derive(Clone, Copy)]
52struct Surface {
53    generation: u32,
54    friction: Friction,
55}
56
57pub struct World {
58    pub(super) physics: PhysicsWorld,
59    pub(super) queries: BroadPhaseBvh,
60    rate: f32,
61    substeps: u32,
62    extras: Vec<Option<Extra>>,
63    surfaces: Vec<Option<Surface>>,
64    sources: Vec<(SourceId, Source)>,
65    next_source: u32,
66    forced: Vec<RigidBodyHandle>,
67    collector: Collector,
68    events: Vec<Event>,
69    steps: u64,
70}
71
72impl Default for World {
73    fn default() -> Self {
74        Self::new(WorldDesc::default())
75    }
76}
77
78impl World {
79    pub fn new(desc: WorldDesc) -> Self {
80        let rate = if desc.rate.is_finite() && desc.rate > 0.0 {
81            desc.rate
82        } else {
83            60.0
84        };
85        let substeps = desc.substeps.max(1);
86        let mut physics = PhysicsWorld::new();
87        physics.gravity = vec(desc.gravity);
88        physics.integration_parameters.dt = 1.0 / rate / substeps as f32;
89        physics.integration_parameters.num_solver_iterations = desc.iterations.max(1) as usize;
90        physics.integration_parameters.max_ccd_substeps = usize::from(desc.ccd);
91        Self {
92            physics,
93            queries: BroadPhaseBvh::new(),
94            rate,
95            substeps,
96            extras: Vec::new(),
97            surfaces: Vec::new(),
98            sources: Vec::new(),
99            next_source: 0,
100            forced: Vec::new(),
101            collector: Collector::default(),
102            events: Vec::new(),
103            steps: 0,
104        }
105    }
106
107    pub fn rate(&self) -> f32 {
108        self.rate
109    }
110
111    pub fn substeps(&self) -> u32 {
112        self.substeps
113    }
114
115    pub fn dt(&self) -> f32 {
116        1.0 / self.rate
117    }
118
119    pub fn steps(&self) -> u64 {
120        self.steps
121    }
122
123    pub fn time(&self) -> f64 {
124        self.steps as f64 / f64::from(self.rate)
125    }
126
127    pub fn gravity(&self) -> [f32; 3] {
128        self.physics.gravity.to_array()
129    }
130
131    pub fn set_gravity(&mut self, gravity: [f32; 3]) {
132        self.physics.gravity = vec(gravity);
133        self.wake_all();
134    }
135
136    pub fn gravity_at(&self, point: [f32; 3]) -> [f32; 3] {
137        let world = self.physics.gravity;
138        if self.sources.is_empty() {
139            return world.to_array();
140        }
141        (world + acceleration(&self.sources, world, vec(point))).to_array()
142    }
143
144    pub(super) fn planned(&self, body: BodyId) -> Option<Iso> {
145        let extra = self.extra(body)?;
146        if extra.kind != Kind::Kinematic {
147            return None;
148        }
149        let from = *self.physics.bodies.get(body_handle(body))?.position();
150        let to = match (extra.drive, extra.target) {
151            (_, Some(target)) => target,
152            (Some(mut drive), None) => drive.advance(1.0 / self.rate, &from),
153            (None, None) => return Some(from),
154        };
155        Some(locked(extra.locks, &extra.anchor, &to))
156    }
157
158    pub fn events(&self) -> &[Event] {
159        &self.events
160    }
161
162    pub fn add_source(&mut self, source: Source) -> SourceId {
163        let id = SourceId(self.next_source);
164        self.next_source += 1;
165        self.sources.push((id, source));
166        self.wake_all();
167        id
168    }
169
170    pub fn remove_source(&mut self, id: SourceId) -> bool {
171        let before = self.sources.len();
172        self.sources.retain(|(s, _)| *s != id);
173        self.sources.len() != before
174    }
175
176    pub fn source_mut(&mut self, id: SourceId) -> Option<&mut Source> {
177        self.sources
178            .iter_mut()
179            .find(|(s, _)| *s == id)
180            .map(|(_, source)| source)
181    }
182
183    pub fn add_body(&mut self, desc: BodyDesc) -> BodyId {
184        let start = iso(desc.pose);
185        let builder = match desc.kind {
186            Kind::Dynamic => RigidBodyBuilder::dynamic(),
187            Kind::Kinematic => RigidBodyBuilder::kinematic_position_based(),
188            Kind::Fixed => RigidBodyBuilder::fixed(),
189        };
190        let mut builder = builder
191            .pose(start)
192            .linvel(masked(vec(desc.linear_velocity), desc.locks.translation))
193            .angvel(masked(vec(desc.angular_velocity), desc.locks.rotation))
194            .gravity_scale(desc.gravity_scale)
195            .linear_damping(desc.linear_damping)
196            .angular_damping(desc.angular_damping)
197            .locked_axes(locked_axes(desc.locks))
198            .ccd_enabled(desc.ccd)
199            .can_sleep(desc.can_sleep)
200            .sleeping(desc.asleep);
201        if let Some(mass) = desc.mass {
202            builder = builder.additional_mass_properties(MassProperties::new(
203                vec(mass.center),
204                mass.mass,
205                vec(mass.inertia),
206            ));
207        }
208        let handle = self.physics.bodies.insert(builder);
209        let (index, generation) = handle.into_raw_parts();
210        let slot = index as usize;
211        if self.extras.len() <= slot {
212            self.extras.resize_with(slot + 1, || None);
213        }
214        self.extras[slot] = Some(Extra {
215            generation,
216            kind: desc.kind,
217            locks: desc.locks,
218            anchor: start,
219            force: Vec3::ZERO,
220            torque: Vec3::ZERO,
221            drive: None,
222            target: None,
223            mass_given: desc.mass.is_some(),
224        });
225        body_id(handle)
226    }
227
228    pub fn add_collider(&mut self, body: BodyId, desc: ColliderDesc) -> Option<ColliderId> {
229        let mass_given = self.extra(body)?.mass_given;
230        let shape = desc.shape.shared()?;
231        let handle = body_handle(body);
232        let mut events = ActiveEvents::COLLISION_EVENTS;
233        let mut builder = ColliderBuilder::new(shape)
234            .position(iso(desc.offset))
235            .density(if mass_given { 0.0 } else { desc.density })
236            .friction(desc.friction.sliding)
237            .friction_combine_rule(desc.friction.combine.rule())
238            .restitution(desc.restitution)
239            .restitution_combine_rule(desc.restitution_combine.rule())
240            .sensor(desc.sensor)
241            .collision_groups(desc.layers.groups());
242        if desc.sensor {
243            builder = builder.active_collision_types(ActiveCollisionTypes::all());
244        }
245        if desc.friction.still != desc.friction.sliding {
246            builder = builder.active_hooks(ActiveHooks::MODIFY_SOLVER_CONTACTS);
247        }
248        if let Some(impulse) = desc.impulse_threshold {
249            events |= ActiveEvents::CONTACT_FORCE_EVENTS;
250            builder = builder
251                .contact_force_event_threshold(impulse / self.physics.integration_parameters.dt);
252        }
253        let builder = builder.active_events(events);
254        let physics = &mut self.physics;
255        let collider = physics
256            .colliders
257            .insert_with_parent(builder, handle, &mut physics.bodies);
258        if let Some(rb) = physics.bodies.get_mut(handle) {
259            rb.recompute_mass_properties_from_colliders(&physics.colliders);
260        }
261        let (index, generation) = collider.into_raw_parts();
262        let slot = index as usize;
263        if self.surfaces.len() <= slot {
264            self.surfaces.resize_with(slot + 1, || None);
265        }
266        self.surfaces[slot] = Some(Surface {
267            generation,
268            friction: desc.friction,
269        });
270        Some(collider_id(collider))
271    }
272
273    pub fn remove_body(&mut self, body: BodyId) -> bool {
274        let handle = body_handle(body);
275        let Some(rb) = self.physics.bodies.get(handle) else {
276            return false;
277        };
278        for collider in rb.colliders().to_vec() {
279            self.forget_surface(collider);
280        }
281        self.physics.remove_body(handle);
282        self.extras[body.index as usize] = None;
283        self.forced.retain(|h| *h != handle);
284        true
285    }
286
287    pub fn remove_collider(&mut self, collider: ColliderId) -> bool {
288        let handle = collider_handle(collider);
289        let removed = self.physics.remove_collider(handle).is_some();
290        if removed {
291            self.forget_surface(handle);
292        }
293        removed
294    }
295
296    pub fn bodies(&self) -> Vec<BodyId> {
297        self.extras
298            .iter()
299            .enumerate()
300            .filter_map(|(index, extra)| {
301                extra.as_ref().map(|e| BodyId {
302                    index: index as u32,
303                    generation: e.generation,
304                })
305            })
306            .collect()
307    }
308
309    pub fn body(&self, body: BodyId) -> Option<BodyState> {
310        let rb = self.physics.bodies.get(body_handle(body))?;
311        Some(BodyState {
312            pose: pose(rb.position()),
313            linear_velocity: rb.linvel().to_array(),
314            angular_velocity: rb.angvel().to_array(),
315            asleep: rb.is_sleeping(),
316        })
317    }
318
319    pub fn pose(&self, body: BodyId) -> Option<Pose> {
320        self.physics
321            .bodies
322            .get(body_handle(body))
323            .map(|rb| pose(rb.position()))
324    }
325
326    pub fn mass(&self, body: BodyId) -> Option<f32> {
327        self.physics
328            .bodies
329            .get(body_handle(body))
330            .map(|rb| rb.mass())
331    }
332
333    pub fn body_of(&self, collider: ColliderId) -> Option<BodyId> {
334        self.physics
335            .colliders
336            .get(collider_handle(collider))?
337            .parent()
338            .map(body_id)
339    }
340
341    pub fn is_asleep(&self, body: BodyId) -> bool {
342        self.physics
343            .bodies
344            .get(body_handle(body))
345            .is_some_and(|rb| rb.is_sleeping())
346    }
347
348    pub fn set_pose(&mut self, body: BodyId, to: Pose) -> bool {
349        let to = iso(to);
350        let Some(extra) = self.extra_mut(body) else {
351            return false;
352        };
353        extra.anchor = to;
354        extra.target = None;
355        let Some(rb) = self.physics.bodies.get_mut(body_handle(body)) else {
356            return false;
357        };
358        rb.set_position(to, true);
359        true
360    }
361
362    pub fn set_velocity(&mut self, body: BodyId, linear: [f32; 3], angular: [f32; 3]) -> bool {
363        let Some(locks) = self.extra(body).map(|e| e.locks) else {
364            return false;
365        };
366        let Some(rb) = self.physics.bodies.get_mut(body_handle(body)) else {
367            return false;
368        };
369        rb.set_linvel(masked(vec(linear), locks.translation), true);
370        rb.set_angvel(masked(vec(angular), locks.rotation), true);
371        true
372    }
373
374    pub fn apply_impulse(&mut self, body: BodyId, impulse: [f32; 3]) -> bool {
375        self.with_body(body, |rb| rb.apply_impulse(vec(impulse), true))
376    }
377
378    pub fn apply_impulse_at(&mut self, body: BodyId, impulse: [f32; 3], point: [f32; 3]) -> bool {
379        self.with_body(body, |rb| {
380            rb.apply_impulse_at_point(vec(impulse), vec(point), true)
381        })
382    }
383
384    pub fn apply_torque_impulse(&mut self, body: BodyId, impulse: [f32; 3]) -> bool {
385        self.with_body(body, |rb| rb.apply_torque_impulse(vec(impulse), true))
386    }
387
388    pub fn add_force(&mut self, body: BodyId, force: [f32; 3]) -> bool {
389        let Some(extra) = self.extra_mut(body) else {
390            return false;
391        };
392        extra.force += vec(force);
393        true
394    }
395
396    pub fn add_force_at(&mut self, body: BodyId, force: [f32; 3], point: [f32; 3]) -> bool {
397        let Some(center) = self
398            .physics
399            .bodies
400            .get(body_handle(body))
401            .map(|rb| rb.center_of_mass())
402        else {
403            return false;
404        };
405        let Some(extra) = self.extra_mut(body) else {
406            return false;
407        };
408        let force = vec(force);
409        extra.force += force;
410        extra.torque += (vec(point) - center).cross(force);
411        true
412    }
413
414    pub fn add_torque(&mut self, body: BodyId, torque: [f32; 3]) -> bool {
415        let Some(extra) = self.extra_mut(body) else {
416            return false;
417        };
418        extra.torque += vec(torque);
419        true
420    }
421
422    pub fn set_gravity_scale(&mut self, body: BodyId, scale: f32) -> bool {
423        self.with_body(body, |rb| rb.set_gravity_scale(scale, true))
424    }
425
426    pub fn set_damping(&mut self, body: BodyId, linear: f32, angular: f32) -> bool {
427        self.with_body(body, |rb| {
428            rb.set_linear_damping(linear);
429            rb.set_angular_damping(angular);
430        })
431    }
432
433    pub fn set_ccd(&mut self, body: BodyId, ccd: bool) -> bool {
434        self.with_body(body, |rb| rb.enable_ccd(ccd))
435    }
436
437    pub fn set_locks(&mut self, body: BodyId, locks: Locks) -> bool {
438        let Some(current) = self
439            .physics
440            .bodies
441            .get(body_handle(body))
442            .map(|rb| *rb.position())
443        else {
444            return false;
445        };
446        let Some(extra) = self.extra_mut(body) else {
447            return false;
448        };
449        extra.locks = locks;
450        extra.anchor = current;
451        self.with_body(body, |rb| rb.set_locked_axes(locked_axes(locks), true))
452    }
453
454    pub fn locks(&self, body: BodyId) -> Option<Locks> {
455        self.extra(body).map(|e| e.locks)
456    }
457
458    pub fn sleep(&mut self, body: BodyId) -> bool {
459        self.with_body(body, |rb| rb.sleep())
460    }
461
462    pub fn wake(&mut self, body: BodyId) -> bool {
463        let handle = body_handle(body);
464        if self.physics.bodies.get(handle).is_none() {
465            return false;
466        }
467        self.physics.wake_up(handle, true);
468        true
469    }
470
471    pub fn set_drive(&mut self, body: BodyId, drive: Option<Drive>) -> bool {
472        let Some(extra) = self.extra_mut(body) else {
473            return false;
474        };
475        extra.drive = drive;
476        true
477    }
478
479    pub fn drive(&self, body: BodyId) -> Option<&Drive> {
480        self.extra(body)?.drive.as_ref()
481    }
482
483    pub fn drive_mut(&mut self, body: BodyId) -> Option<&mut Drive> {
484        self.extra_mut(body)?.drive.as_mut()
485    }
486
487    pub fn move_to(&mut self, body: BodyId, to: Pose) -> bool {
488        let Some(extra) = self.extra_mut(body) else {
489            return false;
490        };
491        extra.target = Some(iso(to));
492        true
493    }
494
495    pub fn step(&mut self) {
496        self.events.clear();
497        let dt = 1.0 / self.rate;
498        let n = self.substeps;
499        let motions = self.motions(dt);
500        let mut started: Vec<(ColliderHandle, ColliderHandle, usize)> = Vec::new();
501        for i in 0..n {
502            self.apply_forces(dt / n as f32);
503            for (handle, from, to) in &motions {
504                let at = if i + 1 == n {
505                    *to
506                } else {
507                    lerp_iso(from, to, (i + 1) as f32 / n as f32)
508                };
509                if let Some(rb) = self.physics.bodies.get_mut(*handle) {
510                    rb.set_next_kinematic_position(at);
511                }
512            }
513            let hooks = Hooks {
514                surfaces: &self.surfaces,
515            };
516            self.physics.step_with_events(&hooks, &self.collector);
517            self.collect(&mut started);
518            self.enforce_locks();
519        }
520        for extra in self.extras.iter_mut().flatten() {
521            extra.force = Vec3::ZERO;
522            extra.torque = Vec3::ZERO;
523        }
524        self.steps += 1;
525        self.refresh_queries();
526    }
527
528    pub fn refresh_queries(&mut self) {
529        let physics = &mut self.physics;
530        let moved: Vec<(ColliderHandle, Iso)> = physics
531            .colliders
532            .iter()
533            .filter_map(|(handle, collider)| {
534                let parent = physics.bodies.get(collider.parent()?)?;
535                let at = *parent.position() * *collider.position_wrt_parent()?;
536                (at != *collider.position()).then_some((handle, at))
537            })
538            .collect();
539        for (handle, at) in moved {
540            if let Some(collider) = physics.colliders.get_mut(handle) {
541                collider.set_position(at);
542            }
543        }
544        let mut queries = BroadPhaseBvh::new();
545        for (handle, collider) in physics.colliders.iter() {
546            queries.set_aabb(
547                &physics.integration_parameters,
548                handle,
549                collider.compute_aabb(),
550            );
551        }
552        self.queries = queries;
553    }
554
555    pub fn run(&mut self, steps: u32) {
556        for _ in 0..steps {
557            self.step();
558        }
559    }
560
561    fn wake_all(&mut self) {
562        for (index, slot) in self.extras.iter().enumerate() {
563            if let Some(extra) = slot
564                && extra.kind == Kind::Dynamic
565            {
566                let handle = RigidBodyHandle::from_raw_parts(index as u32, extra.generation);
567                self.physics
568                    .islands
569                    .wake_up(&mut self.physics.bodies, handle, true);
570            }
571        }
572    }
573
574    fn extra(&self, body: BodyId) -> Option<&Extra> {
575        self.extras
576            .get(body.index as usize)?
577            .as_ref()
578            .filter(|e| e.generation == body.generation)
579    }
580
581    fn extra_mut(&mut self, body: BodyId) -> Option<&mut Extra> {
582        self.extras
583            .get_mut(body.index as usize)?
584            .as_mut()
585            .filter(|e| e.generation == body.generation)
586    }
587
588    fn with_body(
589        &mut self,
590        body: BodyId,
591        f: impl FnOnce(&mut rapier3d::prelude::RigidBody),
592    ) -> bool {
593        match self.physics.bodies.get_mut(body_handle(body)) {
594            Some(rb) => {
595                f(rb);
596                true
597            }
598            None => false,
599        }
600    }
601
602    fn forget_surface(&mut self, handle: ColliderHandle) {
603        let (index, generation) = handle.into_raw_parts();
604        if let Some(slot) = self.surfaces.get_mut(index as usize)
605            && slot.is_some_and(|s| s.generation == generation)
606        {
607            *slot = None;
608        }
609    }
610
611    fn motions(&mut self, dt: f32) -> Vec<(RigidBodyHandle, Iso, Iso)> {
612        let mut motions = Vec::new();
613        for (index, slot) in self.extras.iter_mut().enumerate() {
614            let Some(extra) = slot else {
615                continue;
616            };
617            if extra.kind != Kind::Kinematic {
618                continue;
619            }
620            let handle = RigidBodyHandle::from_raw_parts(index as u32, extra.generation);
621            let Some(rb) = self.physics.bodies.get(handle) else {
622                continue;
623            };
624            let from = *rb.position();
625            let to = match (&mut extra.drive, extra.target.take()) {
626                (_, Some(target)) => target,
627                (Some(drive), None) => drive.advance(dt, &from),
628                (None, None) => continue,
629            };
630            motions.push((handle, from, locked(extra.locks, &extra.anchor, &to)));
631        }
632        motions
633    }
634
635    fn apply_forces(&mut self, h: f32) {
636        for handle in self.forced.drain(..) {
637            if let Some(rb) = self.physics.bodies.get_mut(handle) {
638                rb.reset_forces(false);
639                rb.reset_torques(false);
640            }
641        }
642        let world = self.physics.gravity;
643        for (index, slot) in self.extras.iter().enumerate() {
644            let Some(extra) = slot else {
645                continue;
646            };
647            if extra.kind != Kind::Dynamic {
648                continue;
649            }
650            let handle = RigidBodyHandle::from_raw_parts(index as u32, extra.generation);
651            let Some(rb) = self.physics.bodies.get(handle) else {
652                continue;
653            };
654            let kick = if !self.sources.is_empty() && !rb.is_sleeping() {
655                acceleration(&self.sources, world, rb.center_of_mass())
656                    * (rb.mass() * rb.gravity_scale() * h)
657            } else {
658                Vec3::ZERO
659            };
660            let user = extra.force != Vec3::ZERO || extra.torque != Vec3::ZERO;
661            if kick == Vec3::ZERO && !user {
662                continue;
663            }
664            if let Some(rb) = self.physics.bodies.get_mut(handle) {
665                if kick != Vec3::ZERO {
666                    rb.apply_impulse(kick, false);
667                }
668                if user {
669                    rb.add_force(extra.force, true);
670                    rb.add_torque(extra.torque, true);
671                    self.forced.push(handle);
672                }
673            }
674        }
675    }
676
677    fn collect(&mut self, started: &mut Vec<(ColliderHandle, ColliderHandle, usize)>) {
678        let collisions = self
679            .collector
680            .collisions
681            .lock()
682            .map(|mut events| std::mem::take(&mut *events))
683            .unwrap_or_default();
684        for event in collisions {
685            let (c1, c2, flags, entered) = match event {
686                CollisionEvent::Started(c1, c2, flags) => (c1, c2, flags, true),
687                CollisionEvent::Stopped(c1, c2, flags) => (c1, c2, flags, false),
688            };
689            let (a, b) = (collider_id(c1), collider_id(c2));
690            if flags.contains(CollisionEventFlags::SENSOR) {
691                let second = self
692                    .physics
693                    .colliders
694                    .get(c2)
695                    .is_some_and(|c| c.is_sensor());
696                let first = self
697                    .physics
698                    .colliders
699                    .get(c1)
700                    .is_some_and(|c| c.is_sensor());
701                let (sensor, other) = if second && !first { (b, a) } else { (a, b) };
702                self.events.push(if entered {
703                    Event::SensorEntered { sensor, other }
704                } else {
705                    Event::SensorExited { sensor, other }
706                });
707            } else if entered {
708                started.push((c1, c2, self.events.len()));
709                self.events
710                    .push(Event::ContactStarted { a, b, impulse: 0.0 });
711            } else {
712                self.events.push(Event::ContactStopped { a, b });
713            }
714        }
715        for (c1, c2, at) in started.iter() {
716            let Some(pair) = self.physics.narrow_phase.contact_pair(*c1, *c2) else {
717                continue;
718            };
719            let add = pair.total_impulse_magnitude();
720            if let Some(Event::ContactStarted { impulse, .. }) = self.events.get_mut(*at) {
721                *impulse += add;
722            }
723        }
724        let impulses = self
725            .collector
726            .impulses
727            .lock()
728            .map(|mut impulses| std::mem::take(&mut *impulses))
729            .unwrap_or_default();
730        for (c1, c2, add) in impulses {
731            let (a, b) = (collider_id(c1), collider_id(c2));
732            let existing = self.events.iter_mut().find_map(|event| match event {
733                Event::ContactImpulse {
734                    a: ea,
735                    b: eb,
736                    impulse,
737                } if *ea == a && *eb == b => Some(impulse),
738                _ => None,
739            });
740            match existing {
741                Some(impulse) => *impulse += add,
742                None => self
743                    .events
744                    .push(Event::ContactImpulse { a, b, impulse: add }),
745            }
746        }
747    }
748
749    fn enforce_locks(&mut self) {
750        for (index, slot) in self.extras.iter().enumerate() {
751            let Some(extra) = slot else {
752                continue;
753            };
754            if extra.kind != Kind::Dynamic || !extra.locks.any() {
755                continue;
756            }
757            let handle = RigidBodyHandle::from_raw_parts(index as u32, extra.generation);
758            let Some(rb) = self.physics.bodies.get(handle) else {
759                continue;
760            };
761            let at = *rb.position();
762            let fixed = locked(extra.locks, &extra.anchor, &at);
763            let linear = rb.linvel();
764            let angular = rb.angvel();
765            let held_linear = masked(linear, extra.locks.translation);
766            let held_angular = masked(angular, extra.locks.rotation);
767            let moved = !same_iso(&fixed, &at);
768            let spun = held_linear != linear || held_angular != angular;
769            if !moved && !spun {
770                continue;
771            }
772            if let Some(rb) = self.physics.bodies.get_mut(handle) {
773                if moved {
774                    rb.set_position(fixed, false);
775                }
776                if spun {
777                    rb.set_linvel(held_linear, false);
778                    rb.set_angvel(held_angular, false);
779                }
780            }
781        }
782    }
783}
784
785struct Hooks<'a> {
786    surfaces: &'a [Option<Surface>],
787}
788
789impl Hooks<'_> {
790    fn friction(&self, handle: ColliderHandle) -> Option<Friction> {
791        let (index, generation) = handle.into_raw_parts();
792        self.surfaces
793            .get(index as usize)?
794            .filter(|s| s.generation == generation)
795            .map(|s| s.friction)
796    }
797}
798
799impl PhysicsHooks for Hooks<'_> {
800    fn modify_solver_contacts(&self, context: &mut ContactModificationContext) {
801        let (Some(f1), Some(f2)) = (
802            self.friction(context.collider1),
803            self.friction(context.collider2),
804        ) else {
805            return;
806        };
807        let bodies = context.bodies;
808        let colliders = context.colliders;
809        let (b1, b2, c1) = (context.rigid_body1, context.rigid_body2, context.collider1);
810        let Some(rigid) = context.rigid_mut() else {
811            return;
812        };
813        let Some(point) = rigid.manifold.points.first() else {
814            return;
815        };
816        let Some(position) = colliders.get(c1).map(|c| *c.position()) else {
817            return;
818        };
819        let p = position.transform_point(point.local_p1);
820        let velocity = |handle: Option<RigidBodyHandle>| {
821            handle
822                .and_then(|h| bodies.get(h))
823                .map(|rb| rb.velocity_at_point(p))
824                .unwrap_or(Vec3::ZERO)
825        };
826        let relative = velocity(b1) - velocity(b2);
827        let normal = *rigid.normal;
828        let tangent = relative - normal * relative.dot(normal);
829        let (a, b) = if tangent.length() > Friction::SLIP {
830            (f1.sliding, f2.sliding)
831        } else {
832            (f1.still, f2.still)
833        };
834        *rigid.friction = Combine::apply(a, b, f1.combine, f2.combine);
835    }
836}
837
838fn masked(v: Vec3, locks: [bool; 3]) -> Vec3 {
839    Vec3::new(
840        if locks[0] { 0.0 } else { v.x },
841        if locks[1] { 0.0 } else { v.y },
842        if locks[2] { 0.0 } else { v.z },
843    )
844}
845
846fn locked_axes(locks: Locks) -> LockedAxes {
847    let flags = [
848        LockedAxes::TRANSLATION_LOCKED_X,
849        LockedAxes::TRANSLATION_LOCKED_Y,
850        LockedAxes::TRANSLATION_LOCKED_Z,
851        LockedAxes::ROTATION_LOCKED_X,
852        LockedAxes::ROTATION_LOCKED_Y,
853        LockedAxes::ROTATION_LOCKED_Z,
854    ];
855    locks
856        .translation
857        .iter()
858        .chain(&locks.rotation)
859        .zip(flags)
860        .filter(|(on, _)| **on)
861        .fold(LockedAxes::empty(), |all, (_, flag)| all | flag)
862}
863
864fn locked(locks: Locks, anchor: &Iso, at: &Iso) -> Iso {
865    let mut translation = at.translation;
866    for axis in 0..3 {
867        if locks.translation[axis] {
868            translation[axis] = anchor.translation[axis];
869        }
870    }
871    let mut free = [0usize; 3];
872    let mut count = 0;
873    for axis in (0..3).filter(|&axis| !locks.rotation[axis]) {
874        free[count] = axis;
875        count += 1;
876    }
877    let rotation = match &free[..count] {
878        [] => anchor.rotation,
879        [axis] => {
880            let relative = at.rotation * anchor.rotation.inverse();
881            let mut parts = [0.0, 0.0, 0.0, relative.w];
882            parts[*axis] = relative.to_array()[*axis];
883            let twist = Quat::from_array(parts);
884            let length = twist.length();
885            let twist = if length > f32::EPSILON {
886                twist / length
887            } else {
888                Quat::IDENTITY
889            };
890            twist * anchor.rotation
891        }
892        _ => at.rotation,
893    };
894    Iso::from_parts(translation, rotation)
895}
896
897fn same_iso(a: &Iso, b: &Iso) -> bool {
898    a.translation == b.translation && a.rotation == b.rotation
899}