Merging Meshes and Vertex Data

Each visible mesh costs at least one draw call, the usual bottleneck of web scenes (Bottlenecks). BABYLON.Mesh.MergeMeshes(meshes, disposeSource) bakes each mesh's world transform into its vertices and joins them into one mesh and one draw call. BookNest's bookcase merges two sides, a back and five boards:

bookstore.js: a bookcase merged from eight boxesJavaScript
function createBookcase(scene, name, x) {          // Section 7.4.4: 8 boxes, 1 mesh
  const [w, h, d, t] = [3, 2.2, 0.35, 0.04];        // width, height, depth, board thickness
  const box = (width, height, depth, px, py, pz) => {
    const part = BABYLON.MeshBuilder.CreateBox('part', { width, height, depth }, scene);
    part.position.set(px, py, pz);
    return part;
  };
  const parts = [box(t, h, d, -w / 2, h / 2, 0), box(t, h, d, w / 2, h / 2, 0),  // sides
    box(w, h, t, 0, h / 2, d / 2 - t / 2)];                                     // back
  for (let i = 0; i < 5; i++) parts.push(box(w, t, d, 0, 0.1 + i * 0.5, 0));    // boards
  const bookcase = BABYLON.Mesh.MergeMeshes(parts, true);  // dispose the parts
  bookcase.name = name;
  bookcase.material = paint(scene, 'walnut', '#6d4c33');
  bookcase.position.set(x, 0, ROOM.depth / 2 - ROOM.wall / 2 - d / 2);  // against back wall
  bookcase.checkCollisions = true;
  return bookcase;
}

A SceneInstrumentation's drawCallsCounter read 8 draw calls for the separate parts and 1 for the merged bookcase (192 vertices, 288 indices). Merged parts can no longer move or be picked alone; mixed materials need subdivideWithSubMeshes and multiMultiMaterials, and over 65,536 vertices allow32BitsIndices.

Mesh.MergeMeshes turns a bookcase of eight boxes into one mesh and one draw callHTMLLive
<!doctype html>
<style>
  body { margin: 0; background: #f7f3ec; font: 13px system-ui, sans-serif; color: #333; }
  canvas { width: 100%; max-width: 600px; height: 320px; display: block; touch-action: none; }
  #bar { max-width: 600px; display: flex; gap: 12px; align-items: center; padding: 6px 8px; }
  #info { font-family: monospace; }
  button { font: inherit; padding: 4px 12px; }
</style>
<canvas id="view"></canvas>
<div id="bar"><button id="merge">Merge the left bookcase</button><span id="info"></span></div>
<script src="https://cdn.jsdelivr.net/npm/babylonjs@9.28.0/babylon.js"></script>
<script>
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.4), scene)
  .groundColor = new BABYLON.Color3(0.35, 0.32, 0.3);
const camera = new BABYLON.ArcRotateCamera('cam', -Math.PI / 2 - 0.25, 1.25, 7.5, new BABYLON.Vector3(0, 1.1, 0), scene);
camera.attachControl(canvas, true);
const walnut = new BABYLON.StandardMaterial('walnut', scene);
walnut.diffuseColor = BABYLON.Color3.FromHexString('#6d4c33');
walnut.specularColor = BABYLON.Color3.Black();

function bookcaseParts(x) {                         // two sides, a back and five boards
  const [w, h, d, t] = [3, 2.2, 0.35, 0.04];        // width, height, depth, board thickness
  const box = (width, height, depth, px, py, pz) => {
    const part = BABYLON.MeshBuilder.CreateBox('part', { width, height, depth }, scene);
    part.position.set(x + px, py, pz);
    part.material = walnut;
    return part;
  };
  const parts = [box(t, h, d, -w / 2, h / 2, 0), box(t, h, d, w / 2, h / 2, 0),   // sides
    box(w, h, t, 0, h / 2, d / 2 - t / 2)];                                      // back
  for (let i = 0; i < 5; i++) parts.push(box(w, t, d, 0, 0.1 + i * 0.5, 0));     // boards
  return parts;
}
const leftParts = bookcaseParts(-1.7);
// Bake each part's world transform into its vertices and join them: one mesh, one draw call
const right = BABYLON.Mesh.MergeMeshes(bookcaseParts(1.7), true);   // true: dispose the parts
right.name = 'bookcaseRight';

// SceneInstrumentation counts the draw calls issued each frame
const instrumentation = new BABYLON.SceneInstrumentation(scene);
instrumentation.captureRenderTime = false;
const info = document.getElementById('info');
scene.onAfterRenderObservable.add(() => {
  info.textContent = `${scene.meshes.length} meshes, ${instrumentation.drawCallsCounter.current} draw calls ` +
    `(merged bookcase: ${right.getTotalVertices()} vertices, ${right.getTotalIndices()} indices)`;
});

document.getElementById('merge').addEventListener('click', (event) => {
  BABYLON.Mesh.MergeMeshes(leftParts, true).name = 'bookcaseLeft';
  event.target.disabled = true;
});
engine.runRenderLoop(() => scene.render());
addEventListener('resize', () => engine.resize());
</script>