Babylon.js 89,603 began in 2013 as a spare-time project of two Microsoft employees. David Catuhe wrote the engine, reusing ideas from a 3D engine he had built for Silverlight 5, and David Rousset added VR, gamepad and IndexedDB support. The GitHub 29 repository dates from 27 June 2013; the first npm 2,036 package, babylonjs 1.9.0, from 12 March 2014, under the Apache-2.0 licence the project still uses. In 2014 Ubisoft built an Assassin's Creed web title on it, and the side project became the full-time job of Catuhe's team. The scoped ES module packages, @babylonjs/*, followed in January 2019. Three.js 30,815 , by contrast, began in 2010 as one developer's library (Three.js).
<!doctype html>
<style>
body { margin: 0; background: #f7f3ec; }
canvas { width: 100%; max-width: 600px; height: 360px; 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>
const canvas = document.getElementById('view');
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', -Math.PI / 2 - 0.1, 1.38, 11.5,
new BABYLON.Vector3(0, 2, 0), scene);
camera.attachControl(canvas, true);
new BABYLON.HemisphericLight('sky', new BABYLON.Vector3(0.3, 1, -0.5), scene);
// Milestones: year (fractional), height of the post, text lines, color
const EVENTS = [
[2010.3, 1.0, ['2010', 'Three.js begins', '(one developer)'], '#9e9e9e'],
[2013.5, 2.3, ['27 Jun 2013', 'GitHub repository', 'Catuhe & Rousset'], '#1f5f8b'],
[2014.2, 3.6, ['12 Mar 2014', 'npm babylonjs 1.9.0', 'Apache-2.0'], '#5b3f99'],
[2014.8, 1.0, ['2014', "Ubisoft's web game", 'team goes full time'], '#e09a10'],
[2019.0, 2.3, ['Jan 2019', '@babylonjs/* scoped', 'ES modules'], '#3f7d3a'],
[2024.2, 1.0, ['28 Mar 2024', 'version 7.0'], '#b5452f'],
[2025.2, 2.3, ['27 Mar 2025', 'version 8.0'], '#b5452f'],
[2026.2, 3.6, ['26 Mar 2026', 'version 9.0'], '#b5452f'],
];
const xOf = (year) => (year - 2018) * 0.85; // 16 years across about 14 m
// The time axis with a tick per year
const axis = BABYLON.MeshBuilder.CreateBox('axis', { width: 14.5, height: 0.08, depth: 0.3 }, scene);
axis.position.x = xOf(2018.2);
for (let year = 2010; year <= 2026; year++) {
const tick = BABYLON.MeshBuilder.CreateBox('tick', { width: 0.04, height: 0.1, depth: 0.5 }, scene);
tick.position.set(xOf(year), 0.03, 0);
if (year % 2 === 0) makeLabel([String(year)], 1.8, 0.6, '#555').position.set(xOf(year), -0.4, -0.3);
}
for (const [year, height, lines, hex] of EVENTS) {
const post = BABYLON.MeshBuilder.CreateCylinder('post', { height, diameter: 0.12 }, scene);
post.position.set(xOf(year), height / 2, 0);
const mat = new BABYLON.StandardMaterial('m' + year, scene);
mat.diffuseColor = BABYLON.Color3.FromHexString(hex);
post.material = mat;
const ball = BABYLON.MeshBuilder.CreateSphere('ball', { diameter: 0.3 }, scene);
ball.position.set(xOf(year), height, 0);
ball.material = mat;
const label = makeLabel(lines, 2.6, 1.0, hex);
label.position.set(xOf(year), height + 0.65, -0.4);
label.renderingGroupId = 1; // drawn after (over) the posts
label.billboardMode = BABYLON.Mesh.BILLBOARDMODE_ALL; // always face the camera
}
// A label is a plane showing a DynamicTexture we draw on with the Canvas 2D API
function makeLabel(lines, width, height, hex) {
const tex = new BABYLON.DynamicTexture('label', { width: 512, height: 512 * height / width }, scene);
const ctx = tex.getContext();
const h = tex.getSize().height;
ctx.fillStyle = lines.length > 1 ? 'rgba(255,255,255,0.9)' : 'rgba(0,0,0,0)';
ctx.fillRect(0, 0, 512, h);
ctx.textAlign = 'center';
ctx.fillStyle = hex;
ctx.font = 'bold 64px sans-serif';
ctx.fillText(lines[0], 256, lines.length > 1 ? 62 : h / 2 + 22);
ctx.fillStyle = '#333';
ctx.font = '50px sans-serif';
lines.slice(1).forEach((line, i) => ctx.fillText(line, 256, 122 + i * 54));
tex.update();
tex.hasAlpha = true;
const mat = new BABYLON.StandardMaterial('labelMat', scene);
mat.diffuseTexture = tex;
mat.useAlphaFromDiffuseTexture = true;
mat.emissiveColor = BABYLON.Color3.White();
mat.disableLighting = true;
const plane = BABYLON.MeshBuilder.CreatePlane('label', { width, height }, scene);
plane.material = mat;
return plane;
}
engine.runRenderLoop(() => scene.render());
addEventListener('resize', () => engine.resize());
</script>