Glass cubes

The glass system in URM Lab: one rigid material in three qualities, a puzzle that plays inside, and a single continuous shot from cube to page. The demos on this page run the code at the bottom, unchanged.

Drag a cube to spin it and throw it. Click one to open it.

Principles

Glass carries meaning

A cube's glass says what kind of model is inside, so the quality is never decoration. Crystal is a model trained at scale elsewhere. Glass is one trained here. Frost is a model still learning, and it clears as accuracy rises, so you can watch uncertainty leave the glass.

Light, not lines

No strokes or outlines. Edges are defined by refraction and by coloured light from the surround. Colour comes from the four-colour palette, used as light: inside the cube, behind it, in its dispersion.

Rigid and precise

Glass never bends, stretches or wobbles. Cubes respond to touch by turning, leaning and lifting, and the shot that opens a cube is exact: tiles land on the page's cells to the pixel.

One continuous shot

Opening a cube never cuts. The cube turns, parts, the camera dives, the tiles glide onto the board, and the page cross-fades in over them. Going back plays the same shot in reverse.

Never flash

The glass reflects a dim coloured surround and there are no direct lights, so turning a cube never sweeps a white highlight across the screen. Bloom only catches the brightest tiles.

Time, not frames

Every animation is a function of elapsed time. On a slow or throttled display things move less smoothly but arrive at the same moment. With reduced motion, cubes appear in place and nothing drifts.

Cheap to run

No lights to shade, a capped pixel ratio, shaders compiled before they are needed, and a Solve cube that only renders while something in it moves. The cube scene holds the display's full frame rate.

Qualities

Each quality is a set of physical material values. The table is generated from QUALITIES in the code below.

QualityMeansRoughnessIORDispersionThicknessIridescenceClearcoatAttenuationEnv

Assignment

ModelQualityWhy
Imported, trained at scaleCrystalDense, high index of refraction, splits light sharply.
Trained on this machine, finishedGlassLighter index, clear, with a stronger thin-film sheen.
Still trainingFrostedRoughness goes from 0.45 to 0.08 as the held-out solve rate goes from 0 to 1.

Shared material values

BaseMeshPhysicalMaterial, white, metalness 0, transmission 1, depthWrite off
TintThe challenge's colour mixed 72% toward white, as attenuationColor: the body of the glass is faintly the cube's colour
IridescenceIOR 1.3, film thickness 140 to 520 nm
Clearcoat roughness0.06
Specular intensity0.8
GeometryRounded box 1.5 units, corner radius 0.2, 8 segments; the faces it parts into are 0.07 thick with radius 0.03

Content

Each cube holds a real, unseen puzzle of its kind, as tiles on a plane through the cube's centre. The tiles always face the viewer through the cube's resting tilt (not through your spin), so the puzzle reads once a cube settles.

CellTile
Given digitIts digit's colour, hue4(d / 9), at 0.42 brightness, 86% of a cell
Answer digitThe same colour at 1.2 brightness: a solved Sudoku reads as a mosaic where every row, column and box holds all nine colours
Blank, no answerA dim dot, 32% of a cell
Maze corridor / ends / routeDim white / bright white / colour along the diagonal
Life alive / deadWhite (given) or coloured (answer) / a faint tile

The solve loop

Every 8 seconds a cube shows its model's work: the answer tiles arrive in a diagonal wave from 1.1 s over 2.3 s, each popping in over 420 ms with an overshoot. When the last one lands the cube's glow pulses once, the puzzle holds solved, and the answers clear from 6.7 to 7.3 s. Each cube starts at a random point in the loop, so they never pulse together.

Interaction

InputResponse
HoverLeans toward the pointer (up to 0.45 rad), comes 0.5 units forward, scales 1.07 with a brief pulse, inner glow ×1.6; its label brightens
PressScales to 0.95, uniformly
DragTurns under the finger (0.012 rad per px) and follows the pointer on an elastic tether (16% of the distance, at most 0.45 units)
ThrowKeeps spinning at the release speed (capped at 14 rad/s), friction e^(−1.6t), then eases back to face you once under 1.2 rad/s
ReleaseThe tether springs back, critically damped
Click (under 6 px of travel, under 450 ms)Opens the cube
KeyboardEach label is a button: Tab to it, Enter opens its cube

The shot

Opening takes 1.5 s. The app first lays the page out invisibly where it will appear and measures every cell; the camera then flies to the exact framing that puts the cube's tiles over those cells, while each tile glides to its cell. At 72% the page starts to cross-fade in over the tiles; at 100% it is simply the page.

Closing takes 1.35 s and is the same shot reversed: the page fades out over 380 ms while the tiles, sitting exactly on its cells, glide back into the cube; the faces come together; the cube turns back to its resting tilt. The other cubes fly in from the edges, starting 380 ms in.

Framing math

For a board of local width W inside a cube of scale s, to cover a page rectangle of width w px in a viewport of height H px with vertical field of view θ: distance d = W·s·H / (w·2·tan(θ/2)), world per pixel k = 2·d·tan(θ/2) / H, and the camera sits at (cx − (rx − Vw/2)·k, cy + (ry − H/2)·k, cz + d) looking straight ahead, where (rx, ry) is the rectangle's centre. Tiles go to ((x − rx)·W/w, −(y − ry)·W/w) at 84% of the cell's size.

Rendering

SurroundAn equirectangular canvas: near-black with soft blue, red, yellow, green, violet and cyan light, prefiltered with PMREM. Nothing white and bright.
BackdropA large plane 9 units behind with the four colours, low alpha. It must be opaque: transmission only refracts opaque objects, and a transparent backdrop leaves the glass nothing to bend, so it vanishes.
LightsNone. The surround lights the glass; each cube has a soft additive halo of its colour inside, and its tiles are unlit and bright.
ToneACES filmic, exposure 0.82, sRGB output
BloomStrength 0.5, radius 0.45, threshold 0.9: only the brightest tiles glow
ResolutionDevice pixel ratio capped at 1.25; tiles are one instanced mesh per cube
ShadersCompiled in the background as soon as the cubes exist (a throwaway scene holds one mesh per material, including the hidden faces), so opening a cube never stalls
Solve cubeNo tone mapping, so its background is exactly the page colour; renders only while points arrive, glide or the cube is turning
Camera30° field of view, resting at (0, 0.1, 12.5)

API

ExportUse
new GlassStage(host, labels, hooks)The cube scene. hooks.prepare(id) returns { rect, cells } (viewport px) for the page to land on, or null; hooks.reveal(id, ms) starts the page's cross-fade; hooks.done(id) ends the shot.
stage.setItems(items)[{ id, name, line, color, quality, progress, live, puzzle: { rows, cols, box, given, answer } }]; id is the puzzle kind
stage.update(items)Labels, liveness and quality, in place
stage.setPuzzle(id, puzzle)Put another puzzle in a cube
stage.enter({ from, target })Fly in; with from and target the named cube closes around the page first
stage.choose(id) / stage.leave()Open a cube / stop rendering
new ThoughtCube(el, slots, quality)The thought-cloud in glass; set(points, count), begin(), reset(), setLive, setIdle, setHot, setQuality
glassMaterial(quality, tint, opts) / setQuality(m, q, progress)The material on its own
QUALITIES, MOTION, SCENE, EASE, hue4The tokens

Do and don't

Do

  • Pick the quality from what the model is, never for looks.
  • Keep a direct light out of the scene; let the surround light the glass.
  • Keep the backdrop opaque so the glass has something to refract.
  • Answer touch with turning, leaning and lifting; keep the glass rigid.
  • Hand off to the page through prepare / reveal / done, so the tiles land on the real cells.
  • Drive everything from elapsed time; respect reduced motion.

Don't

  • Spin cubes on their own: a constant turn sweeps reflections and reads as flashing.
  • Add outlines, rings or bars to show selection; use light, lean and lift.
  • Stretch, squash or wobble the glass, or render the Solve cube when nothing in it is moving.
  • Cut from the cube to the page, or fade to black between them.
  • Raise bloom until the glass itself glows; only the tiles should.
  • Put a puzzle in a cube that the model has seen in training.

Code

The complete module, web/glass.js. It needs three.js r170 (the import map at the top of this file). The demos above run exactly this text.