use rapier2d::prelude::*;
struct Harness {
bodies: RigidBodySet,
colliders: ColliderSet,
impulse_joints: ImpulseJointSet,
multibody_joints: MultibodyJointSet,
pipeline: PhysicsPipeline,
bf: BroadPhaseBvh,
nf: NarrowPhase,
islands: IslandManager,
ccd: CCDSolver,
params: IntegrationParameters,
gravity: Vector,
}
impl Harness {
fn new() -> Self {
Self {
bodies: RigidBodySet::new(),
colliders: ColliderSet::new(),
impulse_joints: ImpulseJointSet::new(),
multibody_joints: MultibodyJointSet::new(),
pipeline: PhysicsPipeline::new(),
bf: BroadPhaseBvh::new(),
nf: NarrowPhase::new(),
islands: IslandManager::new(),
ccd: CCDSolver::new(),
params: IntegrationParameters::default(),
gravity: Vector::ZERO,
}
}
fn step(&mut self) {
self.pipeline.step(
self.gravity,
&self.params,
&mut self.islands,
&mut self.bf,
&mut self.nf,
&mut self.bodies,
&mut self.colliders,
&mut self.impulse_joints,
&mut self.multibody_joints,
&mut self.ccd,
&(),
&(),
);
}
fn run(&mut self, steps: usize) {
for _ in 0..steps {
self.step();
}
}
fn x(&self, h: RigidBodyHandle) -> Real {
self.bodies[h].translation().x
}
}
fn insert_thin_fixed_wall(h: &mut Harness) {
let wall = h.bodies.insert(RigidBodyBuilder::fixed());
h.colliders
.insert_with_parent(ColliderBuilder::cuboid(0.05, 5.0), wall, &mut h.bodies);
}
fn insert_fast_dynamic(h: &mut Harness, ccd_enabled: bool) -> RigidBodyHandle {
let body = h.bodies.insert(
RigidBodyBuilder::dynamic()
.translation(Vector::new(-3.0, 0.0))
.linvel(Vector::new(200.0, 0.0))
.ccd_enabled(ccd_enabled),
);
h.colliders
.insert_with_parent(ColliderBuilder::cuboid(0.1, 0.1), body, &mut h.bodies);
body
}
#[test]
fn default_ccd_vs_fixed_no_tunnel() {
let mut h = Harness::new();
insert_thin_fixed_wall(&mut h);
let body = insert_fast_dynamic(&mut h, false);
h.run(120);
assert!(
h.x(body) < 0.0,
"body tunneled through the fixed wall (x = {})",
h.x(body)
);
}
#[test]
fn default_tier_ignores_dynamic() {
let mut h = Harness::new();
let a = h.bodies.insert(
RigidBodyBuilder::dynamic()
.translation(Vector::new(-3.0, 0.0))
.linvel(Vector::new(200.0, 0.0)),
);
h.colliders
.insert_with_parent(ColliderBuilder::cuboid(0.1, 0.1), a, &mut h.bodies);
let b = h.bodies.insert(
RigidBodyBuilder::dynamic()
.translation(Vector::new(3.0, 0.0))
.linvel(Vector::new(-200.0, 0.0)),
);
h.colliders
.insert_with_parent(ColliderBuilder::cuboid(0.1, 0.1), b, &mut h.bodies);
h.run(5);
assert!(
h.x(a) > 0.0 && h.x(b) < 0.0,
"default-tier dynamic bodies should tunnel through each other (a.x = {}, b.x = {})",
h.x(a),
h.x(b)
);
}
#[test]
fn bullet_still_hits_dynamic() {
let mut h = Harness::new();
let bullet = insert_fast_dynamic(&mut h, true);
let target = h
.bodies
.insert(RigidBodyBuilder::dynamic().translation(Vector::new(0.0, 0.0)));
h.colliders
.insert_with_parent(ColliderBuilder::cuboid(0.2, 0.2), target, &mut h.bodies);
h.run(60);
assert!(
h.x(target) > 0.05,
"bullet did not hit the dynamic target (target.x = {})",
h.x(target)
);
assert!(
h.x(bullet) < h.x(target),
"bullet passed through its target (bullet.x = {}, target.x = {})",
h.x(bullet),
h.x(target)
);
}
#[test]
fn global_ccd_off_tunnels() {
let mut h = Harness::new();
h.params.max_ccd_substeps = 0;
insert_thin_fixed_wall(&mut h);
let body = insert_fast_dynamic(&mut h, false);
h.run(60);
assert!(
h.x(body) > 1.0,
"body should tunnel through the wall when CCD is globally disabled (x = {})",
h.x(body)
);
}
#[test]
fn kinematic_not_a_default_target() {
let mut h = Harness::new();
let wall = h
.bodies
.insert(RigidBodyBuilder::kinematic_position_based());
h.colliders
.insert_with_parent(ColliderBuilder::cuboid(0.05, 5.0), wall, &mut h.bodies);
let body = insert_fast_dynamic(&mut h, false);
h.run(60);
assert!(
h.x(body) > 1.0,
"default-tier body should tunnel through a kinematic wall (x = {})",
h.x(body)
);
}