mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
wip(mobile): checkpoint the globe engine and vision mockup in progress
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VVYRAqiQj9Y1V9ZfG1RcDH
This commit is contained in:
36 files changed
+10296
-623
No files matched your search
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* Pins for discourse and capture (DESIGN.md §11.3–11.4): posts, sealed
|
||||
* witness captures, public camera eyes, live broadcasts and holograms. Eyes
|
||||
* and live pins draw their view frustum as a translucent wedge on the ground
|
||||
* along the camera heading. Pins are few (tens), so the wedge primitive is
|
||||
* simply rebuilt on change.
|
||||
*/
|
||||
import * as Cesium from 'cesium';
|
||||
import { glyph } from './glyphs.js';
|
||||
import { offsetLonLat } from './geo.js';
|
||||
|
||||
export const PIN_KINDS = Object.freeze([
|
||||
'post',
|
||||
'witness',
|
||||
'eye',
|
||||
'live',
|
||||
'hologram',
|
||||
]);
|
||||
|
||||
const PIN_COLORS = Object.freeze({
|
||||
post: '#E8EEF7',
|
||||
witness: '#FFC857',
|
||||
eye: '#FFD166',
|
||||
live: '#FF4D6D',
|
||||
hologram: '#C9A7FF',
|
||||
});
|
||||
|
||||
const PIN_TITLES = Object.freeze({
|
||||
post: 'Post',
|
||||
witness: 'Sealed witness capture',
|
||||
eye: 'Public camera',
|
||||
live: 'Live broadcast',
|
||||
hologram: 'Hologram',
|
||||
});
|
||||
|
||||
export function createPins({ scene, requestRender }) {
|
||||
const root = scene.primitives.add(new Cesium.PrimitiveCollection());
|
||||
const billboards = root.add(new Cesium.BillboardCollection());
|
||||
const edges = root.add(new Cesium.PolylineCollection());
|
||||
let wedges = null;
|
||||
const pins = new Map();
|
||||
let pulse = 0;
|
||||
|
||||
function rebuildWedges() {
|
||||
if (wedges) root.remove(wedges);
|
||||
wedges = null;
|
||||
edges.removeAll();
|
||||
const instances = [];
|
||||
for (const p of pins.values()) {
|
||||
if (p.kind !== 'eye' && p.kind !== 'live') continue;
|
||||
if (!Number.isFinite(p.headingDeg)) continue;
|
||||
const half = (p.fovDeg ?? 60) / 2;
|
||||
const ring = [[p.lon, p.lat]];
|
||||
for (let a = -half; a <= half + 0.01; a += Math.max(2, half / 6))
|
||||
ring.push(offsetLonLat(p.lon, p.lat, p.headingDeg + a, p.rangeM));
|
||||
const css = p.color || PIN_COLORS[p.kind];
|
||||
instances.push(
|
||||
new Cesium.GeometryInstance({
|
||||
id: { layer: 'pins', key: p.id },
|
||||
geometry: new Cesium.PolygonGeometry({
|
||||
polygonHierarchy: new Cesium.PolygonHierarchy(
|
||||
Cesium.Cartesian3.fromDegreesArray(ring.flat()),
|
||||
),
|
||||
height: (p.altM || 0) + 3,
|
||||
vertexFormat: Cesium.PerInstanceColorAppearance.FLAT_VERTEX_FORMAT,
|
||||
}),
|
||||
attributes: {
|
||||
color: Cesium.ColorGeometryInstanceAttribute.fromColor(
|
||||
Cesium.Color.fromCssColorString(css).withAlpha(0.28),
|
||||
),
|
||||
},
|
||||
}),
|
||||
);
|
||||
edges.add({
|
||||
positions: Cesium.Cartesian3.fromDegreesArrayHeights(
|
||||
[...ring, ring[0]].flatMap(([lo, la]) => [lo, la, (p.altM || 0) + 4]),
|
||||
),
|
||||
width: 1.5,
|
||||
material: Cesium.Material.fromType('Color', {
|
||||
color: Cesium.Color.fromCssColorString(css).withAlpha(0.85),
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (instances.length)
|
||||
wedges = root.add(
|
||||
new Cesium.Primitive({
|
||||
geometryInstances: instances,
|
||||
appearance: new Cesium.PerInstanceColorAppearance({
|
||||
flat: true,
|
||||
translucent: true,
|
||||
}),
|
||||
asynchronous: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function rebuildBillboards() {
|
||||
billboards.removeAll();
|
||||
for (const p of pins.values()) {
|
||||
const g = glyph(p.kind);
|
||||
p._bb = billboards.add({
|
||||
position: Cesium.Cartesian3.fromDegrees(
|
||||
p.lon,
|
||||
p.lat,
|
||||
(p.altM || 0) + 8,
|
||||
),
|
||||
image: g.image,
|
||||
imageId: g.imageId,
|
||||
color: Cesium.Color.fromCssColorString(p.color || PIN_COLORS[p.kind]),
|
||||
scale: 0.62,
|
||||
scaleByDistance: new Cesium.NearFarScalar(2e3, 1.15, 8e6, 0.55),
|
||||
verticalOrigin: Cesium.VerticalOrigin.CENTER,
|
||||
disableDepthTestDistance: 2e5,
|
||||
id: { layer: 'pins', key: p.id },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function changed() {
|
||||
rebuildBillboards();
|
||||
rebuildWedges();
|
||||
requestRender();
|
||||
}
|
||||
|
||||
return {
|
||||
add(pin) {
|
||||
if (!pin || pin.id == null) throw new TypeError('addPin needs an id');
|
||||
if (!Number.isFinite(pin.lon) || !Number.isFinite(pin.lat))
|
||||
throw new TypeError('addPin needs lon and lat');
|
||||
const kind = PIN_KINDS.includes(pin.kind) ? pin.kind : 'post';
|
||||
const p = {
|
||||
id: String(pin.id),
|
||||
lon: pin.lon,
|
||||
lat: pin.lat,
|
||||
kind,
|
||||
headingDeg: Number.isFinite(pin.headingDeg) ? pin.headingDeg : null,
|
||||
fovDeg: Number.isFinite(pin.fovDeg) ? pin.fovDeg : 60,
|
||||
rangeM: Number.isFinite(pin.rangeM) ? pin.rangeM : 350,
|
||||
color: pin.color || null,
|
||||
label: pin.label || '',
|
||||
altM: Number.isFinite(pin.altM) ? pin.altM : 0,
|
||||
};
|
||||
pins.set(p.id, p);
|
||||
changed();
|
||||
return p.id;
|
||||
},
|
||||
remove(id) {
|
||||
const ok = pins.delete(String(id));
|
||||
if (ok) changed();
|
||||
return ok;
|
||||
},
|
||||
list() {
|
||||
return [...pins.values()].map(({ _bb, ...p }) => ({ ...p }));
|
||||
},
|
||||
locate(id) {
|
||||
const p = pins.get(String(id));
|
||||
return p ? [p.lon, p.lat, p.altM || 0] : null;
|
||||
},
|
||||
pick(id) {
|
||||
const p = pins.get(String(id));
|
||||
if (!p) return null;
|
||||
return {
|
||||
kind: 'pin',
|
||||
pinKind: p.kind,
|
||||
id: p.id,
|
||||
title: p.label || PIN_TITLES[p.kind],
|
||||
subtitle: PIN_TITLES[p.kind],
|
||||
lon: p.lon,
|
||||
lat: p.lat,
|
||||
meta: {
|
||||
layer: 'pins',
|
||||
headingDeg: p.headingDeg,
|
||||
fovDeg: p.fovDeg,
|
||||
label: p.label,
|
||||
},
|
||||
};
|
||||
},
|
||||
/** Live pins breathe once a second; returns true when anything changed. */
|
||||
tick() {
|
||||
let any = false;
|
||||
pulse = (pulse + 1) % 2;
|
||||
for (const p of pins.values())
|
||||
if (p.kind === 'live' && p._bb) {
|
||||
p._bb.scale = pulse ? 0.74 : 0.6;
|
||||
any = true;
|
||||
}
|
||||
return any;
|
||||
},
|
||||
get billboards() {
|
||||
return billboards;
|
||||
},
|
||||
destroy() {
|
||||
scene.primitives.remove(root);
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user