Question about PhysicsCollisionListener

Hi @sgold , I have a doubt. What is the best way to add/remove an object to the scene graph after detecting a collision between objects via PhysicsCollisionListener?

Can I add/remove objects in the PhysicsCollisionListener#collision() method or should I include a request in the next frame using the Application#enqueue() method ?

public class MyPhysxCollisionListener implements PhysicsCollisionListener {
        
	private Application app;
	
	public MyPhysxCollisionListener(Application app) {
		this.app = app;
	}
	
	@Override
	public void collision(PhysicsCollisionEvent event) {
		//System.out.println("Event: " + event.getNodeA() + " " + event.getNodeB());
		if (checkCollision(event, "ObjectA", "ObjectB")) {
			//Option 1 ???
				// detach ObjectA
				// create and attach ParticleEmitter
				// audioNode.play(); //play SFX
				// update UI (e.g. score++)
                
			//or Option 2 ???
			app.enqueue(() -> {
				// detach ObjectA
				// create and attach ParticleEmitter
				// audioNode.play(); //play SFX
				// update UI (e.g. score++)
                });
		}
	}
	
}

Or is there a third option that I am not considering?

Thanks

1 Like

collision() executes on the render thread, so it should be safe to modify the scene directly, without going through enqueue().

4 Likes

Got it, thanks :wink:

1 Like

Hi @sgold , I am trying to optimize the mini-game I posted last month.

I am exploring car physics with Minie and having a lot of fun! During my studies I came across some optimization problems that I hadn’t faced before.
What is the best way to detect collisions between objects?
In my example I have a car, enemies (8 with CapsuleCollisionShape) and obstacles (24 with BoxCollisionShape).

I used a PhysicsCollisionListener to detect collisions between the objects, registering it on the physicsSpace with the addCollisionListener() method.

  • What is the general performance of a PhysicsCollisionListener in terms of speed, objects created (eg. PhysicsCollisionEvent) etc… ?
  • Are there any parameters I need to consider ?
  • Can I optimize something using CollisionGroups?
  • Is this the best way to handle collisions in this scenario or is there a better solution?

I visited the Minie project website at the Collision section.

I found the paragraph about collosion-group listeners interesting, but I could not find examples on your repository on how to use them. Could you please help me with that? Do you think they might be useful in my case?

For each collision group in a space, you can register a collision-group listener. 
Minie invokes the collision-group listeners for each AABB overlap that isn’t 
suppressed by collision groups or ignore lists.

You can dynamically filter AABB overlaps by registering a listener that returns `true` 
for overlaps that may cause collisions and `false` for those that should never cause collisions.

Collision-group listeners are the most flexible mechanism for filtering collisions.

Thanks in advance

1 Like

The cheap answer is that the best way depends on the application. I guess the real question is: why do you want to detect collisions?

Is it for scoring, for sound effects, for deciding when the game is over?

How precise does it need to be? AABB-precise or collision-shape precise?

If multiple collisions happen simultaneously, do you need the details of each one, such as contact locations and normals?

Is there a particular object whose collisions you care about (the car), or do you care about all moving objects (including collisions between enemies)?

Do you want to know the full duration of each collision or just when it begins?

What is the general performance of a PhysicsCollisionListener in terms of speed, objects created (eg. PhysicsCollisionEvent) etc… ?

Registering a collision listener could result in the creation of many events, in other words, garbage. I can imagine that might harm the performance of some applications. To find out whether your game is harmed, you’d have to make measurements.

Can I optimize something using CollisionGroups?

That depends what you’re trying to do.

In Archer-Game-Template2, we used collision groups to ignore collisions between character controls and arrows. That was motivated by verisimilitude, not optimization, but I can imagine situations where there would be a performance benefit.

Just like physics-collision listeners, collision-group listeners cause creation of short-lived event objects. To avoid creating garbage, you might use physicsSpace.listManifoldIds() and/or the ContactListener interface instead. Those are low-level interfaces that don’t create Java objects.

I found the paragraph about collosion-group listeners interesting, but I could not find examples on your repository on how to use them.

I don’t have examples handy because I don’t use collision-group listeners much, just enough to ensure they work. I didn’t invent them; Minie provides them for compatibility with jme3-jbullet.

3 Likes

