diff --git a/vendor/clouds3d.js b/vendor/clouds3d.js index af7d247..bf35b90 100644 --- a/vendor/clouds3d.js +++ b/vendor/clouds3d.js @@ -5390,6 +5390,65 @@ var planeV = null, planeVbo = null, planeProg = null, planeU = null, planeA = nu var ctBuf = null, ctVbo = null, ctVerts = 0; var ctProg = null, ctA = null, ctU = null; +var CT2_SEG = 18; +var ct2Buf = null, ct2Vbo = null, ct2Verts = 0; +var ct2Prog = null, ct2A = null, ct2U = null; + +function _ct2Vert(x, y, z, u, v, s) { + var i = ct2Verts * 6; + ct2Buf[i] = x; ct2Buf[i + 1] = y; ct2Buf[i + 2] = z; + ct2Buf[i + 3] = u; ct2Buf[i + 4] = v; ct2Buf[i + 5] = s; + ct2Verts++; +} + +var CT2_VERT_SRC = [ + 'attribute vec3 aPos;', + 'attribute float aU;', + 'attribute float aV;', + 'attribute float aS;', + 'uniform mat4 uProj;', + 'uniform mat4 uView;', + 'uniform vec3 uCamPos;', + 'uniform float uEarthRadius;', + 'uniform float uCurvatureOn;', + 'varying float vU;', + 'varying float vV;', + 'varying float vS;', + 'varying vec3 vWorld;', + 'void main() {', + 'vU = aU; vV = aV; vS = aS;', + 'vec3 pos = aPos;', + 'if (uCurvatureOn > 0.5) {', + 'vec2 d = pos.xz - uCamPos.xz;', + 'pos.y -= dot(d, d) / (2.0 * max(1.0, uEarthRadius));', + '}', + 'vWorld = pos;', + 'gl_Position = uProj * uView * vec4(pos, 1.0);', + '}' +].join('\n'); + +var CT2_FRAG_SRC = [ + 'precision mediump float;', + 'uniform float uDay;', + 'uniform float uNight;', + 'varying float vU;', + 'varying float vV;', + 'varying float vS;', + 'varying vec3 vWorld;', + 'void main() {', + 'float side = abs(vV);', + 'float edge = 1.0 - smoothstep(0.25, 1.0, side);', + 'float head = 0.35 + 0.65 * vU;', + 'float a = clamp(vS * edge * head, 0.0, 0.85);', + 'if (a <= 0.004) discard;', + 'vec3 dayCol = vec3(1.0, 1.0, 1.0);', + 'vec3 nightCol = vec3(0.45, 0.55, 0.75);', + 'vec3 col = mix(nightCol, dayCol, clamp(uDay, 0.0, 1.0));', + 'col *= 0.35 + 0.65 * clamp(uDay + uNight, 0.0, 1.0);', + 'gl_FragColor = vec4(col, a);', + '}' +].join('\n'); + function _ctVert(x, y, z, u, v, s, p) { var i = ctVerts * 7; ctBuf[i] = x; ctBuf[i + 1] = y; ctBuf[i + 2] = z; @@ -6799,138 +6858,113 @@ window.clouds3dRender = function(target) { gl.depthMask(true); } } - // -- contrails -- - // Only CONFIG.flights.contrailChance of the aircraft leave a - // persistent stripe (the ice-humidity window is not everywhere), - // and the stripe follows the curved leg: walked backwards from - // the aircraft in fixed steps. Visible by day (sun-lit) AND by - // night as dimmed moonlit streaks. + // -- contrails: simple robust ribbon pass -- var tvis = Math.max(dayf, carNight * CT_NIGHT); - if (tvis > 0.02) { - if (!ctBuf) ctBuf = new Float32Array(CT_MAX * CT_SEG * 6 * 7); - ctVerts = 0; - var _cs = { x: 0, z: 0, tx: 1, tz: 0 }; - var _sk0 = { x: 0, z: 0, px: 0, pz: 0, u: 0 }; - var _sk1 = { x: 0, z: 0, px: 0, pz: 0, u: 0 }; + if (tvis > 0.01) { + if (!ct2Buf) ct2Buf = new Float32Array(FLT_MAX * CT2_SEG * 6 * 6); + ct2Verts = 0; + var _cs2 = { x: 0, z: 0, tx: 1, tz: 0 }; for (var cj = 0; cj < fltCount; cj++) { - if (ctVerts + CT_SEG * 6 > CT_MAX * CT_SEG * 6) break; + if (ct2Verts + CT2_SEG * 6 > ct2Buf.length / 6) break; var stc = fltState[cj]; if (!stc || stc.gnd) continue; if (fltRand(cj * 3.7 + 0.77) > CT_CHANCE) continue; - // formation gate: above the configured trail altitude, smooth - var gt = (stc.alt - (CT_START_FRAC - 0.06)) / 0.12; - gt = gt < 0 ? 0 : (gt > 1 ? 1 : gt); - gt = gt * gt * (3 - 2 * gt); - if (gt < 0.05) continue; - var tl = Math.min(CT_T * fltData[cj].v, stc.s); - if (tl < 0.05) continue; - fltLegAt(stc.leg, stc.s - tl * 0.5, _cs); - var cdx = _cs.x - camX, cdy = stc.y - camY, cdz = _cs.z - camZ; - var distc = Math.sqrt(cdx * cdx + cdy * cdy + cdz * cdz); - // distance fade: full out to 35% of contrailFadeKm, gone at - // contrailFadeKm. Trails that thin only at 200-340 km were - // the remaining shimmer at 4x pixel scale. - var dfc = 1 - (distc - CT_FADE_START) / (CT_FADE_END - CT_FADE_START); - if (dfc > 1) dfc = 1; else if (dfc <= 0.02) continue; - // sun-lit by day, moonlit after dusk. dayf alone made the - // stripes vanish completely at night although the pass ran. + var gt = (stc.alt - (CT_START_FRAC - 0.10)) / 0.18; + gt = Math.max(0, Math.min(1, gt)); + if (gt < 0.03) continue; + var tl = Math.min(Math.max(1.0, CT_T * fltData[cj].v), stc.s); + if (tl < 0.2) continue; var lit = Math.max(dayf, carNight * CT_NIGHT); - var strg = gt * lit * dfc; - if (strg < 0.18) strg = 0.18; - // 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 - var hw = CT_HALF_W + distc * 0.0025; - var ph = fltData[cj].phase; + var strg = Math.max(0.30, gt * lit); var hy = stc.y; - var prev = null, cur = _sk0; - for (var sk = 0; sk <= CT_SEG; sk++) { - var dd = stc.s - tl + tl * (sk / CT_SEG); + var prev = null; + var cur = { x: 0, z: 0, px: 0, pz: 0, u: 0 }; + for (var sk = 0; sk <= CT2_SEG; sk++) { + var dd = stc.s - tl + tl * (sk / CT2_SEG); if (dd < 0) dd = 0; - fltLegAt(stc.leg, dd, _cs); - cur.x = _cs.x; cur.z = _cs.z; - cur.px = _cs.tz * hw; cur.pz = -_cs.tx * hw; - cur.u = sk / CT_SEG; + fltLegAt(stc.leg, dd, _cs2); + var dx = _cs2.x - camX, dz = _cs2.z - camZ, dy = hy - camY; + var dist = Math.sqrt(dx * dx + dy * dy + dz * dz); + var hw = CT_HALF_W + dist * 0.004; + var tx = _cs2.tx, tz = _cs2.tz; + var sl = Math.hypot(tx, tz) || 1; + tx /= sl; tz /= sl; + cur.x = _cs2.x; cur.z = _cs2.z; + cur.px = tz * hw; cur.pz = -tx * hw; + cur.u = sk / CT2_SEG; if (prev) { - _ctVert(prev.x + prev.px, hy, prev.z + prev.pz, prev.u, 1, strg, ph); - _ctVert(prev.x - prev.px, hy, prev.z - prev.pz, prev.u, -1, strg, ph); - _ctVert(cur.x - cur.px, hy, cur.z - cur.pz, cur.u, -1, strg, ph); - _ctVert(prev.x + prev.px, hy, prev.z + prev.pz, prev.u, 1, strg, ph); - _ctVert(cur.x - cur.px, hy, cur.z - cur.pz, cur.u, -1, strg, ph); - _ctVert(cur.x + cur.px, hy, cur.z + cur.pz, cur.u, 1, strg, ph); + _ct2Vert(prev.x + prev.px, hy, prev.z + prev.pz, prev.u, 1, strg); + _ct2Vert(prev.x - prev.px, hy, prev.z - prev.pz, prev.u, -1, strg); + _ct2Vert(cur.x - cur.px, hy, cur.z - cur.pz, cur.u, -1, strg); + _ct2Vert(prev.x + prev.px, hy, prev.z + prev.pz, prev.u, 1, strg); + _ct2Vert(cur.x - cur.px, hy, cur.z - cur.pz, cur.u, -1, strg); + _ct2Vert(cur.x + cur.px, hy, cur.z + cur.pz, cur.u, 1, strg); } - prev = cur; cur = (cur === _sk0) ? _sk1 : _sk0; + prev = cur; + cur = { x: 0, z: 0, px: 0, pz: 0, u: 0 }; } } - if (ctVerts > 0) { - if (!ctVbo) ctVbo = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, ctVbo); - gl.bufferData(gl.ARRAY_BUFFER, ctBuf.subarray(0, ctVerts * 7), gl.STREAM_DRAW); - if (!ctProg) { - var cpm = makeProgram(gl, CT_VERT_SRC, CT_FRAG_SRC); - if (cpm) { - ctProg = cpm; - ctA = { pos: gl.getAttribLocation(cpm, 'aPos'), - u: gl.getAttribLocation(cpm, 'aU'), - v: gl.getAttribLocation(cpm, 'aV'), - s: gl.getAttribLocation(cpm, 'aS'), - p: gl.getAttribLocation(cpm, 'aP') }; - ctU = { proj: gl.getUniformLocation(cpm, 'uProj'), - view: gl.getUniformLocation(cpm, 'uView'), - camPos: gl.getUniformLocation(cpm, 'uCamPos'), - earthRadius: gl.getUniformLocation(cpm, 'uEarthRadius'), - curvatureOn: gl.getUniformLocation(cpm, 'uCurvatureOn'), - sunDir: gl.getUniformLocation(cpm, 'uSunDir'), - sunColor: gl.getUniformLocation(cpm, 'uSunColor'), - sunInt: gl.getUniformLocation(cpm, 'uSunInt'), - moonColor: gl.getUniformLocation(cpm, 'uMoonColor'), - moonInt: gl.getUniformLocation(cpm, 'uMoonInt'), - skyAmb: gl.getUniformLocation(cpm, 'uSkyAmb'), - starAmb: gl.getUniformLocation(cpm, 'uStarAmb'), - nightBoost: gl.getUniformLocation(cpm, 'uNightBoost') }; + if (ct2Verts > 0) { + if (!ct2Vbo) ct2Vbo = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, ct2Vbo); + gl.bufferData(gl.ARRAY_BUFFER, ct2Buf.subarray(0, ct2Verts * 6), gl.STREAM_DRAW); + if (!ct2Prog) { + var c2p = makeProgram(gl, CT2_VERT_SRC, CT2_FRAG_SRC); + if (c2p) { + ct2Prog = c2p; + ct2A = { + pos: gl.getAttribLocation(c2p, 'aPos'), + u: gl.getAttribLocation(c2p, 'aU'), + v: gl.getAttribLocation(c2p, 'aV'), + s: gl.getAttribLocation(c2p, 'aS') + }; + ct2U = { + proj: gl.getUniformLocation(c2p, 'uProj'), + view: gl.getUniformLocation(c2p, 'uView'), + camPos: gl.getUniformLocation(c2p, 'uCamPos'), + earthRadius: gl.getUniformLocation(c2p, 'uEarthRadius'), + curvatureOn: gl.getUniformLocation(c2p, 'uCurvatureOn'), + day: gl.getUniformLocation(c2p, 'uDay'), + night: gl.getUniformLocation(c2p, 'uNight') + }; } } - if (ctProg) { - gl.useProgram(ctProg); - gl.uniformMatrix4fv(ctU.proj, false, proj); - gl.uniformMatrix4fv(ctU.view, false, view); - gl.uniform3f(ctU.camPos, camX, camY, camZ); - gl.uniform1f(ctU.earthRadius, EARTH_RADIUS_UNITS); - gl.uniform1f(ctU.curvatureOn, CURVATURE_ON ? 1.0 : 0.0); - gl.uniform3f(ctU.sunDir, sd[0], sd[1], sd[2]); - gl.uniform3f(ctU.sunColor, _beam[0], _beam[1], _beam[2]); - gl.uniform1f(ctU.sunInt, _beamL); - gl.uniform3f(ctU.moonColor, _mBeam[0], _mBeam[1], _mBeam[2]); - gl.uniform1f(ctU.moonInt, _moonI); - gl.uniform1f(ctU.skyAmb, _amb * ATMO_SKY_GAIN); - gl.uniform1f(ctU.starAmb, _star); - gl.uniform1f(ctU.nightBoost, 1.0 + carNight * (CT_NIGHT_BOOST - 1.0)); - gl.enableVertexAttribArray(ctA.pos); - gl.vertexAttribPointer(ctA.pos, 3, gl.FLOAT, false, 28, 0); - gl.enableVertexAttribArray(ctA.u); - gl.vertexAttribPointer(ctA.u, 1, gl.FLOAT, false, 28, 12); - gl.enableVertexAttribArray(ctA.v); - gl.vertexAttribPointer(ctA.v, 1, gl.FLOAT, false, 28, 16); - gl.enableVertexAttribArray(ctA.s); - gl.vertexAttribPointer(ctA.s, 1, gl.FLOAT, false, 28, 20); - gl.enableVertexAttribArray(ctA.p); - gl.vertexAttribPointer(ctA.p, 1, gl.FLOAT, false, 28, 24); + if (ct2Prog) { + gl.useProgram(ct2Prog); + gl.uniformMatrix4fv(ct2U.proj, false, proj); + gl.uniformMatrix4fv(ct2U.view, false, view); + gl.uniform3f(ct2U.camPos, camX, camY, camZ); + gl.uniform1f(ct2U.earthRadius, EARTH_RADIUS_UNITS); + gl.uniform1f(ct2U.curvatureOn, CURVATURE_ON ? 1.0 : 0.0); + gl.uniform1f(ct2U.day, dayf); + gl.uniform1f(ct2U.night, carNight); + gl.enableVertexAttribArray(ct2A.pos); + gl.vertexAttribPointer(ct2A.pos, 3, gl.FLOAT, false, 24, 0); + gl.enableVertexAttribArray(ct2A.u); + gl.vertexAttribPointer(ct2A.u, 1, gl.FLOAT, false, 24, 12); + gl.enableVertexAttribArray(ct2A.v); + gl.vertexAttribPointer(ct2A.v, 1, gl.FLOAT, false, 24, 16); + gl.enableVertexAttribArray(ct2A.s); + gl.vertexAttribPointer(ct2A.s, 1, gl.FLOAT, false, 24, 20); gl.disable(gl.DEPTH_TEST); gl.depthMask(false); gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); - gl.drawArrays(gl.TRIANGLES, 0, ctVerts); + gl.drawArrays(gl.TRIANGLES, 0, ct2Verts); gl.disable(gl.BLEND); gl.depthMask(true); gl.enable(gl.DEPTH_TEST); } } + } + // 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, + ctVerts: ct2Verts, day: +dayf.toFixed(2), night: +carNight.toFixed(2), meshRangeKm: Math.round(16 * KM_PER_TILE) @@ -6939,7 +6973,6 @@ window.clouds3dRender = function(target) { _flightDbgT = now; dbgLog('[clouds3d] flights:', JSON.stringify(window.__flightDbg)); } - } } // Restore default state for the next frame.