rapier icon indicating copy to clipboard operation
rapier copied to clipboard

contacts_enabled behaves incorrectly when there's more than 1 joint between 2 rigid bodies

Open nstoddard opened this issue 5 months ago • 0 comments

From the code in NarrowPhase::compute_contacts, I get the impression that if there's more than one joint between two objects, it's supposed to disregard the contact if any of the joints have contacts_enabled set to false. However, that isn't how it behaves in practice; it seems to only check contacts_enabled for the most recently added joint.

My best guess is that something is wrong with ImpulseJointSet::joints_between: even if there's more than one joint connecting two objects, it always returns only 1 element. ImpulseJointSet::attached_joints, however, returns the correct result.

All of this is demonstrated with the repro code below. Since one of the joints has contacts_enabled set to false, I would expect it to ignore the contact.

use rapier2d::prelude::*;

#[derive(Default)]
struct GamePhysics {
    physics: PhysicsPipeline,
    island_manager: IslandManager,
    broad_phase: DefaultBroadPhase,
    narrow_phase: NarrowPhase,
    bodies: RigidBodySet,
    colliders: ColliderSet,
    impulse_joints: ImpulseJointSet,
    multibody_joints: MultibodyJointSet,
    ccd_solver: CCDSolver,
}

impl GamePhysics {
    fn step(&mut self) {
        self.physics.step(&Vector::new(0.0, 9.8), &Default::default(), &mut self.island_manager, &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_solver, None, &(), &MyEventHandler);
    }
}

struct MyEventHandler;

impl EventHandler for MyEventHandler {
    fn handle_collision_event(&self, _: &RigidBodySet, _: &ColliderSet, _: CollisionEvent, _: Option<&ContactPair>) {
        println!("Collision");
    }
    
    fn handle_contact_force_event(&self, _: f32, _: &RigidBodySet, _: &ColliderSet, _: &ContactPair, _: f32) {}
}

fn main() {
    let mut physics = GamePhysics::default();
    
    let a = physics.bodies.insert(RigidBodyBuilder::dynamic());
    let b = physics.bodies.insert(RigidBodyBuilder::dynamic());
    physics.colliders.insert_with_parent(ColliderBuilder::ball(1.0)
        .active_events(ActiveEvents::COLLISION_EVENTS), a, &mut physics.bodies);
    physics.colliders.insert_with_parent(ColliderBuilder::ball(1.0)
        .active_events(ActiveEvents::COLLISION_EVENTS), b, &mut physics.bodies);

    // If the following two lines are switched, it correctly doesn't detect a collision
    physics.impulse_joints.insert(a, b, RevoluteJointBuilder::new().contacts_enabled(false), true);
    physics.impulse_joints.insert(a, b, RevoluteJointBuilder::new().contacts_enabled(true), true);

    // Should be 2, but instead is 1
    println!("Joints between a and b: {}", physics.impulse_joints.joints_between(a, b).count());
    // This one is correct
    println!("Joints for a: {}", physics.impulse_joints.attached_joints(a).count());
    
    // This incorrectly detects a collision
    physics.step();
}

nstoddard avatar Jul 28 '25 06:07 nstoddard