Thank you Stephen, here are the details you requested:

yep

AABB-precise I think may be fine in my case.

In this case they are not necessary, I just place the VFX effects using the current position of the struck enemy and not the vehicle-enemy impact point.

I need to know the collisions that occur between vehicle-enemy and vehicle-obstacle.
Yes I would prefer to keep collisions between enemies for more realism, but I don’t need to know when they collide with each other. All game objects have COLLISION_GROUP set to 1 = default.

In this case I think it is enough to know only the moment when the collision begins.

I figured as much, but I wanted to make sure, in fact when I start the game the pc cooling fan starts spinning almost immediately and never stops.

Okay, I discard the solution of using collision-group listeners.

Where can I find use cases? How can I use them in my case?

Edit:
My current code is this:


    private void initPhysics() {
        physics = new BulletAppState();
        stateManager.attach(physics);
        
        physics.getPhysicsSpace().addCollisionListener(playerObstacleCollGroup);
        physics.getPhysicsSpace().addCollisionListener(playerEnemyCollGroup);
    }

    private final PhysicsCollisionListener playerObstacleCollGroup = new PhysicsCollisionListener() {
        @Override
        public void collision(PhysicsCollisionEvent event) {
            if (checkCollision(event, "MyVehicle", "Obstacle")) {
                // decrease health, play SFX, update UI ...
            }
        }
    };
    
    private final PhysicsCollisionListener playerEnemyCollGroup = new PhysicsCollisionListener() {
        @Override
        public void collision(PhysicsCollisionEvent event) {
            if (checkCollision(event, "MyVehicle", "Enemy")) {
                // detach Enemy object from scene, play VFX and SFX, update UI ...
            }
        }
    };
1 Like

If you don’t need enemy-enemy collisions, then it makes sense to put the enemies in their own collision group.

For detection, if you can manage with just AABB precision and don’t need contact data, then I suggest adding a GhostControl to the car.

Where can I find use cases?

For GhostControl there are quite a few examples in jme3-examples: TestAttachGhostObject, RollingTheMonkey, and TestGhostObject.

The only example I have that use ContactListener directly is ConveyorDemo. It’s a weird example because it modifies contact points, which isn’t necessary (or recommended) for your game.

If you decide you need more precision than GhostControl can provide, then you would create a custom ContactListener for your PhysicsSpace. The listener would implement an onContactStarted() method that tests whether the vehicle is involved and reacts accordingly. Since you only care about new contacts, the other 2 methods could be no-ops.

Note that onContactStarted() executes on the physics thread. If you specify PARALLEL threading, it must avoid modifying the scene graph directly. (PARALLEL threading isn’t the default, so if you use it, you’d know.)

1 Like
  1. The ContactListener interface exposes these 3 methods:
    private final ContactListener contactListener = new ContactListener() {

        @Override
        public void onContactStarted(long manifoldId) {
            // How do I know between which objects the collision has started?
        }

        @Override
        public void onContactProcessed(PhysicsCollisionObject pcoA, PhysicsCollisionObject pcoB, long manifoldPointId) {
        }

        @Override
        public void onContactEnded(long manifoldId) {
            // How do I know between which objects the collision has ended?
        }
        
    };
  1. Is the ContactListener interface more performant than the PhysicsCollisionListener? What I mean is, does it generate less garbage?
  1. I suppose the physicsSpace.contactTest(PhysicsCollisionObject pco, PhysicsCollisionListener listener) method also generates a lot of garbage (in terms of PhysicsCollisionEvent objects created) if called continuously in an update loop, right?

eg.

    @Override
    public void simpleUpdate(float tpf) {
        int numContacts = physicsSpace().contactTest(ghost, new PhysicsCollisionListener() {
            @Override
            public void collision(PhysicsCollisionEvent event) {
            }
        });
    }
  1. What is the performance of GhostControl ? Does it generate a lot of garbage when used in an update loop or not?

eg.

    @Override
    public void simpleUpdate(float tpf) {
        for (PhysicsCollisionObject pco : ghost.getOverlappingObjects()) {
            System.out.println(pco.getUserObject());
        }
    }
  1. The physicsSpace.hasContact(CollisionShape shape0, CollisionShape shape1) method; sounds interesting. Maybe I could use it to detect vehicle-health pack collisions.

