Babylon.js CSG engine scaling problem

,

Hello,
I am integrating Babylon.js into my website. Everything is working fine, but I am facing an issue with the Babylon CSG engine.

I have encountered a small issue with the Subtract + Scale operation. When scale is enabled, the subtraction does not work correctly. Some polygons disappear and reappear depending on the mesh depth. However, when I disable the scale, the subtraction works as expected.

I also tested this with very simple meshes, such as rectangles, and encountered the same issue.

Video for reference:

Can you create a repro in the playground ? that will help the team address your issue.

I just made a simple babylon.js palyground
can you check if this works.
On my site there are more then 1 subtractions/CSG operation can happen on 1 mesh

Thanks for the repro, that helps a lot. :slightly_smiling_face:

Two separate things are going on here:

1. The immediate failure in your playground isn’t the scale: it’s the vertex limit. Your moulding sphere jumps from 40 segments (HEALTHY) to 500 segments (BROKEN), which pushes the merged mesh well over 65 536 vertices. Mesh.MergeMeshes(meshes, false) returns null in that case because 32-bit indices aren’t enabled, and then CSG.FromMesh(null) fails so the body is never created and only the cutter is left floating. Enabling 32-bit indices fixes that part:

const source_mesh_merged = BABYLON.Mesh.MergeMeshes(meshes, false, true);

(the 3rd arg is allow32BitsIndices).

2. The disappearing/reappearing polygons in your video are a known limitation of the legacy BABYLON.CSG engine. It’s a fragile boolean implementation that’s very sensitive to coplanar faces and floating-point precision, and it bakes the world matrix (scale included) so when scale is enabled or coordinates get large (yours are in the thousands), precision loss makes it misclassify polygons.

The recommended path is to switch to CSG2, our newer Manifold-based engine, which is dramatically more robust for exactly this kind of case (chained subtractions, scaled/large geometry, coplanar faces):

await BABYLON.InitializeCSG2Async();

const a = BABYLON.CSG2.FromMesh(targetMesh);
const b = BABYLON.CSG2.FromMesh(cutterMesh);
const res = a.subtract(b);
const resultMesh = res.toMesh("result", scene);

a.dispose(); b.dispose(); res.dispose();

Docs: Babylon.js docs

Since you mentioned you chain several CSG operations on one mesh in production, CSG2 will also behave much better across those repeated ops.

If you still see an artifact after moving to CSG2, could you share a minimal playground that reproduces it with the scale applied (low vertex counts, so we’re isolated from the merge limit above)? We’ll happily dig in from there.

Hello,
CSG2 is breaking on my site with TypeError: this._scene.getUniqueId is not a function

also the link https://doc.babylonjs.com/features/featuresDeepDive/mesh/csg2 is not working.

Here we are:

Hello,
This is regarding the “If you still see an artifact after moving to CSG2, could you share a minimal playground that reproduces it with the scale applied (low vertex counts, so we’re isolated from the merge limit above)? We’ll happily dig in from there.”

i made a full babylon playground which is rendering in my site.

is this good for you?
also the issue here is on the sides.

One important point: this playground is still using the deprecated BABYLON.CSG API, not CSG2.

The scale appears to expose the issue rather than cause it. Mesh.MergeMeshes only combines the vertex/index buffers; it does not boolean-union the parts or remove overlapping/internal faces. Repeated legacy CSG operations on those nearly coincident surfaces can produce the thin sliver triangles shown.

Also, part of the visible step appears to follow the stepped/chamfered cutter profile itself, so check its shape and placement, especially where it nearly grazes the cabinet top.
I would first make both operands clean, closed/watertight solids, remove overlapping/internal faces, and avoid coplanar or barely touching surfaces. Then migrate to actual CSG2, chain the subtractions as CSG2 objects, and convert to a mesh only once at the end.

Regarding the earlier getUniqueId is not a function error, the signatures differ:
• Legacy CSG: toMesh(name, material, scene, …)
• CSG2: toMesh(name, scene, options)

Passing the legacy arguments to CSG2 puts the material where the scene is expected, which can explain that error.

Hello,

I tried using CSG2 but its still not working.
I didn’t changed any mesh for now just used CSG2 instead of CSG.
Does this means i have issue in my mesh?
issue - Error while creating the CSG: memory access out of bounds

Thanks for the updated playground. I checked it and the scaling is not the cause here.
The failing mesh is created by Mesh.MergeMeshes from several closed parts that touch or overlap. MergeMeshes only concatenates their vertex/index buffers; it does not perform a boolean union or remove internal/coplanar faces. Consequently, the result is not a valid single Manifold solid, even though each individual part can be passed to CSG2.FromMesh successfully.

The recommended fix is to union the parts as CSG2 objects instead of merging their buffers:

function unionMeshes(meshes) {
    let result = BABYLON.CSG2.FromMesh(meshes[0]);
    for (let i = 1; i < meshes.length; ++i) {
        const current = BABYLON.CSG2.FromMesh(meshes[i]);
        const union = result.add(current);
        result.dispose();
        current.dispose();
        result = union;
    }
    return result;
}
const targetCSG = unionMeshes(meshes);
const cutterCSG = unionMeshes(newMesh);
const resultCSG = targetCSG.subtract(cutterCSG);
const resultMesh = resultCSG.toMesh(newMeshName, scene);
resultCSG.dispose();
targetCSG.dispose();
cutterCSG.dispose();

As a short-term workaround for this specific playground, calling:

source_mesh_merged.forceSharedVertices();

before CSG2.FromMesh also prevents the crash. However, it may affect normals on hard edges and does not remove overlapping/internal faces, so performing a real CSG2 union is the cleaner solution.

That said, an invalid/non-manifold input should produce a controlled error, not a WASM memory access out of bounds , so that part is still a robustness issue in the underlying Manifold processing.

Hello,
I made another playground. With the suggestions.

But the issue still persists.

Dude, you are not applying what I told you.

Your new version still uses Mesh.MergeMeshes for both sets of meshes and then passes the merged buffers to CSG2.FromMesh . This is exactly the problematic path described in my previous answer: MergeMeshes does not create a valid union of the solids.

I’m happy to help, but I do need you to make the effort to carefully apply the recommendations before reporting that the issue still persists. Otherwise, we end up investigating the same unchanged code again.

Here is a corrected Playground:

It replaces MergeMeshes with actual CSG2 union operations before performing the subtraction, and it properly disposes the temporary CSG2 objects. This version completes without the memory access out of bounds error.

It’s my bad i have sent u wrong playground link the link i sent is #5 but what i wanted to send is #4

Thanks for the updated babylon playgound tho.
But now my other meshes are gone ( doors and drawers ).
the original design was:

Hello Deltakosh,
Sorry to ping you but in my and your playground the memory issue is resolved, but there is a new issue when the doors and cabinets are not showing/placed on wrong position.
Just migration from CSG to CSG2 wont effect the positions so do you have any idea whats going on?

Thanks in advance.

Hello Deltakosh,
Sorry to bother you, but I’m running into the same issue where some of my other meshes are not showing up. Do you have any idea what could be causing this? All of the meshes were displaying correctly in the CSG engine.

I found the issue. CSG2.toMesh() centers the generated geometry by default, unlike the legacy CSG.toMesh() workflow. This caused the subtraction results, and therefore the doors and drawers, to lose their original world placement.

Simply disabling centering exposes another problem because the code converts each generated mesh back through CSG2.FromMesh() for the next subtraction. The corrected version keeps the corresponding CSG2 object and reuses it directly for subsequent boolean operations.

This preserves the original design and avoids the memory access out of bounds error:

Thanks man,
Greatly appriciated.