rapier icon indicating copy to clipboard operation
rapier copied to clipboard

Odd cylinder collisions

Open BenjaminSchaaf opened this issue 2 years ago • 2 comments

Putting 4 cylinders into a compound collider has some very odd collision behavior:

https://user-images.githubusercontent.com/2748981/160272665-44df6ac1-6935-4393-b9c2-6520e2230d87.mp4

It also happens in other orientations:

https://user-images.githubusercontent.com/2748981/160272672-b79e51eb-29b1-4690-832d-af3222e2112d.mp4

But not for other shapes:

https://user-images.githubusercontent.com/2748981/160272675-494237e3-81dd-472b-93cb-6d51ae7cbf1b.mp4

I've made an easily reproducible example:

use rapier3d::prelude::*;
use rapier_testbed3d::Testbed;

// This shows a bug when a cylinder is in contact with a very large
// but very thin cuboid. In this case the EPA returns an incorrect
// contact normal, resulting in the cylinder falling through the floor.
pub fn init_world(testbed: &mut Testbed) {
    /*
     * World
     */
    let mut bodies = RigidBodySet::new();
    let mut colliders = ColliderSet::new();
    let impulse_joints = ImpulseJointSet::new();
    let multibody_joints = MultibodyJointSet::new();

    /*
     * Ground
     */
    let ground_size = 100.1;
    let ground_height = 0.1;

    let collider = ColliderBuilder::cuboid(ground_size, ground_height, ground_size).translation(vector![0.0, -ground_height, 0.0]).rotation(vector![0.2, 0.0, 0.0]).friction(0.0);
    colliders.insert(collider);

    // Build the rigid body.
    let rigid_body = RigidBodyBuilder::dynamic().translation(vector![0.0, 3.0, 0.0]);
    let handle = bodies.insert(rigid_body);

    // Case 1: Bad collisions with cylinders in this orientation
    let collider1 = ColliderBuilder::cylinder(0.19, 0.45).translation(vector![-1.5, 0.0, 2.0]);
    let collider2 = ColliderBuilder::cylinder(0.19, 0.45).translation(vector![1.5, 0.0, 2.0]);
    let collider3 = ColliderBuilder::cylinder(0.19, 0.45).translation(vector![-1.5, 0.0, -2.0]);
    let collider4 = ColliderBuilder::cylinder(0.19, 0.45).translation(vector![1.5, 0.0, -2.0]);

    // Case 2: Also with this orientation
    // let collider1 = ColliderBuilder::cylinder(0.19, 0.45).translation(vector![-1.5, 0.0, 2.0]).rotation(vector![0.0, 0.0, 90.0_f32.to_radians()]);
    // let collider2 = ColliderBuilder::cylinder(0.19, 0.45).translation(vector![1.5, 0.0, 2.0]).rotation(vector![0.0, 0.0, 90.0_f32.to_radians()]);
    // let collider3 = ColliderBuilder::cylinder(0.19, 0.45).translation(vector![-1.5, 0.0, -2.0]).rotation(vector![0.0, 0.0, 90.0_f32.to_radians()]);
    // let collider4 = ColliderBuilder::cylinder(0.19, 0.45).translation(vector![1.5, 0.0, -2.0]).rotation(vector![0.0, 0.0, 90.0_f32.to_radians()]);

    // Case 3: Same thing doesn't happen with cuboids
    // let collider1 = ColliderBuilder::cuboid(0.38, 0.9, 0.9).translation(vector![-1.5, 0.0, 2.0]);
    // let collider2 = ColliderBuilder::cuboid(0.38, 0.9, 0.9).translation(vector![1.5, 0.0, 2.0]);
    // let collider3 = ColliderBuilder::cuboid(0.38, 0.9, 0.9).translation(vector![-1.5, 0.0, -2.0]);
    // let collider4 = ColliderBuilder::cuboid(0.38, 0.9, 0.9).translation(vector![1.5, 0.0, -2.0]);

    // Case 4: Or with capsules
    // let collider1 = ColliderBuilder::capsule_y(0.19, 0.45).translation(vector![-1.5, 0.0, 2.0]);
    // let collider2 = ColliderBuilder::capsule_y(0.19, 0.45).translation(vector![1.5, 0.0, 2.0]);
    // let collider3 = ColliderBuilder::capsule_y(0.19, 0.45).translation(vector![-1.5, 0.0, -2.0]);
    // let collider4 = ColliderBuilder::capsule_y(0.19, 0.45).translation(vector![1.5, 0.0, -2.0]);

    // Case 5: In either orientation
    // let collider1 = ColliderBuilder::capsule_x(0.19, 0.45).translation(vector![-1.5, 0.0, 2.0]);
    // let collider2 = ColliderBuilder::capsule_x(0.19, 0.45).translation(vector![1.5, 0.0, 2.0]);
    // let collider3 = ColliderBuilder::capsule_x(0.19, 0.45).translation(vector![-1.5, 0.0, -2.0]);
    // let collider4 = ColliderBuilder::capsule_x(0.19, 0.45).translation(vector![1.5, 0.0, -2.0]);

    let shapes = vec![
        (collider1.position, collider1.shape),
        (collider2.position, collider2.shape),
        (collider3.position, collider3.shape),
        (collider4.position, collider4.shape),
    ];
    let collider = ColliderBuilder::compound(shapes).restitution(0.2).friction(0.0).build();
    colliders.insert_with_parent(collider, handle, &mut bodies);

    /*
     * Set up the testbed.
     */
    testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
    testbed.look_at(point![100.0, 100.0, 100.0], Point::origin());
}

BenjaminSchaaf avatar Mar 27 '22 08:03 BenjaminSchaaf

Possible related issue: https://github.com/dimforge/parry/issues/70

BenjaminSchaaf avatar Mar 27 '22 11:03 BenjaminSchaaf

I've narrowed this down to the GJK implementation returning the wrong normal here: https://github.com/dimforge/parry/blob/c3f28389e762cf93d8a1a62337fb806450852017/src/query/gjk/gjk.rs#L131. From the examples most collisions don't hit this code. Passing in exact_dir=false from https://github.com/dimforge/parry/blob/c3f28389e762cf93d8a1a62337fb806450852017/src/query/contact/contact_support_map_support_map.rs#L64 resolves the issue, but I'm not familiar enough with GJK to know what the correct fix is. Maybe it's obvious to @sebcrozet ?

BenjaminSchaaf avatar Aug 06 '22 13:08 BenjaminSchaaf