Skip to content

viewer: sparse tiled room map for daylight; rooms built from settled geometry - #1245

Merged
wass08 merged 4 commits into
mainfrom
feat/daylight-tiled-room-map
Nov 9, 2026
Merged

wass08 merged 4 commits into
mainfrom
feat/daylight-tiled-room-map

Conversation

@wass08

@wass08 wass08 commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

Replaces the daylight room-id map (viewer/src/lib/daylight) with a sparse tiled map, and fixes how the room light is built on large scenes.

The map. The old map was one texture with every level stacked along Y at 10 cm cells, levels found by height across the whole site and capped at 8. A 40-storey tower lit floors 8–39 with whichever room was rasterized last, a campus of buildings fell to ~80 cm cells and still mixed its buildings' floors, and a 300 × 200 m hall took 48 MB at 20 cm.

  • Per level, tiled. Each level keeps its own 10 cm cells, cut into 1.6 m tiles. A tile inside one room (or outside every room) is one value; only tiles a room edge crosses keep their cells, in a shared two-byte atlas. A level's rooms are grouped into clusters with their own rects, so open ground between them costs nothing. Memory follows wall length, not site area, and nothing grows with the number of floors.
  • Per building. A point finds candidate buildings in a coarse directory and its level by binary search over that building's live floor heights (sorted heights stay sorted when levels explode apart). Buildings side by side with different floor heights each keep their floors. No level cap.
  • Built tile row by tile row, never a dense per-level grid; zone holes are rasterized; where zones overlap, the smallest room wins (an automatic floor-wide room or a unit no longer swallows the rooms in it).
  • Presentation. "Open to the sky" and the meters read each level's real presentation (shown, not shadow-only; focused when every level over it in its building is hidden). Texture sizes come from the device. The lighting and the exterior mask share one lookup.

Building the room light.

  • Doorway and cover rays test only the meshes near them (a 2 m grid of mesh bounds taken from current vertices) instead of every wall: room builds on a 2,560-wall scene drop from 37 s to 0.6 s.
  • Rooms wait for the scene to settle and rebuild each time walls, slabs, openings or zones finish changing (they finish without a geometry revision). A scene still loading after 10 s builds anyway and again every 10 s while it progresses. Before, a large scene built its rooms from half-built walls and kept them: doorways read as whole walls and corner rooms got diagonal "openings" pouring outdoor light in.

Measured (CPU bench over every room of each scene, plus browser checks):

Scene Before After
40-storey tower 96/480 rooms right 480/480
Campus, 10 buildings × 5 floors 708/1770, ~80 cm cells, 35 MB 1770/1770, 10 cm, 17 MB
300 × 200 m, 8 floors 48 MB at 20 cm, 1.1 s build 14 MB at 10 cm, 0.3 s
Houses same room at every point sampled at 5 cm (differences only within one cell of room edges); main-pass GPU time within run-to-run noise

Known limits, unchanged: double-height rooms read as outside above the next floor's line; stairs and atriums carry no light between floors.

How to test

  1. bun test packages/viewer/src/lib/daylight: new tests for a 40-storey tower, buildings with different floor heights, courtyard holes, memory along walls for a 300 × 200 m hall, overlapping zones, a room off the tile grid, an index texture within a small device limit, and when a level opens from above.
  2. Open a multi-storey building, select an upper floor at night: every room has its own lamp pool (on main, floors past the 8th share one map layer).
  3. Open a large scene (thousands of walls) at night and let it load: rooms stay lit by their own windows and lamps, with no diagonal bright wedges in corner rooms.

Screenshots / screen recording

Not added yet.

Checklist

  • I've tested this locally with bun dev
  • My code follows the existing code style (run bun check to verify)
  • I've updated relevant documentation (if applicable)
  • This PR targets the main branch

Tested in the hosted app (headless WebGPU and a desktop browser at night and midday), not with this repo's bun dev. Viewer package: typecheck clean, 1359 tests pass, bun check has no new findings.

🤖 Generated with Claude Code


Note

Medium Risk
Large refactor of viewer daylight shaders, GPU room lookup, and rebuild timing; behavior changes on multi-floor and large scenes, though coverage is broad in unit tests.

Overview
Replaces the daylight room-id map with a sparse tiled RoomMap (per level, per building, device-aware textures) so tall towers, multi-building sites, and huge floors resolve the correct room without blowing texture size or an 8-level cap. Zones can include holes (courtyards); overlapping zones prefer the smallest room.

