use rapier3d::prelude::*;
fn leaf_reaches(world: &PhysicsWorld, co: ColliderHandle, p: Vector) -> bool {
world
.intersect_aabb_conservative(Aabb::new(p, p), QueryFilter::default())
.any(|(handle, _)| handle == co)
}
fn wall_and_ccd_ball(world: &mut PhysicsWorld, hit_time: Real) -> RigidBodyHandle {
let dt = world.integration_parameters.dt;
world.insert(
RigidBodyBuilder::fixed().translation(Vector::new(0.0, -20.0, 0.0)),
ColliderBuilder::cuboid(0.1, 2.0, 2.0),
);
let speed = 200.0;
world
.insert(
RigidBodyBuilder::dynamic()
.translation(Vector::new(-0.2 - hit_time * speed * dt, -20.0, 0.0))
.linvel(Vector::new(speed, 0.0, 0.0))
.ccd_enabled(true),
ColliderBuilder::ball(0.1),
)
.0
}
#[test]
fn soft_ccd_aabb_spans_the_full_step_when_the_ccd_splits_it() {
let mut world = PhysicsWorld::new();
world.gravity = Vector::ZERO;
world.integration_parameters.max_ccd_substeps = 2;
let dt = world.integration_parameters.dt;
wall_and_ccd_ball(&mut world, 1.8);
let speed = 120.0;
let (_, co) = world.insert(
RigidBodyBuilder::dynamic()
.translation(Vector::new(0.0, 20.0, 0.0))
.linvel(Vector::new(speed, 0.0, 0.0))
.soft_ccd_prediction(10.0),
ColliderBuilder::ball(0.1),
);
world.step();
world.step();
assert_eq!(world.physics_pipeline.counters.ccd.num_substeps, 2);
let aabb = world.colliders[co].compute_aabb();
let reach = speed * dt;
let p = Vector::new(aabb.maxs.x + 0.9 * reach, aabb.center().y, aabb.center().z);
assert!(
leaf_reaches(&world, co, p),
"the broad-phase leaf misses the soft-CCD prediction at {p:?}"
);
}
#[test]
fn first_step_of_a_fast_ccd_body_is_split_at_its_impact() {
let mut world = PhysicsWorld::new();
world.gravity = Vector::ZERO;
world.integration_parameters.max_ccd_substeps = 2;
let ball = wall_and_ccd_ball(&mut world, 0.8);
world.step();
assert_eq!(world.physics_pipeline.counters.ccd.num_substeps, 2);
let x = world.bodies[ball].translation().x;
assert!(
(-0.21..-0.15).contains(&x),
"the ball didn't stop at the wall: x = {x}"
);
let vx = world.bodies[ball].linvel().x;
assert!(vx < 1.0, "the ball still approaches the wall at {vx}");
}
#[test]
fn soft_contact_history_covers_steps_not_ccd_passes() {
let mut world = PhysicsWorld::new();
world.gravity = Vector::ZERO;
world.integration_parameters.max_ccd_substeps = 2;
let max_extra = world.integration_parameters.soft_bodies.max_extra_substeps;
wall_and_ccd_ball(&mut world, 1.8);
let cube = SoftBodyBuilder::cuboid(Vector::new(0.0, 1.0, 0.0), Vector::splat(0.5), 3, 3, 3)
.cell_model(SoftBodyCellModel::Corotational)
.material(SoftBodyMaterial {
young_modulus: 5.0e4,
poisson_ratio: 0.3,
elastic_damping_ratio: 1.0,
..Default::default()
})
.particle_mass(0.1)
.particle_radius(0.05)
.surface_collider(ColliderBuilder::ball(0.05));
let h = world.insert_soft_body(cube);
let root = world.soft_bodies[h].root_body();
let velocity = Vector::new(30.0, 0.0, 0.0);
let sb = &mut world.soft_bodies[h];
for i in 0..sb.num_particles() {
sb.set_particle_velocity(i, velocity);
}
let (ball, _) = world.insert(
RigidBodyBuilder::dynamic()
.translation(Vector::new(0.85, 1.0, 0.0))
.linvel(velocity),
ColliderBuilder::ball(0.3),
);
world.step();
assert_eq!(world.bodies[root].additional_solver_iterations(), max_extra);
world.remove_body(ball);
world.step();
assert_eq!(world.physics_pipeline.counters.ccd.num_substeps, 2);
assert_eq!(world.bodies[root].additional_solver_iterations(), max_extra);
world.step();
assert!(world.bodies[root].additional_solver_iterations() < max_extra);
}