Colourways and materials in three.js

Every part in the London terraced houses pack has named material slots: surface-london-stock for the brick, trim-stone for sills and lintels, surface-door, surface-glass and so on. An assembly can give each slot a look, and the kit runtime in the pack (jstov-kit.js, loaded for you by assemblies.js) builds the material. That is all a colourway is. The code below is the code we ran to make the picture.

Three terraced houses: a soot-blackened colourway, a three-storey house in painted stucco, and a two-storey house in stucco with green doors

Left: the shipped soot-blackened colourway. Middle: the three-storey house, which the pack ships in plain brick, in the stucco colourway. Right: the two-storey house in stucco with the doors repainted green.

The code

Unzip the pack next to your script. It needs the same import map for three as the pack's index.html.

// Tutorial example: colourways and your own materials on London terraced houses.
// Assumes the pack is unzipped next to this file (assemblies/, assemblies.js, jstov-kit.js).
import { loadAssembly, loadGenerated } from './assemblies.js';

const json = (url) => fetch(url).then((r) => r.json());
const eachPart = (node, fn) => { if (node.asset) fn(node); (node.children ?? []).forEach((c) => eachPart(c, fn)); };

// 1. A colourway ships as its own assembly: load it like any other.
export const sootHouse = (level = 'medium') =>
  loadAssembly('assemblies/london-residential-colourway-soot.json', { level });

// 2. A colourway is a material slot -> look map stored on each part. Read it once…
export async function colourway(url) {
  const looks = {};
  eachPart((await json(url)).root, (part) => Object.assign(looks, part.materials));
  return looks; // e.g. { 'surface-london-stock': 'london-stock-soot', 'trim-stone': 'stone-soot-darkened', … }
}

// …and lay it over any other house. A part only uses the slots its model has.
export async function recoloured(houseUrl, looks, level = 'medium') {
  const house = await json(houseUrl);
  eachPart(house.root, (part) => { part.materials = { ...part.materials, ...looks }; });
  return loadGenerated(house, { level });
}

// 3. Your own look: a look name or a layered spec, base/finish + age layers # tint.
export const greenDoors = { 'surface-door': 'paint-cream/gloss+chips:0.1+grime:0.2#2f5a3c' };

Then:

const stucco = await colourway('assemblies/london-residential-colourway-stucco.json');
scene.add(await sootHouse());
scene.add(await recoloured('assemblies/london-residential-three-storey.json', stucco));
scene.add(await recoloured('assemblies/london-residential-benchmark.json', { ...stucco, ...greenDoors }));

How it works

Going further

See the London terraced houses pack