A room planner for Lunde, a fictional Nordic furniture brand. It goes one step past a single-object AR viewer: you furnish a whole room with six pieces, check that everything fits, then take the same room to your phone. Plain Three.js, no new dependencies.
How it works
- Pieces: a sofa, lounge chair, round coffee table, sideboard with doors that open, arc floor lamp with a real spot light, and a wool rug, all built in code at real metres (no scans, no downloads). Surfaces are drawn on canvases: bouclé loops, wool and linen weave, wood grain, marble, rattan, tufted rug pile. Each piece has two or three finishes that swap materials without rebuilding geometry. Details include piped seams, tapered oak legs, fluted doors with turned pulls and an interior, a dome shade that really lights the room, and a fringed rug.
- Room: an editable shell from 2.5 to 7 m with a window (sky behind it, curtains) and a door ajar. Walls that face the camera drop to a low plinth so the room always opens towards you; in plan view all four drop and the grid and room dimensions show.
- Planner logic (
layout.ts, unit-tested, no engine code): 5 cm grid snap, wall snap within 14 cm (and a turn to face the room for pieces that want their back to a wall), 15 degree rotation, footprint polygons with a separating-axis collision test (round tables and the lamp base are round, an open sideboard needs floor in front of its doors, rugs lie under everything), gaps to the nearest walls, the AR fit sentence and the share link. - Share link: the whole room lives in the URL hash (
#w=420&d=360&v=plan&i=sofa_210_47_0_0_0~..., positions in cm from the back-left corner), written with history.replaceState after changes and read on load. Anything unreadable is dropped and values are clamped. - Picking and dragging use invisible proxy boxes per piece and a ray to the floor plane; the camera orbits on empty floor,
Ctrl/Cmd + wheel or a pinch zooms (the wheel alone still scrolls the page). Touch dragging works while a piece is selected (the stage then takes the touch); buttons and keys cover everything for keyboard and screen-reader use. - Outlines and dimension lines are drawn over the scene (selection in ink, collisions in red); their number labels are HTML chips moved by direct style writes each frame, like Kasvi's spots.
AR
The device choice, the QR code and the USDZ builder are shared with Kasvi in src/experiments/shared/xr/.
- Android (Chrome with ARCore): WebXR
immersive-ar with hit-test, optional anchors, light-estimation and a dom-overlay for the controls. Place pieces one by one at true size (a footprint ghost shows what a tap would place, red if it would overlap), tap a piece to select it, move it, turn it in 15 degree steps, change its finish or switch its doors or light. The measure tool takes two taps on the floor and compares the distance with the selected piece (or the one in the tray): "Hav sofa 2.18 m, gap 2.40 m: fits, 11 cm each side." "Drop my layout" brings the desktop room in as a group, facing you. - iPhone and iPad: Safari has no WebXR AR, so the selected piece in its current finish is built into a USDZ in the browser (
USDZExporter, anchored to a horizontal plane) and opened with <a rel="ar"> in AR Quick Look. Quick Look shows one piece at a time, said plainly on the page. Double-sided and back-face shells (the chair back, the lamp shade) are exported as flipped twins because USDZ has no such materials. - Desktop: the AR button shows a QR code holding the current room, so the phone opens the same layout.
Performance and limits
- One renderer for the planner and AR, driven by
setAnimationLoop; it renders only when something moves and pauses off-screen and in hidden tabs. The page hears about every change at once, so the panel works while the stage is scrolled out of view. - High tier: shadow map from the window light, 512 px textures. Light tier (
?quality=low): no shadow maps, pixel ratio 1, 256 px textures; contact shadows come from a soft blob under each piece. ?shot shows only the room, full frame, for the poster and the card (scripts/card-shots.mjs).- The sofa USDZ is about 5 MB because of its many textured cushions; the other pieces are between 0.3 and 2.4 MB.
- Not checked by hand yet: a real Android phone with ARCore, a real iPhone in AR Quick Look, Safari, 60 fps on a mid-range phone.
- Lunde, its names and every price are invented. Prices are shown as demo prices everywhere.