use glam::{Quat, Vec3};
use rapier3d::prelude::*;
pub const HX: f32 = 3.2;
pub const HY: f32 = 1.9;
pub const HZ: f32 = 2.0;
pub const DIE_R: f32 = 0.36;
pub const STEP: f32 = 1.0 / 60.0;
pub struct Impact {
pub sides: u32,
pub speed: f32,
pub die_die: bool,
}
pub struct Physics {
bodies: RigidBodySet,
colliders: ColliderSet,
gravity: Vec3,
params: IntegrationParameters,
pipeline: PhysicsPipeline,
islands: IslandManager,
broad_phase: DefaultBroadPhase,
narrow_phase: NarrowPhase,
impulse_joints: ImpulseJointSet,
multibody_joints: MultibodyJointSet,
ccd: CCDSolver,
}
impl Physics {
pub fn new() -> Self {
let mut colliders = ColliderSet::new();
let t = 0.2; let walls: [(f32, f32, f32, f32, f32, f32); 6] = [
(HX, t, HZ, 0.0, -HY - t, 0.0), (HX, t, HZ, 0.0, HY + t, 0.0), (t, HY + t, HZ, -HX - t, 0.0, 0.0),
(t, HY + t, HZ, HX + t, 0.0, 0.0),
(HX, HY + t, t, 0.0, 0.0, -HZ - t),
(HX, HY + t, t, 0.0, 0.0, HZ + t),
];
for (hx, hy, hz, x, y, z) in walls {
colliders.insert(
ColliderBuilder::cuboid(hx, hy, hz)
.translation(Vec3::new(x, y, z))
.restitution(0.45)
.friction(0.85)
.build(),
);
}
let params = IntegrationParameters {
dt: STEP,
..Default::default()
};
Self {
bodies: RigidBodySet::new(),
colliders,
gravity: Vec3::new(0.0, -14.0, 0.0), params,
pipeline: PhysicsPipeline::new(),
islands: IslandManager::new(),
broad_phase: DefaultBroadPhase::new(),
narrow_phase: NarrowPhase::new(),
impulse_joints: ImpulseJointSet::new(),
multibody_joints: MultibodyJointSet::new(),
ccd: CCDSolver::new(),
}
}
pub fn spawn(&mut self, mesh_points: &[Vec3], pos: Vec3) -> RigidBodyHandle {
let body = RigidBodyBuilder::dynamic()
.translation(pos)
.linear_damping(0.15)
.angular_damping(0.35)
.can_sleep(true)
.build();
let handle = self.bodies.insert(body);
if let Some(b) = self.bodies.get_mut(handle) {
let act = b.activation_mut();
act.time_until_sleep = 0.2;
act.normalized_linear_threshold = 0.6;
act.angular_threshold = 0.9;
}
let pts: Vec<Vec3> = mesh_points.iter().map(|p| *p * DIE_R).collect();
let collider = ColliderBuilder::convex_hull(&pts)
.unwrap_or_else(|| ColliderBuilder::ball(DIE_R))
.restitution(0.4)
.friction(0.9)
.density(1.0)
.build();
self.colliders
.insert_with_parent(collider, handle, &mut self.bodies);
handle
}
pub fn launch(&mut self, h: RigidBodyHandle, linvel: Vec3, angvel: Vec3) {
if let Some(b) = self.bodies.get_mut(h) {
b.set_linvel(linvel, true);
b.set_angvel(angvel, true);
b.wake_up(true);
}
}
pub fn step(&mut self, dice: &[(RigidBodyHandle, u32, f32)]) -> Vec<Impact> {
self.pipeline.step(
self.gravity,
&self.params,
&mut self.islands,
&mut self.broad_phase,
&mut self.narrow_phase,
&mut self.bodies,
&mut self.colliders,
&mut self.impulse_joints,
&mut self.multibody_joints,
&mut self.ccd,
&(),
&(),
);
let mut impacts = Vec::new();
for &(h, sides, prev_speed) in dice {
let (drop, sleeping, my_pos) = match self.bodies.get(h) {
Some(b) => (
prev_speed - b.linvel().length(),
b.is_sleeping(),
b.translation(),
),
None => continue,
};
if drop > 1.2 && !sleeping {
let die_die = dice.iter().any(|&(oh, _, _)| {
oh != h
&& self
.bodies
.get(oh)
.map(|ob| (ob.translation() - my_pos).length() < 2.4 * DIE_R)
.unwrap_or(false)
});
impacts.push(Impact {
sides,
speed: drop,
die_die,
});
}
}
impacts
}
pub fn speed(&self, h: RigidBodyHandle) -> f32 {
self.bodies
.get(h)
.map(|b| b.linvel().length())
.unwrap_or(0.0)
}
#[cfg(test)]
pub fn velocity_x(&self, h: RigidBodyHandle) -> f32 {
self.bodies.get(h).map(|b| b.linvel().x).unwrap_or(0.0)
}
pub fn pose(&self, h: RigidBodyHandle) -> (Vec3, Quat) {
match self.bodies.get(h) {
Some(b) => (b.translation(), *b.rotation()),
None => (Vec3::ZERO, Quat::IDENTITY),
}
}
pub fn sleeping(&self, h: RigidBodyHandle) -> bool {
self.bodies.get(h).map(|b| b.is_sleeping()).unwrap_or(true)
}
pub fn freeze(&mut self, h: RigidBodyHandle) {
if let Some(b) = self.bodies.get_mut(h) {
b.set_linvel(Vec3::ZERO, false);
b.set_angvel(Vec3::ZERO, false);
b.set_body_type(RigidBodyType::Fixed, false);
}
}
pub fn clear(&mut self) {
let dice: Vec<RigidBodyHandle> = self.bodies.iter().map(|(h, _)| h).collect();
for h in dice {
self.bodies.remove(
h,
&mut self.islands,
&mut self.colliders,
&mut self.impulse_joints,
&mut self.multibody_joints,
true,
);
}
}
}
impl Default for Physics {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clear_removes_frozen_dice_too() {
let mut phys = Physics::new();
let cube: Vec<Vec3> = (0..8)
.map(|i| {
Vec3::new(
if i & 1 == 0 { -1.0 } else { 1.0 },
if i & 2 == 0 { -1.0 } else { 1.0 },
if i & 4 == 0 { -1.0 } else { 1.0 },
)
})
.collect();
let live = phys.spawn(&cube, Vec3::ZERO);
let frozen = phys.spawn(&cube, Vec3::new(1.0, 0.0, 0.0));
phys.freeze(frozen);
phys.clear();
assert_eq!(
phys.bodies.iter().count(),
0,
"clear() left die bodies behind"
);
let _ = live;
}
}