glTF Viewer
A model loaded with loadAsset and adopted via primitive, staged under a hemisphere + two-point directional rig on a dark, fog-enveloped backdrop, orbitable via extend()-registered OrbitControls. Until the asset resolves, the same scene slot holds a spinning wireframe placeholder instead of a blank canvas.
Duck model © Khronos Group glTF-Sample-Assets.
How it works
loadAsset(GLTFLoader, "/models/Duck.glb")returns anAssetResultimmediately;duck.data.get(l)insidesceneisnulluntil the load resolves — see Loading Assets.- Conditional scene children. The same array slot is a ternary: a spinning wireframe icosahedron while
gltfis falsy, the loadedprimitiveonce it isn't. A different tag at the same position means the reconciler disposes the placeholder and mounts the model fresh — see Reconcile semantics. { primitive: null, object: gltf.scene }adopts the loader's ownObject3Dinstead of constructing one — never disposed by the reconciler, so re-loading or unmounting the demo doesn't touch the cached asset. Seeprimitive.onUpdate: fitDuck(rule 3 of the function-prop rules) runs after every props application on theprimitivenode — used here to measure the loaded model's bounding box once and normalize its scale/ground position, since raw glTF assets carry whatever scale they were authored at.hemisphereLight+ a warm key / cool rimdirectionalLightpair is the "stage" rig —attachinference resolvescolor/fogat the top ofsceneontoroot.scene.background/root.scene.fogthe same way a mesh child's geometry/material infers itsattach. See Attach inference.args: (l, root) => [root.camera, root.canvas]onorbitControlsresolves lazily against the live root — seeargsreconstruction — andonFrame: (root, delta, self) => self.update()drives its damping every rendered frame, theuseFrame()analog documented in Animation & Loop.frameloop: "always"(rather than the"demand"mode Orbit Viewer uses) because the loading-state placeholder spins continuously throughonFrame— a time-based animation that doesn't self-sustain under"demand"unless something keeps callinginvalidate(). See frameloop modes.- The starting camera position (
CAMERA_START) is chosen so the duck's face, not the back of its head, is the resting frame —FRONT_AZIMUTHis derived from that same constant withMath.atan2, thenorbitControls'minAzimuthAngle/maxAzimuthAngleclampautoRotate's idle drift to ±40° around it, so the camera can never wander around to the shapeless rear view on its own. - A small canvas-generated radial-gradient texture (
createContactShadowTexture) drives a second, transparentcircleGeometrymesh just above the ground disc — a cheap soft contact shadow that reads as depth/grounding without a real shadow map. - The host
divrenders a second child: an absolutely positioned,pointerEvents: "none"overlay with aradial-gradientbackgroundImagevignette, painted above the plain (non-positioned) canvas by CSS stacking order alone — same technique as Starfield Hero.