eg:

    CollisionShape shape0 = CollisionShapeFactory.createBoxShape(chassis);
    CollisionShape shape1 = CollisionShapeFactory.createBoxShape(healthpack);

    @Override
    public void simpleUpdate(float tpf) {
        if (physicsSpace.hasContact(shape0, shape1)) {
            //increase health, update UI...
        }
    }

I am trying to figure out what is the best way to detect collisions between objects with Minie. Thanks for your patience, I think this is an important lesson for the whole community :slight_smile:

1 Like

You can obtain the object IDs using the getBodyAId() and getBodyBId() methods in the com.jme3.bullet.collision.PersistentManifolds class.

  1. Is the ContactListener interface more performant than the PhysicsCollisionListener? What I mean is, does it generate less garbage?

Yes.

In recent versions of Minie, ContactListener is the underlying mechanism on which PhysicsCollisionListener is implemented, so it’s bound to be more efficient.

As far as I know, ContactListener doesn’t generate any garbage.

I suppose the physicsSpace.contactTest(PhysicsCollisionObject pco, PhysicsCollisionListener listener) method also generates a lot of garbage (in terms of PhysicsCollisionEvent objects created) if called continuously in an update loop, right?

If the 2nd argument of contactTest() is non-null, a PhysicsCollisionEvents object is created for each contact, and that quickly becomes garbage.

On the other hand, if all you need is a count, you can pass null for the listener, which avoids creating events.

What is the performance of GhostControl ? Does it generate a lot of garbage when used in an update loop or not?

It generates garbage, but generally not as much as addCollisionListener().

The physicsSpace.hasContact(CollisionShape shape0, CollisionShape shape1) method; sounds interesting. Maybe I could use it to detect vehicle-health pack collisions.

I think that might work. I’d suggest using convex shapes for both the vehicle and the health pack.

I am trying to figure out what is the best way to detect collisions between objects with Minie. Thanks for your patience, I think this is an important lesson for the whole community :slight_smile:

I agree. It’s a complex topic. There are many mechanisms, each with their own quirks and limitations. The tutorial page provides just a high-level summary.

I encourage you to experiment and report your findings, keeping in mind that the “best” mechanism for your game might not be best for all applications.

Another thing to keep in mind: the mechanisms provided by Bullet weren’t designed for games. They’re all spinoffs from rigid-body simulation, which is Bullet’s central purpose. In some cases, you may do better simply comparing center-to-center squared distances than going through Bullet physics.

1 Like

I did a test in debug mode and noticed that the ContactListener and PhysicsCollisionListener interfaces produce the same level of garbage. There is no difference in performance.

Simple test case:

public class Main extends SimpleApplication {

    private BulletAppState physics;
    
    public static void main(String[] args) {
        Main app = new Main();
        AppSettings settings = new AppSettings(true);
        app.setSettings(settings);
        app.setShowSettings(false);
        app.setPauseOnLostFocus(false);
        app.start();
    }

    @Override
    public void simpleInitApp() {
        physics = new BulletAppState();
        stateManager.attach(physics);
        physics.getPhysicsSpace().addContactListener(contactListener);
    }
    
    @Override
    public void simpleUpdate(float tpf) {
    }
    
    private final ContactListener contactListener = new ContactListener() {

        @Override
        public void onContactStarted(long manifoldId) {
        }

        @Override
        public void onContactProcessed(PhysicsCollisionObject pcoA, PhysicsCollisionObject pcoB, long manifoldPointId) {
        }

        @Override
        public void onContactEnded(long manifoldId) {
        }
        
    };

}

In the PhysicsSpace.onContactStarted() and PhysicsSpace.onContactProcessed() methods, a bunch of PhysicsCollisionEvent objects are always generated even though no PhysicsCollisionListener has been registered.

To solve the problem I think we should create the PhysicsCollisionEvent objects only if some PhysicsCollisionListener has been registered.

This way it makes sense to use the ContactListener interface instead of the PhysicsCollisionListener to reduce the amount of garbage generated.

