1use crate::{collision::*, math::*, *};
2
3struct Slot<const D: usize> {
4 generation: u32,
5 body: Option<Body<D>>,
6}
7#[derive(Clone, Copy)]
8struct Proxy<const D: usize> {
9 index: usize,
10 min: [f32; D],
11 max: [f32; D],
12}
13#[derive(Clone, Copy)]
14struct Motion<const D: usize> {
15 index: usize,
16 position: [f32; D],
17 velocity: [f32; D],
18}
19#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
20pub struct StepStats {
21 pub bodies: usize,
22 pub broadphase_tests: usize,
23 pub candidate_pairs: usize,
24 pub contacts: usize,
25 pub ccd_impacts: usize,
26 pub ccd_budget_stops: usize,
28}
29
30pub struct PhysicsWorld<const D: usize> {
34 config: WorldConfig<D>,
35 slots: Vec<Slot<D>>,
36 free: Vec<usize>,
37 len: usize,
38 proxies: Vec<Proxy<D>>,
39 pairs: Vec<(usize, usize)>,
40 backup: Vec<Motion<D>>,
41 contacts: Vec<Contact<D>>,
42 next_contacts: Vec<Contact<D>>,
43 events: Vec<ContactEvent<D>>,
44 pending_stops: Vec<ContactEvent<D>>,
45}
46impl<const D: usize> PhysicsWorld<D> {
47 pub fn new(gravity: [f32; D]) -> Self {
51 Self::with_config(WorldConfig::new(gravity)).expect("valid physics world configuration")
52 }
53 pub fn with_config(config: WorldConfig<D>) -> Result<Self, PhysicsError> {
54 if D != 2 && D != 3 {
55 return Err(PhysicsError::InvalidDimension);
56 }
57 if !valid(config.gravity)
58 || config.max_bodies == 0
59 || config.max_bodies > 1_000_000
60 || config.max_pairs == 0
61 || config.max_pairs > 10_000_000
62 || config.max_pair_tests == 0
63 || config.max_pair_tests > 100_000_000
64 || !(1..=64).contains(&config.solver_iterations)
65 || !(1..=16).contains(&config.ccd_iterations)
66 || !config.contact_slop.is_finite()
67 || !(1e-6..=0.1).contains(&config.contact_slop)
68 {
69 return Err(PhysicsError::InvalidConfiguration);
70 }
71 Ok(Self {
72 config,
73 slots: Vec::new(),
74 free: Vec::new(),
75 len: 0,
76 proxies: Vec::new(),
77 pairs: Vec::new(),
78 backup: Vec::new(),
79 contacts: Vec::new(),
80 next_contacts: Vec::new(),
81 events: Vec::new(),
82 pending_stops: Vec::new(),
83 })
84 }
85 pub fn config(&self) -> WorldConfig<D> {
86 self.config
87 }
88 pub fn len(&self) -> usize {
89 self.len
90 }
91 pub fn is_empty(&self) -> bool {
92 self.len == 0
93 }
94 pub fn set_gravity(&mut self, gravity: [f32; D]) -> Result<(), PhysicsError> {
95 if !valid(gravity) {
96 return Err(PhysicsError::InvalidConfiguration);
97 }
98 self.config.gravity = gravity;
99 Ok(())
100 }
101 pub fn insert(&mut self, desc: BodyDesc<D>) -> Result<BodyHandle, PhysicsError> {
102 if !desc.valid() || desc.kind == BodyKind::Static && desc.velocity != [0.; D] {
103 return Err(PhysicsError::InvalidBody);
104 }
105 if self.len >= self.config.max_bodies {
106 return Err(PhysicsError::BodyCapacity);
107 }
108 let body = Body {
109 desc,
110 force: [0.; D],
111 };
112 let index = if let Some(index) = self.free.pop() {
113 self.slots[index].body = Some(body);
114 index
115 } else {
116 if self.slots.len() >= self.config.max_bodies {
118 return Err(PhysicsError::BodyCapacity);
119 }
120 self.slots.push(Slot {
121 generation: 0,
122 body: Some(body),
123 });
124 self.slots.len() - 1
125 };
126 self.len += 1;
127 Ok(self.handle(index))
128 }
129 fn handle(&self, index: usize) -> BodyHandle {
130 BodyHandle {
131 index: index as u32,
132 generation: self.slots[index].generation,
133 }
134 }
135 pub fn body(&self, handle: BodyHandle) -> Option<&Body<D>> {
136 let slot = self.slots.get(handle.index as usize)?;
137 (slot.generation == handle.generation)
138 .then_some(slot.body.as_ref())
139 .flatten()
140 }
141 pub fn body_mut(&mut self, handle: BodyHandle) -> Option<&mut Body<D>> {
142 let slot = self.slots.get_mut(handle.index as usize)?;
143 (slot.generation == handle.generation)
144 .then_some(slot.body.as_mut())
145 .flatten()
146 }
147 pub fn bodies(&self) -> impl Iterator<Item = (BodyHandle, &Body<D>)> {
148 self.slots
149 .iter()
150 .enumerate()
151 .filter_map(|(i, s)| s.body.as_ref().map(|b| (self.handle(i), b)))
152 }
153 pub fn remove(&mut self, handle: BodyHandle) -> Option<Body<D>> {
156 let slot = self.slots.get_mut(handle.index as usize)?;
157 if slot.generation != handle.generation {
158 return None;
159 }
160 let body = slot.body.take()?;
161 if slot.generation != u32::MAX {
162 slot.generation += 1;
163 self.free.push(handle.index as usize);
164 }
165 self.len -= 1;
166 self.contacts.retain(|contact| {
167 if contact.a == handle || contact.b == handle {
168 self.pending_stops.push(ContactEvent {
169 kind: ContactEventKind::Stopped,
170 contact: *contact,
171 });
172 false
173 } else {
174 true
175 }
176 });
177 Some(body)
178 }
179 pub fn contacts(&self) -> &[Contact<D>] {
180 &self.contacts
181 }
182 pub fn events(&self) -> &[ContactEvent<D>] {
185 &self.events
186 }
187
188 pub fn step(&mut self, dt: f32) -> Result<StepStats, PhysicsError> {
191 if !dt.is_finite() || dt <= 0. || dt > 0.1 {
192 return Err(PhysicsError::InvalidTimestep);
193 }
194 self.backup.clear();
195 for (index, slot) in self.slots.iter().enumerate() {
196 if let Some(body) = &slot.body {
197 self.backup.push(Motion {
198 index,
199 position: body.desc.position,
200 velocity: body.desc.velocity,
201 });
202 }
203 }
204 let result = self.advance(dt);
205 if result.is_err() {
206 for m in &self.backup {
207 let b = self.slots[m.index].body.as_mut().unwrap();
208 b.desc.position = m.position;
209 b.desc.velocity = m.velocity;
210 }
211 }
212 result
213 }
214 fn advance(&mut self, dt: f32) -> Result<StepStats, PhysicsError> {
215 let mut stats = StepStats {
216 bodies: self.len,
217 ..StepStats::default()
218 };
219 for slot in &mut self.slots {
220 let Some(body) = &mut slot.body else {
221 continue;
222 };
223 if body.inv_mass() > 0. {
224 let acceleration = add(
225 mul(self.config.gravity, body.desc.gravity_scale),
226 mul(body.force, body.inv_mass()),
227 );
228 body.desc.velocity = mul(
229 add(body.desc.velocity, mul(acceleration, dt)),
230 1. / (1. + body.desc.linear_damping * dt),
231 );
232 }
233 if !valid(body.desc.velocity) {
234 return Err(PhysicsError::NumericalLimit);
235 }
236 if body.desc.kind != BodyKind::Static
237 && !(body.desc.ccd
238 && body.inv_mass() > 0.
239 && matches!(body.desc.shape, Shape::Ball { .. })
240 && !body.desc.sensor)
241 {
242 body.desc.position = add(body.desc.position, mul(body.desc.velocity, dt));
243 }
244 }
245 for index in 0..self.slots.len() {
246 let Some(body) = self.slots[index].body.as_ref() else {
247 continue;
248 };
249 if !body.desc.ccd || body.desc.sensor || body.inv_mass() == 0. {
250 continue;
251 }
252 let Shape::Ball { radius } = body.desc.shape else {
253 continue;
254 };
255 let desc = body.desc;
256 let mut p = desc.position;
257 let mut v = desc.velocity;
258 let mut remaining = dt;
259 let skin = self.config.contact_slop * 0.2;
260 for _ in 0..self.config.ccd_iterations {
261 let displacement = mul(v, remaining);
262 let distance = length(displacement);
263 if distance < 1e-8 {
264 remaining = 0.;
265 break;
266 }
267 let direction = mul(displacement, distance.recip());
268 let filter = QueryFilter {
269 exclude: Some(self.handle(index)),
270 layers: desc.layers,
271 only_static: true,
272 ..QueryFilter::default()
273 };
274 let Some(hit) = self.cast_ball(p, radius, direction, distance, filter) else {
275 p = add(p, displacement);
276 remaining = 0.;
277 break;
278 };
279 stats.ccd_impacts += 1;
280 p = add(p, mul(direction, (hit.distance - skin).max(0.)));
281 let target = self.body(hit.body).unwrap();
282 let normal_speed = dot(v, hit.normal);
283 if normal_speed < 0. {
284 let bounce = if normal_speed < -1. {
285 desc.restitution.max(target.desc.restitution)
286 } else {
287 0.
288 };
289 let normal_change = -(1. + bounce) * normal_speed;
290 v = add(v, mul(hit.normal, normal_change));
291 let tangent = sub(v, mul(hit.normal, dot(v, hit.normal)));
292 let tangent_speed = length(tangent);
293 if tangent_speed > 1e-8 {
294 let friction = (desc.friction * target.desc.friction).sqrt();
295 v = sub(
296 v,
297 mul(tangent, (friction * normal_change / tangent_speed).min(1.)),
298 );
299 }
300 }
301 remaining *= 1. - (hit.distance / distance).clamp(0., 1.);
302 if hit.distance < skin && dot(v, hit.normal) <= 1e-6 {
305 break;
306 }
307 }
308 if remaining > 1e-8 {
309 stats.ccd_budget_stops += 1;
310 }
311 let body = self.slots[index].body.as_mut().unwrap();
312 body.desc.position = p;
313 body.desc.velocity = v;
314 }
315 if self
316 .slots
317 .iter()
318 .filter_map(|s| s.body.as_ref())
319 .any(|b| !valid(b.desc.position))
320 {
321 return Err(PhysicsError::NumericalLimit);
322 }
323 self.broadphase(&mut stats)?;
324 let margin = self.config.contact_slop * 2.;
325 for _ in 0..self.config.solver_iterations {
327 for &(a, b) in &self.pairs {
328 let (left, right) = self.slots.split_at_mut(b);
329 let a = left[a].body.as_mut().unwrap();
330 let b = right[0].body.as_mut().unwrap();
331 if a.desc.sensor || b.desc.sensor {
332 continue;
333 }
334 let Some(m) = contact(
335 a.desc.shape,
336 a.desc.position,
337 b.desc.shape,
338 b.desc.position,
339 margin,
340 ) else {
341 continue;
342 };
343 solve(a, b, m, self.config.contact_slop);
344 }
345 }
346 if self
347 .slots
348 .iter()
349 .filter_map(|s| s.body.as_ref())
350 .any(|b| !valid(b.desc.position) || !valid(b.desc.velocity))
351 {
352 return Err(PhysicsError::NumericalLimit);
353 }
354 self.next_contacts.clear();
355 for &(a, b) in &self.pairs {
356 let aa = self.slots[a].body.as_ref().unwrap();
357 let bb = self.slots[b].body.as_ref().unwrap();
358 if let Some(m) = contact(
359 aa.desc.shape,
360 aa.desc.position,
361 bb.desc.shape,
362 bb.desc.position,
363 if aa.desc.sensor || bb.desc.sensor {
364 0.
365 } else {
366 margin
367 },
368 ) {
369 self.next_contacts.push(Contact {
370 a: self.handle(a),
371 b: self.handle(b),
372 normal: m.normal,
373 penetration: m.penetration,
374 sensor: aa.desc.sensor || bb.desc.sensor,
375 });
376 }
377 }
378 self.next_contacts.sort_unstable_by_key(|c| (c.a, c.b));
379 self.events.clear();
380 self.events.append(&mut self.pending_stops);
381 let mut old = 0;
382 let mut new = 0;
383 while old < self.contacts.len() || new < self.next_contacts.len() {
384 let a = self.contacts.get(old);
385 let b = self.next_contacts.get(new);
386 let order = match (a, b) {
387 (Some(a), Some(b)) => (a.a, a.b).cmp(&(b.a, b.b)),
388 (Some(_), None) => std::cmp::Ordering::Less,
389 (None, Some(_)) => std::cmp::Ordering::Greater,
390 (None, None) => unreachable!(),
391 };
392 match order {
393 std::cmp::Ordering::Less => {
394 self.events.push(ContactEvent {
395 kind: ContactEventKind::Stopped,
396 contact: *a.unwrap(),
397 });
398 old += 1;
399 }
400 std::cmp::Ordering::Greater => {
401 self.events.push(ContactEvent {
402 kind: ContactEventKind::Started,
403 contact: *b.unwrap(),
404 });
405 new += 1;
406 }
407 std::cmp::Ordering::Equal => {
408 old += 1;
409 new += 1;
410 }
411 }
412 }
413 std::mem::swap(&mut self.contacts, &mut self.next_contacts);
414 for body in self.slots.iter_mut().filter_map(|s| s.body.as_mut()) {
415 body.clear_forces();
416 }
417 stats.contacts = self.contacts.len();
418 Ok(stats)
419 }
420 fn broadphase(&mut self, stats: &mut StepStats) -> Result<(), PhysicsError> {
421 self.proxies.clear();
422 self.pairs.clear();
423 let margin = self.config.contact_slop * 2.;
424 for (index, slot) in self.slots.iter().enumerate() {
425 if let Some(b) = &slot.body {
426 let half = b.desc.shape.half().map(|v| v + margin);
427 self.proxies.push(Proxy {
428 index,
429 min: sub(b.desc.position, half),
430 max: add(b.desc.position, half),
431 });
432 }
433 }
434 let mut sum = [0.; D];
437 let mut squared = [0.; D];
438 for p in &self.proxies {
439 for axis in 0..D {
440 let c = (p.min[axis] + p.max[axis]) * 0.5;
441 sum[axis] += c;
442 squared[axis] += c * c;
443 }
444 }
445 let count = self.len.max(1) as f32;
446 let axis = (0..D)
447 .max_by(|&a, &b| {
448 (squared[a] - sum[a] * sum[a] / count)
449 .total_cmp(&(squared[b] - sum[b] * sum[b] / count))
450 })
451 .unwrap();
452 self.proxies.sort_unstable_by(|a, b| {
453 a.min[axis]
454 .total_cmp(&b.min[axis])
455 .then(a.index.cmp(&b.index))
456 });
457 for i in 0..self.proxies.len() {
458 let a = self.proxies[i];
459 for b in &self.proxies[i + 1..] {
460 if b.min[axis] > a.max[axis] {
461 break;
462 }
463 stats.broadphase_tests += 1;
464 if stats.broadphase_tests > self.config.max_pair_tests {
465 return Err(PhysicsError::PairCapacity);
466 }
467 if (0..D).any(|axis| a.min[axis] > b.max[axis] || b.min[axis] > a.max[axis]) {
468 continue;
469 }
470 let aa = self.slots[a.index].body.as_ref().unwrap();
471 let bb = self.slots[b.index].body.as_ref().unwrap();
472 if !aa.desc.layers.allows(bb.desc.layers) {
473 continue;
474 }
475 if aa.desc.kind == BodyKind::Static
476 && bb.desc.kind == BodyKind::Static
477 && !aa.desc.sensor
478 && !bb.desc.sensor
479 {
480 continue;
481 }
482 if self.pairs.len() == self.config.max_pairs {
483 return Err(PhysicsError::PairCapacity);
484 }
485 self.pairs
486 .push((a.index.min(b.index), a.index.max(b.index)));
487 }
488 }
489 self.pairs.sort_unstable();
491 stats.candidate_pairs = self.pairs.len();
492 Ok(())
493 }
494 fn query_allows(&self, handle: BodyHandle, body: &Body<D>, filter: QueryFilter) -> bool {
495 filter.exclude != Some(handle)
496 && filter.layers.allows(body.desc.layers)
497 && (filter.include_sensors || !body.desc.sensor)
498 && (!filter.only_static || body.desc.kind == BodyKind::Static)
499 }
500 pub fn raycast(
503 &self,
504 origin: [f32; D],
505 direction: [f32; D],
506 max_distance: f32,
507 filter: QueryFilter,
508 ) -> Option<RayHit<D>> {
509 if !valid(origin)
510 || !valid(direction)
511 || !max_distance.is_finite()
512 || !(0. ..=1_000_000.).contains(&max_distance)
513 {
514 return None;
515 }
516 let direction = normalize(direction)?;
517 let mut max = max_distance;
518 let mut result = None;
519 for (handle, body) in self.bodies() {
520 if !self.query_allows(handle, body, filter) {
521 continue;
522 }
523 let hit = match body.desc.shape {
524 Shape::Ball { radius } => {
525 ray_ball(origin, direction, body.desc.position, radius, max)
526 }
527 Shape::Box { half_extents } => {
528 ray_box(origin, direction, body.desc.position, half_extents, max)
529 }
530 };
531 if let Some((distance, normal)) = hit {
532 if distance == max && result.is_some() {
533 continue;
534 }
535 max = distance;
536 result = Some(RayHit {
537 body: handle,
538 distance,
539 point: add(origin, mul(direction, distance)),
540 normal,
541 });
542 }
543 }
544 result
545 }
546 pub fn cast_ball(
550 &self,
551 origin: [f32; D],
552 radius: f32,
553 direction: [f32; D],
554 max_distance: f32,
555 filter: QueryFilter,
556 ) -> Option<RayHit<D>> {
557 if !valid(origin)
558 || !valid(direction)
559 || !Shape::<D>::ball(radius).valid()
560 || !max_distance.is_finite()
561 || !(0. ..=1_000_000.).contains(&max_distance)
562 {
563 return None;
564 }
565 let direction = normalize(direction)?;
566 let mut max = max_distance;
567 let mut result = None;
568 for (handle, body) in self.bodies() {
569 if !self.query_allows(handle, body, filter) {
570 continue;
571 }
572 let hit = match body.desc.shape {
573 Shape::Ball {
574 radius: target_radius,
575 } => {
576 let hit = ray_ball(
577 origin,
578 direction,
579 body.desc.position,
580 radius + target_radius,
581 max,
582 );
583 hit.filter(|(distance, normal)| {
584 *distance > 1e-6
585 || dot(direction, *normal) < 0.
586 || length(sub(origin, body.desc.position))
587 < radius + target_radius - 1e-5
588 })
589 }
590 Shape::Box { half_extents } => sweep_ball_box(
591 origin,
592 radius,
593 direction,
594 body.desc.position,
595 half_extents,
596 max,
597 ),
598 };
599 if let Some((distance, normal)) = hit {
600 if distance == max && result.is_some() {
601 continue;
602 }
603 max = distance;
604 result = Some(RayHit {
605 body: handle,
606 distance,
607 point: add(origin, mul(direction, distance)),
608 normal,
609 });
610 }
611 }
612 result
613 }
614}
615
616fn solve<const D: usize>(a: &mut Body<D>, b: &mut Body<D>, m: Manifold<D>, slop: f32) {
617 let ma = a.inv_mass();
618 let mb = b.inv_mass();
619 let inverse_mass = ma + mb;
620 if inverse_mass == 0. {
621 return;
622 }
623 let relative = sub(b.desc.velocity, a.desc.velocity);
624 let speed = dot(relative, m.normal);
625 if speed < 0. {
626 let restitution = if speed < -1. {
628 a.desc.restitution.max(b.desc.restitution)
629 } else {
630 0.
631 };
632 let impulse = -(1. + restitution) * speed / inverse_mass;
633 a.desc.velocity = sub(a.desc.velocity, mul(m.normal, impulse * ma));
634 b.desc.velocity = add(b.desc.velocity, mul(m.normal, impulse * mb));
635 let tangent = sub(relative, mul(m.normal, speed));
636 let tangent_speed = length(tangent);
637 if tangent_speed > 1e-8 {
638 let friction = (a.desc.friction * b.desc.friction).sqrt();
639 let friction_impulse = (tangent_speed / inverse_mass).min(friction * impulse);
640 let friction_vector = mul(tangent, friction_impulse / tangent_speed);
641 a.desc.velocity = add(a.desc.velocity, mul(friction_vector, ma));
642 b.desc.velocity = sub(b.desc.velocity, mul(friction_vector, mb));
643 }
644 }
645 let correction = (m.penetration - slop).max(0.) * 0.75 / inverse_mass;
646 a.desc.position = sub(a.desc.position, mul(m.normal, correction * ma));
647 b.desc.position = add(b.desc.position, mul(m.normal, correction * mb));
648}