Detecting OnCollisionExit

Hey monkeys,



Today I’ve kinda been breaking my head making an event/method that detects OnCollisionExit. For this foosball simulator I’m making I need to call some methods as soon as the ball’s collision isn’t with a player anymore but was with a player the last frame it checked.



I’m using PhysicsCollisionEvent but ofcourse this is only triggered if the ball is colliding with something so it seems I can’t use it to check if the ball is colliding with nothing. Also the event is triggered for each single collision and it doesn’t give me a list of all objects it’s currently colliding with. I know that in Unity there’s this awesome event OnCollisionExit but I can’t seem to find an equivalent of it in jME3.0RC2. If anyone has any idea’s how I can check this, preferably on a lightweight-way please let me know!

Use a physics tick listener. If I remember correctly it goes prephysicstick - physicstick - collision, in that order. Physics ticks will always be called regardless of collisions, so if there isn’t a collision in the next physics tick, u know the ball isn’t colliding.

Thanks, it did help me quite a bit but now I’m facing another problem regarding this PhysicsTickListener. Even when the ball is in between 2 players on the same rod, the PhysicsTickListener says the ball is colliding with that particular rod. This is probably because I added the RigidBodyControl on the complete node containing the players of a rod rather than adding a different RigidBodyControl for each single player, but still I thought I’d give you a heads-up. Using bulletAppState.getPhysicsSpace().enableDebug(assetManager); also doesn’t show that there’s anything the ball can collide with so this might as well be a bug.



Here’s a testcase showing the bug (I know it’s quite big but without looking at the imports, most of the code in simpleInitApp and the code in addRawInputListener it’s pretty straight-forward):



[java]

package mygame;



import com.jme3.app.SimpleApplication;

import com.jme3.bullet.BulletAppState;

import com.jme3.bullet.PhysicsSpace;

import com.jme3.bullet.PhysicsTickListener;

import com.jme3.bullet.collision.PhysicsCollisionObject;

import com.jme3.bullet.collision.shapes.CollisionShape;

import com.jme3.bullet.control.RigidBodyControl;

import com.jme3.bullet.objects.PhysicsGhostObject;

import com.jme3.bullet.util.CollisionShapeFactory;

import com.jme3.input.RawInputListener;

import com.jme3.input.event.JoyAxisEvent;

import com.jme3.input.event.JoyButtonEvent;

import com.jme3.input.event.KeyInputEvent;

import com.jme3.input.event.MouseButtonEvent;

import com.jme3.input.event.MouseMotionEvent;

import com.jme3.input.event.TouchEvent;

import com.jme3.light.DirectionalLight;

import com.jme3.material.Material;

import com.jme3.math.ColorRGBA;

import com.jme3.math.FastMath;

import com.jme3.math.Vector3f;

import com.jme3.renderer.RenderManager;

import com.jme3.scene.Geometry;

import com.jme3.scene.Node;

import com.jme3.scene.shape.Box;

import com.jme3.system.AppSettings;

import java.util.Iterator;



public class Main extends SimpleApplication {



private Node boxes = new Node(“boxes”);

private BulletAppState bulletAppState = new BulletAppState();

private RawInputListener ril;

private float mouseX = 0;

private Material changingMat;



/**

  • Set settings

    */

    public Main()

    {

    AppSettings newSetting = new AppSettings(true);

    newSetting.setSamples(4);

    newSetting.setTitle("Testcase");

    newSetting.setFullscreen(false);

    newSetting.setResolution(800, 600);

    newSetting.setVSync(true);

    setSettings(newSetting);

    showSettings = false;

    }



    public static void main(String[] args) {

    Main app = new Main();

    app.start();

    }



    @Override

