dimforge / rapier

2D and 3D physics engines focused on performance.
https://rapier.rs
Apache License 2.0
3.91k stars 244 forks source link

Bug with CollisionEvent::Stopped events after removal if a sensor is involved #330

Closed eldyer closed 2 years ago

eldyer commented 2 years ago

Rapier 0.12.0 introduced the emission of CollisionEvent::Stopped events after a collider is removed.

This seems to work for non-sensor collisions. But for contacts where the other (non-removed) collider is a sensor, stopped events are either missing or wrong. By wrong I mean: the stopped events point to uninvolved contacts and to collider handles that haven't been removed at all, while the CollisionEventFlags::REMOVED flag still being set in these events.

sebcrozet commented 2 years ago

Hi! Do you have an example for us to reproduce this inconsistency?

eldyer commented 2 years ago

Hi! Not yet, I observed it in a larger project only. I'll try to construct a minimal example using the testbed.

eldyer commented 2 years ago

I quickly hacked something together based on the sensor3 example that should do the job - see below.

The green marked cubes have the handle indices: 34, 44, 54, 64, 74.

cube body handle: RigidBodyHandle(Index { index: 34, generation: 0 })
cube collider handle: ColliderHandle(Index { index: 34, generation: 0 })
cube body handle: RigidBodyHandle(Index { index: 44, generation: 0 })
cube collider handle: ColliderHandle(Index { index: 44, generation: 0 })
cube body handle: RigidBodyHandle(Index { index: 54, generation: 0 })
cube collider handle: ColliderHandle(Index { index: 54, generation: 0 })
cube body handle: RigidBodyHandle(Index { index: 64, generation: 0 })
cube collider handle: ColliderHandle(Index { index: 64, generation: 0 })
cube body handle: RigidBodyHandle(Index { index: 74, generation: 0 })
cube collider handle: ColliderHandle(Index { index: 74, generation: 0 })

The sensor has body handle index 101 / collider index 102:

sensor handle: RigidBodyHandle(Index { index: 101, generation: 0 })
sensor collider handle: ColliderHandle(Index { index: 102, generation: 0 })

After 3 seconds, the above green marked cubes are removed.

The resulting 3 STOPPED events are strange:

removed!
collider handle 1: ColliderHandle(Index { index: 74, generation: 0 })
collider handle 2: ColliderHandle(Index { index: 102, generation: 0 })
removed!
collider handle 1: ColliderHandle(Index { index: 78, generation: 0 })
collider handle 2: ColliderHandle(Index { index: 102, generation: 0 })
removed!
collider handle 1: ColliderHandle(Index { index: 56, generation: 0 })
collider handle 2: ColliderHandle(Index { index: 102, generation: 0 })

While the first one seems correct, the other ones have wrong handles of bodies that weren't removed (78, 56). Also, two STOPPED events are missing completely. Here the code:

use rapier3d::prelude::*;
use rapier_testbed3d::{Testbed, TestbedApp};

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 = 200.1;
    let ground_height = 0.1;

    let rigid_body = RigidBodyBuilder::fixed().translation(vector![0.0, -ground_height, 0.0]);
    let ground_handle = bodies.insert(rigid_body);
    let collider = ColliderBuilder::cuboid(ground_size, ground_height, ground_size);
    colliders.insert_with_parent(collider, ground_handle, &mut bodies);

    /*
     * Create some boxes.
     */
    let num = 10;
    let rad = 0.2;

    let shift = rad * 2.0;
    let centerx = shift * num as f32 / 2.0;
    let centerz = shift * num as f32 / 2.0;

    let mut test_cubes = Vec::new();

    for i in 0usize..num {
        for k in 0usize..num {
            let x = i as f32 * shift - centerx;
            let y = 3.0;
            let z = k as f32 * shift - centerz;

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

            let collider = ColliderBuilder::cuboid(rad, rad, rad);
            let collider_handle = colliders.insert_with_parent(collider, handle, &mut bodies);

            testbed.set_initial_body_color(handle, [0.5, 0.5, 1.0]);
            if i >= 3 && i <= 7 && k == 3 {
                println!("cube body handle: {:?}", handle);
                println!("cube collider handle: {:?}", collider_handle);
                test_cubes.push(handle);
                testbed.set_initial_body_color(handle, [0.0, 1.0, 0.0]);
            }
        }
    }
    println!("============");

    /*
     * Create a cube that will have a ball-shaped sensor attached.
     */

    // Rigid body so that the sensor can move.
    let sensor = RigidBodyBuilder::dynamic().translation(vector![0.0, 5.0, 0.0]);
    let sensor_handle = bodies.insert(sensor);

    println!("sensor handle: {:?}", sensor_handle);

    // Solid cube attached to the sensor which
    // other colliders can touch.
    let collider = ColliderBuilder::cuboid(rad, rad, rad);
    colliders.insert_with_parent(collider, sensor_handle, &mut bodies);

    // We create a collider desc without density because we don't
    // want it to contribute to the rigid body mass.
    let sensor_collider = ColliderBuilder::ball(rad * 5.0)
        .density(0.0)
        .sensor(true)
        .active_events(ActiveEvents::COLLISION_EVENTS);
    let sensor_collider_handle =
        colliders.insert_with_parent(sensor_collider, sensor_handle, &mut bodies);
    println!("sensor collider handle: {:?}", sensor_collider_handle);
    println!("==============");

    testbed.set_initial_body_color(sensor_handle, [0.5, 1.0, 1.0]);

    let mut done = false;

    // Callback that will be executed on the main loop to handle proximities.
    testbed.add_callback(move |_, physics, events, run_state| {
        while let Ok(prox) = events.events.try_recv() {
            if prox.removed() {
                println!("removed!");
                println!("collider handle 1: {:?}", prox.collider1());
                println!("collider handle 2: {:?}", prox.collider2());
            }
        }
        if run_state.time >= 3.0 && !done {
            done = true;
            for test_cube in &test_cubes {
                physics.bodies.remove(
                    *test_cube,
                    &mut physics.islands,
                    &mut physics.colliders,
                    &mut physics.impulse_joints,
                    &mut physics.multibody_joints,
                    true,
                );
            }
        }
    });

    /*
     * Set up the testbed.
     */
    testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
    testbed.look_at(point![-6.0, 4.0, -6.0], point![0.0, 1.0, 0.0]);
}

pub fn main() {
    let testbedapp = TestbedApp::from_builders(0, vec![("test", init_world)]);
    testbedapp.run()
}
sebcrozet commented 2 years ago

Thank you for the code reproducing the bug. This will be fixed by #337.