import * as Cesium from 'cesium'; import { positionMarkerGlyph, viewConeMarkerGlyph } from './glyphs.js'; import { COLORS, POSITION_PICK_ID } from './policy.js'; import { createHorizonCull } from './view.js'; const SPRITE_ID = 'street-level:marker'; function markerLook(style) { if (style?.kind === 'view-cone') { const color = style.color || COLORS.position; return { key: `view-cone:${color}`, image: viewConeMarkerGlyph({ color }), size: 48, }; } return { key: 'pin', image: positionMarkerGlyph({ color: COLORS.position }), size: 40, }; } /** On-globe marker for the open image, clamped to the ground. */ export function createMarker({ state }) { const { render, sprites } = state.services; function requestRender() { 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({ scene: viewer.scene, }); viewer.scene.primitives.add(state.marker.collection); sprites?.registerSpriteCollection?.(SPRITE_ID, state.marker.collection); } /** Move (or create) the marker; null removes it. `style`: the provider's `marker`. */ function set(position, bearing, style = null) { const collection = state.marker.collection; if (!collection) return; if (!position) { collection.removeAll(); state.marker.billboard = null; horizon.stop(); requestRender(); return; } const cartesian = Cesium.Cartesian3.fromDegrees(position.lon, position.lat); const look = markerLook(style); if (state.marker.billboard && state.marker.look !== look.key) { collection.remove?.(state.marker.billboard); state.marker.billboard = null; } if (!state.marker.billboard) { state.marker.look = look.key; state.marker.billboard = collection.add({ id: POSITION_PICK_ID, position: cartesian, image: look.image, imageId: `sl-position:${look.key}`, width: look.size, height: look.size, alignedAxis: Cesium.Cartesian3.UNIT_Z, heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, disableDepthTestDistance: Number.POSITIVE_INFINITY, verticalOrigin: Cesium.VerticalOrigin.CENTER, }); } else { state.marker.billboard.position = cartesian; } state.marker.billboard.rotation = -Cesium.Math.toRadians(bearing || 0); horizon.update([state.marker.billboard]); requestRender(); } function setVisible(visible) { if (state.marker.collection) state.marker.collection.show = visible; requestRender(); } function destroy(viewer) { horizon.stop(); const collection = state.marker.collection; if (!collection) return; sprites?.unregisterSpriteCollection?.(SPRITE_ID, collection); viewer?.scene?.primitives?.remove(collection); state.marker.collection = null; state.marker.billboard = null; } return { ensure, set, clear: () => set(null), setVisible, destroy, }; }