Skip to content

Shadow Simulation Addon - #779

Open
mxfh wants to merge 78 commits into
devfrom
feat/shadow-simulation
Open

Shadow Simulation Addon#779
mxfh wants to merge 78 commits into
devfrom
feat/shadow-simulation

Conversation

@mxfh

@mxfh mxfh commented Aug 28, 2026

Copy link
Copy Markdown
Contributor
deploy: ["geoportal"]

@mxfh mxfh self-assigned this Aug 28, 2026
@nx-cloud

nx-cloud Bot commented Aug 28, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 99befce

Command Status Duration Result
nx run geoportal:build --configuration=producti... ✅ Succeeded 1m 56s View ↗
nx run kulturstadtplan:build --configuration=pr... ✅ Succeeded 9s View ↗
nx run cesium-reference:build --configuration=p... ✅ Succeeded 2s View ↗
nx run techdemo:build --configuration=productio... ✅ Succeeded 3s View ↗
nx run fnp-inspektor:build --configuration=prod... ✅ Succeeded 3s View ↗
nx run parkscheinautomaten:build --configuratio... ✅ Succeeded 3s View ↗
nx run geoportal-playground:build --configurati... ✅ Succeeded 5s View ↗
nx run map-relay:build --configuration=producti... ✅ Succeeded <1s View ↗
Additional runs (54) ✅ Succeeded ... View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-09-04 14:28:41 UTC

@github-actions

Copy link
Copy Markdown

🚀 Deployed geoportal

Preview URL: https://cismet.github.io/carma-pr-deployments/779/geoportal/

Based on commit d328f4a

@github-actions

Copy link
Copy Markdown

🚀 Deployed geoportal

Preview URL: https://cismet.github.io/carma-pr-deployments/779/geoportal/

Based on commit 00ca184

@github-actions

Copy link
Copy Markdown

🚀 Deployed geoportal

Preview URL: https://cismet.github.io/carma-pr-deployments/779/geoportal/

Based on commit 96249fb

@github-actions

Copy link
Copy Markdown

🚀 Deployed geoportal

Preview URL: https://cismet.github.io/carma-pr-deployments/779/geoportal/

Based on commit fc24d7f

@github-actions

Copy link
Copy Markdown

🚀 Deployed geoportal

Preview URL: https://cismet.github.io/carma-pr-deployments/779/geoportal/

Based on commit a6a834e

@github-actions

Copy link
Copy Markdown

🚀 Deployed geoportal

Preview URL: https://cismet.github.io/carma-pr-deployments/779/geoportal/

Based on commit 9655076

@mxfh
mxfh force-pushed the feat/shadow-simulation branch from 9cb5734 to d21ee9c Compare August 31, 2026 14:06
@github-actions

Copy link
Copy Markdown

🚀 Deployed geoportal

Preview URL: https://cismet.github.io/carma-pr-deployments/779/geoportal/

Based on commit d1666bb

@github-actions

Copy link
Copy Markdown

🚀 Deployed geoportal

Preview URL: https://cismet.github.io/carma-pr-deployments/779/geoportal/

Based on commit 408a391

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

🚀 Deployed geoportal

Preview URL: https://cismet.github.io/carma-pr-deployments/779/geoportal/

Based on commit 03d8bdc

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

🚀 Deployed geoportal

Preview URL: https://cismet.github.io/carma-pr-deployments/779/geoportal/

Based on commit bc93812

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

🚀 Deployed geoportal

Preview URL: https://cismet.github.io/carma-pr-deployments/779/geoportal/

Based on commit df4a80b

@mxfh
mxfh force-pushed the feat/shadow-simulation branch from ec04799 to 8c4cce0 Compare September 4, 2026 00:27
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

🚀 Deployed geoportal

Preview URL: https://cismet.github.io/carma-pr-deployments/779/geoportal/

Based on commit 283a4c8

mxfh added 14 commits September 4, 2026 14:27
Adds the tiled shadow controller with its light-space tile layout, the
Takram-based atmospheric sunlight evaluator (transmittance and sky
irradiance LUTs), and the shadow projection diagnostics that visualize
the fitted buffers.
The frame cost had crept from ~16 ms to over 500 ms while the camera
stood still. Four causes, all fixed:

