1use crate::{
2 body::{Access, BodySet, Collider, ColliderHandle, ColliderSet, Kind, RigidBodyHandle, Shape},
3 geometry::{self, Bounds, Point},
4};
5use ferravex_math::{Quat, Vec3};
6mod cache;
7mod compounds;
8mod islands;
9mod joints;
10use cache::Manifold;
11pub use cache::{ContactManifold, ContactPoint, SolverSettings};
12pub use compounds::{MAX_CHILDREN, MAX_COLLIDERS};
13use islands::Islands;
14use joints::{JointPhase, JointRow};
15
16#[derive(Clone, Copy, Debug)]
18pub struct ContactPair {
19 pub collider1: ColliderHandle,
20 pub collider2: ColliderHandle,
21 pub normal: [f32; 3],
22 pub sensor: bool,
23}
24impl ContactPair {
25 pub fn has_any_active_contact(&self) -> bool {
26 !self.sensor
27 }
28}
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub enum ContactEvent {
31 Started(ColliderHandle, ColliderHandle),
32 Stopped(ColliderHandle, ColliderHandle),
33}
34#[derive(Clone, Copy, Debug, Default)]
35pub struct StepStats {
36 pub substeps: usize,
37 pub broadphase_pairs: usize,
38 pub contact_points: usize,
39 pub ccd_impacts: usize,
40 pub sleeping_bodies: usize,
41 pub islands: usize,
42 pub active_islands: usize,
43 pub sleeping_islands: usize,
44 pub persisted_points: usize,
45 pub warm_started_points: usize,
46 pub reused_manifolds: usize,
47 pub cached_manifolds: usize,
48 pub cache_dropped: usize,
49 pub velocity_constraints: usize,
50 pub joint_rows: usize,
51 pub joint_constraints: usize,
52}
53#[derive(Clone, Debug)]
54struct Constraint {
55 a: RigidBodyHandle,
56 b: RigidBodyHandle,
57 normal: Vec3,
58 point: Vec3,
59 local_a: Vec3,
60 local_b: Vec3,
61 normal_impulse: f32,
62 tangent_impulse: Vec3,
63 bounce: f32,
64 friction: f32,
65 key: (ColliderHandle, ColliderHandle),
66 feature: u64,
67 cache: Option<(usize, usize)>,
68}
69#[derive(Clone, Copy)]
70struct Proxy {
71 body: RigidBodyHandle,
72 collider: ColliderHandle,
73 bounds: Bounds,
74}
75pub struct World<B> {
78 pub bodies: BodySet<B>,
79 pub colliders: ColliderSet,
80 pub gravity: Vec3,
81 pub joints: crate::JointSet,
82 joint_rows: Vec<JointRow>,
83 joint_solver: Option<fn(&mut Self, JointPhase, f32, &mut StepStats)>,
84 disabled_joint_pairs: Vec<(RigidBodyHandle, RigidBodyHandle)>,
85 planar: bool,
86 contacts: Vec<ContactPair>,
87 next_contacts: Vec<ContactPair>,
88 manifolds: Vec<Manifold>,
89 next_manifolds: Vec<Manifold>,
90 next_feature: u64,
91 settings: SolverSettings,
92 islands: Islands,
93 last_gravity: Vec3,
94 old_positions: Vec<(RigidBodyHandle, Vec3, Quat)>,
95 mass_updates: Vec<(RigidBodyHandle, Option<crate::mass::Mass>)>,
96 events: Vec<ContactEvent>,
97 constraints: Vec<Constraint>,
98 proxies: Vec<Proxy>,
99 points: Vec<Point>,
100 pub last_step: StepStats,
101}
102impl<B: Access> World<B> {
103 pub(crate) fn new(gravity: Vec3, planar: bool) -> Self {
104 assert!(gravity.is_finite());
105 Self {
106 bodies: BodySet::default(),
107 colliders: ColliderSet::default(),
108 gravity,
109 joints: Default::default(),
110 joint_rows: Vec::new(),
111 joint_solver: None,
112 disabled_joint_pairs: Vec::new(),
113 planar,
114 contacts: Vec::new(),
115 next_contacts: Vec::new(),
116 manifolds: Vec::new(),
117 next_manifolds: Vec::new(),
118 next_feature: 1,
119 settings: Default::default(),
120 islands: Default::default(),
121 last_gravity: gravity,
122 old_positions: Vec::new(),
123 mass_updates: Vec::new(),
124 events: Vec::new(),
125 constraints: Vec::new(),
126 proxies: Vec::new(),
127 points: Vec::with_capacity(8),
128 last_step: StepStats::default(),
129 }
130 }
131 pub fn insert(
132 &mut self,
133 b: impl Into<B>,
134 c: impl Into<Collider>,
135 ) -> (RigidBodyHandle, ColliderHandle) {
136 let body = self
137 .insert_compound(b, [c.into()])
138 .expect("valid body/collider within capacities");
139 (body, self.bodies[body].core().colliders[0])
140 }
141
142 pub fn remove_body(&mut self, h: RigidBodyHandle) -> bool {
143 if self.bodies.get(h).is_none() {
144 return false;
145 }
146 loop {
147 let id = self
148 .joints
149 .iter()
150 .find(|(_, j)| j.a == h || j.b == h)
151 .map(|(id, _)| id);
152 let Some(id) = id else {
153 break;
154 };
155 self.remove_joint(id);
156 }
157 let Some(b) = self.bodies.remove(h) else {
158 return false;
159 };
160 for &c in &b.core().colliders {
161 for contact in &self.contacts {
162 if contact.sensor {
163 continue;
164 }
165 let other = if contact.collider1 == c {
166 Some(contact.collider2)
167 } else if contact.collider2 == c {
168 Some(contact.collider1)
169 } else {
170 None
171 };
172 if let Some(other) = other
173 .and_then(|id| self.colliders.get(id))
174 .and_then(|o| o.parent)
175 && let Some(body) = self.bodies.get_mut(other)
176 {
177 body.core_mut().wake();
178 }
179 }
180 self.manifolds.retain(|m| m.key.0 != c && m.key.1 != c);
181 self.next_manifolds.retain(|m| m.key.0 != c && m.key.1 != c);
182 self.colliders.remove(c);
183 }
184 true
185 }
186 pub fn contact_pair(&self, a: ColliderHandle, b: ColliderHandle) -> Option<&ContactPair> {
187 let key = pair_key(a, b);
188 self.contacts
189 .binary_search_by_key(&key, |c| pair_key(c.collider1, c.collider2))
190 .ok()
191 .map(|i| &self.contacts[i])
192 }
193 pub fn contact_events(&self) -> &[ContactEvent] {
194 &self.events
195 }
196 pub fn contacts(&self) -> impl Iterator<Item = &ContactPair> {
197 self.contacts.iter()
198 }
199 pub fn step(&mut self, dt: f32) -> StepStats {
202 assert!(
203 dt.is_finite() && dt > 0. && dt <= 0.1,
204 "use a fixed timestep in (0, 0.1]"
205 );
206 assert!(self.gravity.is_finite());
207 self.refresh_mass_properties()
208 .expect("valid compound mass properties");
209 self.run_joints(JointPhase::Refresh, dt, &mut StepStats::default());
210 self.next_contacts.clear();
211 self.events.clear();
212 let mut substeps = 1;
213 for (_, body) in self.bodies.iter_mut() {
214 let b = body.core_mut();
215 if b.kind == Kind::Kinematic {
216 b.velocity = b.next_position.map_or(Vec3::ZERO, |p| {
217 (p + b.next_rotation.unwrap_or(b.rotation) * b.local_com - b.world_com()) / dt
218 });
219 b.angular_velocity = b
220 .next_rotation
221 .map_or(Vec3::ZERO, |r| rotation_delta(b.rotation, r) / dt);
222 substeps = substeps.max((b.angular_velocity.length() * dt / 0.06).ceil() as usize);
223 }
224 if b.kind == Kind::Dynamic && b.ccd {
225 let mut size = f32::INFINITY;
226 for child in &b.colliders {
227 let extent = match self.colliders[*child].shape {
228 Shape::Ball(r) => r,
229 Shape::Box(h) => h.x.min(h.y).min(if self.planar { h.x } else { h.z }),
230 #[cfg(feature = "dim2")]
231 Shape::Capsule { radius, .. } => radius,
232 #[cfg(feature = "dim3")]
233 Shape::Cylinder {
234 radius,
235 half_height,
236 } => radius.min(half_height),
237 };
238 size = size.min(extent);
239 }
240 substeps = substeps.max((b.velocity.length() * dt / (size * 0.75)).ceil() as usize);
241 substeps = substeps.max((b.angular_velocity.length() * dt / 0.06).ceil() as usize);
242 }
243 }
244 for (_, j) in self.joints.iter() {
245 if j.description().enabled {
246 let speed =
247 joints::motor_speed(j, self.bodies[j.a].core(), self.bodies[j.b].core());
248 substeps = substeps.max((speed * dt / 0.06).ceil() as usize);
249 }
250 }
251 substeps = substeps.clamp(1, 8);
252 let h = dt / substeps as f32;
253 let mut stats = StepStats {
254 substeps,
255 ..StepStats::default()
256 };
257 let gravity_changed = self.gravity != self.last_gravity;
258 self.wake_previous_islands(gravity_changed);
259 if gravity_changed {
260 self.manifolds.clear();
261 }
262 for _ in 0..substeps {
263 self.old_positions.clear();
264 self.old_positions.extend(
265 self.bodies
266 .iter()
267 .map(|(id, b)| (id, b.core().position, b.core().rotation)),
268 );
269 for (_, body) in self.bodies.iter_mut() {
270 let b = body.core_mut();
271 if b.kind == Kind::Dynamic && !b.sleeping {
272 b.velocity += (self.gravity * b.gravity_scale + b.force * b.inv_mass) * h;
273 b.angular_velocity += b.inverse_inertia(b.torque) * h;
274 b.velocity /= 1. + b.linear_damping * h;
275 b.angular_velocity /= 1. + b.angular_damping * h;
276 }
277 if b.kind != Kind::Fixed && !b.sleeping {
278 b.integrate(h);
279 }
280 }
281 stats.ccd_impacts += self.sweep_balls();
282 self.find_contacts(h, &mut stats);
283 self.wake_contact_islands();
284 self.warm_start(&mut stats);
285 self.run_joints(JointPhase::Prepare, h, &mut stats);
286 self.solve_velocities(&mut stats);
287 self.run_joints(JointPhase::Integrate, h, &mut stats);
288 self.solve_positions();
289 self.store_manifolds(h);
290 self.run_joints(JointPhase::Store, h, &mut stats);
291 self.sleep_islands(h, &mut stats);
292 }
293 for (_, body) in self.bodies.iter_mut() {
294 let b = body.core_mut();
295 b.force = Vec3::ZERO;
296 b.torque = Vec3::ZERO;
297 if b.kind == Kind::Kinematic {
298 if let Some(p) = b.next_position.take() {
299 b.position = p;
300 }
301 if let Some(q) = b.next_rotation.take() {
302 b.set_rotation(q);
303 }
304 }
305 b.wake_requested = false;
306 b.solver_dirty = false;
307 if b.sleeping {
308 stats.sleeping_bodies += 1;
309 }
310 }
311 self.next_contacts
312 .sort_unstable_by_key(|c| pair_key(c.collider1, c.collider2));
313 self.next_contacts
314 .dedup_by_key(|c| pair_key(c.collider1, c.collider2));
315 for c in &self.next_contacts {
316 if self
317 .contacts
318 .binary_search_by_key(&pair_key(c.collider1, c.collider2), |c| {
319 pair_key(c.collider1, c.collider2)
320 })
321 .is_err()
322 {
323 self.events
324 .push(ContactEvent::Started(c.collider1, c.collider2));
325 }
326 }
327 for c in &self.contacts {
328 if self
329 .next_contacts
330 .binary_search_by_key(&pair_key(c.collider1, c.collider2), |c| {
331 pair_key(c.collider1, c.collider2)
332 })
333 .is_err()
334 {
335 self.events
336 .push(ContactEvent::Stopped(c.collider1, c.collider2));
337 }
338 }
339 std::mem::swap(&mut self.contacts, &mut self.next_contacts);
340 self.next_contacts.clear();
341 for (_, c) in self.colliders.iter_mut() {
342 c.dirty = false;
343 }
344 self.last_gravity = self.gravity;
345 stats.cached_manifolds = self.manifolds.len();
346 self.last_step = stats;
347 ferravex_core::telemetry::record_physics(
348 stats.broadphase_pairs,
349 stats.contact_points,
350 stats.ccd_impacts,
351 );
352 stats
353 }
354 fn sweep_balls(&mut self) -> usize {
355 let mut hits = 0;
356 for &(id, start_origin, start_rotation) in &self.old_positions {
357 let body = self.bodies[id].core();
358 if !body.ccd || body.kind != Kind::Dynamic {
359 continue;
360 }
361 let mut closest = 1.;
362 let mut correction = None;
363 for &child in &body.colliders {
364 let collider = &self.colliders[child];
365 if collider.sensor {
366 continue;
367 }
368 let Shape::Ball(radius) = collider.shape else {
369 continue;
370 };
371 let start = start_origin + start_rotation * collider.local_translation;
372 let end = collider.pose(body).position;
373 for &(other, old_origin, old_rotation) in &self.old_positions {
374 if other == id || self.joint_contacts_disabled(id, other) {
375 continue;
376 }
377 let target = self.bodies[other].core();
378 for &target_child in &target.colliders {
379 let tc = &self.colliders[target_child];
380 if tc.sensor
381 || collider.membership & tc.filter == 0
382 || tc.membership & collider.filter == 0
383 {
384 continue;
385 }
386 let target_pose = tc.pose(target);
387 let target_start = old_origin + old_rotation * tc.local_translation;
388 let relative_start = start - target_start + target_pose.position;
389 if relative_start.distance_squared(end) < radius * radius * 0.04 {
390 continue;
391 }
392 let swept = Bounds {
393 min: relative_start.min(end) - Vec3::splat(radius),
394 max: relative_start.max(end) + Vec3::splat(radius),
395 };
396 if !swept.intersects(geometry::bounds(
397 tc.shape,
398 target_pose.position,
399 target_pose.rotation,
400 )) {
401 continue;
402 }
403 if let Some((t, n)) = geometry::sweep_ball(
404 relative_start,
405 end,
406 radius,
407 tc.shape,
408 target_pose.position,
409 target_pose.rotation,
410 self.planar,
411 ) && t < closest
412 && (end - relative_start).dot(n) < -1e-7
413 {
414 closest = t;
415 correction = Some(
416 relative_start + (end - relative_start) * t + n * 0.00002 - end,
417 );
418 }
419 }
420 }
421 }
422 if let Some(delta) = correction {
423 self.bodies[id].core_mut().position += delta;
424 hits += 1;
425 }
426 }
427 hits
428 }
429 fn solve_velocities(&mut self, stats: &mut StepStats) {
430 for _ in 0..self.settings.velocity_iterations {
431 for c in &mut self.constraints {
432 let (a, b) = self.bodies.two_mut(c.a, c.b);
433 let a = a.core_mut();
434 let b = b.core_mut();
435 if a.sleeping && b.sleeping
436 || a.sleeping && b.kind != Kind::Dynamic
437 || b.sleeping && a.kind != Kind::Dynamic
438 {
439 continue;
440 }
441 stats.velocity_constraints += 1;
442 let ra = c.point - a.world_com();
443 let rb = c.point - b.world_com();
444 let denom = effective_mass(a, b, ra, rb, c.normal);
445 if denom <= 1e-10 {
446 continue;
447 }
448 let relative = b.point_velocity(c.point) - a.point_velocity(c.point);
449 let change = (c.bounce - relative.dot(c.normal)) / denom;
450 let accumulated = (c.normal_impulse + change).max(0.);
451 let j = c.normal * (accumulated - c.normal_impulse);
452 c.normal_impulse = accumulated;
453 a.impulse(-j, c.point);
454 b.impulse(j, c.point);
455 let relative = b.point_velocity(c.point) - a.point_velocity(c.point);
456 let tangent = relative - c.normal * relative.dot(c.normal);
457 let mut candidate = c.tangent_impulse;
458 if let Some(t) = tangent.try_normalize() {
459 let denominator = effective_mass(a, b, ra, rb, t);
460 if denominator > 1e-10 {
461 candidate -= t * (relative.dot(t) / denominator);
462 }
463 }
464 let new = candidate.clamp_length_max(c.friction * c.normal_impulse);
467 let j = new - c.tangent_impulse;
468 c.tangent_impulse = new;
469 a.impulse(-j, c.point);
470 b.impulse(j, c.point);
471 }
472 self.run_joints(JointPhase::Velocity, 0., stats);
473 }
474 }
475 fn solve_positions(&mut self) {
476 for _ in 0..self.settings.position_iterations {
477 for c in &self.constraints {
478 let (a, b) = self.bodies.two_mut(c.a, c.b);
479 let a = a.core_mut();
480 let b = b.core_mut();
481 if a.contact_inv_mass() + b.contact_inv_mass() == 0. {
482 continue;
483 }
484 let pa = a.position + a.rotation * c.local_a;
485 let pb = b.position + b.rotation * c.local_b;
486 let ra = a.rotation * (c.local_a - a.local_com);
487 let rb = b.rotation * (c.local_b - b.local_com);
488 let depth = -(pb - pa).dot(c.normal);
489 if depth <= 0.0005 {
490 continue;
491 }
492 let denom = effective_mass(a, b, ra, rb, c.normal);
493 if denom <= 1e-10 {
494 continue;
495 }
496 let j = c.normal * ((depth - 0.0005).min(0.2) * 0.4 / denom);
497 a.position_impulse(-j, ra.cross(-j));
498 b.position_impulse(j, rb.cross(j));
499 }
500 self.run_joints(JointPhase::Position, 0., &mut StepStats::default());
501 }
502 }
503 pub(crate) fn cast_ray(
504 &self,
505 origin: Vec3,
506 direction: Vec3,
507 max_distance: f32,
508 ) -> Option<(RigidBodyHandle, ColliderHandle, f32, Vec3, Vec3)> {
509 self.cast_sphere(origin, direction, max_distance, 0., u32::MAX, None)
510 }
511 pub(crate) fn cast_sphere(
513 &self,
514 origin: Vec3,
515 direction: Vec3,
516 max_distance: f32,
517 radius: f32,
518 mask: u32,
519 ignore: Option<RigidBodyHandle>,
520 ) -> Option<(RigidBodyHandle, ColliderHandle, f32, Vec3, Vec3)> {
521 if !origin.is_finite()
522 || !direction.is_finite()
523 || !max_distance.is_finite()
524 || max_distance < 0.
525 || !radius.is_finite()
526 || !(0. ..=1000.).contains(&radius)
527 {
528 return None;
529 }
530 let direction = direction.try_normalize()?;
531 let mut best = None;
532 let mut limit = max_distance;
533 for (id, c) in self.colliders.iter() {
534 if c.sensor || c.membership & mask == 0 || c.parent == ignore {
535 continue;
536 }
537 let body = c.parent.unwrap();
538 let b = self.bodies[body].core();
539 let pose = c.pose(b);
540 let (d, n) =
541 geometry::distance(c.shape, pose.position, pose.rotation, origin, self.planar);
542 let hit = if d <= radius {
543 Some((0., n))
544 } else {
545 geometry::sweep_ball(
546 origin,
547 origin + direction * limit,
548 radius,
549 c.shape,
550 pose.position,
551 pose.rotation,
552 self.planar,
553 )
554 .map(|(t, n)| (t * limit, n))
555 };
556 if let Some((distance, normal)) = hit {
557 limit = distance;
558 best = Some((body, id, distance, origin + direction * distance, normal));
559 }
560 }
561 best
562 }
563}
564fn pair_key(a: ColliderHandle, b: ColliderHandle) -> (ColliderHandle, ColliderHandle) {
565 if a < b { (a, b) } else { (b, a) }
566}
567fn effective_mass(
568 a: &crate::body::Core,
569 b: &crate::body::Core,
570 ra: Vec3,
571 rb: Vec3,
572 n: Vec3,
573) -> f32 {
574 a.contact_inv_mass()
575 + b.contact_inv_mass()
576 + n.dot(
577 a.contact_inverse_inertia(ra.cross(n)).cross(ra)
578 + b.contact_inverse_inertia(rb.cross(n)).cross(rb),
579 )
580}
581fn rotation_delta(from: Quat, to: Quat) -> Vec3 {
582 let mut delta = (to * from.conjugate()).normalize();
583 if delta.w < 0. {
584 delta = -delta;
585 }
586 let axis = Vec3::new(delta.x, delta.y, delta.z);
587 let length = axis.length();
588 if length < 1e-9 {
589 Vec3::ZERO
590 } else {
591 axis * (2. * length.atan2(delta.w) / length)
592 }
593}