Collisions on Flipped Normals

Greetings!

I am facing some unwanted collision behavior in some scenes. Such as being pulled in into a mesh and being stuck around a mesh.

Here is the PG link to an example scene Babylon.js Playground You can use WASD keys to move around.

If you go towards “Forward” ramp, you’ll see the sphere collides properly with the ramp. But if you go towards “Flip” ramp, which has flipped normals, it will sink in and get stuck there.

I’ll be very happy if you can help me devise a workaround for models with such normals.

Thank you!

The babylon collision system is constantly using the mesh’s (or to be more precise the submesh’s) normals to compute the collision. There is no flag to support flipped normals, but we can consider adding this if it is a case that is widely used. I’ll say this, however - this is (AFAIK) the first time someone asks about flipped-normals collisions, and the team is very occupied, so I am not sure when this can be implemented.

If you want the sphere to collide with both front and back normals meshes, the only way I can see is flip the normals right before computing the collisions, and returning them right after. It is an ugly hack, i know, and can be a bit more optimized if you save two normal arrays - flipped and not flipped. But this is really not an optimal solution for this.

Sorry, looking again at the code - it is using the positions array directly and not the normals. this might be a little trickier than I thought

Firstly, the gltf/glb has right handed coordinate system and BabylonJS coordinate system is left handed. So you need to set `scene.useRightHandedSystem = true` before importing. Else you have to consider negative z-scaling, which can cause unpredictable collision problems.

The main cause of the collision failure seems to be that the flipped mesh has inverted normals and also has a right handed coordinate system. Usually the triangles of gltf are counter-clockwise, while Babylons (left handed) are clockwise. So if you enable right handed conversion by scene, then the side orientation is and needs to be set clockwise and vice versa.

Accordingly the workaround that covers both cases would be:

``````mesh.overrideMaterialSideOrientation = scene.useRightHandedSystem ? Material.ClockWiseSideOrientation : Material.CounterClockWiseSideOrientation
``````

To invert normals just get, invert and set vertices normal data:

``````var normals = mesh.getVerticesData(BABYLON.VertexBuffer.NormalKind);
for(let i=0; i<normals.length; i++)
normals[i] *= -1;
mesh.setVerticesData(BABYLON.VertexBuffer.NormalKind, normals);
``````

Another thing is that you need to set measures of “player” collision body, that is defined by `ellipsoid`-property of mesh:

``````player.ellipsoid = new BABYLON.Vector3(1, 1, 1) // radius of 1
``````

From my knowledge `checkCollisions` of meshA only takes effect if another meshB moveWithCollisions(). Therefore it would only make sense if another mesh moves with collision.

The PG with workaround:

But @RaananW may confirm I didn’t miss anything.

2 Likes

Smart solution

Just a small small fix for right and left - glTF Invert Vertex Side Orientation | Babylon.js Playground (babylonjs.com)

If these are acceptable constraints, this is a wonderful solution!

Yeah, you got me on that. I was sure @Caner is capable of swapping “a” and “d” move vectors. So I didn’t care about it.

But I should have known, because I asked you to look over.

i secretly added a movement on the Y axis as well to make the sphere “fall down” when moving

@Takemura I probably forgot saving after the movement direction fix, @RaananW thanks for doing it for me

It seems possible to solve the issue by using right hand system. However, it creates another issue. My application runs with user-uploaded models and it goes through all child meshes and processes them. If I apply material side orientation and flip normals, it breaks some of the meshes. If you compare this PG https://playground.babylonjs.com/#GFCVRL#2 with https://playground.babylonjs.com/#GFCVRL#1, you’ll see some meshes are inside out and collision system doesn’t work well.

Maybe if I could detect which meshes have flipped normals and which don’t, I’ll be able to use the solution for all possible meshes.

Thank you very much!

1 Like

No luck?

I assume you ask how you can find our if the normals are flipped?

Exactly. In order to conditionally process the mesh hierarchy.

Have you found a solution to this? From the top of my head the approach I’d go is to check if the normals point towards the center of the model, (if the model is solid) in this case they’re flipped

I tried it but when the models are custom, terms center/inside/outside are sometimes being vague and inaccurate.
I’m trying to investigate `_testTriangle` method of `Collider` in my free time. I see there’s also a static value `Collider.DoubleSidedCheck` with default false. I set it to true after importing but didn’t work.
If I can pull off adapting the logic in `_testTriangle` to take the opposite normal into account I’ll create a PR. Any guidance on the method is welcome

I think you could just do a second check on _testTriangle with the normal of the testTriangle plane flipped: Babylon.js/packages/dev/core/src/Collisions/collider.ts at master · BabylonJS/Babylon.js (github.com), but if a mesh collides with both normals, to which normal would you give priority?

This might also hurt performance. We could allow the dev to decide if they check against flipped normals, BUT the issue still remains - how do you know if the model’s normals are flipped

Blender knows it somehow. I’m trying to analyze its source code but it’s not very easy
https://github.com/blender/blender/blob/main/source/blender/draw/engines/overlay/overlay_facing.cc

@carolhmj I tried your suggestion anyway, by a one-time recursion into the _testTriangle method by combining some tweaks (creating the triangle planes in different order of vectors; negating the normal’s sign or distance). No luck yet, must be missing something. But in any case, this will be an expensive solution in terms of performance as @RaananW says.