- The terrain LoD selection ran on every rendered frame, and each run
  cost hundreds of milliseconds: the orthographic shadow screen-space
  error recomputed a full matrix chain per candidate tile, including an
  updateMatrixWorld over every loaded terrain mesh. The pixels-per-meter
  term is tile-independent and is now computed once per selection, the
  refinement loop uses a binary heap instead of re-sorting per split,
  and the whole selection only runs when a quantized camera/shadow-fit
  signature changes. It is frozen entirely while a gesture is in
  flight and re-selects on moveend.
- The fitted shadow cameras were handed to the terrain on every dirty
  update, and the terrain fed elevations back into the next fit, so the
  two re-triggered each other; the hand-over now waits for the camera
  to rest and is quantized to a couple of metres so it converges.
- Every move event re-sampled the atmosphere and rebuilt the sun gizmo
  geometry; both now happen on moveend and time changes only, while a
  pan just carries the anchor along. Content changes coming from
  streaming tiles are debounced, materials re-sync only after content
  changes, and the runtime no longer schedules an extra repaint from
  inside its own render frame.
- Projection debug snapshots published (and re-rendered the debug
  panel) even while nothing listened; publishing is now gated on
  subscribers and throttled to 10 Hz during gestures.

While a camera gesture is in flight the shadow buffer drops to half
resolution and the moveend update restores it.

The shadow terrain's shadowLevelOffset returns to 3: offset 0 demands
full-resolution terrain across the whole shadow frustum and was the
single biggest regression against the fast state.

The camera restriction addon now allows tilting to 5 degrees above the
horizon (maxPitch 85) whenever it decides the camera is free, and the
shadow scene waits for the map style before building, so enabling the
simulation from a URL or persisted state no longer crashes the app on a
slow style load. Dev builds expose the map and per-phase frame timings
for console profiling.
Which ground has to stay loaded for shadows is a question about the view
and the sun. Deriving it from the fitted render camera - as before -
coupled the tile selection to a camera that follows every fit, empties
on a failed fit and rests during gestures, which unloaded sun-side tiles
while their shadows were on screen: the bright hole in the middle of a
dusk view. The coverage is now one analytic orthographic volume, the
light-space box of the visible extent swept toward the sun by the caster
reach, handed to the terrain runtime whenever view or sun move. A relief
margin in the reach accounts for ridges beyond the viewport that still
shade into it, in the render fit as well as in the coverage.

Shadow resolution: screen corners near the horizon unproject kilometres
away and used to stretch the single buffer across the whole valley. The
receiver extent is now clamped to a 4 km radius around the view anchor,
which keeps the texels where the viewer looks.

The shading itself sheds its per-texel bias battery. Acne is answered
where it comes from: buildings render their far walls into the depth map
(shadowSide BackSide), which also removes the bright leak line along
their bases, and terrain gets a slope-scaled polygon offset in its own
depth pass. One small constant normal bias and a one-texel filter radius
remain. Shadow intensity defaults to 100 percent.

A gesture no longer coarsens streamed building tiles: the progressive
error target only tightens after load, so detail already on screen stays
through a pan.
Three ways the ground between the view and the sun could still go
missing, all closed:

- The sweep took its vertical extent and caster reach from the elevation
  range of the tiles currently visible. That range grows as tiles
  stream in, the sweep reshaped, and every reshape superseded the tile
  batch that was still loading - at low sun the up-sun tiles never
  settled. The sweep now spans a fixed regional elevation band (0-500 m
  over Wuppertal) and is a pure function of view and sun; its box is
  snapped to a 50 m grid on top. Superseded batches keep their fetched
  tiles as hidden meshes, so the selection that follows activates them
  instead of re-fetching.

- The visible relief also underestimated the reach itself: the ridges
  that shade a valley at dusk are usually not in the viewport. The
  relief margin rises to 300 m of regional headroom, in the coverage and
  in the render fit's far plane alike; at 10 degrees of sun that is
  roughly a 5 km sweep.

- Refining an available parent whose child quadrant has no data swapped
  real coarse ground for a sea-level plate - a hole in the view, and
  up-sun a hole in the shadow. Such parents now stay whole; refinement
  ends at the availability boundary.

