CSG2

Constructive Solid Geometry with CSG2

Constructive solid geometry (CSG) combines closed meshes with union, subtract and intersect. CSG2, which replaced the deprecated CSG class, wraps Manifold, a C++ kernel compiled to WebAssembly (Apache-2.0, elalish/manifold (https://github.com/elalish/manifold 2,302 )). InitializeCSG2Async() loads it once, by default manifold-3d@3.4.0 from unpkg (manifoldUrl changes that). BookNest's front wall subtracts a window and a door:

bookstore.js: the storefront wall, with a window and a door cut by CSG2JavaScript
async function createFrontWall(scene) {            // Section 7.4.5: CSG2 window and door
  await BABYLON.InitializeCSG2Async();              // loads the Manifold WebAssembly library
  const box = (width, height, depth, x, y) => {
    const mesh = BABYLON.MeshBuilder.CreateBox('cut', { width, height, depth }, scene);
    mesh.position.set(x, y, -ROOM.depth / 2);
    return mesh;
  };
  const solid = box(ROOM.width, ROOM.height, ROOM.wall, 0, ROOM.height / 2);
  const windowHole = box(3.2, 1.6, 1, -1.4, 1.6);
  const doorHole = box(1.1, 2.2, 1, 2.3, 1.1);
  const plaster = paint(scene, 'plaster', '#fff3e0');   // the side walls' material
  const wall = BABYLON.CSG2.FromMesh(solid)          // world-space copies of the meshes
    .subtract(BABYLON.CSG2.FromMesh(windowHole))
    .subtract(BABYLON.CSG2.FromMesh(doorHole))
    .toMesh('frontWall', scene, { centerMesh: false, materialToUse: plaster });
  [solid, windowHole, doorHole].forEach((mesh) => mesh.dispose());
  wall.checkCollisions = true;
  scene.getMeshByName('frontCollider')?.dispose();  // the real wall replaces Section 7.3.5's
  return wall;
}

Loading Manifold took 756 ms in one headless run. Two defaults bite: toMesh() centers the result on the origin unless you pass centerMesh: false (this wall landed 3 m from its place), and it reuses the inputs' materials unless you pass materialToUse. FromMesh works in world space. CSG runs on the CPU, so do it at load time, with closed, watertight inputs.

A storefront wall with a window and a door subtracted by constructive solid geometryHTMLLive
<!doctype html>
<style>
  body { margin: 0; background: #f7f3ec; }
  canvas { width: 100%; max-width: 600px; height: 340px; display: block; touch-action: none; }
</style>
<canvas id="view"></canvas>
<script src="https://cdn.jsdelivr.net/npm/babylonjs@9.28.0/babylon.js"></script>
<script>
/*
  In a normal page, CSG2 does this with the Manifold WebAssembly kernel:
    await BABYLON.InitializeCSG2Async();            // fetches manifold-3d from unpkg
    const wall = BABYLON.CSG2.FromMesh(solid).subtract(BABYLON.CSG2.FromMesh(windowHole))
      .subtract(BABYLON.CSG2.FromMesh(doorHole))
      .toMesh('frontWall', scene, { centerMesh: false, materialToUse: plaster });
  This preview's sandbox blocks the .wasm download, so it runs the same union/subtract idea
  with the older, pure-JavaScript BABYLON.CSG class (deprecated, but still in 9.28).
*/
const ROOM = { width: 8, depth: 6, height: 3, wall: 0.15 };
const canvas = document.getElementById('view');
const engine = new BABYLON.Engine(canvas, true);
const scene = new BABYLON.Scene(engine);
scene.clearColor = BABYLON.Color4.FromHexString('#cfd8dcff');
new BABYLON.HemisphericLight('sky', new BABYLON.Vector3(0.3, 1, -0.6), scene)
  .groundColor = new BABYLON.Color3(0.35, 0.32, 0.3);
const camera = new BABYLON.ArcRotateCamera('shopper', -1.9, 1.35, 11, new BABYLON.Vector3(0, 1.3, 0), scene);
camera.attachControl(canvas, true);
const paint = (name, hex) => {
  const m = new BABYLON.StandardMaterial(name, scene);
  m.diffuseColor = BABYLON.Color3.FromHexString(hex);
  m.specularColor = BABYLON.Color3.Black();
  return m;
};
const plaster = paint('plaster', '#fff3e0');

// Room behind the wall so the openings show something
BABYLON.MeshBuilder.CreateGround('floor', { width: 8, height: 6 }, scene).material = paint('floor', '#b08d6a');
for (const [width, x, z] of [[8, 0, 3], [6, -4, 0], [6, 4, 0]]) {
  const wall = BABYLON.MeshBuilder.CreateBox('wall', { width, height: 3, depth: ROOM.wall }, scene);
  wall.position.set(x, 1.5, z);
  wall.rotation.y = x ? Math.PI / 2 : 0;
  wall.material = plaster;
}
['#1f5f8b', '#5b3f99', '#e09a10', '#3f7d3a', '#b5452f', '#2a9d8f'].forEach((hex, i) => {
  const book = BABYLON.MeshBuilder.CreateBox('book', { width: 0.5, height: 1.8 - (i % 3) * 0.2, depth: 0.3 }, scene);
  book.position.set(-2.6 + i * 0.9, book.getBoundingInfo().boundingBox.extendSize.y, 2.6);
  book.material = paint('book' + i, hex);
});

const box = (width, height, depth, x, y) => {
  const mesh = BABYLON.MeshBuilder.CreateBox('cut', { width, height, depth }, scene);
  mesh.position.set(x, y, -ROOM.depth / 2);
  return mesh;
};
const solid = box(ROOM.width, ROOM.height, ROOM.wall, 0, ROOM.height / 2);
const windowHole = box(3.2, 1.6, 1, -1.4, 1.6);
const doorHole = box(1.1, 2.2, 1, 2.3, 1.1);
const wall = BABYLON.CSG.FromMesh(solid)                      // world-space copies of the meshes
  .subtract(BABYLON.CSG.FromMesh(windowHole))
  .subtract(BABYLON.CSG.FromMesh(doorHole))
  .toMesh('frontWall', plaster, scene);
[solid, windowHole, doorHole].forEach((mesh) => mesh.dispose());   // the inputs are no longer needed
wall.enableEdgesRendering();                                   // outline the cut edges
wall.edgesColor = BABYLON.Color4.FromHexString('#6d4c33ff');
wall.edgesWidth = 2;
engine.runRenderLoop(() => scene.render());
addEventListener('resize', () => engine.resize());
</script>