1use std::sync::mpsc;
10
11use rapier2d::prelude as rp;
12use serde::{Deserialize, Serialize};
13
14use crate::collision::{BodyKind, CollisionStarted, CollisionStopped};
15use crate::{Ctx, Entity, Transform2D, Vec2, World};
16
17#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
28#[serde(default)]
29pub struct RigidBody {
30 pub kind: BodyKind,
32 pub gravity_scale: f32,
34 pub linear_damping: f32,
36 pub angular_damping: f32,
38 pub lock_rotation: bool,
40 pub ccd: bool,
43}
44
45impl Default for RigidBody {
46 fn default() -> Self {
47 Self::dynamic()
48 }
49}
50
51impl RigidBody {
52 #[must_use]
54 pub const fn dynamic() -> Self {
55 Self {
56 kind: BodyKind::Dynamic,
57 gravity_scale: 1.0,
58 linear_damping: 0.0,
59 angular_damping: 0.0,
60 lock_rotation: false,
61 ccd: false,
62 }
63 }
64 #[must_use]
66 pub const fn fixed() -> Self {
67 Self {
68 kind: BodyKind::Fixed,
69 ..Self::dynamic()
70 }
71 }
72 #[must_use]
74 pub const fn kinematic() -> Self {
75 Self {
76 kind: BodyKind::Kinematic,
77 ..Self::dynamic()
78 }
79 }
80}
81
82pub const MAX_POLYGON_POINTS: usize = 8;
84
85#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
88#[non_exhaustive]
89pub enum Shape {
90 Circle {
92 radius: f32,
94 },
95 Rect {
97 size: Vec2,
99 },
100 Capsule {
102 height: f32,
104 radius: f32,
106 },
107 Polygon {
111 points: [Vec2; MAX_POLYGON_POINTS],
113 count: u8,
115 },
116}
117
118impl Shape {
119 #[must_use]
128 pub fn points(&self) -> &[Vec2] {
129 match self {
130 Shape::Polygon { points, count } => &points[..usize::from(*count).min(MAX_POLYGON_POINTS)],
131 _ => &[],
132 }
133 }
134}
135
136#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
146pub struct Collider {
147 pub shape: Shape,
149 #[serde(default = "one")]
151 pub density: f32,
152 #[serde(default = "half")]
154 pub friction: f32,
155 #[serde(default)]
157 pub restitution: f32,
158 #[serde(default)]
160 pub sensor: bool,
161 #[serde(default)]
163 pub offset: Vec2,
164}
165
166fn one() -> f32 {
168 1.0
169}
170fn half() -> f32 {
171 0.5
172}
173
174impl Collider {
175 fn of(shape: Shape) -> Self {
176 Self {
177 shape,
178 density: 1.0,
179 friction: 0.5,
180 restitution: 0.0,
181 sensor: false,
182 offset: Vec2::ZERO,
183 }
184 }
185 #[must_use]
187 pub fn circle(radius: f32) -> Self {
188 Self::of(Shape::Circle { radius })
189 }
190 #[must_use]
192 pub fn rect(size: Vec2) -> Self {
193 Self::of(Shape::Rect { size })
194 }
195 #[must_use]
197 pub fn capsule(height: f32, radius: f32) -> Self {
198 Self::of(Shape::Capsule { height, radius })
199 }
200 #[must_use]
215 #[track_caller]
216 pub fn polygon(points: &[Vec2]) -> Self {
217 assert!(
218 (3..=MAX_POLYGON_POINTS).contains(&points.len()),
219 "Collider::polygon: 3 to {MAX_POLYGON_POINTS} points, got {}",
220 points.len()
221 );
222 assert!(
223 points.iter().all(|p| p.is_finite()),
224 "Collider::polygon: points must be finite"
225 );
226 let mut corners = [Vec2::ZERO; MAX_POLYGON_POINTS];
227 corners[..points.len()].copy_from_slice(points);
228 Self::of(Shape::Polygon {
229 points: corners,
230 count: points.len() as u8,
231 })
232 }
233 #[must_use]
235 pub fn density(self, density: f32) -> Self {
236 Self { density, ..self }
237 }
238 #[must_use]
240 pub fn friction(self, friction: f32) -> Self {
241 Self { friction, ..self }
242 }
243 #[must_use]
245 pub fn restitution(self, restitution: f32) -> Self {
246 Self { restitution, ..self }
247 }
248 #[must_use]
250 pub fn sensor(self) -> Self {
251 Self { sensor: true, ..self }
252 }
253 #[must_use]
255 pub fn offset(self, offset: Vec2) -> Self {
256 Self { offset, ..self }
257 }
258}
259
260#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
269#[serde(default)]
270#[repr(C)] pub struct Velocity {
272 pub linear: Vec2,
274 pub angular: f32,
276}
277
278impl Velocity {
279 #[must_use]
281 pub const fn linear(linear: Vec2) -> Self {
282 Self { linear, angular: 0.0 }
283 }
284}
285
286#[derive(Clone, Copy, Debug, PartialEq)]
288pub struct RayHit {
289 pub entity: Entity,
291 pub point: Vec2,
293 pub normal: Vec2,
295 pub distance: f32,
297}
298
299#[derive(Clone, Copy)]
301struct Link {
302 entity: Entity,
303 body: rp::RigidBodyHandle,
304 rigid: RigidBody,
305 shape: Collider,
306 transform: Transform2D,
307 velocity: Velocity,
308 seen: u64,
309}
310
311pub struct Physics {
315 world: rp::PhysicsWorld,
316 links: Vec<Option<Link>>,
318 gravity: Vec2,
319 step: u64,
320 collisions: mpsc::Receiver<rp::CollisionEvent>,
321 collector: rp::ChannelEventCollector,
322 _forces: mpsc::Receiver<rp::ContactForceEvent>,
323 _tears: mpsc::Receiver<rp::SoftBodyTearEvent>,
324}
325
326impl Default for Physics {
327 fn default() -> Self {
328 Self::new(Vec2::ZERO)
329 }
330}
331
332fn to_rp(v: Vec2) -> rp::Vector {
333 rp::Vector::new(v.x, v.y)
334}
335fn from_rp(v: rp::Vector) -> Vec2 {
336 Vec2::new(v.x, v.y)
337}
338
339impl Physics {
340 #[must_use]
343 pub fn new(gravity: Vec2) -> Self {
344 let (collision_tx, collisions) = mpsc::channel();
345 let (force_tx, forces) = mpsc::channel();
346 let (tear_tx, tears) = mpsc::channel();
347 let mut world = rp::PhysicsWorld::new();
348 world.integration_parameters.length_unit = 50.0;
351 Self {
352 world,
353 links: Vec::new(),
354 gravity,
355 step: 0,
356 collisions,
357 collector: rp::ChannelEventCollector::new(collision_tx, force_tx, tear_tx),
358 _forces: forces,
359 _tears: tears,
360 }
361 }
362
363 pub fn gravity(&self) -> Vec2 {
365 self.gravity
366 }
367 pub fn set_gravity(&mut self, gravity: Vec2) {
369 self.gravity = gravity;
370 }
371
372 pub fn body_count(&self) -> usize {
374 self.world.bodies.len()
375 }
376
377 fn entity_of(&self, collider: rp::ColliderHandle) -> Option<Entity> {
378 self.world
379 .colliders
380 .get(collider)
381 .map(|c| Entity::from_bits(c.user_data as u64))
382 }
383
384 pub fn cast_ray(&self, origin: Vec2, direction: Vec2, max_distance: f32) -> Option<RayHit> {
395 let dir = direction.normalize_or_zero();
396 if dir == Vec2::ZERO {
397 return None;
398 }
399 let ray = rp::Ray::new(to_rp(origin), to_rp(dir));
400 let (handle, hit) = self
401 .world
402 .cast_ray_and_get_normal(&ray, max_distance, true, rp::QueryFilter::default())?;
403 Some(RayHit {
404 entity: self.entity_of(handle)?,
405 point: origin + dir * hit.time_of_impact,
406 normal: from_rp(hit.normal),
407 distance: hit.time_of_impact,
408 })
409 }
410
411 pub fn entities_at(&self, point: Vec2, out: &mut Vec<Entity>) {
413 out.clear();
414 for (handle, _) in self.world.intersect_point(to_rp(point), rp::QueryFilter::default()) {
415 out.extend(self.entity_of(handle));
416 }
417 }
418
419 pub fn entities_in_circle(&self, center: Vec2, radius: f32, out: &mut Vec<Entity>) {
421 out.clear();
422 let ball = rp::Ball::new(radius);
423 let pose = rp::Pose::from_translation(to_rp(center));
424 for (handle, _) in self.world.intersect_shape(pose, &ball, rp::QueryFilter::default()) {
425 out.extend(self.entity_of(handle));
426 }
427 }
428
429 pub fn apply_impulse(&mut self, e: Entity, impulse: Vec2) -> bool {
432 let Some(link) = self.link(e) else { return false };
433 let handle = link.body;
434 match self.world.bodies.get_mut(handle) {
435 Some(body) => {
436 body.apply_impulse(to_rp(impulse), true);
437 true
438 }
439 None => false,
440 }
441 }
442
443 pub fn mass(&self, e: Entity) -> Option<f32> {
445 let link = self.link(e)?;
446 self.world.bodies.get(link.body).map(|b| b.mass())
447 }
448
449 fn link(&self, e: Entity) -> Option<&Link> {
450 self.links.get(e.index() as usize)?.as_ref().filter(|l| l.entity == e)
451 }
452
453 fn insert(&mut self, e: Entity, t: &Transform2D, rigid: RigidBody, shape: Collider, velocity: Velocity) {
454 let body = self.world.insert_body(body_of(t, rigid, velocity));
455 self.world.insert_collider(collider_of(shape, e), Some(body));
456 let i = e.index() as usize;
457 if self.links.len() <= i {
458 self.links.resize(i + 1, None);
459 }
460 self.links[i] = Some(Link {
461 entity: e,
462 body,
463 rigid,
464 shape,
465 transform: *t,
466 velocity,
467 seen: self.step,
468 });
469 }
470
471 fn remove(&mut self, i: usize) {
472 if let Some(link) = self.links[i].take() {
473 self.world.remove_body(link.body);
474 }
475 }
476}
477
478fn body_of(t: &Transform2D, rigid: RigidBody, velocity: Velocity) -> rp::RigidBody {
479 let builder = match rigid.kind {
480 BodyKind::Dynamic => rp::RigidBodyBuilder::dynamic(),
481 BodyKind::Fixed => rp::RigidBodyBuilder::fixed(),
482 BodyKind::Kinematic => rp::RigidBodyBuilder::kinematic_velocity_based(),
483 };
484 let mut builder = builder
485 .pose(rp::Pose::new(to_rp(t.position), t.rotation))
486 .linvel(to_rp(velocity.linear))
487 .angvel(velocity.angular)
488 .gravity_scale(rigid.gravity_scale)
489 .linear_damping(rigid.linear_damping)
490 .angular_damping(rigid.angular_damping)
491 .ccd_enabled(rigid.ccd);
492 if rigid.lock_rotation {
493 builder = builder.lock_rotations();
494 }
495 builder.build()
496}
497
498fn collider_of(c: Collider, e: Entity) -> rp::Collider {
499 let builder = match c.shape {
500 Shape::Circle { radius } => rp::ColliderBuilder::ball(radius.max(f32::EPSILON)),
501 Shape::Rect { size } => {
502 rp::ColliderBuilder::cuboid((size.x * 0.5).max(f32::EPSILON), (size.y * 0.5).max(f32::EPSILON))
503 }
504 Shape::Capsule { height, radius } => {
505 rp::ColliderBuilder::capsule_y((height * 0.5).max(0.0), radius.max(f32::EPSILON))
506 }
507 Shape::Polygon { .. } => {
508 let points: Vec<rp::Vector> = c.shape.points().iter().map(|&p| to_rp(p)).collect();
509 rp::ColliderBuilder::convex_hull(&points).unwrap_or_else(|| rp::ColliderBuilder::ball(f32::EPSILON))
511 }
512 };
513 builder
514 .density(c.density)
515 .friction(c.friction)
516 .restitution(c.restitution)
517 .sensor(c.sensor)
518 .translation(to_rp(c.offset))
519 .active_events(rp::ActiveEvents::COLLISION_EVENTS)
520 .user_data(u128::from(e.to_bits()))
521 .build()
522}
523
524pub(crate) fn start(ctx: &mut Ctx, gravity: Vec2) {
527 use crate::WorldReflect;
528 ctx.physics = Physics::new(gravity);
529 ctx.world.reflect::<RigidBody>();
530 ctx.world.reflect::<Collider>();
531 ctx.world.reflect::<Velocity>();
532}
533
534pub fn step_physics(ctx: &mut Ctx) {
540 let dt = ctx.dt();
541 let physics = &mut ctx.physics;
542 physics.step += 1;
543 sync_into(physics, &mut ctx.world);
544 physics.world.gravity = to_rp(physics.gravity);
545 physics.world.integration_parameters.dt = dt;
546 physics.world.step_with_events(&(), &physics.collector);
547 write_back(physics, &mut ctx.world);
548 while let Ok(event) = physics.collisions.try_recv() {
549 let (a, b, flags, started) = match event {
550 rp::CollisionEvent::Started(a, b, f) => (a, b, f, true),
551 rp::CollisionEvent::Stopped(a, b, f) => (a, b, f, false),
552 };
553 let (Some(a), Some(b)) = (physics.entity_of(a), physics.entity_of(b)) else {
554 continue;
555 };
556 let sensor = flags.contains(rp::CollisionEventFlags::SENSOR);
557 if started {
558 ctx.events.send(CollisionStarted { a, b, sensor });
559 } else {
560 ctx.events.send(CollisionStopped { a, b, sensor });
561 }
562 }
563}
564
565fn sync_into(physics: &mut Physics, world: &mut World) {
567 let step = physics.step;
568 world
569 .query::<(Entity, &Transform2D, &RigidBody, &Collider, Option<&Velocity>)>()
570 .for_each(|(e, t, rigid, shape, velocity)| {
571 let i = e.index() as usize;
572 let Some(link) = physics
573 .links
574 .get_mut(i)
575 .and_then(Option::as_mut)
576 .filter(|l| l.entity == e)
577 else {
578 if let Some(Some(old)) = physics.links.get(i).copied() {
579 physics.world.remove_body(old.body);
581 physics.links[i] = None;
582 }
583 physics.insert(e, t, *rigid, *shape, velocity.copied().unwrap_or_default());
584 return;
585 };
586 link.seen = step;
587 if link.rigid != *rigid || link.shape != *shape {
588 let current = link.velocity;
590 physics.world.remove_body(link.body);
591 physics.insert(e, t, *rigid, *shape, velocity.copied().unwrap_or(current));
592 return;
593 }
594 if link.transform != *t
597 && let Some(body) = physics.world.bodies.get_mut(link.body)
598 {
599 body.set_position(rp::Pose::new(to_rp(t.position), t.rotation), true);
600 link.transform = *t;
601 }
602 if let Some(&v) = velocity
603 && link.velocity != v
604 && let Some(body) = physics.world.bodies.get_mut(link.body)
605 {
606 body.set_linvel(to_rp(v.linear), true);
607 body.set_angvel(v.angular, true);
608 link.velocity = v;
609 }
610 });
611 for i in 0..physics.links.len() {
614 if physics.links[i].is_some_and(|l| l.seen != step) {
615 physics.remove(i);
616 }
617 }
618}
619
620fn write_back(physics: &mut Physics, world: &mut World) {
621 world
622 .query::<(Entity, &mut Transform2D, Option<&mut Velocity>)>()
623 .with::<RigidBody>()
624 .with::<Collider>()
625 .for_each(|(e, t, velocity)| {
626 let Some(link) = physics
627 .links
628 .get_mut(e.index() as usize)
629 .and_then(Option::as_mut)
630 .filter(|l| l.entity == e)
631 else {
632 return;
633 };
634 if link.rigid.kind == BodyKind::Fixed {
635 return;
636 }
637 let Some(body) = physics.world.bodies.get(link.body).filter(|b| !b.is_sleeping()) else {
639 return;
640 };
641 t.position = from_rp(body.translation());
642 t.rotation = body.rotation().angle();
643 link.transform = *t;
644 let v = Velocity {
645 linear: from_rp(body.linvel()),
646 angular: body.angvel(),
647 };
648 link.velocity = v;
649 if let Some(velocity) = velocity {
650 *velocity = v;
651 }
652 });
653}