– see my comments

    @Override
    public void onContactStarted(long manifoldId) {
        assert NativeLibrary.jniEnvId() == jniEnvId() : "wrong thread";

        for (ContactListener listener : contactListeners) {
            listener.onContactStarted(manifoldId);
        }

// if (!contactStartedListeners.isEmpty()) { // <- NEW
        int numPoints = PersistentManifolds.countPoints(manifoldId);
        if (numPoints == 0) {
            return;
        }

        long bodyAId = PersistentManifolds.getBodyAId(manifoldId);
        PhysicsCollisionObject pcoA
                = PhysicsCollisionObject.findInstance(bodyAId);
        long bodyBId = PersistentManifolds.getBodyBId(manifoldId);
        PhysicsCollisionObject pcoB
                = PhysicsCollisionObject.findInstance(bodyBId);

        for (int i = 0; i < numPoints; ++i) {
            long pointId = PersistentManifolds.getPointId(manifoldId, i);
            PhysicsCollisionEvent event
                    = new PhysicsCollisionEvent(pcoA, pcoB, pointId);

            // Queue the event to be handled later by distributeEvents().
            contactStartedEvents.add(event);
        }
//}
    }

– see my comments

    @Override
    public void onContactProcessed(PhysicsCollisionObject pcoA,
            PhysicsCollisionObject pcoB, long pointId) {
        assert NativeLibrary.jniEnvId() == jniEnvId() : "wrong thread";

        for (ContactListener listener : contactListeners) {
            listener.onContactProcessed(pcoA, pcoB, pointId);
        }

// if (!contactProcessedListeners.isEmpty()) { // <- NEW
        PhysicsCollisionEvent event
                = new PhysicsCollisionEvent(pcoA, pcoB, pointId);

        // Queue the event to be handled later by distributeEvents().
        contactProcessedEvents.add(event);
    }
//}
  1. I am curious about these flags used in the stepSimulation() method: doStarted, doProcessed and doEnded. I would like to use them to exclude events that I don’t need. Can they be configured? If yes, how?

  2. I am doing some tests on the PhysicsSpace.hasContact(CollisionShape shape0, CollisionShape shape1) method, but they don’t look promising. If the shapes are boxes, it is easier to use the BoundingVolume.intersects(BoundingVolume bv) method.

  3. I will also do some tests on GhostControl performance and let you know the results.

Totally agree! I would like to know the full potential offered by Minie to find the best solution for me. During this process I will try to explore as many solutions as possible for the benefit of all. Thank you as always for your support.

1 Like

I think we should create the PhysicsCollisionEvent objects only if some PhysicsCollisionListener has been registered.

That’s a good optimization. I’ll incorporate your idea into Minie ASAP.

I am curious about these flags used in the stepSimulation() method: doStarted, doProcessed and doEnded. I would like to use them to exclude events that I don’t need. Can they be configured? If yes, how?

Currently there’s no direct way to configure them if you use BulletAppState. However, if you create a PhysicsSpace directly you can choose to invoke the 5-argument update() method, which specifies those flags. See the tutorial page Physics simulation without appstates :: The Minie project for more info about bypassing BulletAppState.

I’ll think about how to make the flags accessible when using BulletAppState.

I also should mention the 5-argument update() method in the tutorial!

2 Likes

While waiting for you to develop a way to configure the doEnded, doProcessed, doStarted flags from BulletAppState, I found a trick to call the 5-parameter PhysicsSpace.update() method without losing the debugging functionality of BulletAppState. I cloned the BulletAppState class by renaming it MyBulletAppState, and rewrote the render(RenderManager rm) method like this:

    // New variable with getter/setter methods
    private StepSimulationConfig stepSimulationConfig;

    @Override
    public void render(RenderManager rm) {
        super.render(rm);

        if (threadingType == ThreadingType.PARALLEL) {
            this.physicsFuture = executor.submit(parallelPhysicsUpdate);
        } else if (threadingType == ThreadingType.SEQUENTIAL) {
            PhysicsSpace pSpace = debugConfig.getSpace();
            float timeInterval = isEnabled() ? (tpf * speed) : 0f;

            if (stepSimulationConfig != null) { 
                // <-- new configuration -->
                if (pSpace.maxSubSteps() == 0) {
                    timeInterval = Math.min(timeInterval, pSpace.maxTimeStep());
                }
                pSpace.update(timeInterval, pSpace.maxSubSteps(), 
                        stepSimulationConfig.isDoEnded(), stepSimulationConfig.isDoProcessed(), stepSimulationConfig.isDoStarted());
            } else { 
                // <-- default configuration -->
                pSpace.update(timeInterval);
            }
        }
    }

–

public class StepSimulationConfig {

    private boolean doEnded = true;
    private boolean doProcessed = true;
    private boolean doStarted = true;

    public StepSimulationConfig() {}
    
    public StepSimulationConfig(boolean doEnded, boolean doProcessed, boolean doStarted) {
        this.doEnded = doEnded;
        this.doProcessed = doProcessed;
        this.doStarted = doStarted;
    }

    // getters/setters
}

Everything works perfectly, the only drawback of this approach, is that I lost the BulletAppState parallelization function because the CollisionSpace.setLocalThreadPhysicsSpace(pSpace) method is not public. I do not currently use ThreadingType.PARALLEL, so I commented out the code from BulletAppState.stateAttached() method.

    @Override
    public void stateAttached(AppStateManager stateManager) {
        super.stateAttached(stateManager);

        this.stateManager = stateManager;
        if (!isRunning) {
            startPhysics();
        }

        //TODO: make setLocalThreadPhysicsSpace() method public
//        if (threadingType == ThreadingType.PARALLEL) {
//            PhysicsSpace pSpace = debugConfig.getSpace();
//            CollisionSpace.setLocalThreadPhysicsSpace(pSpace); 
//        }
    }

I think if you made that method public everyone could customize the BulletAppState class as they like, without having to manually update the PhysicsSpace. That would allow a lot of possibilities. It might be worth thinking about.

Here is an example:

public class Main extends SimpleApplication {

    private MyBulletAppState physics;
    
    public static void main(String[] args) {
        Main app = new Main();
        AppSettings settings = new AppSettings(true);
        app.setSettings(settings);
        app.setShowSettings(false);
        app.setPauseOnLostFocus(false);
        app.start();
    }

    @Override
    public void simpleInitApp() {
        physics = new MyBulletAppState();
        stateManager.attach(physics);
        physics.setStepSimulationConfig(new StepSimulationConfig(false, false, true));
        physics.setDebugEnabled(true);
        // initScene();
        physics.getPhysicsSpace().addContactListener(contactListener);
    }
    
    @Override
    public void simpleUpdate(float tpf) {
    }
    
    private final ContactListener contactListener = new ContactListener() {

        @Override
        public void onContactStarted(long manifoldId) {
                System.out.println("onContactStarted");
        }

        @Override
        public void onContactProcessed(PhysicsCollisionObject pcoA, PhysicsCollisionObject pcoB, long manifoldPointId) {
                System.out.println("onContactProcessed");
        }

        @Override
        public void onContactEnded(long manifoldId) {
                System.out.println("onContactEnded");
        }
        
    };

}

– Output: “onContactEnded”

I saw that you have already added the changes I suggested. Thank you so much! It is always a pleasure to collaborate with you!

1 Like

I cloned the BulletAppState class

Wouldn’t it be simpler to extend BulletAppState instead of copying it?

1 Like

I considered your solution and I think it works only if you use ThreadingType.SEQUENTIAL mode.

// BulletAppState class code...

    @Override
    public void render(RenderManager rm) {
        super.render(rm);

        if (threadingType == ThreadingType.PARALLEL) {
            this.physicsFuture = executor.submit(parallelPhysicsUpdate);
        } else if (threadingType == ThreadingType.SEQUENTIAL) {
            PhysicsSpace pSpace = debugConfig.getSpace();
            pSpace.update(isEnabled() ? tpf * speed : 0f);
        }
    }

The variables regarding physicsFuture, executor, parallelPhysicsUpdate, tpf objects are not accessible by extending the BulletAppState class. If I wanted to use ThreadingType.PARALLEL mode I would also have to modify the parallelPhysicsUpdate object that invokes the pSpace.update(float tpf) method, and I could not do that by extending BulletAppState.

// BulletAppState class code...

    final private Callable<Boolean> parallelPhysicsUpdate
            = new Callable<Boolean>() {
        @Override
        public Boolean call() throws Exception {
            PhysicsSpace pSpace = debugConfig.getSpace();
            pSpace.update(isEnabled() ? tpf * speed : 0f); // <- replace it with 5-parameters method
            return true;
        }
    };

