GODSIGH/js/styles.js

121 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 14, 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;
}