mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
refactor(street-level): drop FOLLOW and Google 3D ground casting
FOLLOW is gone: no setFollow, follow state, map-stack availability or handoff listener. Opening a photo still frames it with one flight through the application's deferred navigation (begin/reassert), so a slow photo never overrides newer navigation and tracking is released as before. Ground casting is gone: groundCast.js, meshSampler.js, the surface mode, the draped-to-cast swap and remesh. Coverage lines are always draped GroundPolylinePrimitives (ClassificationType.BOTH); cones and the marker clamp to the ground and keep the horizon cull. The terrain/meshFloor/ground services and meshFloor.visibleTilesetLoaded are no longer needed. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
7dec7883aa
commit
0f41fb04cd
37 files changed
+430
-3323
No files matched your search
@@ -1,9 +1,7 @@
|
||||
import * as Cesium from 'cesium';
|
||||
import { imageConeGlyph } from '../../glyphs.js';
|
||||
import { passesImageryFilter } from '../../filter.js';
|
||||
import { refineHeights } from '../../groundCast.js';
|
||||
import { createHorizonCull, metresBetween } from '../../view.js';
|
||||
import { meshCellKey } from '../../meshSampler.js';
|
||||
import {
|
||||
COLORS,
|
||||
IMAGE_CONE_MIN_SPACING_M,
|
||||
@@ -58,14 +56,13 @@ export function createSequences({ state, source, parts }) {
|
||||
state.context.notify();
|
||||
}
|
||||
|
||||
const nothingDrawn = () => ({ images: [], cones: [], cells: new Set() });
|
||||
/** Drawn cones, plus the mesh cells under them, so a sample moves only those. */
|
||||
let drawn = nothingDrawn();
|
||||
/** Drawn cone billboards. */
|
||||
let cones = [];
|
||||
|
||||
// The cones skip the depth test: hide the ones behind the globe.
|
||||
const horizon = createHorizonCull({
|
||||
getViewer: () => state.viewer,
|
||||
items: () => drawn.cones.map((cone) => cone.billboard),
|
||||
items: () => cones,
|
||||
onChange: requestRender,
|
||||
});
|
||||
|
||||
@@ -81,58 +78,15 @@ export function createSequences({ state, source, parts }) {
|
||||
function clearCones() {
|
||||
state.sequence.collection?.removeAll();
|
||||
state.sequence.images = [];
|
||||
drawn = nothingDrawn();
|
||||
cones = [];
|
||||
horizon.stop();
|
||||
}
|
||||
|
||||
/** Where a cone stands: at `height`, or clamped to the ground when null. */
|
||||
function placement(image, height) {
|
||||
return {
|
||||
position: Cesium.Cartesian3.fromDegrees(
|
||||
image.lon,
|
||||
image.lat,
|
||||
height ?? 0,
|
||||
),
|
||||
heightReference:
|
||||
height === null
|
||||
? Cesium.HeightReference.CLAMP_TO_GROUND
|
||||
: Cesium.HeightReference.NONE,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Terrain-mode cone heights on the street, not on roofs: bare earth refined
|
||||
* by mesh samples. Null when draped or while terrain heights are fetched.
|
||||
*/
|
||||
function coneHeights(images, { request = true } = {}) {
|
||||
const ground = state.context.groundCaster;
|
||||
if (state.context.getSurface?.() !== 'terrain' || !ground) return null;
|
||||
const dems = images.map((image) => ground.groundAt(image.lon, image.lat));
|
||||
if (dems.includes(null)) {
|
||||
ground
|
||||
.prepare(images.map((image) => [image.lon, image.lat]))
|
||||
.then((ready) => {
|
||||
if (ready && state.sequence.images === images) renderCones(images);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
const sampler = state.context.meshSampler;
|
||||
if (request)
|
||||
sampler?.request(images.map((image) => [image.lon, image.lat]));
|
||||
return refineHeights(
|
||||
images.map((image, i) => ({
|
||||
dem: dems[i],
|
||||
mesh: sampler?.meshAt(image.lon, image.lat),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
function renderCones(images) {
|
||||
const collection = state.sequence.collection;
|
||||
if (!collection) return;
|
||||
collection.removeAll();
|
||||
drawn = { ...nothingDrawn(), images };
|
||||
const heights = coneHeights(images);
|
||||
cones = [];
|
||||
// One colour per source; 360° images keep the ring shape.
|
||||
const cone = imageConeGlyph({ size: 32, color: COLORS.coverage });
|
||||
const ring = imageConeGlyph({
|
||||
@@ -142,12 +96,12 @@ export function createSequences({ state, source, parts }) {
|
||||
});
|
||||
// Resolved now, so a "since N days" window keeps up with the clock.
|
||||
const filter = state.context.getFilter();
|
||||
images.forEach((image, index) => {
|
||||
if (!passesImageryFilter(image, filter)) return;
|
||||
const height = heights?.[index] ?? null;
|
||||
for (const image of images) {
|
||||
if (!passesImageryFilter(image, filter)) continue;
|
||||
const billboard = collection.add({
|
||||
id: `${PICK_PREFIX.image}${image.id}`,
|
||||
...placement(image, height),
|
||||
position: Cesium.Cartesian3.fromDegrees(image.lon, image.lat),
|
||||
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
||||
image: image.isPano ? ring : cone,
|
||||
imageId: image.isPano ? 'mly-cone-pano' : 'mly-cone',
|
||||
width: IMAGE_CONE_SIZE_PX,
|
||||
@@ -158,41 +112,9 @@ export function createSequences({ state, source, parts }) {
|
||||
verticalOrigin: Cesium.VerticalOrigin.CENTER,
|
||||
scaleByDistance: new Cesium.NearFarScalar(200, 1.1, 6000, 0.35),
|
||||
});
|
||||
drawn.cones.push({ index, billboard, height });
|
||||
drawn.cells.add(meshCellKey(image.lon, image.lat));
|
||||
});
|
||||
horizon.update();
|
||||
requestRender();
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-place cones whose height new mesh samples changed. A sample can move
|
||||
* a neighbour too, so every height is recomputed but only moved cones touched.
|
||||
*/
|
||||
function onMeshSampled(batch) {
|
||||
if (state.context.getSurface?.() !== 'terrain' || !drawn.cones.length)
|
||||
return;
|
||||
if (!batch.some(([lon, lat]) => drawn.cells.has(meshCellKey(lon, lat))))
|
||||
return;
|
||||
const { images, cones } = drawn;
|
||||
// Already asked for when drawn; a missing terrain cell redraws them all.
|
||||
const heights = coneHeights(images, { request: false });
|
||||
if (!heights) return;
|
||||
const moved = [];
|
||||
for (const cone of cones) {
|
||||
const height = heights[cone.index] ?? null;
|
||||
if (height === cone.height) continue;
|
||||
cone.height = height;
|
||||
const { position, heightReference } = placement(
|
||||
images[cone.index],
|
||||
height,
|
||||
);
|
||||
cone.billboard.heightReference = heightReference;
|
||||
cone.billboard.position = position;
|
||||
moved.push(cone.billboard);
|
||||
cones.push(billboard);
|
||||
}
|
||||
if (!moved.length) return;
|
||||
horizon.update(moved);
|
||||
horizon.update();
|
||||
requestRender();
|
||||
}
|
||||
|
||||
@@ -288,9 +210,6 @@ export function createSequences({ state, source, parts }) {
|
||||
if (state.sequence.images.length) renderCones(state.sequence.images);
|
||||
}
|
||||
|
||||
// New mesh samples may move the cones onto (or off) the road surface.
|
||||
const stopMeshListener = state.context.meshSampler?.onSampled(onMeshSampled);
|
||||
|
||||
function setVisible(visible) {
|
||||
if (state.sequence.collection) state.sequence.collection.show = visible;
|
||||
requestRender();
|
||||
@@ -299,8 +218,7 @@ export function createSequences({ state, source, parts }) {
|
||||
function destroy(viewer) {
|
||||
state.sequence.abort?.abort();
|
||||
horizon.stop();
|
||||
drawn = nothingDrawn();
|
||||
stopMeshListener?.();
|
||||
cones = [];
|
||||
const collection = state.sequence.collection;
|
||||
if (collection) {
|
||||
sprites?.unregisterSpriteCollection?.(SPRITE_ID, collection);
|
||||
|
||||
Reference in new issue
Block a user