Physics collision is not detected if collider spawned 91 units away

Story:
You are little shy UniversalCamera with ellipsoid hitBox attached via hitBox.position = camera.position. Your hitBox has a PhysicsAggregate, so you can participate in collisions, also it has spherePhysicsAggregate.body.disablePreStep = false, so you can move around using keyboard keys.

There is evil bullet spawned around 40 units away which immediately was launched directly into your face. The collision definitely happened and the Bullet touched actorHitBox message was posted in the console.

However, if bullet was spawned, for example, 120 units away it just flies through the hitBox and posts Bullet touched ground when it lands on the ground somewhere behind you.

From my experiments in the playground, the distance threshold is 91. If bullet is spawned further than that, the collision with hitBox is never detected.

The bug is very reproducible in my local app as well. If distance is more than around 100, the collision handler is not invoked.

The hitBox associated with camera is necessary condition. Bug is not reproducible if we just place the independent sphere somewhere as a target.

Playground: https://playground.babylonjs.com/#RE1THJ#4

I’m not really surprised. high velocity + collision detection is always messy, even with CCD. Usually, a raycast (or multiple raycasts, one per frame) is used for that task.

Here is something else. Because bug appears only on certain distance (around 100) and it’s stable on distances over 100.

If it was a regular collision detection issue (when collision check happens after the bullet has passed already), it would appear randomly on any distance. Also, this bug doesn’t appear on independent sphere with any distance as I mentioned in the post.

So, there are several factors involved which guarantee lead to a bug. It doesn’t seem to me like a trivial collision detection issue.

I guess there are different code paths in Havok for different speeds.ccd even if really great, it’s not a silver bullet (pun intended) and for fast moving elements, a raycast is more appropriate.

There is nothing to do with fast-moving objects. The bug is reproducible even at low speeds.

I noticed that the lower the speed the smaller the threshold distance. Also, it’s only happening with sphere attached to camera not stand-alone sphere.

Let’s take a look at this example with extremely low speed: https://playground.babylonjs.com/#RE1THJ#7

The speed is 25. The timestep is 0.005 (which is 200 ticks per second). Even If we assume that there is no air-friction and speed is not decreasing, the bullet travels 25 * 0.005 = 0.125 units each tick. The ellipsoid width is 1.5 which means that collision detection should happen at least 1.5 / 0.125 - 1 = 11 times.

In the example above. If we launch bullet from 10 units away (close), the collision happens. If we launch it from 13 units away (or ANY! distance which is further than that) bullets go through camera-binded sphere (if sphere is not camera-binded everything works fine even at velocity = 1000).

I guess there are different code paths in Havok for different speeds.

This can’t be the reason. The behavior is the same for any speed, the cut off happens if we launch a bullet from further than some distance.

Could you please take a look at my last playground and play with lines 130 and 132 with browser console opened? The speed and distances are very low. It’s not a CD issue in a classical sense. There is either sphere position data that is not synced with havok properly when the sphere is attached to camera, or there is hardcoded cut off (probably a bug) happening if launch distance is larger than some computed value. The latter is less likely, because everything works fine with stand-alone sphere.

I think it’s because of the gravity. if moved away enough, it takes more time for the bullet to travel the distance and it falls below the camera sphere.
Do the test with a gravity of 0 and there is collision detected with actorHitBox every time.

Oh, I see what’s happening.

I thought that when we do camera.applyGravity = true; the camera ellipsoid is always touching the ground. Apparently, it’s not. It’s still floating above. Bug or feature?

As a workaround I think I can just use the hitBox which is higher than camera ellipsoid.

There are some discussion on the forum related to camera ellipsoid and gravity. like Camera gravity and slope collisions - #7 by RaananW

1 Like

Okay, I fixed the issue, including high velocity. Briefly, the solution is this:

var hitbox = BABYLON.MeshBuilder.CreateSphere("actorHitBox", {
    diameterX: camera.ellipsoid.x,
    diameterY: camera.ellipsoid.y * 2,
    diameterZ: camera.ellipsoid.z},
scene);

const tn = new BABYLON.TransformNode("referenceTransformNode");
tn.position = camera.position;
hitBox.parent = tn;
hitBox.position = new BABYLON.Vector3(0, -camera.ellipsoid.y, 0);

After experimenting I found out that camera.position.y is approximately camera.ellipsoid.y * 2. Which means that if we want to create an ellipsoid based hitBox, we need to set diameterY: camera.ellipsoid.y * 2 for the hitBox.

Still the center of our point of view will also be the center of the hitBox, but this is not what we want for humanoids. People have eyes almost on top of their hitBox. So, we need to do a vertical offset for our hitBox, which is half of hitBox height, thus we add -camera.ellipsoid.y to move hitBox down, so it will touch the ground. One can tune those values more precisely to assume, for example, a forehead above the eyes.

The main problem is that if we want to add an offset, we need to use parenting and to set camera as a hitBox parent. But in this case the hitBox will also inherit the rotation of the camera when we look around, which is especially bad when we look up or down. In this case the hitBox orbits the center of the camera. To decouple the rotation between camera and hitBox we can introduce an intermediate TransformNode.

I also did a necessary fix on the line 41:
let movementDirectionVector = target.getAbsolutePosition().subtract(bullet.position).normalize();

I changed the target.position to target.getAbsolutePosition(), which makes sence.

Playground: https://playground.babylonjs.com/#RE1THJ#8

MORE:
JavaScript pro tip. While pairing positions like that tn.position = camera.position; you need to make sure that you don’t overwrite the camera position manually by assigning a new vector, which might be a case if you want to implement teleportation. But it’s easy to fix, you may link transform node and camera positions again or write a helper function that will mutate x, y and z components of given vector instead of replacing the vector.