Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CollisionWorld::contact_pairs doesn't change after resolving collisions before CollisionWorld::update #310

Open
bonsairobo opened this issue Sep 28, 2019 · 2 comments

Comments

@bonsairobo
Copy link

I have a short repro:

use nalgebra::{Isometry3, Translation, Vector3, zero};
use ncollide3d::{
    shape::{Cuboid, ShapeHandle},
    world::{CollisionGroups, CollisionWorld, GeometricQueryType},
};
use std::collections::HashMap;

fn two_colliding_cuboids() {
    let mut world = CollisionWorld::new(0.0);

    // Add two intersecting cuboids to the world.
    let mut groups = CollisionGroups::new();
    groups.set_membership(&[1]);
    groups.set_whitelist(&[1]);
    let contacts_query = GeometricQueryType::Contacts(0.0, 0.0);
    let shape = ShapeHandle::new(Cuboid::new(Vector3::new(1.0, 1.0, 1.0)));
    let pos = Isometry3::new(zero(), zero());
    world.add(pos, shape.clone(), groups, contacts_query, 1);
    world.add(pos, shape, groups, contacts_query, 2);

    loop {
        // BUG: updating doesn't clear out the old contact pairs, so in the loop below, we will try
        // pushing apart objects that, according to their positions and shapes, should not be
        // in contact anymore.
        world.update();

        // Resolve intersections by pushing cubes apart along the contact normal.
        let mut move_actions = HashMap::new();
        for (handle1, handle2, _, manifold) in world.contact_pairs(true) {
            // Once a room from a contact has been moved, it may invalidate other contacts that
            // it was a part of, so skip them.
            if move_actions.contains_key(&handle1) || move_actions.contains_key(&handle2) {
                continue;
            }

            let contact = &manifold.deepest_contact()
                .expect("No penetration in contact").contact;

            if contact.depth == 0.0 {
                continue;
            }

            // The normal should be parallel to some axis. To ensure that there are no endless
            // cycles of resolutions, only allow resolutions to push in the positive direction
            // along each axis.
            let n: Vector3<f32> = contact.depth * contact.normal.into_inner();
            let (push_v, move_handle) = if n.x > 0.0 || n.y > 0.0 || n.z > 0.0 {
                println!("Pushing room 1");
                (-n, handle1)
            } else {
                println!("Pushing room 2");
                (n, handle2)
            };

            move_actions.insert(move_handle, push_v);

            let obj1 = world.collision_object(handle1).unwrap();
            let obj2 = world.collision_object(handle2).unwrap();
            println!("C1 = {:?}", obj1.shape().as_shape::<Cuboid<f32>>());
            println!("C2 = {:?}", obj2.shape().as_shape::<Cuboid<f32>>());
            println!("C1 = {:?}", obj1.position().translation.vector);
            println!("C2 = {:?}", obj2.position().translation.vector);
            println!("Depth = {}", contact.depth);
            println!("N = {}", contact.normal.into_inner());
        }

        // Need to perform movements after releasing the borrow on world.
        for (handle, push_v) in move_actions.iter() {
            let move_obj = world.collision_object_mut(*handle)
                .expect("Collision object does not exist for handle");
            let mut pos = move_obj.position().clone();
            pos.append_translation_mut(&Translation::from(*push_v));
            move_obj.set_position(pos);
        }

        if move_actions.is_empty() {
            break;
        }
    }
}

fn main() {
    two_colliding_cuboids();
}

Basically, I'm trying to use a CollisionWorld to push apart a bunch of colliding cuboids. I've simplified the example here to just 2 cubes. You'll see that with ncollide3d = "0.19", the example will loop forever because it never realizes that the cubes are not in contact after being pushed apart.

This must be a regression in behavior according to the documentation of an older version (0.14.1).

It states that contact_pairs "iterates through all the contact pairs detected since the last update." So you can see the problem is that it also iterates through pairs detected before the last update.

I think maybe I can work around this using the contact_events function, but it makes things more complicated because of my particular application. I don't want to push the objects more than necessary, so, for each iteration over the contacts, I will only push an object the first time I see it in a contact pair. By pushing an object, it might resolve more collisions that come up in the contact iterator later, and I don't want to try resolving these unnecessarily. If I have to use contact_events, then when I ignore a "stale" contact, I may never see the contact pair again if the collision is left unresolved. At least this is my interpretation of the docs here. Specifically, this part about ContactEvent::Started: "This event is generated whenever the narrow-phase finds a contact between two collision objects that did not have any contact at the last update."

It would be greatly appreciated if someone could suggest another workaround while this bug is addressed. Thanks!

@bonsairobo bonsairobo changed the title CollisionWorld::contact_pairs doesn't change after CollisionWorld::update CollisionWorld::contact_pairs doesn't change after resolving collisions before CollisionWorld::update Sep 28, 2019
@sebcrozet
Copy link
Member

Hi! Your example terminates after one iteration with the latest version of ncollide (0.20.1). Can you try again after updating your ncollide3d dependency?

@bonsairobo
Copy link
Author

bonsairobo commented Sep 30, 2019

That helps, thanks! I can't remember why I took a specific dependency on 0.19, but now I can upgrade.

So the new version doesn't get stuck, but it does give me an assertion at

ncollide3d-0.20.1/src/query/algorithms/epa3.rs:143

for my actual use-case (not the simple 2 cube repro).

I'll make a new issue for that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants