Havok The collision callback function is called multiple times

I want to do something after the object collides, like delete him

I used this cdn address
https://cdn.babylonjs.com/havok/HavokPhysics_umd.js

I set the spring coefficient of the floor and the ball to 0
I added setEventMask event


var sphere =new CreateSphere("sphere", {diameter: 2, segments: 32}, scene);
      var sphereAggregate = new PhysicsAggregate(sphere, PhysicsShapeType.BOX, { mass: 1, restitution: 0}, scene);
      sphereAggregate.body.setCollisionCallbackEnabled(true);
      sphereAggregate.body.setEventMask(hk._hknp.EventType.COLLISION_STARTED.value)

const groundPhy = new PhysicsAggregate(ground,PhysicsShapeType.BOX, { mass: 0, restitution: 0 }, scene);
      groundPhy.body.setCollisionCallbackEnabled(true)
      groundPhy.body.setEventMask(hk._hknp.EventType.COLLISION_STARTED.value)

      const observable = groundPhy.body.getCollisionObservable()
      const observer = observable.add((collisionEvent) => {
        console.log(collisionEvent.collidedAgainst.transformNode.name,collisionEvent.collider.transformNode.name)
      });

But the callback function of the floor being collided is always executed 4 times,this number confuses me。I copied the above code to playground,
The callback is executed only once

4 times with the same colliders/transform ? Is it with same distance/position values as well? Did you check the code observable.add is only executed once?

yes,after creating physics world and camera and some objects, I added collision detection function at the end of createScene

await initPhysics(scene)
await initScene(scene)
const observable = hk.onCollisionObservable
const observer = observable.add((collisionEvent) => {
console.log('collisionEvent', collisionEvent.collidedAgainst.transformNode.name, collisionEvent.collider.transformNode.name)
)}

four is a magic number


20230803173215

I debugged this source code. When I received a collision event id, the function inside the while was executed 4 times. It seems that the eventAddress was not destroyed in time after use, and the id was used again to trigger the event.

/**
     * Runs thru all detected collisions and filter by body
     */
    _notifyCollisions() {
        let eventAddress = this._hknp.HP_World_GetCollisionEvents(this.world)[1];
        const event = new CollisionEvent();
        const worldAddr = Number(this.world);
        console.log(eventAddress,'wrapper')
        while (eventAddress) {
            console.log(eventAddress,'inner')
            CollisionEvent.readToRef(this._hknp.HEAPU8.buffer, eventAddress, event);
            event.contactOnB.position.subtractToRef(event.contactOnA.position, this._tmpVec3[0]);
            const distance = Vector3.Dot(this._tmpVec3[0], event.contactOnA.normal);
            const bodyInfoA = this._bodies.get(event.contactOnA.bodyId);
            const bodyInfoB = this._bodies.get(event.contactOnB.bodyId);
            const collisionInfo = {
                collider: bodyInfoA.body,
                colliderIndex: bodyInfoA.index,
                collidedAgainst: bodyInfoB.body,
                collidedAgainstIndex: bodyInfoB.index,
                point: event.contactOnA.position,
                distance: distance,
                impulse: event.impulseApplied,
                normal: event.contactOnA.normal,
            };
            this.onCollisionObservable.notifyObservers(collisionInfo);
            if (this._bodyCollisionObservable.size) {
                const observableA = this._bodyCollisionObservable.get(event.contactOnA.bodyId);
                const observableB = this._bodyCollisionObservable.get(event.contactOnB.bodyId);
                if (observableA) {
                    observableA.notifyObservers(collisionInfo);
                }
                else if (observableB) {
                    //<todo This seems like it would give unexpected results when both bodies have observers?
                    // Flip collision info:
                    collisionInfo.collider = bodyInfoB.body;
                    collisionInfo.colliderIndex = bodyInfoB.index;
                    collisionInfo.collidedAgainst = bodyInfoA.body;
                    collisionInfo.collidedAgainstIndex = bodyInfoA.index;
                    collisionInfo.normal = event.contactOnB.normal;
                    observableB.notifyObservers(collisionInfo);
                }
            }
            eventAddress = this._hknp.HP_World_GetNextCollisionEvent(worldAddr, eventAddress);
        }
    }

20230803175109

And this does not repro in Playground? Are you using latest Babylonjs/Havok version?

yes,i use

"@babylonjs/core": "^6.13.0",
https://cdn.babylonjs.com/havok/HavokPhysics_umd.js

