[SOLVED] ContactListener: onContactStarted get called multiple times

I have functions that get called when two shapes collide, each step as long shapes collide and when shapes don’t collide anymore. First I tried my own solution based on PhysicsCollisionListener, but was very unhappy with it and tried to ported it to ContactListener, since it seem it does exactly what I want.

But I noted, onContactStarted get called multiple times, basing on the used shapes and controls:

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.ContactListener;
import com.jme3.bullet.collision.PersistentManifolds;
import com.jme3.bullet.collision.PhysicsCollisionObject;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.collision.shapes.SphereCollisionShape;
import com.jme3.bullet.control.CharacterControl;
import com.jme3.bullet.control.GhostControl;
import com.jme3.input.ChaseCamera;
import com.jme3.light.DirectionalLight;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;

public final class Main extends SimpleApplication {
	@Override
	public void simpleInitApp() {
		final Node ninjaNode = new Node();
		
		final Spatial ninja = assetManager.loadModel("Models/Ninja/Ninja.mesh.xml");
		ninja.scale(0.05f, 0.05f, 0.05f);
		ninja.rotate(0.0f, -3.0f, 0.0f);
		ninja.setLocalTranslation(0.0f, -5.0f, -2.0f);
		ninjaNode.attachChild(ninja);
		
		final Node ninjaShapeNode = new Node("NinjaShape");
		ninjaNode.attachChild(ninjaShapeNode);
		
		getRootNode().attachChild(ninjaNode);
		
		
		final BulletAppState bullet = new BulletAppState();
		bullet.setDebugEnabled(true);
		getStateManager().attach(bullet);
		
		bullet.getPhysicsSpace().addContactListener(new Listener());
		
		final GhostControl ninjaGhost = new GhostControl(new SphereCollisionShape(25));
		ninjaShapeNode.addControl(ninjaGhost);
		bullet.getPhysicsSpace().add(ninjaGhost);
		
		final CharacterControl ninjaControl = new CharacterControl(new BoxCollisionShape(10, 10, 10), 0);
		ninjaControl.setGravity(0);
		//final RigidBodyControl ninjaControl = new RigidBodyControl(CollisionShapeFactory.createMeshShape(ninja), 0);
		//final RigidBodyControl ninjaControl = new RigidBodyControl(new BoxCollisionShape(10, 10, 10), 0);
		ninja.addControl(ninjaControl);
		bullet.getPhysicsSpace().add(ninjaControl);
		
		
		ninja.addControl(new ChaseCamera(getCamera(), ninja, getInputManager()));
		
		getRootNode().addLight(new DirectionalLight(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal()));
	}
	
	private static class Listener implements ContactListener {
		@Override
		public void onContactStarted(final long manifoldId) {
			final long bodyAId = PersistentManifolds.getBodyAId(manifoldId);
			final PhysicsCollisionObject pcoA = PhysicsCollisionObject.findInstance(bodyAId);
			final long bodyBId = PersistentManifolds.getBodyBId(manifoldId);
			final PhysicsCollisionObject pcoB = PhysicsCollisionObject.findInstance(bodyBId);
			
			final Spatial spatialA = getSpatialFromPCO(pcoA);
			final Spatial spatialB = getSpatialFromPCO(pcoB);
			
			if(spatialA == null || spatialB == null) {
				return;
			}
			
			final String nameA = spatialA.getName();
			final String nameB = spatialB.getName();
			
			System.out.println("Started:" + nameA + " - " + nameB);
		}
		@Override
		public void onContactProcessed(final PhysicsCollisionObject pcoA, final PhysicsCollisionObject pcoB, final long manifoldId) {
			
		}
		@Override
		public void onContactEnded(final long manifoldId) {
			final long bodyAId = PersistentManifolds.getBodyAId(manifoldId);
			final PhysicsCollisionObject pcoA = PhysicsCollisionObject.findInstance(bodyAId);
			final long bodyBId = PersistentManifolds.getBodyBId(manifoldId);
			final PhysicsCollisionObject pcoB = PhysicsCollisionObject.findInstance(bodyBId);
			
			final Spatial spatialA = getSpatialFromPCO(pcoA);
			final Spatial spatialB = getSpatialFromPCO(pcoB);
			
			if(spatialA == null || spatialB == null) {
				return;
			}
			
			final String nameA = spatialA.getName();
			final String nameB = spatialB.getName();
			
			System.out.println("Ended:" + nameA + " - " + nameB);
		}
		public Spatial getSpatialFromPCO(final PhysicsCollisionObject obj) {
			final Object userObject = obj.getUserObject();
			if (userObject instanceof Spatial) {
				return (Spatial) (userObject);
			}
			return(null);
		}
	}
	
	public static void main(final String[] args) {
		new Main().start();
	}
}

With

final CharacterControl ninjaControl = new CharacterControl(new BoxCollisionShape(10, 10, 10), 0);
ninjaControl.setGravity(0);

it prints

Started:NinjaShape - Ninja-ogremesh
Started:NinjaShape - Ninja-ogremesh

With

final RigidBodyControl ninjaControl = new RigidBodyControl(CollisionShapeFactory.createMeshShape(ninja), 0);

also.

With

final RigidBodyControl ninjaControl = new RigidBodyControl(new BoxCollisionShape(10, 10, 10), 0);

it prints just one line.

Did I done something wrong?

1 Like

Did I done something wrong?

Probably not. It’s common for a collision between 2 objects to involve multiple contact manifolds.

If you merely want to know when the box and sphere first come into contact (you’re not interested in further details) then you need only pay attention to the first contact manifold; you can ignore the rest.

If you care about how long they stay in contact, then you might want to keep track of how many contact manifolds there are.

In case you care about the locations of the contacts, then you need to do further analysis of the callback results.

1 Like

Thank you. I was irritated by the different call count by different shapes and controls. I added filter and tested it in the game code. Needs more tests, but looks very good.

space.addContactListener(new ContactListener() {
	private final Map<Mapping<PhysicsCollisionObject, PhysicsCollisionObject>, Integer> contacts =
			new HashMap<>();
	
	@Override
	public void onContactStarted(final long manifoldId) {
		final long bodyAId = PersistentManifolds.getBodyAId(manifoldId);
		final long bodyBId = PersistentManifolds.getBodyBId(manifoldId);
		
		// Read sorted by id 
		final PhysicsCollisionObject pcoA;
		final PhysicsCollisionObject pcoB;
		if(bodyAId < bodyBId) {
			pcoA = PhysicsCollisionObject.findInstance(bodyAId);
			pcoB = PhysicsCollisionObject.findInstance(bodyBId);
		} else {
			pcoA = PhysicsCollisionObject.findInstance(bodyBId);
			pcoB = PhysicsCollisionObject.findInstance(bodyAId);
		}
		
		final Spatial spatialA = getSpatialFromPCO(pcoA);
		final Spatial spatialB = getSpatialFromPCO(pcoB);
		
		if(spatialA == null || spatialB == null) {
			return;
		}
		
		final Mapping<PhysicsCollisionObject, PhysicsCollisionObject> pair = new FinalMapping<>(pcoA, pcoB);
		final Integer contactCount = contacts.get(pair);
		if(contactCount != null) {
			contacts.put(pair, contactCount + 1);
			return;
		} else {
			contacts.put(pair, 0);
		}
		
		final String nameA = spatialA.getName();
		final String nameB = spatialB.getName();
		
		handleContactStarted(pcoA, nameA, pcoB, nameB);
	}
	@Override
	public void onContactProcessed(final PhysicsCollisionObject pcoA, final PhysicsCollisionObject pcoB, final long manifoldPointId) {
		final Spatial spatialA = getSpatialFromPCO(pcoA);
		final Spatial spatialB = getSpatialFromPCO(pcoB);
		
		if(spatialA == null || spatialB == null) {
			return;
		}
		
		final String nameA = spatialA.getName();
		final String nameB = spatialB.getName();
		
		handleContactProcessed(pcoA, nameA, pcoB, nameB);
	}
	@Override
	public void onContactEnded(final long manifoldId) {
		final long bodyAId = PersistentManifolds.getBodyAId(manifoldId);
		final long bodyBId = PersistentManifolds.getBodyBId(manifoldId);
		
		// Read sorted by id 
		final PhysicsCollisionObject pcoA;
		final PhysicsCollisionObject pcoB;
		if(bodyAId < bodyBId) {
			pcoA = PhysicsCollisionObject.findInstance(bodyAId);
			pcoB = PhysicsCollisionObject.findInstance(bodyBId);
		} else {
			pcoA = PhysicsCollisionObject.findInstance(bodyBId);
			pcoB = PhysicsCollisionObject.findInstance(bodyAId);
		}
		
		final Spatial spatialA = getSpatialFromPCO(pcoA);
		final Spatial spatialB = getSpatialFromPCO(pcoB);
		
		if(spatialA == null || spatialB == null) {
			return;
		}
		
		final Mapping<PhysicsCollisionObject, PhysicsCollisionObject> pair = new FinalMapping<>(pcoA, pcoB);
		final Integer contactCount = contacts.get(pair);
		if(contactCount != null) {
			if(contactCount > 1) {
				contacts.put(pair, contactCount - 1);
			} else {
				contacts.remove(pair);
			}
		} else {
			return;
		}
		
		final String nameA = spatialA.getName();
		final String nameB = spatialB.getName();
		
		handleContactEnded(pcoA, nameA, pcoB, nameB);
	}
	private Spatial getSpatialFromPCO(final PhysicsCollisionObject obj) {
		final Object userObject = obj.getUserObject();
		if (userObject instanceof Spatial) {
			return (Spatial) (userObject);
		}
		return(null);
	}
	// Handle-methods
});
1 Like