1use std::collections::{BTreeMap, HashSet};
28
29use glam::Vec3;
30
31use crate::vehicle::Vehicle;
32
33pub const EARTH_GRAVITY: Vec3 = Vec3::new(0.0, 0.0, -9.81);
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
38pub struct BodyId(u32);
39
40#[derive(Debug, Clone, Copy, PartialEq)]
42pub enum Shape {
43 Box(Vec3),
45 Sphere(f32),
47}
48
49impl Shape {
50 fn half_extents(&self) -> Vec3 {
51 match *self {
52 Shape::Box(h) => h,
53 Shape::Sphere(r) => Vec3::splat(r),
54 }
55 }
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum BodyKind {
68 Dynamic,
71 Fixed,
73 Kinematic,
78 Sensor,
80}
81
82#[derive(Debug, Clone)]
84pub struct Body {
85 pub position: Vec3,
87 pub velocity: Vec3,
89 pub shape: Shape,
91 pub kind: BodyKind,
93 pub gravity_scale: f32,
96 pub bounciness: f32,
99 pub friction: f32,
103 pub damping: f32,
107 pub tag: String,
109 pub user_data: u64,
111 pub facing: f32,
115 pub pitch: f32,
120 pub mass: f32,
125 pub max_speed: f32,
128 pub on_ground: bool,
131 pub ground: Option<BodyId>,
134 pub vehicle: Option<Vehicle>,
138 force: Vec3,
139}
140
141impl Body {
142 fn new(kind: BodyKind, position: Vec3) -> Self {
143 Self {
144 position,
145 velocity: Vec3::ZERO,
146 shape: Shape::Box(Vec3::splat(0.5)),
147 kind,
148 gravity_scale: 1.0,
149 bounciness: 0.0,
150 friction: 8.0,
151 damping: 0.0,
152 tag: String::new(),
153 user_data: 0,
154 facing: 0.0,
155 pitch: 0.0,
156 mass: 1.0,
157 max_speed: f32::INFINITY,
158 on_ground: false,
159 ground: None,
160 vehicle: None,
161 force: Vec3::ZERO,
162 }
163 }
164
165 pub fn dynamic(position: Vec3) -> Self {
168 Self::new(BodyKind::Dynamic, position)
169 }
170
171 pub fn fixed(position: Vec3) -> Self {
173 Self::new(BodyKind::Fixed, position)
174 }
175
176 pub fn kinematic(position: Vec3) -> Self {
180 Self::new(BodyKind::Kinematic, position)
181 }
182
183 pub fn sensor(position: Vec3) -> Self {
186 Self::new(BodyKind::Sensor, position)
187 }
188
189 pub fn car(position: Vec3) -> Self {
194 Self::kinematic(position).with_sphere(1.0).with_mass(1300.0).with_tag("car").with_vehicle(Vehicle::car())
195 }
196
197 pub fn truck(position: Vec3) -> Self {
200 Self::kinematic(position)
201 .with_sphere(1.5)
202 .with_mass(12_000.0)
203 .with_tag("truck")
204 .with_vehicle(Vehicle::truck())
205 }
206
207 pub fn with_vehicle(mut self, vehicle: Vehicle) -> Self {
210 self.kind = BodyKind::Kinematic;
211 self.vehicle = Some(vehicle);
212 self
213 }
214
215 pub fn with_box(mut self, width: f32, depth: f32, height: f32) -> Self {
218 self.shape = Shape::Box(Vec3::new(width, depth, height) * 0.5);
219 self
220 }
221
222 pub fn with_sphere(mut self, radius: f32) -> Self {
224 self.shape = Shape::Sphere(radius);
225 self
226 }
227
228 pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
231 self.tag = tag.into();
232 self
233 }
234
235 pub fn with_velocity(mut self, velocity: Vec3) -> Self {
237 self.velocity = velocity;
238 self
239 }
240
241 pub fn with_bounciness(mut self, bounciness: f32) -> Self {
244 self.bounciness = bounciness.clamp(0.0, 1.0);
245 self
246 }
247
248 pub fn with_friction(mut self, friction: f32) -> Self {
252 self.friction = friction.max(0.0);
253 self
254 }
255
256 pub fn with_damping(mut self, damping: f32) -> Self {
259 self.damping = damping.max(0.0);
260 self
261 }
262
263 pub fn with_gravity_scale(mut self, scale: f32) -> Self {
266 self.gravity_scale = scale;
267 self
268 }
269
270 pub fn with_mass(mut self, mass: f32) -> Self {
272 self.mass = mass.max(0.001);
273 self
274 }
275
276 pub fn with_max_speed(mut self, max_speed: f32) -> Self {
278 self.max_speed = max_speed.max(0.0);
279 self
280 }
281
282 pub fn with_facing(mut self, degrees: f32) -> Self {
285 self.facing = crate::math::normalize_angle(degrees);
286 self
287 }
288
289 pub fn with_pitch(mut self, degrees: f32) -> Self {
291 self.pitch = degrees.clamp(-90.0, 90.0);
292 self
293 }
294
295 pub fn with_user_data(mut self, data: u64) -> Self {
297 self.user_data = data;
298 self
299 }
300
301 fn min(&self) -> Vec3 {
302 self.position - self.shape.half_extents()
303 }
304
305 fn max(&self) -> Vec3 {
306 self.position + self.shape.half_extents()
307 }
308
309 fn is_solid(&self) -> bool {
310 self.kind != BodyKind::Sensor
311 }
312
313 pub fn contains(&self, point: Vec3) -> bool {
315 match self.shape {
316 Shape::Box(_) => point.cmpge(self.min()).all() && point.cmple(self.max()).all(),
317 Shape::Sphere(r) => point.distance_squared(self.position) <= r * r,
318 }
319 }
320}
321
322#[derive(Debug, Clone, PartialEq)]
324pub enum PhysicsEvent {
325 CollisionStarted {
328 a: BodyId,
330 b: BodyId,
332 speed: f32,
336 },
337 CollisionEnded {
339 a: BodyId,
341 b: BodyId,
343 },
344 Landed {
347 body: BodyId,
349 on: BodyId,
351 speed: f32,
353 },
354 Entered {
356 sensor: BodyId,
358 body: BodyId,
360 },
361 Left {
363 sensor: BodyId,
365 body: BodyId,
367 },
368 Crashed {
370 vehicle: BodyId,
372 other: BodyId,
374 speed: f32,
376 },
377 GearChanged {
379 vehicle: BodyId,
381 gear: i32,
383 },
384}
385
386impl PhysicsEvent {
387 pub fn involves(&self, id: BodyId) -> bool {
389 match *self {
390 PhysicsEvent::CollisionStarted { a, b, .. } | PhysicsEvent::CollisionEnded { a, b } => a == id || b == id,
391 PhysicsEvent::Landed { body, on, .. } => body == id || on == id,
392 PhysicsEvent::Entered { sensor, body } | PhysicsEvent::Left { sensor, body } => sensor == id || body == id,
393 PhysicsEvent::Crashed { vehicle, other, .. } => vehicle == id || other == id,
394 PhysicsEvent::GearChanged { vehicle, .. } => vehicle == id,
395 }
396 }
397
398 pub fn other(&self, id: BodyId) -> Option<BodyId> {
400 let (x, y) = match *self {
401 PhysicsEvent::CollisionStarted { a, b, .. } | PhysicsEvent::CollisionEnded { a, b } => (a, b),
402 PhysicsEvent::Landed { body, on, .. } => (body, on),
403 PhysicsEvent::Entered { sensor, body } | PhysicsEvent::Left { sensor, body } => (sensor, body),
404 PhysicsEvent::Crashed { vehicle, other, .. } => (vehicle, other),
405 PhysicsEvent::GearChanged { .. } => return None,
406 };
407 if x == id {
408 Some(y)
409 } else if y == id {
410 Some(x)
411 } else {
412 None
413 }
414 }
415}
416
417#[derive(Debug, Clone, Copy, PartialEq)]
419pub struct RayHit {
420 pub body: BodyId,
422 pub point: Vec3,
424 pub distance: f32,
426 pub normal: Vec3,
428}
429
430#[derive(Debug, Clone, Default, PartialEq)]
432pub struct MoveResult {
433 pub moved: Vec3,
436 pub blocked: bool,
439 pub hits: Vec<BodyId>,
441}
442
443#[derive(Debug, Clone)]
445pub struct World {
446 bodies: BTreeMap<BodyId, Body>,
447 next_id: u32,
448 gravity: Vec3,
449 contacts: HashSet<(BodyId, BodyId)>,
450 sensor_overlaps: HashSet<(BodyId, BodyId)>,
451 events: Vec<PhysicsEvent>,
452}
453
454impl Default for World {
455 fn default() -> Self {
456 Self::new()
457 }
458}
459
460fn penetration(a: &Body, b: &Body) -> Option<(Vec3, f32)> {
462 match (a.shape, b.shape) {
463 (Shape::Sphere(ra), Shape::Sphere(rb)) => {
464 let d = a.position - b.position;
465 let dist = d.length();
466 let depth = ra + rb - dist;
467 if depth <= 0.0 {
468 return None;
469 }
470 let n = if dist > 1e-6 { d / dist } else { Vec3::Z };
471 Some((n, depth))
472 }
473 (Shape::Sphere(r), Shape::Box(_)) => sphere_box(a.position, r, b),
474 (Shape::Box(_), Shape::Sphere(r)) => sphere_box(b.position, r, a).map(|(n, d)| (-n, d)),
475 (Shape::Box(_), Shape::Box(_)) => {
476 let overlap = a.max().min(b.max()) - a.min().max(b.min());
477 if overlap.x <= 0.0 || overlap.y <= 0.0 || overlap.z <= 0.0 {
478 return None;
479 }
480 let d = a.position - b.position;
481 let sign = |v: f32| if v >= 0.0 { 1.0 } else { -1.0 };
482 if overlap.x < overlap.y && overlap.x < overlap.z {
483 Some((Vec3::X * sign(d.x), overlap.x))
484 } else if overlap.y < overlap.z {
485 Some((Vec3::Y * sign(d.y), overlap.y))
486 } else {
487 Some((Vec3::Z * sign(d.z), overlap.z))
488 }
489 }
490 }
491}
492
493fn sphere_box(center: Vec3, radius: f32, b: &Body) -> Option<(Vec3, f32)> {
495 let closest = center.clamp(b.min(), b.max());
496 let d = center - closest;
497 let dist = d.length();
498 if dist > 1e-6 {
499 let depth = radius - dist;
500 return (depth > 0.0).then(|| (d / dist, depth));
501 }
502 let to_max = b.max() - center;
504 let to_min = center - b.min();
505 let candidates = [
506 (Vec3::X, to_max.x),
507 (-Vec3::X, to_min.x),
508 (Vec3::Y, to_max.y),
509 (-Vec3::Y, to_min.y),
510 (Vec3::Z, to_max.z),
511 (-Vec3::Z, to_min.z),
512 ];
513 let (n, d) = candidates.into_iter().min_by(|a, b| a.1.total_cmp(&b.1))?;
514 Some((n, d + radius))
515}
516
517fn pair(a: BodyId, b: BodyId) -> (BodyId, BodyId) {
518 if a < b { (a, b) } else { (b, a) }
519}
520
521impl World {
522 pub fn new() -> Self {
524 Self {
525 bodies: BTreeMap::new(),
526 next_id: 1,
527 gravity: Vec3::ZERO,
528 contacts: HashSet::new(),
529 sensor_overlaps: HashSet::new(),
530 events: Vec::new(),
531 }
532 }
533
534 pub fn set_gravity(&mut self, gravity: Vec3) {
537 self.gravity = gravity;
538 }
539
540 pub fn up(&self) -> Vec3 {
542 (-self.gravity).normalize_or(Vec3::Z)
543 }
544
545 pub fn gravity(&self) -> Vec3 {
547 self.gravity
548 }
549
550 pub fn add(&mut self, body: Body) -> BodyId {
552 let id = BodyId(self.next_id);
553 self.next_id += 1;
554 self.bodies.insert(id, body);
555 id
556 }
557
558 pub fn remove(&mut self, id: BodyId) -> Option<Body> {
560 self.contacts.retain(|&(a, b)| a != id && b != id);
561 self.sensor_overlaps.retain(|&(a, b)| a != id && b != id);
562 self.bodies.remove(&id)
563 }
564
565 pub fn clear(&mut self) {
567 self.bodies.clear();
568 self.contacts.clear();
569 self.sensor_overlaps.clear();
570 self.events.clear();
571 }
572
573 pub fn get(&self, id: BodyId) -> Option<&Body> {
575 self.bodies.get(&id)
576 }
577
578 pub fn get_mut(&mut self, id: BodyId) -> Option<&mut Body> {
580 self.bodies.get_mut(&id)
581 }
582
583 pub fn position(&self, id: BodyId) -> Vec3 {
585 self.get(id).map_or(Vec3::ZERO, |b| b.position)
586 }
587
588 pub fn set_position(&mut self, id: BodyId, position: Vec3) {
590 if let Some(b) = self.get_mut(id) {
591 b.position = position;
592 }
593 }
594
595 pub fn velocity(&self, id: BodyId) -> Vec3 {
597 self.get(id).map_or(Vec3::ZERO, |b| b.velocity)
598 }
599
600 pub fn set_velocity(&mut self, id: BodyId, velocity: Vec3) {
602 if let Some(b) = self.get_mut(id) {
603 b.velocity = velocity;
604 }
605 }
606
607 pub fn apply_impulse(&mut self, id: BodyId, impulse: Vec3) {
610 if let Some(b) = self.get_mut(id) {
611 b.velocity += impulse;
612 }
613 }
614
615 pub fn apply_force(&mut self, id: BodyId, force: Vec3) {
620 if let Some(b) = self.get_mut(id) {
621 b.force += force;
622 }
623 }
624
625 pub fn jump(&mut self, id: BodyId, height: f32) -> bool {
638 let (up, gravity) = (self.up(), self.gravity.length());
639 let Some(b) = self.get_mut(id) else {
640 return false;
641 };
642 let g = gravity * b.gravity_scale;
643 if !b.on_ground || g <= 0.0 {
644 return false;
645 }
646 let speed = (2.0 * g * height.max(0.0)).sqrt();
647 b.velocity += up * (speed - b.velocity.dot(up));
648 b.on_ground = false;
649 true
650 }
651
652 pub fn on_ground(&self, id: BodyId) -> bool {
654 self.get(id).is_some_and(|b| b.on_ground)
655 }
656
657 pub fn tag(&self, id: BodyId) -> &str {
659 self.get(id).map_or("", |b| b.tag.as_str())
660 }
661
662 pub fn bodies(&self) -> impl Iterator<Item = (BodyId, &Body)> {
664 self.bodies.iter().map(|(id, b)| (*id, b))
665 }
666
667 pub fn with_tag<'a>(&'a self, tag: &'a str) -> impl Iterator<Item = BodyId> + 'a {
669 self.bodies.iter().filter(move |(_, b)| b.tag == tag).map(|(id, _)| *id)
670 }
671
672 pub fn len(&self) -> usize {
674 self.bodies.len()
675 }
676
677 pub fn is_empty(&self) -> bool {
679 self.bodies.is_empty()
680 }
681
682 pub fn events(&self) -> &[PhysicsEvent] {
684 &self.events
685 }
686
687 pub(crate) fn clear_events(&mut self) {
688 self.events.clear();
689 }
690
691 pub fn sensors_of(&self, body: BodyId) -> Vec<BodyId> {
693 self.sensor_overlaps.iter().filter(|(_, b)| *b == body).map(|(s, _)| *s).collect()
694 }
695
696 pub fn overlaps(&self, a: BodyId, b: BodyId) -> bool {
698 match (self.get(a), self.get(b)) {
699 (Some(x), Some(y)) => penetration(x, y).is_some(),
700 _ => false,
701 }
702 }
703
704 pub fn at_point(&self, point: Vec3) -> Vec<BodyId> {
706 self.bodies.iter().filter(|(_, b)| b.contains(point)).map(|(id, _)| *id).collect()
707 }
708
709 pub fn in_radius(&self, center: Vec3, radius: f32) -> Vec<BodyId> {
711 let mut found: Vec<(f32, BodyId)> = self
712 .bodies
713 .iter()
714 .map(|(id, b)| (b.position.distance(center), *id))
715 .filter(|(d, _)| *d <= radius)
716 .collect();
717 found.sort_by(|a, b| a.0.total_cmp(&b.0));
718 found.into_iter().map(|(_, id)| id).collect()
719 }
720
721 pub fn nearest_with_tag(&self, center: Vec3, tag: &str) -> Option<BodyId> {
723 self.bodies
724 .iter()
725 .filter(|(_, b)| b.tag == tag)
726 .min_by(|a, b| a.1.position.distance_squared(center).total_cmp(&b.1.position.distance_squared(center)))
727 .map(|(id, _)| *id)
728 }
729
730 pub fn raycast(&self, origin: Vec3, direction: Vec3, max_distance: f32, ignore: Option<BodyId>) -> Option<RayHit> {
734 let dir = direction.normalize_or_zero();
735 if dir == Vec3::ZERO {
736 return None;
737 }
738 let mut best: Option<RayHit> = None;
739 for (id, body) in &self.bodies {
740 if Some(*id) == ignore || !body.is_solid() {
741 continue;
742 }
743 let hit = match body.shape {
744 Shape::Box(_) => ray_box(origin, dir, body.min(), body.max()),
745 Shape::Sphere(r) => ray_sphere(origin, dir, body.position, r),
746 };
747 if let Some((t, normal)) = hit
748 && t <= max_distance
749 && best.as_ref().is_none_or(|b| t < b.distance)
750 {
751 best = Some(RayHit { body: *id, point: origin + dir * t, distance: t, normal });
752 }
753 }
754 best
755 }
756
757 pub fn move_by(&mut self, id: BodyId, delta: Vec3) -> MoveResult {
762 let Some(body) = self.bodies.get(&id) else {
763 return MoveResult::default();
764 };
765 let start = body.position;
766 let size = body.shape.half_extents().min_element().max(0.01);
767 let steps = ((delta.length() / size).ceil() as usize).clamp(1, 1000);
768 let step = delta / steps as f32;
769 let mut result = MoveResult::default();
770 for _ in 0..steps {
771 if let Some(b) = self.bodies.get_mut(&id) {
772 b.position += step;
773 }
774 for (other, normal, _) in self.resolve(id) {
775 if !result.hits.contains(&other) {
776 result.hits.push(other);
777 }
778 if step.dot(normal) < 0.0 {
780 result.blocked = true;
781 }
782 }
783 }
784 result.moved = self.position(id) - start;
785 self.update_sensors_for(id);
786 result
787 }
788
789 fn resolve(&mut self, id: BodyId) -> Vec<(BodyId, Vec3, f32)> {
792 let mut contacts = Vec::new();
793 let Some(body) = self.bodies.get(&id) else {
794 return contacts;
795 };
796 if !body.is_solid() {
797 return contacts;
798 }
799 for _ in 0..4 {
801 let body = &self.bodies[&id];
802 let mut push = None;
803 for (other_id, other) in &self.bodies {
804 if *other_id == id || !other.is_solid() {
805 continue;
806 }
807 if let Some((n, depth)) = penetration(body, other)
808 && push.as_ref().is_none_or(|(_, _, d)| depth > *d)
809 {
810 push = Some((*other_id, n, depth));
811 }
812 }
813 let Some((other, n, depth)) = push else {
814 break;
815 };
816 let other_body = &self.bodies[&other];
819 let share = match (body.kind, other_body.kind) {
820 (BodyKind::Kinematic, BodyKind::Dynamic) => 1.0,
821 (BodyKind::Dynamic, BodyKind::Dynamic) => body.mass / (body.mass + other_body.mass),
822 _ => 0.0,
823 };
824 let mut moved_other = 0.0;
825 if share > 0.0 {
826 let old = other_body.position;
827 if let Some(o) = self.bodies.get_mut(&other) {
828 o.position -= n * depth * share;
829 }
830 if self.obstructed(other, id, -n) {
831 if let Some(o) = self.bodies.get_mut(&other) {
833 o.position = old;
834 }
835 } else {
836 moved_other = share;
837 }
838 }
839 if let Some(b) = self.bodies.get_mut(&id) {
840 b.position += n * depth * (1.0 - moved_other);
841 }
842 contacts.push((other, n, depth));
843 }
844 contacts
845 }
846
847 fn obstructed(&self, id: BodyId, pusher: BodyId, direction: Vec3) -> bool {
850 let body = &self.bodies[&id];
851 self.bodies.iter().any(|(other_id, other)| {
852 *other_id != id
853 && *other_id != pusher
854 && other.is_solid()
855 && penetration(body, other).is_some_and(|(n, _)| n.dot(direction) < -0.5)
856 })
857 }
858
859 fn update_sensors_for(&mut self, id: BodyId) {
860 let Some(body) = self.bodies.get(&id) else {
861 return;
862 };
863 if !body.is_solid() {
864 return;
865 }
866 for (sid, sensor) in &self.bodies {
867 if sensor.kind != BodyKind::Sensor {
868 continue;
869 }
870 let inside = penetration(body, sensor).is_some();
871 let key = (*sid, id);
872 if inside && self.sensor_overlaps.insert(key) {
873 self.events.push(PhysicsEvent::Entered { sensor: *sid, body: id });
874 } else if !inside && self.sensor_overlaps.remove(&key) {
875 self.events.push(PhysicsEvent::Left { sensor: *sid, body: id });
876 }
877 }
878 }
879
880 pub fn step(&mut self, dt: f32) {
882 if dt <= 0.0 {
883 return;
884 }
885 let ids: Vec<BodyId> = self.bodies.keys().copied().collect();
886 let gravity = self.gravity;
887 let up = self.up();
888 let mut touching = HashSet::new();
889
890 for &id in &ids {
892 self.step_vehicle(id, dt);
893 }
894
895 let mut carried = Vec::new();
897 for (id, body) in self.bodies.iter_mut() {
898 if body.kind == BodyKind::Kinematic && body.vehicle.is_none() {
899 let delta = body.velocity * dt;
900 body.position += delta;
901 if delta != Vec3::ZERO {
902 carried.push((*id, delta));
903 }
904 }
905 }
906
907 for &id in &ids {
908 let Some(body) = self.bodies.get_mut(&id) else {
909 continue;
910 };
911 if body.kind != BodyKind::Dynamic {
912 continue;
913 }
914 if let Some((_, delta)) = carried.iter().find(|(platform, _)| Some(*platform) == body.ground) {
915 body.position += *delta;
916 }
917
918 let force = std::mem::take(&mut body.force);
919 body.velocity += (gravity * body.gravity_scale + force / body.mass) * dt;
920 if body.damping > 0.0 {
921 body.velocity *= (1.0 - body.damping * dt).max(0.0);
922 }
923 if body.on_ground && body.friction > 0.0 {
924 let keep = (1.0 - body.friction * dt).max(0.0);
925 let vertical = up * body.velocity.dot(up);
926 body.velocity = vertical + (body.velocity - vertical) * keep;
927 }
928 if body.max_speed.is_finite() {
929 body.velocity = body.velocity.clamp_length_max(body.max_speed);
930 }
931
932 let was_on_ground = body.on_ground;
933 let delta = body.velocity * dt;
934 let size = body.shape.half_extents().min_element().max(0.01);
935 let substeps = ((delta.length() / size).ceil() as usize).clamp(1, 64);
936 let step = delta / substeps as f32;
937 let mut ground = None;
938 let mut landing_speed = 0.0f32;
939 for _ in 0..substeps {
940 if let Some(b) = self.bodies.get_mut(&id) {
941 b.position += step;
942 }
943 for (other, n, _) in self.resolve(id) {
944 let impact = self.bounce(id, other, n);
945 if impact > 0.0 {
946 let key = pair(id, other);
947 if !self.contacts.contains(&key) && !touching.contains(&key) {
948 self.events.push(PhysicsEvent::CollisionStarted { a: id, b: other, speed: impact });
949 }
950 }
951 if n.dot(up) > 0.7 {
952 ground = Some(other);
953 landing_speed = landing_speed.max(impact);
954 }
955 touching.insert(pair(id, other));
956 }
957 }
958 if let Some(b) = self.bodies.get_mut(&id) {
961 b.on_ground = ground.is_some();
962 b.ground = ground;
963 }
964 if let (false, Some(on)) = (was_on_ground, ground)
965 && landing_speed > 0.0
966 {
967 self.events.push(PhysicsEvent::Landed { body: id, on, speed: landing_speed });
968 }
969 }
970
971 for &(a, b) in self.contacts.difference(&touching) {
972 self.events.push(PhysicsEvent::CollisionEnded { a, b });
973 }
974 self.contacts = touching;
975
976 for &id in &ids {
977 self.update_sensors_for(id);
978 }
979 }
980
981 fn step_vehicle(&mut self, id: BodyId, dt: f32) {
983 let Some(body) = self.bodies.get_mut(&id) else {
984 return;
985 };
986 let Some(mut vehicle) = body.vehicle.take() else {
987 return;
988 };
989 let (turn, report) = vehicle.simulate(body.mass, dt);
990 body.facing = crate::math::normalize_angle(body.facing + turn);
991 let facing = body.facing;
992 if let Some(gear) = report.gear_changed {
993 self.events.push(PhysicsEvent::GearChanged { vehicle: id, gear });
994 }
995
996 let delta = crate::vehicle::velocity(facing, vehicle.speed()) * dt;
997 if delta != Vec3::ZERO {
998 let result = self.move_by(id, delta);
999 let along = result.moved.dot(crate::math::forward(facing));
1000 vehicle.add_distance(along);
1001 if result.blocked
1002 && let Some((other, normal)) = self.contact_ahead(id, delta)
1003 {
1004 let impact = vehicle.blocked(facing, normal);
1005 if impact > 0.5 {
1006 self.events.push(PhysicsEvent::Crashed { vehicle: id, other, speed: impact });
1007 }
1008 }
1009 }
1010 if let Some(body) = self.bodies.get_mut(&id) {
1011 body.velocity = crate::vehicle::velocity(facing, vehicle.speed());
1012 body.vehicle = Some(vehicle);
1013 }
1014 }
1015
1016 fn contact_ahead(&self, id: BodyId, delta: Vec3) -> Option<(BodyId, Vec3)> {
1019 let mut probe = self.bodies.get(&id)?.clone();
1020 probe.position += delta.normalize_or_zero() * 0.02;
1021 self.bodies
1022 .iter()
1023 .filter(|(other, b)| **other != id && b.is_solid())
1024 .filter_map(|(other, b)| penetration(&probe, b).map(|(n, depth)| (*other, n, depth)))
1025 .max_by(|a, b| a.2.total_cmp(&b.2))
1026 .map(|(other, n, _)| (other, n))
1027 }
1028
1029 fn bounce(&mut self, id: BodyId, other: BodyId, n: Vec3) -> f32 {
1032 let (Some(a), Some(b)) = (self.bodies.get(&id), self.bodies.get(&other)) else {
1033 return 0.0;
1034 };
1035 let other_velocity = if b.kind == BodyKind::Fixed { Vec3::ZERO } else { b.velocity };
1036 let closing = (a.velocity - other_velocity).dot(n);
1037 if closing >= 0.0 {
1038 return 0.0;
1039 }
1040 let restitution = a.bounciness.max(b.bounciness);
1041 if b.kind == BodyKind::Dynamic {
1042 let (ma, mb) = (a.mass, b.mass);
1044 let j = -(1.0 + restitution) * closing / (1.0 / ma + 1.0 / mb);
1045 if let Some(a) = self.bodies.get_mut(&id) {
1046 a.velocity += n * (j / ma);
1047 }
1048 if let Some(b) = self.bodies.get_mut(&other) {
1049 b.velocity -= n * (j / mb);
1050 }
1051 } else if let Some(a) = self.bodies.get_mut(&id) {
1052 a.velocity -= n * closing * (1.0 + restitution);
1054 }
1055 -closing
1056 }
1057}
1058
1059
1060impl World {
1062 pub fn facing(&self, id: BodyId) -> f32 {
1064 self.get(id).map_or(0.0, |b| b.facing)
1065 }
1066
1067 pub fn set_facing(&mut self, id: BodyId, degrees: f32) {
1069 if let Some(b) = self.get_mut(id) {
1070 b.facing = crate::math::normalize_angle(degrees);
1071 }
1072 }
1073
1074 pub fn turn(&mut self, id: BodyId, degrees: f32) -> f32 {
1077 let facing = crate::math::normalize_angle(self.facing(id) + degrees);
1078 self.set_facing(id, facing);
1079 facing
1080 }
1081
1082 pub fn pitch(&self, id: BodyId) -> f32 {
1085 self.get(id).map_or(0.0, |b| b.pitch)
1086 }
1087
1088 pub fn set_pitch(&mut self, id: BodyId, degrees: f32) {
1090 if let Some(b) = self.get_mut(id) {
1091 b.pitch = degrees.clamp(-90.0, 90.0);
1092 }
1093 }
1094
1095 pub fn tilt(&mut self, id: BodyId, degrees: f32) -> f32 {
1098 let pitch = (self.pitch(id) + degrees).clamp(-90.0, 90.0);
1099 self.set_pitch(id, pitch);
1100 pitch
1101 }
1102
1103 pub fn look_at(&mut self, id: BodyId, point: Vec3) {
1105 let (yaw, pitch) = crate::math::look_at(self.position(id), point);
1106 self.set_facing(id, yaw);
1107 self.set_pitch(id, pitch);
1108 }
1109
1110 pub fn move_forward(&mut self, id: BodyId, distance: f32) -> MoveResult {
1114 let delta = crate::math::forward_3d(self.facing(id), self.pitch(id)) * distance;
1115 self.move_by(id, delta)
1116 }
1117
1118 pub fn move_up(&mut self, id: BodyId, distance: f32) -> MoveResult {
1122 let delta = self.up() * distance;
1123 self.move_by(id, delta)
1124 }
1125
1126 pub fn strafe(&mut self, id: BodyId, distance: f32) -> MoveResult {
1128 let delta = crate::math::right(self.facing(id)) * distance;
1129 self.move_by(id, delta)
1130 }
1131
1132 pub fn distance(&self, a: BodyId, b: BodyId) -> f32 {
1134 match (self.get(a), self.get(b)) {
1135 (Some(a), Some(b)) => a.position.distance(b.position),
1136 _ => f32::INFINITY,
1137 }
1138 }
1139
1140 pub fn describe(&self, from: BodyId, target: BodyId) -> String {
1153 crate::math::describe_position(self.position(from), self.facing(from), self.position(target))
1154 }
1155
1156 pub fn landed(&self, body: BodyId) -> Option<f32> {
1161 self.events.iter().find_map(|e| match *e {
1162 PhysicsEvent::Landed { body: b, speed, .. } if b == body => Some(speed),
1163 _ => None,
1164 })
1165 }
1166
1167 pub fn collided(&self, body: BodyId) -> Option<(BodyId, f32)> {
1170 self.events.iter().find_map(|e| match *e {
1171 PhysicsEvent::CollisionStarted { a, b, speed } if a == body => Some((b, speed)),
1172 PhysicsEvent::CollisionStarted { a, b, speed } if b == body => Some((a, speed)),
1173 _ => None,
1174 })
1175 }
1176
1177 pub fn entered(&self, body: BodyId) -> Option<BodyId> {
1179 self.events.iter().find_map(|e| match *e {
1180 PhysicsEvent::Entered { sensor, body: b } if b == body => Some(sensor),
1181 _ => None,
1182 })
1183 }
1184
1185 pub fn entered_tag(&self, body: BodyId, tag: &str) -> Option<BodyId> {
1197 self.events.iter().find_map(|e| match *e {
1198 PhysicsEvent::Entered { sensor, body: b } if b == body && self.tag(sensor) == tag => Some(sensor),
1199 _ => None,
1200 })
1201 }
1202
1203 pub fn left(&self, body: BodyId) -> Option<BodyId> {
1205 self.events.iter().find_map(|e| match *e {
1206 PhysicsEvent::Left { sensor, body: b } if b == body => Some(sensor),
1207 _ => None,
1208 })
1209 }
1210
1211 pub fn vehicle(&self, id: BodyId) -> Option<&Vehicle> {
1225 self.get(id).and_then(|b| b.vehicle.as_ref())
1226 }
1227
1228 pub fn vehicle_mut(&mut self, id: BodyId) -> Option<&mut Vehicle> {
1230 self.get_mut(id).and_then(|b| b.vehicle.as_mut())
1231 }
1232
1233 pub fn weight(&self, id: BodyId) -> f32 {
1236 self.get(id).map_or(0.0, |b| b.mass + b.vehicle.as_ref().map_or(0.0, |v| v.load))
1237 }
1238
1239 pub fn speed(&self, id: BodyId) -> f32 {
1243 self.velocity(id).length()
1244 }
1245
1246 pub fn crashed(&self, vehicle: BodyId) -> Option<(BodyId, f32)> {
1249 self.events.iter().find_map(|e| match *e {
1250 PhysicsEvent::Crashed { vehicle: v, other, speed } if v == vehicle => Some((other, speed)),
1251 _ => None,
1252 })
1253 }
1254
1255 pub fn gear_changed(&self, vehicle: BodyId) -> Option<i32> {
1257 self.events.iter().rev().find_map(|e| match *e {
1258 PhysicsEvent::GearChanged { vehicle: v, gear } if v == vehicle => Some(gear),
1259 _ => None,
1260 })
1261 }
1262
1263 pub fn triggered(&self, sensor: BodyId) -> Option<BodyId> {
1265 self.events.iter().find_map(|e| match *e {
1266 PhysicsEvent::Entered { sensor: s, body } if s == sensor => Some(body),
1267 _ => None,
1268 })
1269 }
1270}
1271
1272fn ray_box(origin: Vec3, dir: Vec3, min: Vec3, max: Vec3) -> Option<(f32, Vec3)> {
1274 let mut t_min = 0.0f32;
1275 let mut t_max = f32::INFINITY;
1276 let mut normal = Vec3::ZERO;
1277 for axis in 0..3 {
1278 let (o, d, lo, hi) = (origin[axis], dir[axis], min[axis], max[axis]);
1279 if d.abs() < 1e-8 {
1280 if o < lo || o > hi {
1281 return None;
1282 }
1283 continue;
1284 }
1285 let (mut t1, mut t2) = ((lo - o) / d, (hi - o) / d);
1286 let mut n = Vec3::ZERO;
1287 n[axis] = -d.signum();
1288 if t1 > t2 {
1289 std::mem::swap(&mut t1, &mut t2);
1290 }
1291 if t1 > t_min {
1292 t_min = t1;
1293 normal = n;
1294 }
1295 t_max = t_max.min(t2);
1296 if t_min > t_max {
1297 return None;
1298 }
1299 }
1300 Some((t_min, normal))
1301}
1302
1303fn ray_sphere(origin: Vec3, dir: Vec3, center: Vec3, radius: f32) -> Option<(f32, Vec3)> {
1304 let oc = origin - center;
1305 let b = oc.dot(dir);
1306 let c = oc.length_squared() - radius * radius;
1307 let disc = b * b - c;
1308 if disc < 0.0 {
1309 return None;
1310 }
1311 let sq = disc.sqrt();
1312 let t = if -b - sq >= 0.0 { -b - sq } else { -b + sq };
1313 if t < 0.0 {
1314 return None;
1315 }
1316 let point = origin + dir * t;
1317 Some((t, (point - center).normalize_or_zero()))
1318}
1319
1320#[cfg(test)]
1321mod tests {
1322 use super::*;
1323 use glam::vec3;
1324
1325 #[test]
1326 fn falls_and_lands() {
1327 let mut w = World::new();
1328 w.set_gravity(vec3(0.0, 0.0, -9.81));
1329 let floor = w.add(Body::fixed(vec3(0.0, 0.0, -0.5)).with_box(10.0, 10.0, 1.0));
1330 let ball = w.add(Body::dynamic(vec3(0.0, 0.0, 2.0)).with_sphere(0.5));
1331 let mut landed = false;
1332 for _ in 0..180 {
1333 w.clear_events();
1334 w.step(1.0 / 60.0);
1335 landed |= w.events().iter().any(|e| matches!(e, PhysicsEvent::CollisionStarted { b, .. } if *b == floor));
1336 }
1337 assert!(landed);
1338 assert!(w.on_ground(ball));
1339 assert!((w.position(ball).z - 0.5).abs() < 0.05, "z = {}", w.position(ball).z);
1340 }
1341
1342 #[test]
1343 fn walls_block_movement() {
1344 let mut w = World::new();
1345 let wall = w.add(Body::fixed(vec3(0.0, 3.0, 0.0)).with_box(10.0, 1.0, 3.0).with_tag("wall"));
1346 let player = w.add(Body::kinematic(Vec3::ZERO).with_box(0.5, 0.5, 1.8));
1347 let r = w.move_by(player, vec3(0.0, 5.0, 0.0));
1348 assert!(r.blocked);
1349 assert_eq!(r.hits, vec![wall]);
1350 assert!(w.position(player).y < 2.3);
1351 let r = w.move_by(player, vec3(0.0, -1.0, 0.0));
1352 assert!(!r.blocked);
1353 }
1354
1355 #[test]
1356 fn sensors_and_rays() {
1357 let mut w = World::new();
1358 let zone = w.add(Body::sensor(vec3(5.0, 0.0, 0.0)).with_box(2.0, 2.0, 2.0));
1359 let p = w.add(Body::kinematic(Vec3::ZERO).with_sphere(0.3));
1360 w.move_by(p, vec3(5.0, 0.0, 0.0));
1361 assert!(w.events().contains(&PhysicsEvent::Entered { sensor: zone, body: p }));
1362 let target = w.add(Body::fixed(vec3(5.0, 10.0, 0.0)).with_box(1.0, 1.0, 1.0));
1363 let hit = w.raycast(w.position(p), Vec3::Y, 50.0, Some(p)).unwrap();
1364 assert_eq!(hit.body, target);
1365 assert!((hit.distance - 9.5).abs() < 1e-3);
1366 }
1367
1368 fn earth() -> (World, BodyId) {
1369 let mut w = World::new();
1370 w.set_gravity(EARTH_GRAVITY);
1371 let floor = w.add(Body::fixed(vec3(0.0, 0.0, -0.5)).with_box(100.0, 100.0, 1.0));
1372 (w, floor)
1373 }
1374
1375 fn run(w: &mut World, seconds: f32) -> Vec<PhysicsEvent> {
1376 let mut events = Vec::new();
1377 for _ in 0..(seconds * 60.0) as usize {
1378 w.clear_events();
1379 w.step(1.0 / 60.0);
1380 events.extend_from_slice(w.events());
1381 }
1382 events
1383 }
1384
1385 #[test]
1386 fn landed_event_reports_fall_speed() {
1387 let (mut w, floor) = earth();
1388 let ball = w.add(Body::dynamic(vec3(0.0, 0.0, 5.5)).with_sphere(0.5));
1389 let events = run(&mut w, 2.0);
1390 let landed: Vec<_> = events.iter().filter(|e| matches!(e, PhysicsEvent::Landed { .. })).collect();
1391 assert_eq!(landed.len(), 1);
1392 let PhysicsEvent::Landed { body, on, speed } = landed[0] else { unreachable!() };
1393 assert_eq!((*body, *on), (ball, floor));
1394 assert!((speed - 9.9).abs() < 0.6, "speed {speed}");
1396 }
1397
1398 #[test]
1399 fn jump_reaches_height() {
1400 let (mut w, _) = earth();
1401 let p = w.add(Body::dynamic(vec3(0.0, 0.0, 1.0)).with_box(1.0, 1.0, 2.0));
1402 run(&mut w, 0.5);
1403 assert!(w.on_ground(p));
1404 assert!(w.jump(p, 2.0));
1405 assert!(!w.jump(p, 2.0), "no double jump");
1406 let mut highest: f32 = 0.0;
1407 for _ in 0..120 {
1408 w.step(1.0 / 60.0);
1409 highest = highest.max(w.position(p).z);
1410 }
1411 assert!((highest - 3.0).abs() < 0.15, "highest {highest}");
1412 }
1413
1414 #[test]
1415 fn forces_mass_and_speed_limit() {
1416 let mut w = World::new();
1417 let light = w.add(Body::dynamic(Vec3::ZERO));
1418 let heavy = w.add(Body::dynamic(vec3(10.0, 0.0, 0.0)).with_mass(10.0));
1419 w.apply_force(light, vec3(60.0, 0.0, 0.0));
1420 w.apply_force(heavy, vec3(60.0, 0.0, 0.0));
1421 w.step(1.0 / 60.0);
1422 assert!((w.velocity(light).x - 1.0).abs() < 1e-4);
1423 assert!((w.velocity(heavy).x - 0.1).abs() < 1e-4);
1424 w.step(1.0 / 60.0);
1426 assert!((w.velocity(light).x - 1.0).abs() < 1e-4);
1427
1428 let (mut w, _) = earth();
1429 let rock = w.add(Body::dynamic(vec3(0.0, 0.0, 100.0)).with_max_speed(5.0));
1430 run(&mut w, 1.0);
1431 assert!(w.velocity(rock).length() <= 5.0 + 1e-3);
1432 }
1433
1434 #[test]
1435 fn heavy_ball_pushes_light_ball() {
1436 let mut w = World::new();
1437 let heavy = w.add(
1438 Body::dynamic(Vec3::ZERO).with_sphere(0.5).with_mass(10.0).with_bounciness(1.0).with_velocity(vec3(5.0, 0.0, 0.0)),
1439 );
1440 let light = w.add(Body::dynamic(vec3(2.0, 0.0, 0.0)).with_sphere(0.5).with_bounciness(1.0));
1441 let events = run(&mut w, 0.5);
1442 assert!(events.iter().any(|e| matches!(e, PhysicsEvent::CollisionStarted { .. })));
1443 assert!((w.velocity(light).x - 9.09).abs() < 0.2, "light {}", w.velocity(light).x);
1445 assert!((w.velocity(heavy).x - 4.09).abs() < 0.2, "heavy {}", w.velocity(heavy).x);
1446 }
1447
1448 #[test]
1449 fn player_pushes_crate_until_wall() {
1450 let (mut w, _) = earth();
1451 w.add(Body::fixed(vec3(5.0, 0.0, 1.0)).with_box(1.0, 10.0, 2.0));
1452 let crate_ = w.add(Body::dynamic(vec3(2.0, 0.0, 0.5)).with_box(1.0, 1.0, 1.0));
1453 let player = w.add(Body::kinematic(vec3(0.0, 0.0, 0.5)).with_box(1.0, 1.0, 1.0));
1454 run(&mut w, 0.3);
1455 let mut blocked = false;
1456 for _ in 0..40 {
1457 blocked = w.move_by(player, vec3(0.1, 0.0, 0.0)).blocked;
1458 run(&mut w, 1.0 / 60.0);
1459 }
1460 assert!((w.position(crate_).x - 4.0).abs() < 0.1, "crate {}", w.position(crate_).x);
1462 assert!(w.position(player).x < 3.1);
1463 assert!(blocked);
1464 }
1465
1466 #[test]
1467 fn platforms_carry_bodies() {
1468 let mut w = World::new();
1469 w.set_gravity(EARTH_GRAVITY);
1470 let lift = w.add(Body::kinematic(Vec3::ZERO).with_box(4.0, 4.0, 1.0).with_velocity(vec3(1.0, 0.0, 0.0)));
1471 let p = w.add(Body::dynamic(vec3(0.0, 0.0, 1.5)).with_box(1.0, 1.0, 1.0));
1472 run(&mut w, 0.5);
1473 assert_eq!(w.get(p).unwrap().ground, Some(lift));
1474 let offset = w.position(p).x - w.position(lift).x;
1475 run(&mut w, 1.5);
1476 assert!((w.position(p).x - w.position(lift).x - offset).abs() < 0.02);
1477 }
1478
1479 #[test]
1480 fn facing_and_event_helpers() {
1481 let mut w = World::new();
1482 let p = w.add(Body::kinematic(Vec3::ZERO).with_sphere(0.3).with_facing(90.0));
1483 let coin = w.add(Body::sensor(vec3(2.0, 0.0, 0.0)).with_sphere(0.5).with_tag("coin"));
1484 assert_eq!(w.turn(p, -90.0), 0.0);
1485 w.turn(p, 90.0);
1486 assert_eq!(w.describe(p, coin), "12 o'clock, 2 meters");
1487 w.move_forward(p, 2.0);
1488 assert_eq!(w.entered_tag(p, "coin"), Some(coin));
1489 assert_eq!(w.triggered(coin), Some(p));
1490 assert_eq!(w.entered_tag(p, "door"), None);
1491 assert!((w.position(p).x - 2.0).abs() < 1e-4);
1492 }
1493
1494 #[test]
1495 fn three_d_movement() {
1496 let mut w = World::new();
1497 let p = w.add(Body::kinematic(Vec3::ZERO).with_sphere(0.3));
1498 let bird = w.add(Body::sensor(vec3(0.0, 4.0, 4.0)).with_sphere(0.3));
1499 assert_eq!(w.describe(p, bird), "12 o'clock, 6 meters, 4 meters above");
1500 assert_eq!(w.tilt(p, 45.0), 45.0);
1501 assert_eq!(w.tilt(p, 100.0), 90.0);
1502 w.set_pitch(p, 45.0);
1503 w.move_forward(p, 2.0_f32.sqrt());
1504 assert!((w.position(p) - vec3(0.0, 1.0, 1.0)).length() < 1e-4);
1505 w.move_up(p, -3.0);
1506 assert!((w.position(p).z + 2.0).abs() < 1e-4);
1507 w.look_at(p, vec3(0.0, 1.0, 5.0));
1508 assert!((w.pitch(p) - 90.0).abs() < 1e-3);
1509 w.add(Body::fixed(vec3(0.0, 1.0, 3.0)).with_box(10.0, 10.0, 1.0));
1511 assert!(w.move_forward(p, 10.0).blocked);
1512 assert!(w.position(p).z < 2.5);
1513 }
1514}