Here is what the code would look like by extending the BulletAppState class:

public class MyBulletAppState2 extends BulletAppState  {
    
    private float tpf;
    private StepSimulationConfig stepSimulationConfig;
    
    public StepSimulationConfig getStepSimulationConfig() {
        return stepSimulationConfig;
    }

    public void setStepSimulationConfig(StepSimulationConfig stepSimulationConfig) {
        this.stepSimulationConfig = stepSimulationConfig;
    }
    
    @Override
    public void update(float tpf) {
        super.update(tpf);
        this.tpf = tpf;
    }

    @Override
    public void render(RenderManager rm) {
//        if (threadingType == ThreadingType.PARALLEL) {
//            this.physicsFuture = executor.submit(parallelPhysicsUpdate);
//        } else if (threadingType == ThreadingType.SEQUENTIAL) {
//            PhysicsSpace pSpace = debugConfig.getSpace();
//            pSpace.update(isEnabled() ? tpf * speed : 0f);
//        }

        if (getThreadingType() == ThreadingType.SEQUENTIAL) {
            PhysicsSpace pSpace = getPhysicsSpace();
            float timeInterval = isEnabled() ? (tpf * getSpeed()) : 0f;

            if (stepSimulationConfig != null) {
                if (pSpace.maxSubSteps() == 0) {
                    timeInterval = Math.min(timeInterval, pSpace.maxTimeStep());
                }
                pSpace.update(timeInterval, pSpace.maxSubSteps(), 
                        stepSimulationConfig.isDoEnded(), stepSimulationConfig.isDoProcessed(), stepSimulationConfig.isDoStarted());
            } else {
                pSpace.update(timeInterval);
            }
        } else {
            super.render(rm);
        }
    }

}

In any case, ThreadingType.PARALLEL mode is not my priority at the moment. To run a test right away, I can extend the BulletAppState class (the ThreadingType.PARALLEL mode would remain unchanged but still work and this is already better than my solution)… But I am not in a hurry, I can wait for you to find the most suitable solution to configure the stepSimulation flags in the next version of Minie. :wink:

2 Likes

I designed a solution while I was out on my walk. It’s all right here in my noodle. The rest is just scribbling. :wink:

EDIT: @capdevon please take a look at add a 4-argument addContactListener() method (addresses issue #37) · stephengold/Minie@543c158 · GitHub (not tested yet)

2 Likes

I have a doubt… I want to make sure I am interpreting the code correctly.
Suppose we have this case:

    @Override
    public void simpleInitApp() {

        BulletAppState bullet = new BulletAppState();
        stateManager.attach(bullet);
        PhysicsSpace pSpace = bullet.getPhysicsSpace();

        ...
        pSpace.addContactListener(c1, true, false, false);
        pSpace.addContactListener(c2)
        pSpace.addContactListener(c3, false, true, false);
        pSpace.addCollisionListener(L1);
    }
  • What will be the final value of the boolean flags doE, doP and doS of the ContactManager class?
  • Which events will be notified to listener c1?
  • Which events will be notified to listener c2?
  • Which events will be notified to listener c3?
  • Which events will be notified to listener L1?

Before I tell you what I think, I want to make sure that your answers match mine. :slight_smile:

2 Likes

I was just thinking about this sort of situation last night after I went to bed.

As currently written:

  • doE, doP, and doS will all be true.
  • c1, c2, and c3 will receive all events (new contact, contact processed, and contact end).
  • L1 will receive new-contact events.

With a simple change, I believe I can make it behave as the javadoc suggests:

  • doE, doP, and doS will all be true.
  • c1 will receive contact-end events.
  • c2 will receive all events.
  • c3 will receive contact-processed events.
  • L1 will receive new-contact events.
2 Likes

Okay, I think it’s working now. The solution is brilliant! Have you done any testing?

One last thing: since you were able to encapsulate flags handling in the ContactManager class, why not add an interface and getter/setter ContactManager methods in the PhysicsSpace class to allow special customizations to users? That way everyone can replace the code base with their own optimizations and have easier access to the 5-parameter PhysicsSpace.update() method.

PhysicsSpace :

public class PhysicsSpace extends CollisionSpace implements ContactListener {

    // manage contact/collision listeners and events
    private ContactManager contactManager = new DefaultContactManager(this);
    
    public ContactManager getContactManager() {
        return contactManager;
    }

    public void setContactManager(ContactManager contactManager) {
        this.contactManager = contactManager;
    }

}

ContactManager :

public abstract class ContactManager implements ContactListener {
    
    // PhysicsSpace whose notifications are being managed.
    private final PhysicsSpace space;
    
    public ContactManager(PhysicsSpace space) {
        this.space = space;
    }

    public abstract void addCollisionListener(PhysicsCollisionListener listener);

    public abstract void addOngoingCollisionListener(PhysicsCollisionListener listener);
    
    public abstract void addContactListener(ContactListener listener, boolean doEnded, boolean doProcessed, boolean doStarted);

    public abstract void removeCollisionListener(PhysicsCollisionListener listener);

    public abstract void removeOngoingCollisionListener(PhysicsCollisionListener listener);
    
    public abstract void removeContactListener(ContactListener listener);
    
    public abstract int countCollisionListeners();
    
    public abstract void distributeEvents();

    public abstract void update(float timeInterval, int maxSteps);

    public PhysicsSpace getPhysicsSpace() {
        return space;
    }

}

DefaultContactManager :

public class DefaultContactManager extends ContactManager {

    public DefaultContactManager(PhysicsSpace space) {
        super(space);
    }
    
    /**
     * Update the doEnded, doProcessed, and doStarted flags after a listener is
     * removed.
     */
    private void updateFlags() {
    }
    
    @Override
    public synchronized int countCollisionListeners() {
        // your implementation
    }

    @Override
    public void onContactEnded(long manifoldId) {
        // your implementation
    }

    @Override
    public void onContactProcessed(PhysicsCollisionObject pcoA, PhysicsCollisionObject pcoB, long manifoldPointId) {
        // your implementation
    }

    @Override
    public void onContactStarted(long manifoldId) {
        // your implementation
    }

    @Override
    public synchronized void addCollisionListener(PhysicsCollisionListener listener) {
        // your implementation
    }

    @Override
    public synchronized void addOngoingCollisionListener(PhysicsCollisionListener listener) {
        // your implementation
    }

    @Override
    public synchronized void addContactListener(ContactListener listener, boolean doEnded, boolean doProcessed, boolean doStarted) {
        // your implementation
    }

    @Override
    public synchronized void removeCollisionListener(PhysicsCollisionListener listener) {
        // your implementation
    }

    @Override
    public synchronized void removeOngoingCollisionListener(PhysicsCollisionListener listener) {
        // your implementation
    }

    @Override
    public synchronized void removeContactListener(ContactListener listener) {
        // your implementation
    }

    @Override
    public synchronized void distributeEvents() {
        // your implementation
    }

    @Override
    public void update(float timeInterval, int maxSteps) {
        // your implementation
    }

}

If you don’t like the idea, that’s okay with me. I will wait for the release of the new version of Minie and I will be your tester :wink:

2 Likes

This thread is very informative.
I was unsure how to implement a feature to detect a continuous collision (e.g. is the player under the water?) and I didn’t know about the ContactListener with separate start-end events (I only knew about the PhysicsCollisionListener).

I like @capdevon’s suggestion, it would make the api clearer in my opinion. :slight_smile:

2 Likes

Yes, but not enough. TestCollisionListener is cr*p. I should write additional tests. You could help, if you want.

since you were able to encapsulate flags handling in the ContactManager class, why not add an interface and getter/setter ContactManager methods in the PhysicsSpace class to allow special customizations to users?

An intriguing idea!

What would the use case be? Do you foresee a situation where you couldn’t just create an object that implements ContactListener and add it to the space?

I will wait for the release of the new version of Minie

… which should be soon

and I will be your tester

I’d like your help with testing prior to the release. Here are instructions for installing a local snapshot: How to build Minie from source :: The Minie project (checking out the latest “master” instead of the 7.7.0 tag, of course)

Then, to use your local snapshot in a Gradle project, you would do something like:

repositories {
    mavenLocal() // to find libraries installed locally
    mavenCentral() // to find libraries released to the Maven Central repository
}
dependencies {
    implementation 'com.github.stephengold:Minie:7.7.1-SNAPSHOT'
}
1 Like