I modified the source code in this way, and now it is called back once, but I am not sure if there are any side effects,I set eventAddress to 0, I observed that the default value is 0.right now。

_notifyCollisions() {
        let eventAddress = this._hknp.HP_World_GetCollisionEvents(this.world)[1];
        while (eventAddress) {
            this.onCollisionObservable.notifyObservers(collisionInfo);
            // eventAddress = this._hknp.HP_World_GetNextCollisionEvent(worldAddr, eventAddress);
            eventAddress = 0;
        }
    }

i want to know what happen in _hknp.HP_World_GetNextCollisionEvent,but does’t find this function

it means only 1 collision can be detected per frame. so if you have multiple objects/collision, it will only detect one.

So how to filter out those collisions that are repeatedly detected? If the collision has a counter, then the counter increments not by 1, but by 4.Do I need special handling for every 4 collisions?

Do you have an idea why 4 collisions here @carolhmj ?
@bateer_li collision distance and position are the same for all 4 events?

Not sure why there are 4 collisions, maybe @eoin can help with this. We could just add a Set to store the event addresses that we already processed, and skip processing these ones?

This behaviour is what I’d expect, actually. That callback is fired once per contact point, not once per body pair. The number of contact points you see depends on the shape and relative orientation of the bodies, but four would be pretty common for a pair of convex hulls or boxes like you have. The eventAddress staying constant for a few calls is just a side-effect of how we’re storing the events, but we are reporting different contacts each time - you should see that the positions and distances will differ each time the observer is called.

1 Like

My usage scenario is that the box falls freely from the air to the plane. The elastic coefficients of the box and the plane are both 0, preventing them from rebounding after hitting and triggering another collision event. Now I use the ball as a soldier, and every time he collides with another soldier, 10 hp will be deducted, and when they are all deducted, they will be deleted from the physical world. If each point of collision triggers a callback event, there will be problems when blood is deducted and deleted. The blood will be deducted more, and when deleting, it will try to delete an object that has been deleted during the first collision. Is it possible to add a parameter and only return a callback function when there is a collision, or I modify the source code myself, if the eventAddress is the same as last time, skip this callback trigger

eventAddress = this._hknp.HP_World_GetNextCollisionEvent(worldAddr, eventAddress);
let newEventAddress=this._hknp.HP_World_GetNextCollisionEvent(worldAddr, eventAddress)
if(newEventAddress==eventAddress){
 eventAddress=0
}else{
 eventAddress=newEventAddress
}

I try to follow the above code, I thought A, B collision will generate a unique eventAddress, I see by printing, A, C collision will also use the same eventAddress as A, B, which will make me miss some collision callbacks, I think Solving this problem seems to go a little deeper

I use balls to collide with cylinders and cubes to collide with cylinders. The previous case callback is triggered once, and the latter case callback is triggered 4 times. It seems that it is appropriate to use a ball as a physical simulation, but if a soldier model is added, the soldier model will also roll with the ball. This does not seem to be in line with reality

When the first collision is detected, you could add a period of “invincibility” under which damage is not dealt to the player. That is a pretty common practice in games. The same for deleting your objects, you can check if they exist before deleting.

Maybe you could keep an array of eventAddress’ ?

Then on collision event:
Check if eventAddress exists in array
If it doesn’t, put it into array, accept and process the event.
If it does, ignore the event.

And depending on your game, (e.g is there protential for several million events per run?) It might be a good idea to clean up the array once in a while so it won’t become a bottleneck with regards of performance.

Yes, that’s what I’m doing at the moment. At the beginning, I will delete this element after the collision, but there is still this object after the collision callback is triggered. I think it’s a bug in the engine. I didn’t get the latest before the next collision callback is triggered. Objects within the physics world, but I print hk.numbodies again and it’s correct, which confused me all afternoon. Sometimes the engine is too precise and annoying. If the engine only detects A and B collisions, and doesn’t care how many points are collided, will the efficiency of the engine be improved? At present, I will let 4000 cubes collide, and the frame rate can be maintained at 60 frames. If you add objects, frame number will drop

This method is not possible. I saw in the log that AB collides 4 times, and the eventAddress is the same for the first three times, but the eventAddress changes for the fourth time, and it will be the same as the collisions of B and C. If you do it according to your idea, the detection accuracy will be problematic

A timeout period as @carolhmj suggested where collision events are ignored might be the way to go then, but protentially if something is hit by 2 different sources at the same time could cause accuracy issues aswell.