    public void simpleInitApp() {

    cam.lookAtDirection(new Vector3f(0.5104376f, -0.43036112f, -0.74447465f), Vector3f.UNIT_XYZ);

    cam.setLocation(new Vector3f(-3.2150862f, -2.4237566f, 0.78225267f));



    getStateManager().attach(bulletAppState);

    rootNode.attachChild(boxes);

    flyCam.setEnabled(false);



    Box b = new Box(Vector3f.ZERO, .2f, 3, .2f);

    Geometry geom = new Geometry("Box", b);

    geom.setLocalTranslation(0f, -6f, -6f);



    Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

    mat.setColor("Color", ColorRGBA.Red);

    geom.setMaterial(mat);

    boxes.attachChild(geom);



    Box b2 = new Box(Vector3f.ZERO, .2f, 3f, .2f);

    Geometry geom2 = geom.clone();

    geom2.setLocalTranslation(0f, -6f, -3f);

    geom2.setMaterial(mat);

    boxes.attachChild(geom2);



    changingMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");



    Box b3 = new Box(Vector3f.ZERO, .2f, .2f, .2f);

    Geometry geom3 = geom2.clone();

    geom3.setLocalTranslation(1f, -7f, -4.5f);

    geom3.setMaterial(changingMat);

    changingMat.setColor("Color", ColorRGBA.White);



    rootNode.attachChild(geom3);



    addRigidBodyToObject(boxes);

    AddRigidBodyToMiddleBox midBoxRB = new AddRigidBodyToMiddleBox(geom3);

    bulletAppState.getPhysicsSpace().addTickListener(midBoxRB);



    DirectionalLight sun = new DirectionalLight();

    sun.setColor(ColorRGBA.White);

    sun.setDirection(new Vector3f(-.5f, -.5f, -.5f).normalizeLocal());

    rootNode.addLight(sun);



    addRawInputListener();

    bulletAppState.getPhysicsSpace().enableDebug(assetManager);

    }



    /**
  • Adds a {@code RigidBodyControl} to the given object and sets the right variables.

    *
  • @param target The {@code Node} to apply the {@code RigidBodyControl} on.

    */

    private void addRigidBodyToObject(Node target) {

    CollisionShape boxColl = CollisionShapeFactory.createDynamicMeshShape(target);

    target.addControl(new RigidBodyControl(boxColl, 1f));

    RigidBodyControl targetRB = target.getControl(RigidBodyControl.class);

    targetRB.setKinematic(true);

    targetRB.setKinematicSpatial(true);

    bulletAppState.getPhysicsSpace().add(targetRB);

    }



    /**
  • Adds a {@code RawInputListener} to the game handling the X-coördinate of the mouse.

    */

    private void addRawInputListener() {

    ril = new RawInputListener() {



    public void beginInput() {



    }



    public void endInput() {



    }



    public void onJoyAxisEvent(JoyAxisEvent evt) {



    }



    public void onJoyButtonEvent(JoyButtonEvent evt) {



    }



    public void onMouseMotionEvent(MouseMotionEvent evt) {

    mouseX = evt.getX();

    }



    public void onMouseButtonEvent(MouseButtonEvent evt) {



    }



    public void onKeyEvent(KeyInputEvent evt) {



    }



    public void onTouchEvent(TouchEvent evt) {



    }

    };

    inputManager.addRawInputListener(ril);

    }



    @Override

    public void simpleUpdate(float tpf) {

    boxes.setLocalRotation(boxes.getLocalRotation().fromAngleAxis((mouseX / 30) * FastMath.DEG_TO_RAD, Vector3f.UNIT_Z));

    inputManager.setCursorVisible(false);

    }



    @Override

    public void simpleRender(RenderManager rm) {

    //TODO: add render code

    }



    /**
  • Adds a {@code RigidBodyControl} to the middle box in the game and sets the variables

    */

    private class AddRigidBodyToMiddleBox implements PhysicsTickListener {

    private PhysicsGhostObject ghostObject;

    private Geometry target;



    private AddRigidBodyToMiddleBox(Geometry target) {

    this.target = target;

    CollisionShape boxColl = CollisionShapeFactory.createDynamicMeshShape(target);

    target.addControl(new RigidBodyControl(boxColl, 1f));

    RigidBodyControl targetRB = target.getControl(RigidBodyControl.class);

    targetRB.setKinematic(true);

    targetRB.setKinematicSpatial(true);

    bulletAppState.getPhysicsSpace().add(targetRB);



    ghostObject = new PhysicsGhostObject(boxColl);

    ghostObject.setPhysicsLocation(target.getLocalTranslation());



    bulletAppState.getPhysicsSpace().add(ghostObject);

    }



