fix(street-level): budgeted mesh sampling, in-place cones, date line, horizon

From the IC8 review of #768, each with tests that fail before:

- P1: a mesh batch no longer rebuilds the selected sequence's cones. The
  drawn cones are indexed by mesh cell; a batch that touches none of them
  is ignored and the affected cones are moved in place (no removeAll/add).
- P2: mesh sampling keeps to its 6 ms slices. Requests are taken in inside
  the budget, only cells within range of the camera are queued, the queue
  is a persistent min-heap re-ranked in one pass only after a 100 m camera
  move, and the deadline starts before any work. Coverage keeps a bounding
  box per line and densifies a line only once it comes within range. On a
  synthetic dense tile (600 x 40 points): slice median ~10 ms -> 5 ms, max
  18-30 ms -> 6.7 ms, refresh cost 11-24 ms -> 1.5-3 ms.
- P2: image cones and the photo marker are horizon-culled like the
  overview dots (one createHorizonCull in view.js, shared by all three).
- P2: a selection change recolours the previous zoom's lines still on
  screen and the old lines a cast swap keeps showing.
- P2: distances (metresBetween), line densification and ground-corner
  requests go the short way round +-180 deg (wrapLon in tileMath.js), so
  nearest ordering, cone thinning, mesh range and cast lines are right
  across the date line.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Said TurkseverandClaude Opus 5.5 committed 2026-10-06 15:16:57 +01:00
1 parent 09fc10756c
commit bd97d38a99
15 files changed
+1085 -164

No files matched your search

+11
View File
@@ -2,6 +2,7 @@ import * as Cesium from 'cesium';
import { positionMarkerGlyph } from './glyphs.js';
import { COLORS, POSITION_PICK_ID } from './policy.js';
import { refineHeights } from './groundCast.js';
import { createHorizonCull } from './view.js';
const SPRITE_ID = 'street-level:marker';
@@ -18,6 +19,13 @@ export function createMarker({ state, parts }) {
render?.governorRequestRender?.('street-level-marker');
}
// It skips the depth test: hide it while it is behind the globe.
const horizon = createHorizonCull({
getViewer: () => state.viewer,
items: () => (state.marker.billboard ? [state.marker.billboard] : []),
onChange: requestRender,
});
function ensure(viewer) {
if (state.marker.collection) return;
state.marker.collection = new Cesium.BillboardCollection({
@@ -61,6 +69,7 @@ export function createMarker({ state, parts }) {
if (!position) {
collection.removeAll();
state.marker.billboard = null;
horizon.stop();
requestRender();
return;
}
@@ -92,6 +101,7 @@ export function createMarker({ state, parts }) {
state.marker.billboard.position = cartesian;
}
state.marker.billboard.rotation = -Cesium.Math.toRadians(bearing || 0);
horizon.update([state.marker.billboard]);
requestRender();
}
@@ -101,6 +111,7 @@ export function createMarker({ state, parts }) {
}
function destroy(viewer) {
horizon.stop();
const collection = state.marker.collection;
if (!collection) return;
sprites?.unregisterSpriteCollection?.(SPRITE_ID, collection);