PRJ-001 · 3d-spatial
Aether Configurator
Freelance — Haddad Woodworks · Design Engineer — concept, 3D pipeline, frontend · 2026
A real-time WebGL furniture configurator that lets a family woodworking shop sell bespoke pieces online — every material, joint, and dimension rendered live at 60fps.
Core Philosophy
A configurator is a promise: what you see is what gets built. The whole project hinged on honesty — physically-plausible walnut, not a marketing render. I spent more time calibrating roughness maps against photographs of the workshop's actual oil finish than on any other single task.
The second conviction was that performance is part of the craft story. A furniture maker obsessed with clean joinery deserves a site that never stutters. Every mesh was budgeted like a joint: nothing decorative, everything load-bearing.
Outcomes
- First fully interactive quote flow in the shop's 32-year history
- 1.9s cold load on mid-tier Android (4x CPU throttle)
- 38% of visitors touch the 3D model; median session 3m 40s
Deliverables
- WebGL product configurator
- GLTF asset pipeline & compression budget
- Order-intake integration
Technical Anatomy
Asset pipeline
Each furniture piece is authored in Blender, decimated to a 45k-triangle budget, and exported as Draco-compressed GLTF. A build script validates triangle counts and texture sizes so a new upload can never regress load time.
// budget-check.ts — fails CI if a model exceeds its budget
const BUDGETS = { chair: 45_000, table: 60_000, shelf: 38_000 };
for (const [name, mesh] of Object.entries(scene.meshes)) {
const tris = mesh.index.count / 3;
if (tris > BUDGETS[kind]) {
throw new Error(`${name}: ${tris} tris exceeds ${BUDGETS[kind]}`);
}
}
Material system
Wood species are a single PBR shader with swappable albedo/roughness atlases. Switching species swaps two textures — no shader recompile, no frame drop. Finish (oil vs. lacquer) is a roughness multiplier calibrated against photos.
Render loop discipline
The canvas renders on demand: frames are produced only during camera damping, material transitions, or pointer interaction. Idle draw calls drop to zero, which keeps fans silent and laptops cool — invalidate() over a running loop.
useFrame(() => {
if (controls.isDamping || transition.active) invalidate();
}, -1);