Primitive Bridge
A 56-building skyline (one grid column left empty as a street) where every footprint, height, body color and "lit window" tint is a one-off constructor decision made once — not scene state, not something a re-render would ever touch. That makes it a bad fit for 56 individual { mesh: [...] } scene nodes and a good fit for the imperative escape hatch: build a THREE.Group with plain three.js code, then hand the finished object to primitive. The camera sits low and looks down the empty column as a street canyon, at a shallow ~12° elevation rather than a flat top-down isometric shot, so the towers read at scale. Lights, fog, and the background stay declarative around it.
How it works
- The whole skyline is built with plain
new THREE.Mesh(...)/THREE.Group.add(...)calls, outside the scene grammar entirely — nothing about it is expressed as{ tag: ... }objects. Seeprimitivefor when to reach for this over declaring children. { primitive: [], object: city, dispose: null }adopts the finished group as-is.dispose: nullis written explicitly even though it's already implied for anyprimitivenode — its 56 meshes and materials are owned by this module, not the reconciler, so removal from the scene must never call.dispose()on them.objectis re-checked on every patch; swapping in a different group would reconstruct the node the same way a changedargsdoes.onFrame: (root, delta, self) => { self.rotation.y += delta * 0.05 }is theuseFrame()analog —selfhere iscity, the adopted primitive, not a declaratively-created instance. See Animation & Loop.{ color: null, attach: "background" }and{ fogExp2: null, args: [...] }set scene-level state through the same attach inference (.isFog) a mesh child uses for its own geometry/material — they stay fully declarative even though the buildings around them don't.FogExp2's density (vs. linearFog's near/far) is what fades distant towers gradually instead of hitting a hard wall.onCreated: (root) => root.camera.lookAt(2.5, 2, 20)runs once at mount to aim the camera down the street canyon — nothing here depends onroot, so it doesn't needargsas a function the wayorbitControlsdoes elsewhere.