    public void prePhysicsTick(PhysicsSpace space, float tpf) {

    ghostObject.setPhysicsLocation(target.getLocalTranslation());

    }



    public void physicsTick(PhysicsSpace space, float tpf) {

    boolean collisionMade = false;



    for (Iterator<PhysicsCollisionObject> it = ghostObject.getOverlappingObjects().iterator(); it.hasNext():wink: {

    PhysicsCollisionObject pco = it.next();

    if(pco instanceof RigidBodyControl) {

    if(!pco.getUserObject().toString().equals(“Box (Geometry)”)) {

    System.out.println(pco.getUserObject().toString());

    collisionMade = true;

    }

    }

    }

    if(!collisionMade) {

    System.out.println(“No collision detected…”);

    }

    }

    }

    }

    [/java]

    I’m printing out wether there are collisions and with which objects these collisions are. You can rotate the red boxes with the mouse. It shouldn’t collide with the white box ever, but according to the PhysicsTickListener it does.



    There’s also another minor-problem, regarding the mouse (so quite off-topic). When I call inputManager.setCursorVisible(false); in simpleInitApp it stays visible.

I got the OnCollisionExit working now, using the PhysicsCollisionListener… This is more or less how I’m doing it in case anyone is interested:



[java]

public class BallRB extends RigidBodyControl implements PhysicsCollisionListener {



private boolean playerOnCollisionExitIsTriggered = true; //true = default.

private long timePlayerCollidedWithBall = System.currentTimeMillis();



/**

  • Polls wether the last collision between the ball and the player
  • is equal to the current time. This is called in {@code simpleUpdate(float tpf)}.

    */

    public void pollPlayerCollisionOnExit() {

    if(!playerOnCollisionExitIsTriggered) {

    if(timePlayerCollidedWithBall != System.currentTimeMillis()) {

    //Code to handle.

    playerOnCollisionExitIsTriggered = true;

    }

    }

    }



    /**
  • Checks if the collision this {@code BallRB} made was with a player.
  • @param collisionWith The name of the object this {@code BallRB} collided with.
  • @return {@code true} if the collisionWith argument equals "player".
  • <br>{@code false} otherwise.

    */

    private boolean checkIfCollisionWasWithPlayer(String collisionWith) {

    if(collisionWith.equals("player")) {

    return true;

    }

    return false;

    }



    /**
  • The event-handler for this PhysicsCollisionListener.
  • @param event

    */

    public void collision(PhysicsCollisionEvent event) {

    String collisionWith = “”;

    if(event.getNodeA().getName().equals(“ball”) || event.getNodeB().getName().equals(“ball”)) {

    if(event.getNodeA().getName().equals(“ball”)) {

    collisionWith = event.getNodeB().getName();

    }

    else {

    collisionWith = event.getNodeA().getName();

    }

    }



    if(checkIfCollisionWasWithPlayer(collisionWith)) {

    playerOnCollisionExitIsTriggered = false;

    timePlayerCollidedWithBall = System.currentTimeMillis();

    return; //Only has to be called in case other collision-

    //checks are made after this check to save time.

    }

    }

    }

    [/java]



    I’m not sure if this is the most persistent way of doing this, but hey… It works for now! To make it more persistent you might want to do the polling as follows, however this does mean that the code to handle is triggered 1 frame after the ball actually stopped colliding with the player.



    [java]

    public void pollPlayerCollisionOnExit(float tpf) {

    if(!playerOnCollisionExitIsTriggered) {

    long tpfInMillis = tpf * 1000;

    if(timePlayerCollidedWithBall <= System.currentTimeMillis() - tpfInMillis) {

    //Code to handle.

    playerOnCollisionExitIsTriggered = true;

    }

    }

    }

    [/java]