Build and animate a modular tower crane in three.js

The Tower cranes and lifting pack ships a tower crane as separate parts rather than one mesh. That means you choose its height, and the slewing head, trolley and hook can move. This guide assembles one in code; it is the code that drew the crane in the picture.

A tower crane with six mast sections and an eight metre hook drop, built with the code in this guide

The parts and how they fit

The code

Unzip the pack next to your script. Parts load from models/<id>/<level>-<id>.glb; the files are meshopt-compressed, so the loader needs three's MeshoptDecoder.

// Tutorial example: a tower crane built from the pack's separate parts, so it can
// be any height and its slew, trolley and hook can move in code.
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';

const loader = new GLTFLoader().setMeshoptDecoder(MeshoptDecoder);
const cache = new Map();
const part = async (id, level = 'medium') => {
  const url = `models/${id}/${level}-${id}.glb`;
  if (!cache.has(url)) cache.set(url, loader.loadAsync(url).then(g => g.scene));
  return (await cache.get(url)).clone();
};
const P = 'construction-lifting-';

export async function towerCrane({ masts = 4, hookDrop = 3 } = {}) {
  const crane = new THREE.Group();
  crane.add(await part(P + 'tower-base'));
  for (let i = 0; i < masts; i++) {            // mast sections are 3 m, the first sits 1 m up on the base
    const m = await part(P + 'tower-mast'); m.position.y = 1 + i * 3; crane.add(m);
  }
  const slew = new THREE.Group();              // everything above the mast turns with the slewing head
  slew.position.y = 1 + masts * 3; crane.add(slew);
  slew.add(await part(P + 'tower-slew'));
  const counterjib = await part(P + 'tower-counterjib'); counterjib.position.set(-1.5, 0.9, 0); slew.add(counterjib);
  for (const x of [-5.35, -6.07, -6.79]) { const b = await part(P + 'tower-ballast'); b.position.set(x, 1.02, 0); slew.add(b); }
  for (const x of [1.5, 5.5, 9.5]) { const j = await part(P + 'tower-jib-section'); j.position.set(x, 0.9, 0); slew.add(j); }   // 4 m sections
  const tip = await part(P + 'tower-jib-tip'); tip.position.set(13.5, 0.9, 0); slew.add(tip);
  const trolley = new THREE.Group(); trolley.position.set(8.94, 0.9, 0); slew.add(trolley);
  trolley.add(await part(P + 'tower-trolley'));
  const line = await part(P + 'hoist-line');   // a 3 m rope hanging from its top: scale it to lengthen
  line.position.y = -0.5; line.scale.y = hookDrop / 3; trolley.add(line);
  const hook = await part(P + 'hook-block'); hook.position.y = -0.5 - hookDrop; trolley.add(hook);
  return { crane, slew, trolley, line, hook };
}

// Move it: turn the jib and run the trolley out and back along it.
export function animateCrane({ slew, trolley }, seconds) {
  slew.rotation.y = seconds * 0.15;
  trolley.position.x = 3 + 9 * (0.5 + 0.5 * Math.sin(seconds * 0.4));   // stays between the tower and the jib tip
}

Then const c = await towerCrane({ masts: 6, hookDrop: 8 }); scene.add(c.crane); and call animateCrane(c, clock.getElapsedTime()) each frame. In our test that crane stood 21.5 m tall with the hook 8 m under the trolley, and the animation carried the hook 7.9 m in five seconds.

Why the parts are separate

A crane that is one mesh can only be placed. Separate parts can be built to the height a scene needs, show a construction phase moving, or stand still at different angles across a skyline. The pack also includes a mobile crane, material hoist, concrete skip, lifting cage, pallet fork and slings in the same style, plus assembled versions of each rig.

See the Tower cranes and lifting pack