← WORK

Baking a procedural tree at build time

3 min read

The clearing at the end of this site's journey is anchored by an oak. It comes from ez-tree, an excellent MIT procedural tree generator — which weighs ~3MB gzipped because it bundles every bark and leaf texture for every species as base64 inside the JavaScript.

The insight: the tree is deterministic

The site always grows the same tree: preset "Ash Medium", seed 7, one leaf tint. If the input never changes, the generator is build tooling, not a runtime dependency. So a Node script runs the generator once at build time, serializes the two meshes (branches, leaves) into a compact binary — positions, normals, UVs, indices, 4-byte aligned — plus a small JSON manifest of material parameters, and copies out ONLY the four textures the materials actually sample.

That last clause hides a real finding: the library assigns a roughnessMap to a MeshPhongMaterial. Phong has no roughness slot — the assignment is a silent no-op. Every visitor to every ez-tree site downloads roughness textures their GPU never reads.

Byte-identical, 70% smaller

A ~130-line runtime loader rebuilds the meshes from the binary — same buffers, same material flags, same wind-sway vertex shader lifted verbatim (MIT) from the library. The payload went from ~3MB gzipped JS to ~1MB of parallel binary fetches, with zero generation cost in the browser and no JS parse tax. The hand-built fallback tree stays in the bundle in case the fetch fails.

The .bin ambush

One deployment lesson for free: the geometry file originally shipped as tree.bin, and on some machines it arrived as an empty 204 response — security software on the client silently swallows .bin downloads. Renaming the same bytes to tree.dat fixed it. If your WebGL site works everywhere except "that one corporate laptop", check what your file extensions look like to an endpoint filter.

More notes