// Sensor styles — full-screen GLSL post-process skins over the whole globe, // ported (compacted) from the open-sourced God's Eye View style system. // NVG = night vision · FLIR = Ironbow thermal ramp · CRT = phosphor console. // Keyless and client-side: Cesium PostProcessStage, no imagery change. const STYLES = { none: null, nvg: ` uniform sampler2D colorTexture; uniform float time; in vec2 v_textureCoordinates; float hash(vec2 p){ vec3 p3 = fract(vec3(p.xyx) * 0.1031); p3 += dot(p3, p3.yzx + 33.33); return fract((p3.x + p3.y) * p3.z); } void main() { vec2 uv = v_textureCoordinates; vec3 c = texture(colorTexture, uv).rgb; float lum = dot(c, vec3(0.299, 0.587, 0.114)); lum = pow(lum, 0.62); // intensifier gain lifts shadows lum += (hash(uv * 900.0 + time * 60.0) - 0.5) * 0.13; // photon noise lum *= 0.94 + 0.06 * sin(uv.y * 800.0); // faint scanlines float d = distance(uv, vec2(0.5)); lum *= smoothstep(0.78, 0.42, d); // tube vignette out_FragColor = vec4(vec3(0.06, 1.0, 0.28) * lum, 1.0); }`, flir: ` uniform sampler2D colorTexture; uniform float time; in vec2 v_textureCoordinates; // Ironbow "Predator" ramp: black→purple→magenta→red→orange→yellow→white. vec3 ironbow(float t) { t = clamp(t, 0.0, 1.0); float s = t * 6.0; if (s < 1.0) return mix(vec3(0.0), vec3(0.13, 0.0, 0.30), s); if (s < 2.0) return mix(vec3(0.13, 0.0, 0.30), vec3(0.49, 0.0, 0.45), s - 1.0); if (s < 3.0) return mix(vec3(0.49, 0.0, 0.45), vec3(0.86, 0.10, 0.18), s - 2.0); if (s < 4.0) return mix(vec3(0.86, 0.10, 0.18), vec3(1.0, 0.55, 0.0), s - 3.0); if (s < 5.0) return mix(vec3(1.0, 0.55, 0.0), vec3(1.0, 0.91, 0.32), s - 4.0); return mix(vec3(1.0, 0.91, 0.32), vec3(1.0), s - 5.0); } float hash(vec2 p){ vec3 p3 = fract(vec3(p.xyx) * 0.1031); p3 += dot(p3, p3.yzx + 33.33); return fract((p3.x + p3.y) * p3.z); } void main() { vec2 uv = v_textureCoordinates; // Sensor pixelation grid. vec2 grid = floor(uv * vec2(640.0, 360.0)) / vec2(640.0, 360.0); vec3 c = texture(colorTexture, grid).rgb; float t = dot(c, vec3(0.299, 0.587, 0.114)); t = clamp((t - 0.08) * 1.5, 0.0, 1.0); // stretch the "temperature" range t += (hash(grid * 700.0 + time * 30.0) - 0.5) * 0.05; // thermal noise out_FragColor = vec4(ironbow(t), 1.0); }`, crt: ` uniform sampler2D colorTexture; uniform float time; in vec2 v_textureCoordinates; void main() { vec2 uv = v_textureCoordinates - 0.5; uv *= 1.0 + 0.12 * dot(uv, uv); // barrel distortion uv += 0.5; if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) { out_FragColor = vec4(0.0, 0.0, 0.0, 1.0); return; } // Chromatic aberration at the edges. float ab = 0.0015 * distance(uv, vec2(0.5)) * 4.0; vec3 c = vec3( texture(colorTexture, uv + vec2(ab, 0.0)).r, texture(colorTexture, uv).g, texture(colorTexture, uv - vec2(ab, 0.0)).b); c *= 0.82 + 0.18 * sin(uv.y * 1200.0); // scanlines c *= 0.97 + 0.03 * sin(time * 90.0); // flicker c = mix(c, vec3(0.1, 0.85, 0.45) * dot(c, vec3(0.333)), 0.35); // phosphor cast float d = distance(uv, vec2(0.5)); c *= smoothstep(0.85, 0.45, d); // vignette out_FragColor = vec4(c, 1.0); }`, }; let viewer = null; let activeId = 'none'; let activeStage = null; export function initStyles(v) { viewer = v; viewer.scene.preUpdate.addEventListener(() => { if (activeStage) activeStage.uniforms.time = performance.now() / 1000; }); for (const id of Object.keys(STYLES)) { const btn = document.getElementById(`style-${id}`); if (btn) btn.addEventListener('click', () => setStyle(id)); } // Keyboard 1–4, matching the upstream project's style hotkeys. const keys = Object.keys(STYLES); document.addEventListener('keydown', (e) => { if (e.target && /INPUT|TEXTAREA/.test(e.target.tagName)) return; const i = Number(e.key) - 1; if (i >= 0 && i < keys.length) setStyle(keys[i]); }); } export function setStyle(id) { if (!viewer || !(id in STYLES) || id === activeId) return; if (activeStage) { viewer.scene.postProcessStages.remove(activeStage); // remove() also destroys activeStage = null; } if (STYLES[id]) { activeStage = viewer.scene.postProcessStages.add(new window.Cesium.PostProcessStage({ fragmentShader: STYLES[id], uniforms: { time: 0.0 }, })); } activeId = id; for (const k of Object.keys(STYLES)) { document.getElementById(`style-${k}`)?.classList.toggle('active', k === id); } } export function getStyle() { return activeId; }