stephengold / Libbulletjme

A JNI interface to Bullet Physics and V-HACD
https://stephengold.github.io/Libbulletjme/lbj-en/English/overview.html
Other
84 stars 10 forks source link

Strange behaviour when combining CollisionListeners with .addToIgnoreList #16

Closed JBraig closed 2 years ago

JBraig commented 2 years ago

I'm still fairly new to libbulletjme, and when I was playing around with the CollisionListeners, I discovered a fairly strange behavior when combining them with ignore lists of colliding bodies.

I attached a MWE below, where I drop two boxes (located above each other) on a third one that serves as a static floor. When a collision between the two dropped boxes is detected, I add box B to the ignore list of box A. I would normally expect the boxes to initially collide, and then drop through each other. Instead, the boxes collide, but then the upper one just hovers in the air and actually even rises a tiny bit, instead of falling down to the floor. This issue doesn't occur if you either...

a) change the initial position of the boxes, so that they're not dropped from perfectly above each other. If I set the starting position of box B to (0.25, 1, 0.25) instead of (0, 1, 0), it seems to work just fine, and box B ends up on the floor.

b) reset the position of box B when the CollisionListener is called. Interestingly, it depends on where you place box B after the collision. If you reset the position it to (0, 0, 0), box B floats in mid-air. If you reset it to (1, 0, 0), it immediately falls down (and through box A, as expected). If you reset it to (0.5, 0, 0), it stays afloat for a few timesteps, then drops down.

I'm pretty confused by this behaviour, and suspect that it isn't intentional.

The minimum working example:

    public class CubesListenerMWE {

        public static void main(String[] args) throws IOException {

            File workingDirectory = new File("./lib").getCanonicalFile();
            NativeLibraryLoader.loadLibbulletjme(true, workingDirectory, "Release", "Sp");
            PhysicsSpace.BroadphaseType bPhase = PhysicsSpace.BroadphaseType.DBVT;
            PhysicsSpace space = new PhysicsSpace(bPhase);

            /*
             * Add "floor" Box
             */
            Vector3f floorDimensions = new Vector3f(3f, 0.05f, 3f);
            Vector3f floorPos = new Vector3f(0f, -1.005f, 0f);
            CollisionShape floorShape = new BoxCollisionShape(floorDimensions);
            PhysicsRigidBody floor = new PhysicsRigidBody(floorShape, PhysicsBody.massForStatic);
            floor.setPhysicsLocation(floorPos);
            space.addCollisionObject(floor);

            /*
             * Add first dropped Box
             */
            Vector3f boxADimensions = new Vector3f(0.3f, 0.3f, 0.3f);
            Vector3f boxAPos = new Vector3f(0, 0, 0);
            CollisionShape boxAShape = new BoxCollisionShape(boxADimensions);
            PhysicsRigidBody boxA = new PhysicsRigidBody(boxAShape, 1f);
            boxA.setPhysicsLocation(boxAPos);
            space.addCollisionObject(boxA);

            /*
             * Add second dropped Box
             */
            Vector3f boxBDimensions = new Vector3f(0.3f, 0.3f, 0.3f);
            Vector3f boxBPos = new Vector3f(0, 1, 0);
            CollisionShape boxBShape = new BoxCollisionShape(boxBDimensions);
            PhysicsRigidBody boxB = new PhysicsRigidBody(boxBShape, 1f);
            boxB.setPhysicsLocation(boxBPos);
            space.addCollisionObject(boxB);

            /*
             * Add collision Listener
             */
            space.addCollisionListener(event -> {
                PhysicsRigidBody a = (PhysicsRigidBody) event.getObjectA();
                PhysicsRigidBody b = (PhysicsRigidBody) event.getObjectB();
                if (a.getMass() * b.getMass() != 0) { // Only applies for the two dropped boxes
                    a.addToIgnoreList(b);
                    // b.setPhysicsLocation(new Vector3f(0, 0, 0)); // To reset position after CollisionListener is called
                }
            });

            /*
             * Loop simulation
             */
            Vector3f boxPos = new Vector3f();
            for (int i = 0; i < 300; i++) {
                space.update(0.01f, 0);
                space.distributeEvents();
                System.out.println(boxB.getPhysicsLocation(boxPos));
            }
        }
    }

On a semi-related note: Is there a way to intercept a collision between two objects before the response is applied? With the code above, there's always an initial collision, and only afterwards, box B is added to the ignore list of box A. Could you also, for example, detect the collision, then add B to A's ignore list before the collision response happens, and then continue the simulation, so that B effectively just drops right through A? I could obviously do the whole ignore list stuff right at the start, but what if I for some reason only want to or can decide whether I'd like to have a collision between two objects when it actually happens?

stephengold commented 2 years ago

Thanks for bringing this to my attention.

Like many physics engines, Bullet includes performance optimizations that cache contacts from one time step to the next. While I haven't stepped through the code, I suspect that's what happened in your example. Perhaps there is a mechanism to clear cached contacts, or if not perhaps I can add one. But maybe the callback happens too late to avoid contact forces. I'll investigate further.

As for detecting the collision earlier, Bullet includes a mechanism to report axis-aligned bounding-box (AABB) overlaps during broadphase. Not all AABB overlaps turn into actual collisions, but I'm pretty sure every collision is preceded by an overlap. Said mechanism can be used to disable collisions dynamically. The mechanism isn't quite exposed in Libbulletjme. All that would be needed would be to make the notifyCollisionGroupListeners_native() method public or add a listener mechanism to it. I suspect that's the best solution to your (hypothetical?) problem. Look for something in the next release...

stephengold commented 2 years ago

@JBraig I added a simple mechanism to disable collisions between particular objects on the fly. Please take a look and tell me whether you think the mechanism will meet your needs: f7cfb8700bd3c5522587ba7efe5d61e6bcb36483

JBraig commented 2 years ago

That should be a sufficient solution for my needs, thanks a bunch!

stephengold commented 2 years ago

That should be a sufficient solution for my needs, thanks a bunch!

Glad to hear it! The "simple mechanism" of needsCollision() will be included in the v12.8.0 release, which should be published to MavenCentral later today.

But maybe the callback happens too late to avoid contact forces. I'll investigate further.

I investigated this question. For the record, the callback is invoked during space.distributeEvents(), not during space.update(). That's definitely too late to avoid contact forces. I'm working on an interface to obtain collision events sooner, equivalent to gContactStartedCallback and gContactProcessedCallback in Bullet. It's partially implemented in v12.8.0 .