The Three.js Version

What BookNest's Bookstore Would Cost in Three.js

Every feature of the store has a Three.js 30,815 answer, but not always in the box:

The store's features and their Three.js counterparts
Store feature Babylon.js 89,603 (section) Three.js route
Walk camera with collisions UniversalCamera (Walkthrough Camera) PointerLockControls; collisions by hand
Window and door cut-outs CSG2 (CSG2) three-bvh-csg 0.0.18 (MIT)
Cascaded shadows Built in (Room Lighting) CSM add-on
HDR environment, skybox .env, default skybox (HDR Room Lighting) HDRLoader, PMREMGenerator
Spine highlight Node material (Spine Highlight) TSL (5.12.4)
Details panel 2D GUI (BookNest's Book Details Panel) HTML overlay, CSS2DRenderer
Floating 3D panel 3D GUI (Info Panel) three-mesh-ui 6.5.4, last release March 2023
Falling books Havok 516,349 (Falling Books) Rapier 0.21.0 (Apache-2.0)
Dust motes Particle system (Dust Motes) Points updated by hand
Storefront look Rendering pipeline (Storefront Look) EffectComposer passes
Debugging Inspector (Debugging the Scene) lil-gui 1,638 , browser extensions
Ambient sound Audio engine v2 (Ambient Sound) PositionalAudio
VR visit Default XR experience (VR Bookstore) VRButton; teleporting by hand

To price it in bytes, two entry files constructed each feature once, one with three 0.186.1 and its add-ons plus the three packages above, one with deep imports from @babylonjs/* 9.28.0, and esbuild 126 bundled both (they were bundled, not run). Without physics, Three.js's set came to 948,983 bytes (252,130 gzipped) and Babylon's to 5,010,034 (1,132,483). Physics nearly evens the score: Rapier's -compat build embeds its WebAssembly and adds 4,334,647 bytes (1,651,923), bringing Three.js to 5,285,415 (1,904,855), while Havok adds 34,901 bytes of JavaScript and a 2,094,563-byte .wasm file (658,471 gzipped), for about 1.80 MB gzipped in all.

So the store would cost about the same to download in Three.js, less if you drop physics, and more to build: three third-party packages, one of them dormant, plus teleportation, collisions and particles written by hand, and no Inspector. That trade, a leaner core against an engine that arrives assembled, is the whole comparison in miniature.

The whole bookstore's feature set bundled both ways, as stacked bars: engine features plus physics, gzippedHTMLLive
<!doctype html>
<style>
  body { margin: 0; background: #f7f3ec; font: 13px system-ui, sans-serif; color: #333; }
  canvas { width: 100%; max-width: 600px; height: 340px; display: block; touch-action: none; outline: none; }
</style>
<canvas id="view"></canvas>
<script src="https://cdn.jsdelivr.net/npm/babylonjs@9.28.0/babylon.js"></script>
<script src="https://cdn.jsdelivr.net/npm/babylonjs-gui@9.28.0/babylon.gui.min.js"></script>
<script>
// A small 3D bar chart: boxes on a floor, GUI labels under and above them
function createChart(canvas, { radius = 12, target = new BABYLON.Vector3(0, 1.2, 0), alpha = -Math.PI / 2 } = {}) {
  const engine = new BABYLON.Engine(canvas, true);
  const scene = new BABYLON.Scene(engine);
  scene.clearColor = BABYLON.Color4.FromHexString('#f7f3ecff');
  const camera = new BABYLON.ArcRotateCamera('cam', alpha, 1.25, radius, target, scene);
  camera.attachControl(canvas, true);
  new BABYLON.HemisphericLight('sky', new BABYLON.Vector3(0.4, 1, -0.7), scene);
  const ui = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI('ui');
  const text = (anchor, value, offsetY, { size = 11, color = '#333', bold = false } = {}) => {
    const block = new BABYLON.GUI.TextBlock('', value);
    Object.assign(block, { color, fontSize: size, resizeToFit: true, fontWeight: bold ? 'bold' : 'normal' });
    ui.addControl(block);
    block.linkWithMesh(anchor);
    block.linkOffsetY = offsetY;
    return block;
  };
  const bar = (x, height, hex, top, bottom, z = 0, width = 0.8) => {
    const box = BABYLON.MeshBuilder.CreateBox('bar', { width, depth: width, height: Math.max(height, 0.02) }, scene);
    box.position.set(x, Math.max(height, 0.02) / 2, z);
    box.material = new BABYLON.StandardMaterial(hex, scene);
    box.material.diffuseColor = BABYLON.Color3.FromHexString(hex);
    const head = new BABYLON.TransformNode('head', scene);
    head.position.set(x, height, z);
    const foot = new BABYLON.TransformNode('foot', scene);
    foot.position.set(x, 0, z);
    if (top) text(head, top, -14, { bold: true });
    if (bottom) text(foot, bottom, 22);
    return box;
  };
  const title = (value) => {
    const block = new BABYLON.GUI.TextBlock('', value);
    Object.assign(block, { color: '#555', fontSize: 14, height: '24px', top: '8px', verticalAlignment: 0 });
    ui.addControl(block);
  };
  engine.runRenderLoop(() => scene.render());
  addEventListener('resize', () => engine.resize());
  return { engine, scene, camera, ui, bar, text, title };
}

const { bar, text, title, scene } = createChart(document.getElementById('view'), { radius: 9.5, target: new BABYLON.Vector3(0, 1.5, 0) });
title('The store\'s features, bundled with esbuild, gzipped');
const MB = 1e6, SCALE = 1.4;                         // metres per megabyte
// [name, features without physics, physics, colors]
const STACKS = [
  ['Three.js + add-ons', 252130, 1651923, ['#e09a10', '#f3c56b'], 'Rapier -compat (embedded wasm)'],
  ['Babylon.js deep imports', 1132483, 658471, ['#1f5f8b', '#7fa7c6'], 'Havok JS + .wasm'],
];
STACKS.forEach(([name, features, physics, [dark, light], physicsName], i) => {
  const x = (i - 0.5) * 3.2;
  const lower = bar(x, features / MB * SCALE, dark, '', '', 0, 1.3);
  const upper = BABYLON.MeshBuilder.CreateBox('physics', { width: 1.3, depth: 1.3, height: physics / MB * SCALE }, scene);
  upper.position.set(x, (features + physics / 2) / MB * SCALE, 0);
  upper.material = new BABYLON.StandardMaterial(light, scene);
  upper.material.diffuseColor = BABYLON.Color3.FromHexString(light);
  const top = new BABYLON.TransformNode('top', scene);
  top.position.set(x, (features + physics) / MB * SCALE, 0);
  text(top, `${((features + physics) / MB).toFixed(2)} MB in all`, -14, { bold: true });
  text(upper, `+ ${physicsName}\n${(physics / 1000).toFixed(0)} KB`, 0, { size: 10 });
  text(lower, `${(features / 1000).toFixed(0)} KB`, 0, { size: 10, color: 'white' });
  const foot = new BABYLON.TransformNode('foot', scene);
  foot.position.x = x;
  text(foot, name, 22, { size: 12 });
});
/* About the same download either way, less in Three.js without physics, but more to build
   there: three third-party packages (one dormant) plus collisions, teleporting and particles
   written by hand, and no Inspector. */
</script>