feat(street-level): refine ground casting with the Google 3D surface

The raycast challenge on #768 showed bare-earth casting misplacing roads
that are not at grade: lines floated over freeways in trenches and steep
streets (rendered line pixels floating: Austin 8.5%, SF 27.1%, LA 43.8%),
because a ~111 m terrain grid cannot see a 30 m-wide cut or a slope between
its corners.

meshSampler.js samples the rendered Google 3D surface (scene.sampleHeight,
overlays excluded) once per ~11 m cell within 900 m of the camera, nearest
first, in idle time within a 6 ms budget; misses are retried, not latched.
groundCast.refineHeights blends it in, per point along a line:
- mesh up to 3 m above bare earth is the road: follow it, 1 m above;
- higher mesh (canopy, deck, roof): carry the road's offset from bare earth
  across from the road samples on both sides (up to 8 points);
- otherwise bare earth + 2 m, as before.
Lines, cones and the marker use it; tiles touched by new samples are
redrawn one per idle slice with the no-blink swap.

Rendered line pixels on the surface / floating, before -> after:
Austin 90.0 -> 95.3% / 8.5 -> 0.7%; SF 69.0 -> 86.4% / 27.1 -> 9.1%;
LA 52.3 -> 91.0% / 43.8 -> 4.0%; Innsbruck 95.0 -> 95.7% / 0.2 -> 0.2%;
Sacramento 80.7 -> 79.3% / 3.8 -> 2.7%; buried lines stay under 1%.
Cones floating in SF 44 -> 5%, buried at LA 59 -> 28%. Main-thread long
tasks added during refinement: at most 134 ms (Austin, densest tile).
Roads on elevated decks are still drawn at ground level under the deck.

Unit tests cover the blend (trench, slope, canopy bridge, deck, bad probe),
the finer cast with meshAt, and the sampler (once per cell, nearest first,
overlays excluded, far cells skipped, misses not latched, idle when off).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Said TurkseverandClaude Opus 5.5 committed 2026-10-02 01:43:16 +01:00
1 parent abbf69a512
commit 514788303d
13 files changed
+592 -30

No files matched your search

+11 -1
View File
@@ -2900,7 +2900,17 @@ buildings and trees. Heights are sampled on the ground-floor grid (0.001°,
tile needs at most a few hundred lookups, sent once per tile and aborted when
the tile is dropped. A tile is drawn draped first and swapped for its cast
lines when the heights arrive; a geoid fallback (terrain proxy down) counts as
unknown and leaves the lines draped. Cones and the marker keep drawing over
unknown and leaves the lines draped. The bare-earth grid cannot see a
freeway in a trench, a steep street between grid corners or the road under a
tree, so `src/layers/streetLevel/meshSampler.js` then samples the rendered
Google 3D surface (`scene.sampleHeight`, overlays excluded) once per ~11 m
cell within 900 m of the camera, nearest first, in idle time within a 6 ms
budget, and `refineHeights` blends it in: mesh up to 3 m above bare earth is
the road and is followed 1 m above it; a higher mesh (canopy, deck, roof) takes
the road's offset from bare earth carried across from the road samples on
either side; anything else stays bare earth + 2 m. Tiles touched by new
samples are redrawn one per idle slice with the same no-blink swap. Roads on
elevated decks are still drawn at ground level under the deck. Cones and the marker keep drawing over
buildings so they stay clickable. Framing or following a photo also checks the
sampled mesh height against the bare earth: `sampleHeight` can return values
kilometres underground before the tiles under the photo have loaded, and such