--- name: room-from-photos description: Turn a few phone photos of an existing room into a near-photoreal 3D model and plan with furniture layouts to compare and drag around, as a single Three.js HTML file. Use when the user sends photos of a room, with no floor plan, and wants to see it in 3D or work out where furniture goes: "will the new bed fit in this room", "where should the desk go", "can we try a few layouts". --- # Room from photos Photos in, a 3D room out, then layouts the user moves around. **Copy `starter.html` and edit only its `CONFIG` block.** The engine draws the plan (the working drawing), a dollhouse model that cuts away the near walls, live fit checks, option tabs, drag and turn in either view, and each photo with the model drawn over it. The model opens in the room's real finishes (paint, floorboards, paneling, trim, each piece's colours), and a toggle switches to a white architectural model for reading the layout alone. It works like a configurator. The model renders close to photoreal: three.js 0.162 loaded as ES modules from jsDelivr, physically based finishes from CC0 photo scans in `textures/` (ambientCG wood cut board by board into a strip floor and plank by plank into the paneling, a linen weave, brick and carpet pile, each tiled at its real size; greyscale, so every swatch still tints them; drawn stand-ins when the scans can't load), soft sun shadows, ambient occlusion at half resolution, and a view through each window. The dollhouse is the working view, so it stays bright and clean under a studio light: every swatch reads as itself (a lit wall or floor lands within a few ΔE of its hex), each window is a soft area light, lamp shades glow with a warm bulb inside, and every piece stands in its own contact shadow. Through a photo's camera the room lights itself the way the photos show it instead: the environment is captured from the room's own centre (six small cube faces, blurred, two bounces from a studio light, so it comes out the same after every edit), so the floor's gloss streaks with the windows, the ceiling is a lit warm grey rather than a painted white, and the paneling's bounce warms the corners; exposure, a cooler sun, the view outside blown to white and a phone lens's vignette are graded to match, measured against a real room's five photos. The white model keeps its own studio light and sun, unchanged. It redraws only on change, and a slow GPU drops the occlusion pass while things move; the room-light capture and the shadow redraw run only once a change settles, never per frame. Select a piece in the model and a gizmo appears: arrows slide it and a ring turns it in 15° steps. One-click looks recolour the room, swatch chips set each surface, and a selected piece offers its style variants (a longer sofa, a bigger rug) and its colours. "Add a piece" opens a library: a catalogue of common pieces in real sizes (sofas, tables, storage, beds, rugs, lamps, plants, a TV, art) plus the room's own entries, each shown as the engine's own render of it; a piece added from it lands in the clearest spot, selected, and drags, turns, takes variants and colours and is checked like the rest. Every choice belongs to the current option. The sample room renders on its own. ## Process 1. **Ask only for what blocks you.** You need the photos, ideally one from the doorway and one from the opposite corner, and the new thing's size (a product link or W × D × H). Estimate everything else. 2. **Read each photo by perspective before drawing anything.** Work out which wall faces the camera, which floor lines recede, and what each doorway looks through to. Write the room out wall by wall (top wall left to right, then clockwise), with every door, window, closet, radiator and vent, and check that text against every photo. Skipping this is the most common way to get the room wrong. 3. **Scale from known objects** and list each estimate in `ROOM.verify`. Don't wait for a tape measure. | Object | Size | |---|---| | Interior door | 80″ high; 28–32″ wide (bedrooms often 30) | | Outlet / switch | centre 12–16″ / 48″ off the floor; cover plate 4.5″ tall | | Ceiling | 96″ unless the photo says otherwise | | Mattress | twin 38 × 75, full 54 × 75, queen 60 × 80 | | Window sill | 24–36″; head usually lines up with the door head | 4. **Fill `CONFIG`.** Pick any wall as the top of the plan (the one facing you from the doorway reads well); every yaw and `rot` is measured from it. The outline traces the inner wall faces clockwise from the top-left corner, in inches. Place each opening by its centre point on the wall. Existing furniture goes in as items, built-ins go in `fixed`, and the thing being placed gets `new: true`. Write 2–3 layouts that are different ideas (corner, centred, along a wall), each with a one-sentence trade-off. When the room is furnished, make the first option the room as it is. 5. **Colour it from the photos.** Sample the wall paint, flooring, trim and every piece you keep from a lit patch of the photo, away from corners and window glare. A swatch is the finish itself, not the pixel: the engine lights it again, so a sample taken in shade renders about twice as dark. Take the brightest unglared patch and lift it toward the material's own colour (one white wall can read anywhere from #625a47 to #f6edcf in a single photo). Metals go in `paint`. Name each one in `PALETTE` ("Pale sage", "Walnut") so the pickers show names, not hex. Set `ROOM.walls`, `floor`, `trim` and `boards`, and each item's `color` and `color2`. Colours for things not yet bought (the new bed's bedding, a rug) come from the user's inspiration, or else sit quietly with the room's colours. Group the swatches as `paint`, `wood`, `fabric` and `clay` so each surface offers the right ones. Add `LOOKS` for the room's big palette levers (paint the paneling, a darker wall) and `variants` for any piece whose size or kind is in question. When the palette is part of the question, give an option its own `colors` so the options differ as a design choice, and say so in its note. Otherwise keep one palette and let the layouts differ. 6. **Match every photo.** Give each photo a `PHOTOS` entry: `{src: 'photos/door.jpg', label: 'From the door', at: [x, y], h: 60, yaw: 0, pitch: 0, roll: 0, fov: 67}`. The camera stands at plan `at` with its lens `h` inches up; yaw 0 looks at the top wall and 90 at the right one; pitch is up-positive, roll clockwise-positive, and fov is the vertical angle. On a phone's 1× lens that's about 67° for a portrait shot and 53° for landscape; the 0.5× ultra-wide is about 106° and 90°. Read the lens from EXIF when it's there (a 35 mm-equivalent focal length near 13 mm is the 0.5×, 24–26 mm the 1×); when it's stripped, judge it by how much of the room one frame holds. Fix fov from the lens: left free, a solve slides along fov and distance at almost the same error. With three or more photos, solve them jointly (a background agent works well for this): click corners, jambs, sills and board lines in every photo, then least-squares the room's size, the openings and all the cameras together. A five-photo living room came back within 1–4 px per photo and corrected an eyeballed reading by 9″ in length and 3″ in height. The absolute scale rests on known objects (an 80″ door, 2¼″ floor strips). Photos that share no feature can't fix the distance between them: two shots facing opposite ends of a room give its width but not its length. Take that from a known object or a symmetry (a centred fireplace), and list it in `verify`. A second pass with the solved cameras measures each piece's footprint by back-projecting its feet onto the floor. Open the photo in the page and tune the fields until the model's corners, jambs and sill land on the photo's. **If no camera lines them all up, the room is wrong: fix the outline, not the camera.** "Copy camera" hands the numbers back. 7. **Verify.** Serve the page over localhost (`python3 -m http.server`). Judge the look on a real GPU, not headless: open the page with `#debug`, run `python3 snapsink.py