progress logs behind CONFIG.debug, CONFIG.antialias + high-performance context, cheaper cloud sampling (skip far detail octave and redundant self-shading probe), brighter night contrails via CONFIG.flights.contrailNight, window.__flightDbg counters, real sun/moon by default

This commit is contained in:
Richard Zink committed 2026-10-11 18:42:48 +02:00
1 parent 113e4c522d
commit 6d21b37e8f
2 files changed
+94 -33

No files matched your search

+16 -5
View File
@@ -17,6 +17,7 @@
* tile/DEM/Overpass origin, figure-eight centre * tile/DEM/Overpass origin, figure-eight centre
* renderScale internal 3D resolution (0.2..1) with blocky upscale * renderScale internal 3D resolution (0.2..1) with blocky upscale
* maxFps scene frame cap (0 = free running) * maxFps scene frame cap (0 = free running)
* antialias MSAA on the WebGL framebuffer (true/false)
* weatherSim ignore Open-Meteo and cycle the weather themes * weatherSim ignore Open-Meteo and cycle the weather themes
* tiles basemap/elevation cache behaviour + debug overlays * tiles basemap/elevation cache behaviour + debug overlays
* ground basemap style * ground basemap style
@@ -51,7 +52,7 @@ window.CONFIG = {
theme: 'ash', // 'ash' | 'nord' | 'gruvbox' (see window.THEMES) theme: 'ash', // 'ash' | 'nord' | 'gruvbox' (see window.THEMES)
language: 'en', // 'en', 'de' - keys of the i18n.js dictionaries language: 'en', // 'en', 'de' - keys of the i18n.js dictionaries
timeFormat: 'DD. MMMM, YYYY ‒ HH:mm:SS', timeFormat: 'DD. MMMM, YYYY ‒ HH:mm:SS',
debug: 0, // 1 = debug panel with the theme palette debug: 0, // 1 = debug panel + progress logs in the console
credits: true, // false = hide the attribution line credits: true, // false = hide the attribution line
units: { temperature: 'C' }, // 'F' for Fahrenheit units: { temperature: 'C' }, // 'F' for Fahrenheit
@@ -67,6 +68,10 @@ window.CONFIG = {
// Scene frame cap. All motion is time-based, so this only changes // Scene frame cap. All motion is time-based, so this only changes
// smoothness, never speed. 0 = free running (vsync). // smoothness, never speed. 0 = free running (vsync).
maxFps: 30, maxFps: 30,
// MSAA on the WebGL framebuffer. Costs fill rate, and with renderScale
// below 1 the pixel blocks dominate anyway - set false if the scene is
// GPU bound. Read once at context creation, so a reload is needed.
antialias: true,
// ------------------------------------------------------------------ // ------------------------------------------------------------------
// Location // Location
@@ -169,11 +174,16 @@ window.CONFIG = {
wStrip: 1, wStrip: 1,
// Contrails: contrailChance of the aircraft leaves a trail once // Contrails: contrailChance of the aircraft leaves a trail once
// above contrailStartKm, visible for contrailLifeS seconds. // above contrailStartKm, visible for contrailLifeS seconds.
contrailChance: 0.85, contrailChance: 1.00,
contrailStartKm: 6, contrailStartKm: 6,
contrailLifeS: 300, contrailLifeS: 300,
contrailWidthM: 620, // full trail width contrailWidthM: 620, // full trail width
contrailFadeKm: 100, // fully faded at this distance (ramp starts at 35 %) contrailFadeKm: 100, // fully faded at this distance (ramp starts at 35 %)
// Night contrast: a physically scaled contrail is a faint grey thread
// that disappears at 4x pixel scale, so this is the night share of its
// opacity (0.34 was the old value) and it also drives the gain on the
// moon/star light. 0 = night contrails off, 1 = strongest.
contrailNight: 0.85,
// Night strobes. Point size shrinks with distance but never below // Night strobes. Point size shrinks with distance but never below
// lightPx and never above lightMaxPx for a jet directly overhead. // lightPx and never above lightMaxPx for a jet directly overhead.
// The white strobe is a double flash: two flashes blinkFlashS long, // The white strobe is a double flash: two flashes blinkFlashS long,
@@ -301,11 +311,12 @@ window.CONFIG = {
// east, culminates south, sets west) and the clock plus the sun/moon // east, culminates south, sets west) and the clock plus the sun/moon
// tables follow the simulated date. false uses the real SunCalc positions. // tables follow the simulated date. false uses the real SunCalc positions.
day: { day: {
simulation: true, simulation: false, // false = the real sun and moon (default)
cycleSeconds: 60, // 60 s = one simulated day per minute cycleSeconds: 60, // only used when simulation is true:
// 60 s = one simulated day per minute
fixedTime: null // 'HH:MM' or 0-24 freezes the sim clock fixedTime: null // 'HH:MM' or 0-24 freezes the sim clock
// (clouds, traffic and flights keep moving) // (clouds, traffic and flights keep moving)
// day: { simulation: false } // real sun and moon // day: { simulation: true, cycleSeconds: 60 } // accelerated day
}, },
// ------------------------------------------------------------------ // ------------------------------------------------------------------
+78 -28
View File
@@ -10,6 +10,13 @@
(function() { (function() {
// Progress and build diagnostics are only interesting while the debug panel
// is on; a wall display must not fill the console with them.
var DEBUG_LOG = !!(window.CONFIG && window.CONFIG.debug);
function dbgLog() {
if (DEBUG_LOG) console.log.apply(console, arguments);
}
function makeShader(gl, type, src) { function makeShader(gl, type, src) {
var s = gl.createShader(type); var s = gl.createShader(type);
gl.shaderSource(s, src); gl.shaderSource(s, src);
@@ -147,8 +154,13 @@ var FRAG_SRC = [
// the fine structure that cannot be resolved disappears. // the fine structure that cannot be resolved disappears.
'float cloudDensity(vec3 p, float baseThresh, float soft) {', 'float cloudDensity(vec3 p, float baseThresh, float soft) {',
'float n = fbm3(p * 0.35 + vec3(0.0, 0.0, uTime * 0.06));', 'float n = fbm3(p * 0.35 + vec3(0.0, 0.0, uTime * 0.06));',
// The detail octave weighs at most 0.20 and fades to zero with
// distance; past soft = 0.95 it contributes under 1 %, so the whole
// 4-octave lookup is skipped for the (numerous) far samples.
'if (soft < 0.95) {',
'float d = fbm3(p * 1.4 + vec3(uTime * 0.04, 7.3, uTime * 0.03));', 'float d = fbm3(p * 1.4 + vec3(uTime * 0.04, 7.3, uTime * 0.03));',
'n = n + d * mix(0.20, 0.0, soft);', 'n = n + d * mix(0.20, 0.0, soft);',
'}',
// Cloud layer between uCloudBot and uCloudTop, densest in the // Cloud layer between uCloudBot and uCloudTop, densest in the
// middle (bell curve). // middle (bell curve).
'float h = clamp((p.y - uCloudBot) / (uCloudTop - uCloudBot), 0.0, 1.0);', 'float h = clamp((p.y - uCloudBot) / (uCloudTop - uCloudBot), 0.0, 1.0);',
@@ -343,7 +355,10 @@ var FRAG_SRC = [
'vec3 albC = uCloudAlbedo * mix(0.78, 1.0, lit);', 'vec3 albC = uCloudAlbedo * mix(0.78, 1.0, lit);',
// Self-shading: probe the density towards the sun, so // Self-shading: probe the density towards the sun, so
// thick cloud interior stays dark and thin edges light up. // thick cloud interior stays dark and thin edges light up.
'float thick = cloudDensity(p + uSunDir * 2.2, baseThresh, cSoft);', // d > 0.85 already clamps selfLit to its floor, so the
// second density lookup (a whole fbm) is skipped there.
'float thick = (d > 0.85) ? 1.0 :',
'cloudDensity(p + uSunDir * 2.2, baseThresh, cSoft);',
'float selfLit = clamp(1.0 - thick * 1.3, 0.12, 1.0);', 'float selfLit = clamp(1.0 - thick * 1.3, 0.12, 1.0);',
// Forward scattering - clouds brighten towards the sun // Forward scattering - clouds brighten towards the sun
// (silver lining) instead of a flat day/night ramp. // (silver lining) instead of a flat day/night ramp.
@@ -484,8 +499,13 @@ var CLOUD_SAMPLER_GLSL = [
'}', '}',
'float cloudDensity(vec3 p, float baseThresh, float soft) {', 'float cloudDensity(vec3 p, float baseThresh, float soft) {',
'float n = fbm3(p * 0.35 + vec3(0.0, 0.0, uTime * 0.06));', 'float n = fbm3(p * 0.35 + vec3(0.0, 0.0, uTime * 0.06));',
// The detail octave weighs at most 0.20 and fades to zero with
// distance; past soft = 0.95 it contributes under 1 %, so the whole
// 4-octave lookup is skipped for the (numerous) far samples.
'if (soft < 0.95) {',
'float d = fbm3(p * 1.4 + vec3(uTime * 0.04, 7.3, uTime * 0.03));', 'float d = fbm3(p * 1.4 + vec3(uTime * 0.04, 7.3, uTime * 0.03));',
'n = n + d * mix(0.20, 0.0, soft);', 'n = n + d * mix(0.20, 0.0, soft);',
'}',
'float h = clamp((p.y - uCloudBot) / (uCloudTop - uCloudBot), 0.0, 1.0);', 'float h = clamp((p.y - uCloudBot) / (uCloudTop - uCloudBot), 0.0, 1.0);',
'float profile = 4.0 * h * (1.0 - h);', 'float profile = 4.0 * h * (1.0 - h);',
'profile = max(profile, 0.0);', 'profile = max(profile, 0.0);',
@@ -627,8 +647,12 @@ var TERRAIN_FRAG_SRC = [
].join('\n'); ].join('\n');
function init(canvas) { function init(canvas) {
var gl = canvas.getContext('webgl2', { antialias: false, alpha: false }) // CONFIG.antialias (default on) asks for MSAA on the default framebuffer.
|| canvas.getContext('webgl', { antialias: false, alpha: false }); // It costs fill rate, and at renderScale < 1 the pixel blocks dominate
// anyway - set it to false if the scene is GPU bound.
var AA_ON = !(window.CONFIG && window.CONFIG.antialias === false);
var glOpts = { antialias: AA_ON, alpha: false, powerPreference: 'high-performance' };
var gl = canvas.getContext('webgl2', glOpts) || canvas.getContext('webgl', glOpts);
if (!gl) { if (!gl) {
console.error('clouds3d: WebGL not available'); console.error('clouds3d: WebGL not available');
return null; return null;
@@ -1195,7 +1219,7 @@ function tileProbeProxy() {
var type = (r.headers.get('Content-Type') || '').toLowerCase(); var type = (r.headers.get('Content-Type') || '').toLowerCase();
if (r.ok && type.indexOf('image/') === 0) { if (r.ok && type.indexOf('image/') === 0) {
apply(); apply();
console.log('[clouds3d] tiles: using dev proxy ' + path); dbgLog('[clouds3d] tiles: using dev proxy ' + path);
} }
}, function () {}); }, function () {});
} }
@@ -2151,7 +2175,7 @@ function opProbeProxy() {
}).then(function (r) { }).then(function (r) {
if (r.status === 200) { if (r.status === 200) {
CAR_ENDPOINTS = OP_PUBLIC.concat(OP_PROXY); CAR_ENDPOINTS = OP_PUBLIC.concat(OP_PROXY);
console.log('[clouds3d] overpass: dev proxy available'); dbgLog('[clouds3d] overpass: dev proxy available');
} }
}, function () {}); }, function () {});
} }
@@ -2433,11 +2457,11 @@ function fetchMotorways() {
var fresh = 0; // cells that needed a real fetch this cycle var fresh = 0; // cells that needed a real fetch this cycle
var fpParts = []; // "id:savedAt" of every raw cell -> fp() var fpParts = []; // "id:savedAt" of every raw cell -> fp()
var doneCells = 0, totalCells = CAR_QUERIES.length; var doneCells = 0, totalCells = CAR_QUERIES.length;
console.log('[clouds3d] road cells: starting 0/' + totalCells); dbgLog('[clouds3d] road cells: starting 0/' + totalCells);
function progCell(cached) { function progCell(cached) {
doneCells++; doneCells++;
if (doneCells % 10 === 0 || doneCells === totalCells) { if (doneCells % 10 === 0 || doneCells === totalCells) {
console.log('[clouds3d] road cells: ' + doneCells + '/' + totalCells + dbgLog('[clouds3d] road cells: ' + doneCells + '/' + totalCells +
(cached ? ' (cached)' : ' (fresh ' + fresh + ')')); (cached ? ' (cached)' : ' (fresh ' + fresh + ')'));
} }
} }
@@ -2535,14 +2559,14 @@ function fetchMotorways() {
// be replayed forever and never grow. // be replayed forever and never grow.
if (newest > rec.savedAt) return false; if (newest > rec.savedAt) return false;
if (missing > Math.max(1, total * 0.1)) { if (missing > Math.max(1, total * 0.1)) {
console.log('[clouds3d] car layer: ' + missing + '/' + total + dbgLog('[clouds3d] car layer: ' + missing + '/' + total +
' cells missing - rebuilding instead of replaying the cache'); ' cells missing - rebuilding instead of replaying the cache');
return false; return false;
} }
carGeo = { motorway: { cnts: Int32Array.from(rec.mCnts), pts: b64ToF32(rec.mData) }, carGeo = { motorway: { cnts: Int32Array.from(rec.mCnts), pts: b64ToF32(rec.mData) },
secondary: { cnts: Int32Array.from(rec.bCnts), pts: b64ToF32(rec.bData) } }; secondary: { cnts: Int32Array.from(rec.bCnts), pts: b64ToF32(rec.bData) } };
kickCarBuild(false); kickCarBuild(false);
console.log('[clouds3d] car layer: ' + carGeo.motorway.cnts.length + dbgLog('[clouds3d] car layer: ' + carGeo.motorway.cnts.length +
' motorway + ' + carGeo.secondary.cnts.length + ' chains (from cache' + ' motorway + ' + carGeo.secondary.cnts.length + ' chains (from cache' +
(missing ? ', ' + missing + ' cells unavailable' : '') + ')'); (missing ? ', ' + missing + ' cells unavailable' : '') + ')');
return true; return true;
@@ -2558,7 +2582,7 @@ function fetchMotorways() {
carPersistFp = geoFp(fpParts); carPersistFp = geoFp(fpParts);
carCycleFp = carPersistFp; carCycleFp = carPersistFp;
kickCarBuild(true); kickCarBuild(true);
console.log('[clouds3d] car layer: ' + carRaw.motorway.length + dbgLog('[clouds3d] car layer: ' + carRaw.motorway.length +
' motorway + ' + carRaw.secondary.length + ' motorway + ' + carRaw.secondary.length +
' secondary polylines queued (' + fresh + ' cells re-fetched)'); ' secondary polylines queued (' + fresh + ' cells re-fetched)');
}); });
@@ -2594,7 +2618,7 @@ function fetchSettlements() {
function progCell(cached) { function progCell(cached) {
doneCells++; doneCells++;
if (doneCells % 5 === 0 || doneCells === totalCells) { if (doneCells % 5 === 0 || doneCells === totalCells) {
console.log('[clouds3d] place cells: ' + doneCells + '/' + totalCells + dbgLog('[clouds3d] place cells: ' + doneCells + '/' + totalCells +
(cached ? ' (cached)' : ' (fresh ' + fresh + ')')); (cached ? ' (cached)' : ' (fresh ' + fresh + ')'));
} }
} }
@@ -2623,7 +2647,7 @@ function fetchSettlements() {
} }
} }
totalCells = cells.length; totalCells = cells.length;
console.log('[clouds3d] place cells: starting 0/' + totalCells); dbgLog('[clouds3d] place cells: starting 0/' + totalCells);
var list = []; var list = [];
var seenB = {}; // bucket -> index in list: dedupe node/way/rel of one place var seenB = {}; // bucket -> index in list: dedupe node/way/rel of one place
function parse(data) { function parse(data) {
@@ -2688,7 +2712,7 @@ function fetchSettlements() {
var before = list.length; var before = list.length;
parse(obj); parse(obj);
if (list.length > before) if (list.length > before)
console.log('[clouds3d] place cell ' + entry.id + ': +' + dbgLog('[clouds3d] place cell ' + entry.id + ': +' +
(list.length - before) + ' places (total ' + list.length + ')'); (list.length - before) + ' places (total ' + list.length + ')');
if (!entry._cached) fresh++; if (!entry._cached) fresh++;
// dot geometry is generated in the geo worker; the debounce // dot geometry is generated in the geo worker; the debounce
@@ -2715,7 +2739,7 @@ function fetchSettlements() {
cityGeo = b64ToF32(rec.data); cityGeo = b64ToF32(rec.data);
kickCityBuild(); kickCityBuild();
if (carGeo) kickCarBuild(false); if (carGeo) kickCarBuild(false);
console.log('[clouds3d] settlements: ' + rec.cnt + ' places (from cache)'); dbgLog('[clouds3d] settlements: ' + rec.cnt + ' places (from cache)');
return true; return true;
}); });
}); });
@@ -2731,7 +2755,7 @@ function fetchSettlements() {
tileDbPut(db, { id: 'geo/placecache_v1', fp: geoFp(fpParts), tileDbPut(db, { id: 'geo/placecache_v1', fp: geoFp(fpParts),
cnt: list.length, data: f32ToB64(packPlaces(list)), savedAt: Date.now() }); cnt: list.length, data: f32ToB64(packPlaces(list)), savedAt: Date.now() });
}); });
console.log('[clouds3d] settlements: ' + list.length + ' places queued for build' + dbgLog('[clouds3d] settlements: ' + list.length + ' places queued for build' +
(terrainSample ? '' : ' (terrain pending)')); (terrainSample ? '' : ' (terrain pending)'));
}); });
}); });
@@ -3511,7 +3535,7 @@ function geoWorkerGet(lane) {
w.onmessage = function (e) { w.onmessage = function (e) {
var res = e.data || {}; var res = e.data || {};
if (res.progress) { if (res.progress) {
console.log('[clouds3d] ' + lane + ' worker ' + res.kind + ': ' + dbgLog('[clouds3d] ' + lane + ' worker ' + res.kind + ': ' +
res.done + '/' + res.total); res.done + '/' + res.total);
return; return;
} }
@@ -3698,13 +3722,13 @@ function runCarBuild(done) {
if (res.joins) carJoinCount = res.joins; if (res.joins) carJoinCount = res.joins;
if (res.maxChainKm) carMaxChainKm = res.maxChainKm; if (res.maxChainKm) carMaxChainKm = res.maxChainKm;
applyCarBuild(gl, res); applyCarBuild(gl, res);
console.log('[clouds3d] car build: ' + carCountM + ' + ' + carCountB + dbgLog('[clouds3d] car build: ' + carCountM + ' + ' + carCountB +
' ribbon verts, ' + carGeo.motorway.cnts.length + '+' + ' ribbon verts, ' + carGeo.motorway.cnts.length + '+' +
carGeo.secondary.cnts.length + ' chains, ' + carGeo.secondary.cnts.length + ' chains, ' +
carPtsCountM + ' motorway + ' + carPtsCountB + ' side lights, longest ~' + carPtsCountM + ' motorway + ' + carPtsCountB + ' side lights, longest ~' +
Math.round(carMaxChainKm) + ' km, ' + Math.round(carMaxChainKm) + ' km, ' +
Math.round(performance.now() - t0) + ' ms off-thread'); Math.round(performance.now() - t0) + ' ms off-thread');
console.log('[clouds3d] car slots: motorway ' + (carPtsCountM / 2) + dbgLog('[clouds3d] car slots: motorway ' + (carPtsCountM / 2) +
' slots on ' + Math.round(res.mLen * KM_PER_TILE) + ' km (want ' + ' slots on ' + Math.round(res.mLen * KM_PER_TILE) + ' km (want ' +
Math.round(res.mLen / CAR_M_DASH) + ', dash ' + Math.round(res.mLen / CAR_M_DASH) + ', dash ' +
Math.round(CAR_M_DASH * KM_PER_TILE * 1000) + ' m) | side ' + (carPtsCountB / 2) + Math.round(CAR_M_DASH * KM_PER_TILE * 1000) + ' m) | side ' + (carPtsCountB / 2) +
@@ -3815,20 +3839,20 @@ function runCityBuild(done) {
var gl = elevGl || (clouds3dState && clouds3dState.gl); var gl = elevGl || (clouds3dState && clouds3dState.gl);
if (!gl || !terrainSample || !cityGeo) { if (!gl || !terrainSample || !cityGeo) {
// silent skip made the layer look dead: say what is still missing // silent skip made the layer look dead: say what is still missing
console.log('[clouds3d] city build skipped - ' + dbgLog('[clouds3d] city build skipped - ' +
(!gl ? 'no gl context' : '') + (!gl ? 'no gl context' : '') +
(!gl || terrainSample ? '' : ', no elevation sample yet') + (!gl || terrainSample ? '' : ', no elevation sample yet') +
(cityGeo ? '' : ', no place nodes fetched yet')); (cityGeo ? '' : ', no place nodes fetched yet'));
done(); return; done(); return;
} }
console.log('[clouds3d] city build: ' + (cityGeo.length / 4) + ' places queued'); dbgLog('[clouds3d] city build: ' + (cityGeo.length / 4) + ' places queued');
var t0 = performance.now(); var t0 = performance.now();
geoRun({ op: 'city', places: cityGeo, terrain: terrainMsg(), cfg: cityCfg() }, geoRun({ op: 'city', places: cityGeo, terrain: terrainMsg(), cfg: cityCfg() },
function (res) { function (res) {
if (!res || res.error) { done(); return; } if (!res || res.error) { done(); return; }
cityBuildMs = Math.round(performance.now() - t0); cityBuildMs = Math.round(performance.now() - t0);
applyCityBuild(gl, res); applyCityBuild(gl, res);
console.log('[clouds3d] settlements: ' + cityCount + ' lights from ' + dbgLog('[clouds3d] settlements: ' + cityCount + ' lights from ' +
(cityGeo.length / 4) + ' places, ' + cityBuildMs + ' ms off-thread'); (cityGeo.length / 4) + ' places, ' + cityBuildMs + ' ms off-thread');
done(); done();
}); });
@@ -4021,6 +4045,11 @@ var CT_T = Math.max(10, fltNum(FLT_CFG.contrailLifeS, 300));
var CT_HALF_W = Math.max(0.002, fltNum(FLT_CFG.contrailWidthM, 310) / 2000 / KM_PER_TILE); var CT_HALF_W = Math.max(0.002, fltNum(FLT_CFG.contrailWidthM, 310) / 2000 / KM_PER_TILE);
// world units: trails are gone by contrailFadeKm, ramping from 35% of it // world units: trails are gone by contrailFadeKm, ramping from 35% of it
var CT_FADE_END = Math.max(1, fltNum(FLT_CFG.contrailFadeKm, 100) / KM_PER_TILE); var CT_FADE_END = Math.max(1, fltNum(FLT_CFG.contrailFadeKm, 100) / KM_PER_TILE);
// Night readability. A physically scaled contrail on a starlit night is a
// faint grey thread that the pixel grid removes entirely, so the night share
// of its opacity and a gain on the moon/star light are configurable.
var CT_NIGHT = Math.max(0, Math.min(1, fltNum(FLT_CFG.contrailNight, 0.85)));
var CT_NIGHT_BOOST = 1.0 + CT_NIGHT * 2.2;
var CT_FADE_START = CT_FADE_END * 0.35; var CT_FADE_START = CT_FADE_END * 0.35;
var CT_MAX = FLT_MAX; var CT_MAX = FLT_MAX;
var CT_SEG = 8; // curve resolution per trail var CT_SEG = 8; // curve resolution per trail
@@ -4218,7 +4247,7 @@ function fetchAirports() {
}); });
if (!airports.length) return; if (!airports.length) return;
buildFlights(airports); buildFlights(airports);
console.log('[clouds3d] flights: ' + airports.length + ' airfields -> ' + dbgLog('[clouds3d] flights: ' + airports.length + ' airfields -> ' +
fltData.length + ' aircraft (destinations rolled per take-off)'); fltData.length + ' aircraft (destinations rolled per take-off)');
}); });
} }
@@ -4388,6 +4417,7 @@ var CT_FRAG_SRC = [
'uniform float uMoonInt;', 'uniform float uMoonInt;',
'uniform float uSkyAmb;', 'uniform float uSkyAmb;',
'uniform float uStarAmb;', 'uniform float uStarAmb;',
'uniform float uNightBoost;', // extra gain on the moon/star terms
'uniform vec3 uSunDir;', 'uniform vec3 uSunDir;',
'uniform vec3 uCamPos;', 'uniform vec3 uCamPos;',
'varying float vU;', 'varying float vU;',
@@ -4426,8 +4456,9 @@ var CT_FRAG_SRC = [
// Same multipliers as the cloud deck, so the contrail/cloud // Same multipliers as the cloud deck, so the contrail/cloud
// brightness ratio at night is the same as in daylight. // brightness ratio at night is the same as in daylight.
'vec3 light = uSunColor * uSunInt * phase * 1.25', 'vec3 light = uSunColor * uSunInt * phase * 1.25',
' + uMoonColor * uMoonInt * 3.0', ' + (uMoonColor * uMoonInt * 3.0',
' + max(vec3(uSkyAmb), uStarAmb * vec3(0.75, 0.85, 1.0)) * 1.7;', ' + max(vec3(uSkyAmb), uStarAmb * vec3(0.75, 0.85, 1.0)) * 1.7)',
' * uNightBoost;',
'gl_FragColor = vec4(vec3(0.95, 0.96, 1.0) * light, a);', 'gl_FragColor = vec4(vec3(0.95, 0.96, 1.0) * light, a);',
'}' '}'
].join('\n'); ].join('\n');
@@ -4616,7 +4647,7 @@ function buildSkyTexture(stars) {
var b = 0.7 + 0.3 * Math.min(1, Math.pow(2.512, -mag * 0.7) * 1.3); var b = 0.7 + 0.3 * Math.min(1, Math.pow(2.512, -mag * 0.7) * 1.3);
data[di++] = b; data[di++] = b;
} }
console.log('[clouds3d] stars: ' + stars.length + ' points, mean rgb ' + dbgLog('[clouds3d] stars: ' + stars.length + ' points, mean rgb ' +
(mr / stars.length).toFixed(3) + '/' + (mg / stars.length).toFixed(3) + '/' + (mr / stars.length).toFixed(3) + '/' + (mg / stars.length).toFixed(3) + '/' +
(mb / stars.length).toFixed(3)); (mb / stars.length).toFixed(3));
var prog = makeProgram(gl, SKYPTS_VERT, SKYPTS_FRAG); var prog = makeProgram(gl, SKYPTS_VERT, SKYPTS_FRAG);
@@ -5513,6 +5544,7 @@ window.clouds3dRender = function(target) {
// CT_HALF_W doubled with the trail width, so the factor // CT_HALF_W doubled with the trail width, so the factor
// halves: the aircraft silhouette keeps its old size. // halves: the aircraft silhouette keeps its old size.
var S0 = CT_HALF_W * 1.0; var S0 = CT_HALF_W * 1.0;
var _meshDrawn = 0;
for (var dj = 0; dj < fltCount; dj++) { for (var dj = 0; dj < fltCount; dj++) {
var briJ = fltBuf[dj * 6 + 3]; var briJ = fltBuf[dj * 6 + 3];
if (briJ <= 0) continue; if (briJ <= 0) continue;
@@ -5535,6 +5567,7 @@ window.clouds3dRender = function(target) {
gl.uniform4f(planeU.orit, Math.cos(yaw), Math.sin(yaw), pitch, 0); gl.uniform4f(planeU.orit, Math.cos(yaw), Math.sin(yaw), pitch, 0);
gl.uniform1f(planeU.s, scl); gl.uniform1f(planeU.s, scl);
gl.drawArrays(gl.TRIANGLES, 0, planeN); gl.drawArrays(gl.TRIANGLES, 0, planeN);
_meshDrawn++;
} }
gl.depthMask(true); gl.depthMask(true);
} }
@@ -5545,7 +5578,7 @@ window.clouds3dRender = function(target) {
// and the stripe follows the curved leg: walked backwards from // and the stripe follows the curved leg: walked backwards from
// the aircraft in fixed steps. Visible by day (sun-lit) AND by // the aircraft in fixed steps. Visible by day (sun-lit) AND by
// night as dimmed moonlit streaks. // night as dimmed moonlit streaks.
var tvis = Math.max(dayf, carNight * 0.42); var tvis = Math.max(dayf, carNight * CT_NIGHT);
if (tvis > 0.02) { if (tvis > 0.02) {
if (!ctBuf) ctBuf = new Float32Array(CT_MAX * CT_SEG * 6 * 7); if (!ctBuf) ctBuf = new Float32Array(CT_MAX * CT_SEG * 6 * 7);
ctVerts = 0; ctVerts = 0;
@@ -5574,7 +5607,7 @@ window.clouds3dRender = function(target) {
if (dfc > 1) dfc = 1; else if (dfc <= 0.02) continue; if (dfc > 1) dfc = 1; else if (dfc <= 0.02) continue;
// sun-lit by day, moonlit after dusk. dayf alone made the // sun-lit by day, moonlit after dusk. dayf alone made the
// stripes vanish completely at night although the pass ran. // stripes vanish completely at night although the pass ran.
var lit = Math.max(dayf, carNight * 0.34); var lit = Math.max(dayf, carNight * CT_NIGHT);
var strg = gt * lit * dfc; var strg = gt * lit * dfc;
// widen with distance: a fixed 310 m ribbon is sub-pixel at // widen with distance: a fixed 310 m ribbon is sub-pixel at
// 150 km and blends away; grow it to hold ~2-4 screen px // 150 km and blends away; grow it to hold ~2-4 screen px
@@ -5622,7 +5655,8 @@ window.clouds3dRender = function(target) {
moonColor: gl.getUniformLocation(cpm, 'uMoonColor'), moonColor: gl.getUniformLocation(cpm, 'uMoonColor'),
moonInt: gl.getUniformLocation(cpm, 'uMoonInt'), moonInt: gl.getUniformLocation(cpm, 'uMoonInt'),
skyAmb: gl.getUniformLocation(cpm, 'uSkyAmb'), skyAmb: gl.getUniformLocation(cpm, 'uSkyAmb'),
starAmb: gl.getUniformLocation(cpm, 'uStarAmb') }; starAmb: gl.getUniformLocation(cpm, 'uStarAmb'),
nightBoost: gl.getUniformLocation(cpm, 'uNightBoost') };
} }
} }
if (ctProg) { if (ctProg) {
@@ -5637,6 +5671,7 @@ window.clouds3dRender = function(target) {
gl.uniform1f(ctU.moonInt, _moonI); gl.uniform1f(ctU.moonInt, _moonI);
gl.uniform1f(ctU.skyAmb, _amb * ATMO_SKY_GAIN); gl.uniform1f(ctU.skyAmb, _amb * ATMO_SKY_GAIN);
gl.uniform1f(ctU.starAmb, _star); gl.uniform1f(ctU.starAmb, _star);
gl.uniform1f(ctU.nightBoost, 1.0 + carNight * (CT_NIGHT_BOOST - 1.0));
gl.enableVertexAttribArray(ctA.pos); gl.enableVertexAttribArray(ctA.pos);
gl.vertexAttribPointer(ctA.pos, 3, gl.FLOAT, false, 28, 0); gl.vertexAttribPointer(ctA.pos, 3, gl.FLOAT, false, 28, 0);
gl.enableVertexAttribArray(ctA.u); gl.enableVertexAttribArray(ctA.u);
@@ -5655,6 +5690,21 @@ window.clouds3dRender = function(target) {
gl.depthMask(true); gl.depthMask(true);
} }
} }
// Diagnostic snapshot: how many aircraft are in range, how many
// meshes were actually drawn and how much contrail geometry made
// it past the distance gates.
window.__flightDbg = {
aircraft: fltCount,
meshDrawn: _meshDrawn,
ctVerts: ctVerts,
day: +dayf.toFixed(2),
night: +carNight.toFixed(2),
meshRangeKm: Math.round(16 * KM_PER_TILE)
};
if (DEBUG_LOG && now - _flightDbgT > 5000) {
_flightDbgT = now;
dbgLog('[clouds3d] flights:', JSON.stringify(window.__flightDbg));
}
} }
} }