use std::sync::mpsc;
use rapier2d::prelude as rp;
use serde::{Deserialize, Serialize};
use crate::collision::{BodyKind, CollisionStarted, CollisionStopped};
use crate::{Ctx, Entity, Transform2D, Vec2, World};
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct RigidBody {
pub kind: BodyKind,
pub gravity_scale: f32,
pub linear_damping: f32,
pub angular_damping: f32,
pub lock_rotation: bool,
pub ccd: bool,
}
impl Default for RigidBody {
fn default() -> Self {
Self::dynamic()
}
}
impl RigidBody {
#[must_use]
pub const fn dynamic() -> Self {
Self {
kind: BodyKind::Dynamic,
gravity_scale: 1.0,
linear_damping: 0.0,
angular_damping: 0.0,
lock_rotation: false,
ccd: false,
}
}
#[must_use]
pub const fn fixed() -> Self {
Self {
kind: BodyKind::Fixed,
..Self::dynamic()
}
}
#[must_use]
pub const fn kinematic() -> Self {
Self {
kind: BodyKind::Kinematic,
..Self::dynamic()
}
}
}
pub const MAX_POLYGON_POINTS: usize = 8;
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum Shape {
Circle {
radius: f32,
},
Rect {
size: Vec2,
},
Capsule {
height: f32,
radius: f32,
},
Polygon {
points: [Vec2; MAX_POLYGON_POINTS],
count: u8,
},
}
impl Shape {
#[must_use]
pub fn points(&self) -> &[Vec2] {
match self {
Shape::Polygon { points, count } => &points[..usize::from(*count).min(MAX_POLYGON_POINTS)],
_ => &[],
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct Collider {
pub shape: Shape,
#[serde(default = "one")]
pub density: f32,
#[serde(default = "half")]
pub friction: f32,
#[serde(default)]
pub restitution: f32,
#[serde(default)]
pub sensor: bool,
#[serde(default)]
pub offset: Vec2,
}
fn one() -> f32 {
1.0
}
fn half() -> f32 {
0.5
}
impl Collider {
fn of(shape: Shape) -> Self {
Self {
shape,
density: 1.0,
friction: 0.5,
restitution: 0.0,
sensor: false,
offset: Vec2::ZERO,
}
}
#[must_use]
pub fn circle(radius: f32) -> Self {
Self::of(Shape::Circle { radius })
}
#[must_use]
pub fn rect(size: Vec2) -> Self {
Self::of(Shape::Rect { size })
}
#[must_use]
pub fn capsule(height: f32, radius: f32) -> Self {
Self::of(Shape::Capsule { height, radius })
}
#[must_use]
#[track_caller]
pub fn polygon(points: &[Vec2]) -> Self {
assert!(
(3..=MAX_POLYGON_POINTS).contains(&points.len()),
"Collider::polygon: 3 to {MAX_POLYGON_POINTS} points, got {}",
points.len()
);
assert!(
points.iter().all(|p| p.is_finite()),
"Collider::polygon: points must be finite"
);
let mut corners = [Vec2::ZERO; MAX_POLYGON_POINTS];
corners[..points.len()].copy_from_slice(points);
Self::of(Shape::Polygon {
points: corners,
count: points.len() as u8,
})
}
#[must_use]
pub fn density(self, density: f32) -> Self {
Self { density, ..self }
}
#[must_use]
pub fn friction(self, friction: f32) -> Self {
Self { friction, ..self }
}
#[must_use]
pub fn restitution(self, restitution: f32) -> Self {
Self { restitution, ..self }
}
#[must_use]
pub fn sensor(self) -> Self {
Self { sensor: true, ..self }
}
#[must_use]
pub fn offset(self, offset: Vec2) -> Self {
Self { offset, ..self }
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(default)]
#[repr(C)] pub struct Velocity {
pub linear: Vec2,
pub angular: f32,
}
impl Velocity {
#[must_use]
pub const fn linear(linear: Vec2) -> Self {
Self { linear, angular: 0.0 }
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct RayHit {
pub entity: Entity,
pub point: Vec2,
pub normal: Vec2,
pub distance: f32,
}
#[derive(Clone, Copy)]
struct Link {
entity: Entity,
body: rp::RigidBodyHandle,
rigid: RigidBody,
shape: Collider,
transform: Transform2D,
velocity: Velocity,
seen: u64,
}
pub struct Physics {
world: rp::PhysicsWorld,
links: Vec<Option<Link>>,
gravity: Vec2,
step: u64,
collisions: mpsc::Receiver<rp::CollisionEvent>,
collector: rp::ChannelEventCollector,
_forces: mpsc::Receiver<rp::ContactForceEvent>,
_tears: mpsc::Receiver<rp::SoftBodyTearEvent>,
}
impl Default for Physics {
fn default() -> Self {
Self::new(Vec2::ZERO)
}
}
fn to_rp(v: Vec2) -> rp::Vector {
rp::Vector::new(v.x, v.y)
}
fn from_rp(v: rp::Vector) -> Vec2 {
Vec2::new(v.x, v.y)
}
impl Physics {
#[must_use]
pub fn new(gravity: Vec2) -> Self {
let (collision_tx, collisions) = mpsc::channel();
let (force_tx, forces) = mpsc::channel();
let (tear_tx, tears) = mpsc::channel();
let mut world = rp::PhysicsWorld::new();
world.integration_parameters.length_unit = 50.0;
Self {
world,
links: Vec::new(),
gravity,
step: 0,
collisions,
collector: rp::ChannelEventCollector::new(collision_tx, force_tx, tear_tx),
_forces: forces,
_tears: tears,
}
}
pub fn gravity(&self) -> Vec2 {
self.gravity
}
pub fn set_gravity(&mut self, gravity: Vec2) {
self.gravity = gravity;
}
pub fn body_count(&self) -> usize {
self.world.bodies.len()
}
fn entity_of(&self, collider: rp::ColliderHandle) -> Option<Entity> {
self.world
.colliders
.get(collider)
.map(|c| Entity::from_bits(c.user_data as u64))
}
pub fn cast_ray(&self, origin: Vec2, direction: Vec2, max_distance: f32) -> Option<RayHit> {
let dir = direction.normalize_or_zero();
if dir == Vec2::ZERO {
return None;
}
let ray = rp::Ray::new(to_rp(origin), to_rp(dir));
let (handle, hit) = self
.world
.cast_ray_and_get_normal(&ray, max_distance, true, rp::QueryFilter::default())?;
Some(RayHit {
entity: self.entity_of(handle)?,
point: origin + dir * hit.time_of_impact,
normal: from_rp(hit.normal),
distance: hit.time_of_impact,
})
}
pub fn entities_at(&self, point: Vec2, out: &mut Vec<Entity>) {
out.clear();
for (handle, _) in self.world.intersect_point(to_rp(point), rp::QueryFilter::default()) {
out.extend(self.entity_of(handle));
}
}
pub fn entities_in_circle(&self, center: Vec2, radius: f32, out: &mut Vec<Entity>) {
out.clear();
let ball = rp::Ball::new(radius);
let pose = rp::Pose::from_translation(to_rp(center));
for (handle, _) in self.world.intersect_shape(pose, &ball, rp::QueryFilter::default()) {
out.extend(self.entity_of(handle));
}
}
pub fn apply_impulse(&mut self, e: Entity, impulse: Vec2) -> bool {
let Some(link) = self.link(e) else { return false };
let handle = link.body;
match self.world.bodies.get_mut(handle) {
Some(body) => {
body.apply_impulse(to_rp(impulse), true);
true
}
None => false,
}
}
pub fn mass(&self, e: Entity) -> Option<f32> {
let link = self.link(e)?;
self.world.bodies.get(link.body).map(|b| b.mass())
}
fn link(&self, e: Entity) -> Option<&Link> {
self.links.get(e.index() as usize)?.as_ref().filter(|l| l.entity == e)
}
fn insert(&mut self, e: Entity, t: &Transform2D, rigid: RigidBody, shape: Collider, velocity: Velocity) {
let body = self.world.insert_body(body_of(t, rigid, velocity));
self.world.insert_collider(collider_of(shape, e), Some(body));
let i = e.index() as usize;
if self.links.len() <= i {
self.links.resize(i + 1, None);
}
self.links[i] = Some(Link {
entity: e,
body,
rigid,
shape,
transform: *t,
velocity,
seen: self.step,
});
}
fn remove(&mut self, i: usize) {
if let Some(link) = self.links[i].take() {
self.world.remove_body(link.body);
}
}
}
fn body_of(t: &Transform2D, rigid: RigidBody, velocity: Velocity) -> rp::RigidBody {
let builder = match rigid.kind {
BodyKind::Dynamic => rp::RigidBodyBuilder::dynamic(),
BodyKind::Fixed => rp::RigidBodyBuilder::fixed(),
BodyKind::Kinematic => rp::RigidBodyBuilder::kinematic_velocity_based(),
};
let mut builder = builder
.pose(rp::Pose::new(to_rp(t.position), t.rotation))
.linvel(to_rp(velocity.linear))
.angvel(velocity.angular)
.gravity_scale(rigid.gravity_scale)
.linear_damping(rigid.linear_damping)
.angular_damping(rigid.angular_damping)
.ccd_enabled(rigid.ccd);
if rigid.lock_rotation {
builder = builder.lock_rotations();
}
builder.build()
}
fn collider_of(c: Collider, e: Entity) -> rp::Collider {
let builder = match c.shape {
Shape::Circle { radius } => rp::ColliderBuilder::ball(radius.max(f32::EPSILON)),
Shape::Rect { size } => {
rp::ColliderBuilder::cuboid((size.x * 0.5).max(f32::EPSILON), (size.y * 0.5).max(f32::EPSILON))
}
Shape::Capsule { height, radius } => {
rp::ColliderBuilder::capsule_y((height * 0.5).max(0.0), radius.max(f32::EPSILON))
}
Shape::Polygon { .. } => {
let points: Vec<rp::Vector> = c.shape.points().iter().map(|&p| to_rp(p)).collect();
rp::ColliderBuilder::convex_hull(&points).unwrap_or_else(|| rp::ColliderBuilder::ball(f32::EPSILON))
}
};
builder
.density(c.density)
.friction(c.friction)
.restitution(c.restitution)
.sensor(c.sensor)
.translation(to_rp(c.offset))
.active_events(rp::ActiveEvents::COLLISION_EVENTS)
.user_data(u128::from(e.to_bits()))
.build()
}
pub(crate) fn start(ctx: &mut Ctx, gravity: Vec2) {
use crate::WorldReflect;
ctx.physics = Physics::new(gravity);
ctx.world.reflect::<RigidBody>();
ctx.world.reflect::<Collider>();
ctx.world.reflect::<Velocity>();
}
pub fn step_physics(ctx: &mut Ctx) {
let dt = ctx.dt();
let physics = &mut ctx.physics;
physics.step += 1;
sync_into(physics, &mut ctx.world);
physics.world.gravity = to_rp(physics.gravity);
physics.world.integration_parameters.dt = dt;
physics.world.step_with_events(&(), &physics.collector);
write_back(physics, &mut ctx.world);
while let Ok(event) = physics.collisions.try_recv() {
let (a, b, flags, started) = match event {
rp::CollisionEvent::Started(a, b, f) => (a, b, f, true),
rp::CollisionEvent::Stopped(a, b, f) => (a, b, f, false),
};
let (Some(a), Some(b)) = (physics.entity_of(a), physics.entity_of(b)) else {
continue;
};
let sensor = flags.contains(rp::CollisionEventFlags::SENSOR);
if started {
ctx.events.send(CollisionStarted { a, b, sensor });
} else {
ctx.events.send(CollisionStopped { a, b, sensor });
}
}
}
fn sync_into(physics: &mut Physics, world: &mut World) {
let step = physics.step;
world
.query::<(Entity, &Transform2D, &RigidBody, &Collider, Option<&Velocity>)>()
.for_each(|(e, t, rigid, shape, velocity)| {
let i = e.index() as usize;
let Some(link) = physics
.links
.get_mut(i)
.and_then(Option::as_mut)
.filter(|l| l.entity == e)
else {
if let Some(Some(old)) = physics.links.get(i).copied() {
physics.world.remove_body(old.body);
physics.links[i] = None;
}
physics.insert(e, t, *rigid, *shape, velocity.copied().unwrap_or_default());
return;
};
link.seen = step;
if link.rigid != *rigid || link.shape != *shape {
let current = link.velocity;
physics.world.remove_body(link.body);
physics.insert(e, t, *rigid, *shape, velocity.copied().unwrap_or(current));
return;
}
if link.transform != *t
&& let Some(body) = physics.world.bodies.get_mut(link.body)
{
body.set_position(rp::Pose::new(to_rp(t.position), t.rotation), true);
link.transform = *t;
}
if let Some(&v) = velocity
&& link.velocity != v
&& let Some(body) = physics.world.bodies.get_mut(link.body)
{
body.set_linvel(to_rp(v.linear), true);
body.set_angvel(v.angular, true);
link.velocity = v;
}
});
for i in 0..physics.links.len() {
if physics.links[i].is_some_and(|l| l.seen != step) {
physics.remove(i);
}
}
}
fn write_back(physics: &mut Physics, world: &mut World) {
world
.query::<(Entity, &mut Transform2D, Option<&mut Velocity>)>()
.with::<RigidBody>()
.with::<Collider>()
.for_each(|(e, t, velocity)| {
let Some(link) = physics
.links
.get_mut(e.index() as usize)
.and_then(Option::as_mut)
.filter(|l| l.entity == e)
else {
return;
};
if link.rigid.kind == BodyKind::Fixed {
return;
}
let Some(body) = physics.world.bodies.get(link.body).filter(|b| !b.is_sleeping()) else {
return;
};
t.position = from_rp(body.translation());
t.rotation = body.rotation().angle();
link.transform = *t;
let v = Velocity {
linear: from_rp(body.linvel()),
angular: body.angvel(),
};
link.velocity = v;
if let Some(velocity) = velocity {
*velocity = v;
}
});
}