Dev builds expose the sweep as window.__carmaShadowCoverage.
mxfh added 26 commits September 4, 2026 14:27
releaseTilesOutsideCameraUnion unmarked the REPLACE parents that
3d-tiles-renderer keeps as anti-flicker retention when all of their
children lie outside the frustum. Once evicted, an external tileset
stub loses its children, is traversed as in-frustum again on the next
frame and is requested anew: about 60 tileset.json fetches per second
on the Mesh 2024 layer while the camera stands still. The LRU cache
already evicts unused content by itself, so the release goes.
…ls draped

The point-label overlay rewrote the coverage filter of every point symbol
layer on each idle and styledata event: 179 layers with a 677-polygon
`within` filter, ~32 ms per setFilter, and each write forced a repaint whose
idle landed in the same listener again. A single pass blocked the main
thread for up to 8 s and the terrain drape never settled.

- shared-three-scene-registry: rate limit the label overlay maintenance to
  once per second (trailing), cache the point-label layer list by layer order
  instead of serializing the style per event, union adjacent tile boxes
  before building the coverage polygon, skip untouched filters by reference
  and write filters without style-spec validation. User toggles stay
  immediate.
- map-style-layer-suppression: only a literal or omitted `symbol-placement`
  counts as point-anchored; basemap.de street names with a zoom function
  stay in the draped style.
- map-controls-layout: swap a re-rendered control in place instead of a
  remove/add pair and memoize the layout context.
- geoportal main: reuse the React root across hot updates that bubble up to
  the export-less entry, which previously created a second root and broke
  the old tree's teardown.
- shadow-simulation: point-label overlay toggle (Beschriftungen) from the
  quick settings.
Back-face casting left a lit gap under LOD2 solids that do not sit flush on
the terrain. Closed building solids now write both faces into the shadow
map, so the sun-facing wall shadows the ground beneath and behind the
building. Open surfaces keep front-face casting; the per-texel normal bias
covers the lit faces that are now part of the shadow map.
…errain retries

Textured mesh tilesets that provide the terrain now take a label-only drape:
the basemap pass below Three keeps only symbol layers and contour lines, the
capture is composited over the mesh texture by alpha instead of replacing
it, and MapLibre's DEM depth of the same frame occludes labels behind roofs
and facades. Label color is applied after lighting so white street names stay
white in the sun and darken in shadow.

Registry label rules for the mesh drape: street names white with a grey halo,
enlarged and crisp; point labels (places, POIs, house numbers, stations) in
the sun color with a grey halo; water names blue without halo; elevation
labels sun colored without halo; road shields untouched in every mode.
Sprites are lit by the sun color (albedo times light). Place names float by
prominence (150 to 600 m), other point labels 10 m, as a per-frame viewport
translate. Near-white halos count as white for the default halo tint.

Terrain requests never stay stale: Cesium terrain tiles and the provider
bootstrap retry with backoff until a confirmed 4xx, a partly failed selection
is asked for again, and the MapLibre DEM source fetches through a retrying
protocol.

Shadow scene: double-sided casting for closed solids, label drape mode chosen
from the shared scene's terrain provider, overlay label toggle.
The label drape (hide fills, strokes and rasters below Three, keep symbols
and contour lines at half opacity) moves from the shadow scene into the
shared scene registry and switches on whenever a textured mesh supplies the
terrain, so a loaded mesh looks the same with the shadow simulation off:
draped white street names, lifted point labels, no basemap fills. Without a
sun color nothing is tinted: point labels and sprites keep their authored
paint, elevation labels are white. The shadow scene keeps the opaque basemap
drape for bare terrain and only hands its changes back in labels mode.
@mxfh
mxfh force-pushed the feat/shadow-simulation branch from de58ac6 to 751cbb0 Compare September 4, 2026 12:35
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

🚀 Deployed geoportal

Preview URL: https://cismet.github.io/carma-pr-deployments/779/geoportal/

Based on commit 448650c

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

🚀 Deployed geoportal

Preview URL: https://cismet.github.io/carma-pr-deployments/779/geoportal/

Based on commit 49ddd65

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