RoomLighting now builds through that map, shares one probe between lighting and exterior mask, and tracks levels via setLevelPresentation (shown, shadow-only, focus / open-to-sky) instead of stacked levelY + offset uniforms. Doorway and cover rays use a spatial mesh grid so large wall counts build in sub-second time.

useDaylightRooms defers rebuilds until geometry settles (or after a 10s cap), skips builds while levels are exploding/moving, reapplies when loading still shrinks dirty nodes, and sets textureLimit from the renderer.

Scene input switches from height-stacked levels to MapLevel[] (floor Y + building index) in live and baked paths.

Reviewed by Cursor Bugbot for commit df92fb0. Bugbot is set up for automated code reviews on this repo. Configure here.

wass08 and others added 3 commits November 9, 2026 17:12
The room-id map was one texture with every level stacked along Y at 10 cm cells,
levels found by height across the whole site and capped at 8. A 40-storey tower
lit floors 8-39 with whichever room was rasterized last, a campus fell to ~80 cm
cells and still mixed its buildings' floors, a datacenter took 48 MB at 20 cm.

Each level now keeps its own map of 10 cm cells cut into 1.6 m tiles: a tile inside
one room (or outside all of them) is one value, only tiles a room edge crosses keep
their cells, packed into a shared two-byte atlas. A point finds its buildings in a
coarse directory and its level by a binary search over that building's live floor
heights, which stay sorted when levels explode apart. Zone holes are rasterized.
"Open to the sky" and the meters read the real presentation per level (shown, not
shadow-only; focused when the level over it is hidden), not an index order. Texture
sizes come from the device. The lighting and the exterior mask share one lookup.

CPU bench, every room on every floor: tower 96/480 -> 480/480 rooms right, campus
708/1770 -> 1770/1770 at 10 cm (35 -> 17 MB), datacenter 48 MB at 20 cm -> 14 MB at
10 cm, street block 21 MB -> 0.4 MB; houses resolve the same rooms (differences only
inside the 2-cell edge band). Rebuilds 2-90x faster. Main-pass GPU time within
run-to-run noise on houses, tower and datacenter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…room wins

Review and browser findings on the tiled room map:

- Build by rows of tiles (with a 3-cell margin) and group each level's rooms
  into clusters with their own rects: no dense per-level grid, so a level with
  rooms kilometres apart neither runs out of memory nor pays for the ground
  between them. Tile rects are computed in whole tiles (a rounded origin could
  start a level at tile -1 and crash the directory).
- Doorway and cover rays test only the meshes near them (a 2 m grid of mesh
  bounds) instead of every wall and slab: room builds drop from 37 s to 0.6 s on
  a 2,560-wall datacenter and from 6.7 s to 1.2 s on a 40-storey tower.
- Where zones overlap, the smallest room wins (largest rasterized first): an
  automatic floor-wide room or a unit around its rooms no longer swallows them.
- A level opens from above only when every level over it in its building is
  hidden; the index texture is as wide as the device allows; room ids are
  capped at 65,535 with a warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… vertices

On a large scene the rooms were built while walls and windows were still being
built, and never again: walls and openings finish without a geometry revision.
Rays then passed through unbuilt walls, so doorways read as whole walls and an
L of exterior wall became one diagonal opening across a corner room, pouring
outdoor light in (the bright wedges); most windows had no portal yet.

- Rooms wait for the scene to settle (no dirty nodes) and build again each time
  it does after walls, slabs, openings or zones changed (not furniture).
- A scene still loading after 10 s builds anyway, then again every 10 s while
  its dirty count keeps falling, so the rooms catch up with the geometry.
- The ray index takes each mesh's bounds from its current vertices: a cached
  bounding box can predate a wall rebuilt in place, filing it where no ray
  looks.

Campus: corner room 8-1 goes from one 17.6 m diagonal exterior opening and no
windows to its four windows and two 1.2 m doorways; interior rooms from four
12 m "openings" to their four 1.2 m doorways.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b04ab24. Configure here.

… levels move

Bugbot review on #1245:

- A level's cluster rects can overlap (a C-shaped wing's rect covers a courtyard
  pavilion in its own cluster). The lookup stopped at the first rect holding the
  point, empty there, so the pavilion got no room light. It now keeps going
  until a cluster has a room at the point (CPU and shader).
- Cover and doorway rays are cast at stacked floor heights against meshes where
  they are; a rebuild while levels are exploded (or still moving) read doorways
  as open and covers at exploded heights. Rebuilds wait until levels are
  stacked and still.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wass08
wass08 merged commit 63b81b4 into main Nov 9, 2026
4 checks passed
@wass08
wass08 deleted the feat/daylight-tiled-room-map branch November 9, 2026 22:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant