Repository navigation
viewer: sparse tiled room map for daylight; rooms built from settled geometry - #1245
Merged
Merged
Conversation
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>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
❌ 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

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.
Building the room light.
Measured (CPU bench over every room of each scene, plus browser checks):
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
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.main, floors past the 8th share one map layer).Screenshots / screen recording
Not added yet.
Checklist
bun devbun checkto verify)mainbranchTested 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 checkhas 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.RoomLightingnow builds through that map, shares one probe between lighting and exterior mask, and tracks levels viasetLevelPresentation(shown, shadow-only, focus / open-to-sky) instead of stackedlevelY+ offset uniforms. Doorway and cover rays use a spatial mesh grid so large wall counts build in sub-second time.useDaylightRoomsdefers rebuilds until geometry settles (or after a 10s cap), skips builds while levels are exploding/moving, reapplies when loading still shrinks dirty nodes, and setstextureLimitfrom 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.