moon: real lunar map (CC BY 4.0) with correct near-side geometry - the old normal sign lit the far side, so the disc came out black; stars are now occluded by the disc, sun and moon discs drawn at 12x physical without changing surface brightness; build.py inlines the map as a data URI

This commit is contained in:
Richard Zink committed 2026-10-11 22:46:35 +02:00
1 parent afc779e27e
commit ee56f2a1c8
4 files changed
+215 -25

No files matched your search

+14 -5
View File
@@ -11,6 +11,7 @@ What gets inlined:
- vendor/suncalc.js, vendor/plane3d.js,
vendor/clouds3d.js (plain <script> swap)
- vendor/stars.json (window.__starsData)
- vendor/moon.jpg (window.__moonTex, data URI)
- every stylesheet referenced by window.THEMES
(window.__inlineThemeCss, so
theme switching works offline)
@@ -24,6 +25,7 @@ Overpass cache, which is the gentlest way to hit those upstreams.
The build fails loudly if any external script or stylesheet reference survives.
"""
import base64
import json
import os
import re
@@ -128,12 +130,18 @@ def main():
# embedded data preamble (must run before the scene scripts)
stars = json.loads(read('vendor/stars.json'))
theme_css = {name: read(fname) for name, fname in themes.items()}
moon_path = os.path.join(ROOT, 'vendor', 'moon.jpg')
moon_uri = ''
if os.path.exists(moon_path):
with open(moon_path, 'rb') as f:
moon_uri = 'data:image/jpeg;base64,' + base64.b64encode(f.read()).decode('ascii')
embed = (
'<script>\n'
'window.__starsData = %s;\n'
'window.__moonTex = "%s";\n'
'window.__inlineThemeCss = %s;\n'
'</script>\n'
% (json.dumps(stars, separators=(',', ':')), json.dumps(theme_css))
% (json.dumps(stars, separators=(',', ':')), moon_uri, json.dumps(theme_css))
)
# must come before config.js so the early theme loader sees the CSS
anchor = '<script src="config.js"></script>'
@@ -158,8 +166,8 @@ def main():
with tempfile.NamedTemporaryFile('w', suffix='.html', delete=False) as f:
f.write(html)
tmp = f.name
print('OK: self-contained (%d bytes, %d themes, %d stars)'
% (len(html), len(themes), len(stars)))
print('OK: self-contained (%d bytes, %d themes, %d stars, moon map %d KB)'
% (len(html), len(themes), len(stars), len(moon_uri) // 1024))
os.unlink(tmp)
return
@@ -167,8 +175,9 @@ def main():
f.write(html)
print('Wrote %s (%d bytes)' % (out, len(html)))
print('Inlined: %s, stars.json (%d entries), themes: %s'
% (', '.join(INLINED_SCRIPTS), len(stars), ', '.join(sorted(themes))))
print('Inlined: %s, stars.json (%d entries), moon.jpg (%d KB), themes: %s'
% (', '.join(INLINED_SCRIPTS), len(stars), len(moon_uri) // 1024,
', '.join(sorted(themes))))
if __name__ == '__main__':
+8
View File
@@ -8,6 +8,14 @@ stars.json - star catalogue derived from the Yale Bright Star Catalogue
(5th ed., public catalogue data). Generated for this project; see the
real-sky notes in clouds3d.js.
moon.jpg - equirectangular near-side lunar map (1024x512, downscaled from
the 2048x1024 original) used for the moon disc texture.
Source: https://www.solarsystemscope.com/textures/ (via Wikimedia Commons,
"Solarsystemscope texture 2k moon.jpg")
Author: Solar System Scope
License: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
Attribution required: Solar System Scope / solarsystemscope.com
plane3d.js - low-poly airliner mesh (480 triangles, flat shaded, no textures)
Original geometry generated by tools/plane_gen.py in this repository; offered
under CC0 1.0 like the rest of the project's own assets.
+193 -20
View File
@@ -97,6 +97,17 @@ var FRAG_SRC = [
'uniform vec3 uCloudAlbedo;', // theme-tinted but brightness-normalised
'uniform float uAirDensity;', // slant optical depth per world unit
'uniform float uCloudHaze;', // clouds sit above most of the haze: much weaker
'uniform sampler2D uMoonTex;', // equirectangular near-side lunar map
'uniform float uMoonTexOn;', // 0 until the map is loaded (flat disc)
'uniform float uMoonR;', // disc angular radius in radians
'uniform float uMoonFrac;', // illuminated fraction (drives the halo)
'uniform float uMoonEarth;', // earthshine on the dark side
'uniform float uMoonLimb;', // limb darkening strength
'uniform float uMoonBright;', // per-pixel luminance of the disc
'uniform float uMoonHalo;',
'uniform float uMoonFlipU;',
'uniform float uMoonFlipV;',
'uniform float uSunR;', // sun disc angular radius (radians)
// 3D hash and value noise
'float hash3(vec3 p) {',
'p = fract(p * vec3(0.1031, 0.1030, 0.0973));',
@@ -224,6 +235,11 @@ var FRAG_SRC = [
'vec3 col = mix(uBg, uSky, skyT);',
'float nightFactor = 1.0 - smoothstep(-0.15, 0.05, uSunAlt);',
'float skyMask = smoothstep(-0.20, 0.40, rd.y);',
// Stars must not sit on the moon: the disc is opaque, so every
// star term is masked out inside (a little beyond) its rim.
// 0 inside the disc, 1 outside it - the smoothstep runs along the
// angular distance, so an extra "1.0 -" would blank the whole sky.
'float moonOcc = smoothstep(uMoonR * 0.92, uMoonR * 1.35, length(rd - uMoonDir));',
// night darkening of the gradient BEFORE star emission is added
'col *= (1.0 - nightFactor * 0.55);',
// ---- real star catalog (Yale BSC5 baked into a texture) ----
@@ -243,38 +259,68 @@ var FRAG_SRC = [
// stars only once the sky is actually dark: the dusk palette
// is rust/blood, and additive white stars on a red sky read
// as red stars
'col += st * starVis * skyMask * twk * 1.45;',
'col += st * starVis * skyMask * twk * 1.45 * moonOcc;',
'}',
// ---- Sun disc ------------------------------------------------
// angular radius ~0.27 deg -> chord length ~0.0047
'if (uSunAlt > -0.03) {',
'vec3 qs = rd - uSunDir;',
'float angS = length(qs);',
'float discS = 1.0 - smoothstep(0.0040, 0.0050, angS);',
'float haloS = exp(-angS * 55.0) * 0.30 + exp(-angS * 14.0) * 0.12;',
// The disc is drawn at uSunR (CONFIG.sun.angularRadius, 12x
// physical by default) but the surface brightness stays
// exactly what it was: the halo profile is normalised to the
// disc radius, so the sun is bigger, not brighter.
'float angN = angS * (0.00465 / uSunR);',
'float discS = 1.0 - smoothstep(uSunR * 0.86, uSunR, angS);',
'float haloS = exp(-angN * 55.0) * 0.30 + exp(-angN * 14.0) * 0.12;',
'vec3 sunCol = mix(uLight, vec3(1.0, 0.92, 0.72), 0.35);',
'col += sunCol * (discS * 1.15 + haloS) * (1.0 - nightFactor * 0.85);',
'}',
// ---- Moon disc with real terminator ---------------------------
// lit side = dot(surface normal, sun dir) -> crescent/gibbous
// falls out of geometry, phase tracks __currentMoonDir.
'if (uMoonDir.y > -0.01) {',
// ---- Moon disc: lunar map + geometric terminator --------------
// The near side always faces the observer, so the map is sampled
// in a selenographic frame built from the north celestial pole:
// north up, east to the right, exactly like a printed lunar map,
// and it rotates as the moon crosses the sky. The terminator is
// pure geometry (surface normal dot sun direction), so crescent,
// gibbous and the orientation towards the sun all fall out.
'if (uMoonDir.y > -0.02) {',
'float MR = uMoonR;',
'vec3 qm = rd - uMoonDir;',
'float angM = length(qm);',
'if (angM < 0.02) {',
'float z = sqrt(max(0.0, 0.0046 * 0.0046 - angM * angM));',
'vec3 nM = normalize(qm + uMoonDir * (z + 0.00001));',
'if (angM < MR) {',
'float rr = angM / MR;',
'float z = sqrt(max(0.0, 1.0 - rr * rr));',
// outward normal of the NEAR hemisphere: the offset part
// plus -uMoonDir (towards the observer), not +uMoonDir.
// With the wrong sign the lit side was the far side and
// the disc came out black at and near full moon.
'vec3 nM = normalize(qm / MR - uMoonDir * z);',
'vec3 upTry = uPole - uMoonDir * dot(uPole, uMoonDir);',
'vec3 fb = (abs(uMoonDir.y) < 0.9) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);',
'vec3 fb2 = fb - uMoonDir * dot(fb, uMoonDir);',
'vec3 uU = (dot(upTry, upTry) > 1e-6) ? normalize(upTry) : normalize(fb2);',
'vec3 uE = cross(uU, uMoonDir);',
'float lon = atan(dot(nM, uE), dot(nM, -uMoonDir));',
'float lat = asin(clamp(dot(nM, uU), -1.0, 1.0));',
'vec2 uv = vec2(lon / 6.2831853 + 0.5, lat / 3.14159265 + 0.5);',
'uv.x = mix(uv.x, 1.0 - uv.x, uMoonFlipU);',
'uv.y = mix(uv.y, 1.0 - uv.y, uMoonFlipV);',
'vec3 alb = mix(vec3(0.62, 0.63, 0.66), texture2D(uMoonTex, uv).rgb, uMoonTexOn);',
'float litM = dot(nM, uSunDir);',
'float phase = smoothstep(-0.08, 0.12, litM);',
'float discM = 1.0 - smoothstep(0.0041, 0.0046, angM);',
'vec3 moonCol = mix(vec3(0.62, 0.64, 0.68), vec3(0.97, 0.95, 0.88), phase);',
'float haloM = exp(-angM * 60.0) * 0.10 * (0.25 + 0.75 * phase);',
'col += (moonCol * discM * phase + vec3(0.75, 0.78, 0.85) * haloM) * 1.25;',
'float phase = smoothstep(-0.05, 0.12, litM);',
'float lam = max(0.0, litM);',
'float limb = 1.0 - uMoonLimb * rr * rr * rr;',
'float shade = lam * lam * 0.85 + lam * 0.18;',
'float earth = uMoonEarth * (1.0 - phase) * (0.35 + 0.65 * (1.0 - rr));',
'float discM = 1.0 - smoothstep(MR * 0.965, MR, angM);',
'col += alb * (shade * limb + earth) * discM * uMoonBright;',
'}',
'float haloM = exp(-angM * 60.0) * 0.10 + exp(-angM * 16.0) * 0.04;',
'col += vec3(0.75, 0.78, 0.85) * haloM * uMoonHalo * (0.25 + 0.75 * uMoonFrac);',
'}',
// faint procedural fill for stars below the catalog limit
'float stars = starField(rd) * skyMask * nightFactor;',
'col += stars * uLight * 0.85;',
'col += stars * uLight * 0.85 * moonOcc;',
// ---- Horizon airlight -------------------------------------
// At and below the horizon the sky shows the same scattered
// column the terrain fades into. Without it the outer rim of
@@ -703,6 +749,17 @@ function init(canvas) {
var uSkyOn = gl.getUniformLocation(prog, 'uSkyOn');
var uStarLon = gl.getUniformLocation(prog, 'uStarLon');
var uPole = gl.getUniformLocation(prog, 'uPole');
var uMoonTex = gl.getUniformLocation(prog, 'uMoonTex');
var uMoonTexOn = gl.getUniformLocation(prog, 'uMoonTexOn');
var uMoonR = gl.getUniformLocation(prog, 'uMoonR');
var uMoonFrac = gl.getUniformLocation(prog, 'uMoonFrac');
var uMoonEarth = gl.getUniformLocation(prog, 'uMoonEarth');
var uMoonLimb = gl.getUniformLocation(prog, 'uMoonLimb');
var uMoonBright = gl.getUniformLocation(prog, 'uMoonBright');
var uMoonHalo = gl.getUniformLocation(prog, 'uMoonHalo');
var uMoonFlipU = gl.getUniformLocation(prog, 'uMoonFlipU');
var uMoonFlipV = gl.getUniformLocation(prog, 'uMoonFlipV');
var uSunR = gl.getUniformLocation(prog, 'uSunR');
var uSunColor = gl.getUniformLocation(prog, 'uSunColor');
var uSunInt = gl.getUniformLocation(prog, 'uSunInt');
var uMoonColor = gl.getUniformLocation(prog, 'uMoonColor');
@@ -801,6 +858,10 @@ function init(canvas) {
uPhysical: uPhysical, uAirColor: uAirColor, uCloudAlbedo: uCloudAlbedo,
uAirDensity: uAirDensity, uCloudHaze: uCloudHaze,
uSkyTex: uSkyTex, uSkyOn: uSkyOn, uStarLon: uStarLon, uPole: uPole,
uMoonTex: uMoonTex, uMoonTexOn: uMoonTexOn, uMoonR: uMoonR,
uMoonFrac: uMoonFrac, uMoonEarth: uMoonEarth, uMoonLimb: uMoonLimb,
uMoonBright: uMoonBright, uMoonHalo: uMoonHalo,
uMoonFlipU: uMoonFlipU, uMoonFlipV: uMoonFlipV, uSunR: uSunR,
// terrain program
terrain: {
prog: terrainProg,
@@ -918,6 +979,17 @@ var CAM_MID_Y = (FL_MIN_KM + FL_MAX_KM) / 2 / KM_PER_TILE;
var BOB_AMP = typeof ALT_BOB.amp === 'number' ? ALT_BOB.amp : 0.77;
var BOB_W = 6.2831853 / (typeof ALT_BOB.periodS === 'number' ? ALT_BOB.periodS : 90);
var BOB_TILT = typeof ALT_BOB.tilt === 'number' ? ALT_BOB.tilt : 0.30; // pitch response
// ---- Camera aim (CONFIG.camera) ---------------------------------------
// The camera always flies the configured path; 'aim' only changes where it
// looks. 'moon' turns the view to the moon - heading to its azimuth, pitch
// to its altitude - while the flight path, the fetch corridor and the
// traffic stay untouched. aimBob keeps a little altitude bob so the view
// never looks frozen, aimEase smooths the turn.
var CAM_CFG = (window.CONFIG && window.CONFIG.camera) || {};
var CAM_AIM_MOON = (CAM_CFG.aim === 'moon');
var CAM_AIM_EASE = (typeof CAM_CFG.aimEase === 'number') ? Math.max(0, Math.min(0.999, CAM_CFG.aimEase)) : 0.94;
var CAM_AIM_BOB = (typeof CAM_CFG.aimBob === 'number') ? Math.max(0, Math.min(1, CAM_CFG.aimBob)) : 0.25;
var aimHdX = 0, aimHdZ = 1, aimPitch = 0.45, aimReady = false;
// CONFIG.clouds = { top: ..., bottom: ... } overrides the defaults; the
// deck must stay below the camera and keep a minimum thickness.
var CLOUD_TOP = 0.9, CLOUD_BOT = -0.3;
@@ -1925,6 +1997,30 @@ function buildTerrainMesh(gl, state) {
var ATMO = (window.CONFIG && window.CONFIG.atmosphere) || {};
var ATMO_TURBIDITY = (typeof ATMO.turbidity === 'number') ? Math.max(0.2, ATMO.turbidity) : 1.0;
var ATMO_MOON_GAIN = (typeof ATMO.moonGain === 'number') ? Math.max(0, ATMO.moonGain) : 0.16;
// ---- Moon appearance (CONFIG.moon) ------------------------------------
// The real disc is 0.0046 rad; the scene draws it larger so texture and
// phase are readable. Scene moonlight follows the disc, but only half of
// the excess over the physical size counts (see CONFIG.moon.lightGain).
var MOON_CFG = (window.CONFIG && window.CONFIG.moon) || {};
var MOON_PHYS_R = 0.0046;
var MOON_ANG_R = (typeof MOON_CFG.angularRadius === 'number')
? Math.max(MOON_PHYS_R, MOON_CFG.angularRadius) : MOON_PHYS_R * 8;
var MOON_LIGHT_GAIN = (typeof MOON_CFG.lightGain === 'number')
? Math.max(0, MOON_CFG.lightGain) : 1 + 0.5 * (MOON_ANG_R / MOON_PHYS_R - 1);
var MOON_EARTHSHINE = (typeof MOON_CFG.earthshine === 'number') ? Math.max(0, MOON_CFG.earthshine) : 0.10;
var MOON_LIMB_DARK = (typeof MOON_CFG.limbDarkening === 'number') ? Math.max(0, Math.min(0.9, MOON_CFG.limbDarkening)) : 0.30;
var MOON_BRIGHT = (typeof MOON_CFG.brightness === 'number') ? Math.max(0, MOON_CFG.brightness) : 1.25;
var MOON_HALO = (typeof MOON_CFG.halo === 'number') ? Math.max(0, MOON_CFG.halo) : 1.0;
var MOON_FLIP_U = MOON_CFG.flipU ? 1 : 0;
var MOON_FLIP_V = MOON_CFG.flipV ? 1 : 0;
// The real sun is 0.00465 rad. Scaling the disc changes its size only -
// the scene lighting (_beam/_beamL) is untouched.
var SUN_CFG = (window.CONFIG && window.CONFIG.sun) || {};
var SUN_ANG_R = (typeof SUN_CFG.angularRadius === 'number')
? Math.max(0.00465, SUN_CFG.angularRadius) : 0.00465 * 12;
var MOON_TEX_SRC = (typeof window.__moonTex === 'string' && window.__moonTex)
? window.__moonTex
: ((typeof MOON_CFG.texture === 'string' && MOON_CFG.texture) ? MOON_CFG.texture : 'vendor/moon.jpg');
var ATMO_NIGHT_FLOOR = (typeof ATMO.nightFloor === 'number') ? Math.max(0.01, ATMO.nightFloor) : 0.05;
var ATMO_ALBEDO_GAIN = (typeof ATMO.albedoGain === 'number') ? Math.max(0.1, ATMO.albedoGain) : 1.0;
var ATMO_SKY_GAIN = (typeof ATMO.skyGain === 'number') ? Math.max(0.05, ATMO.skyGain) : 0.55;
@@ -1976,7 +2072,7 @@ function airlightColor(altRad, moonAlt, moonFrac) {
var beam = beamColor(altRad);
var kDay = ATMO_AIR_GAIN * skyAmbient(altRad) * beamLuma(altRad);
var up = Math.max(0, Math.sin(moonAlt || 0));
var mDiff = ATMO_MOON_GAIN * (moonFrac === undefined ? 0.6 : moonFrac) * up * 0.9;
var mDiff = ATMO_MOON_GAIN * MOON_LIGHT_GAIN * (moonFrac === undefined ? 0.6 : moonFrac) * up * 0.9;
var mBeam = beamColor(moonAlt || 0);
var kMoon = ATMO_AIR_GAIN * mDiff * beamLuma(moonAlt || 0);
return [RAYLEIGH[0] * (beam[0] * kDay + mBeam[0] * kMoon),
@@ -4551,9 +4647,12 @@ var SKYPTS_VERT = [
'uniform vec3 uPole;',
'uniform float uLon;', // sidereal angle
'uniform float uSizeScale;',
'uniform vec3 uMoonDir;',
'uniform float uMoonR;',
'varying vec4 vC;',
'varying float vAlt;',
'varying float vSeed;',
'varying float vMoon;',
'void main() {',
'vec3 d = aSd.xyz;',
// stable per-star seed: derived from the star's own direction, so it
@@ -4563,6 +4662,7 @@ var SKYPTS_VERT = [
'float c = cos(uLon), s = sin(uLon);',
'vec3 rp = d * c + cross(uPole, d) * s + uPole * dot(uPole, d) * (1.0 - c);',
'vAlt = rp.y;',
'vMoon = smoothstep(uMoonR * 0.92, uMoonR * 1.35, length(rp - uMoonDir));',
// finite radius (inside the far plane): w=0 would put z_ndc just
// outside [-1,1] and clip every single point away
'gl_Position = uMVP * vec4(rp * 88.0, 1.0);',
@@ -4579,6 +4679,7 @@ var SKYPTS_FRAG = [
'varying vec4 vC;',
'varying float vAlt;',
'varying float vSeed;',
'varying float vMoon;',
'void main() {',
'vec2 pc = gl_PointCoord - 0.5;',
'float d = length(pc) * 2.0;',
@@ -4590,7 +4691,7 @@ var SKYPTS_FRAG = [
'float altf = smoothstep(-0.02, 0.12, vAlt);',
// vC.w = per-star brightness (mag-based, floored so faint stars
// stay clearly visible), giving an even overall look
'float a = uNight * altf * twk * vC.w;',
'float a = uNight * altf * twk * vC.w * vMoon;',
'gl_FragColor = vec4(vC.rgb * g * a * uBright, 1.0);',
'}'
].join('\n');
@@ -4703,6 +4804,8 @@ function buildSkyTexture(stars) {
pole: gl.getUniformLocation(prog, 'uPole'),
lon: gl.getUniformLocation(prog, 'uLon'),
sizeScale: gl.getUniformLocation(prog, 'uSizeScale'),
moonDir: gl.getUniformLocation(prog, 'uMoonDir'),
moonR: gl.getUniformLocation(prog, 'uMoonR'),
night: gl.getUniformLocation(prog, 'uNight'),
time: gl.getUniformLocation(prog, 'uTime'),
bright: gl.getUniformLocation(prog, 'uBright')
@@ -4803,6 +4906,29 @@ if (window.CONFIG && window.CONFIG.clouds && window.CONFIG.clouds.density) {
}
}
var moonTexture = null, moonTexFailed = false;
function loadMoonTexture(gl) {
var img = new Image();
img.onload = function () {
moonTexture = gl.createTexture();
// flip so v = 0 is the southern edge: the map is north-up
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.bindTexture(gl.TEXTURE_2D, moonTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
dbgLog('[clouds3d] moon map ready (' + img.width + 'x' + img.height + ')');
};
img.onerror = function () {
moonTexFailed = true;
console.warn('[clouds3d] moon map failed to load - flat disc instead');
};
img.src = MOON_TEX_SRC;
}
window.clouds3dRender = function(target) {
if (!target || typeof target !== 'object') {
console.error('clouds3d: invalid target');
@@ -4890,6 +5016,22 @@ window.clouds3dRender = function(target) {
gl.bindTexture(gl.TEXTURE_2D, skyTexture || s.blackTex);
gl.uniform1i(s.uSkyTex, 4);
gl.uniform1f(s.uSkyOn, skyTexture ? 1.0 : 0.0);
// Lunar map on texture unit 6. Until it is loaded the shader falls back
// to a flat grey disc (uMoonTexOn = 0), so a missing asset never blanks
// the moon.
if (!moonTexture && !moonTexFailed) loadMoonTexture(gl);
gl.activeTexture(gl.TEXTURE6);
gl.bindTexture(gl.TEXTURE_2D, moonTexture || s.blackTex);
gl.uniform1i(s.uMoonTex, 6);
gl.uniform1f(s.uMoonTexOn, moonTexture ? 1.0 : 0.0);
gl.uniform1f(s.uMoonR, MOON_ANG_R);
gl.uniform1f(s.uMoonEarth, MOON_EARTHSHINE);
gl.uniform1f(s.uMoonLimb, MOON_LIMB_DARK);
gl.uniform1f(s.uMoonBright, MOON_BRIGHT);
gl.uniform1f(s.uMoonHalo, MOON_HALO);
gl.uniform1f(s.uMoonFlipU, MOON_FLIP_U);
gl.uniform1f(s.uMoonFlipV, MOON_FLIP_V);
gl.uniform1f(s.uSunR, SUN_ANG_R);
// texture path: RA alpha must land at world angle alpha + PI/2 - LST,
// which with u = fract(0.5 + (H + uStarLon)/2PI) means uStarLon = LST - 3PI/2
var _sidTex = starSidereal();
@@ -4943,12 +5085,13 @@ window.clouds3dRender = function(target) {
var _star = starAmbient(sunAlt);
var _mBeam = beamColor(_moonAlt), _mL = beamLuma(_moonAlt);
var _moonUp = Math.max(0, Math.sin(_moonAlt));
var _moonI = ATMO_MOON_GAIN * _moonFrac * _moonUp * _mL;
var _moonI = ATMO_MOON_GAIN * MOON_LIGHT_GAIN * _moonFrac * _moonUp * _mL;
// diffuse moonlight: the whole sky gets brighter with the phase, not
// only the side the moon happens to point at
var _moonDiff = ATMO_MOON_GAIN * _moonFrac * _moonUp * 0.9;
var _moonDiff = ATMO_MOON_GAIN * MOON_LIGHT_GAIN * _moonFrac * _moonUp * 0.9;
var _amb = skyAmbient(sunAlt) + _moonDiff;
var _air = airlightColor(sunAlt, _moonAlt, _moonFrac);
gl.uniform1f(s.uMoonFrac, _moonFrac);
// Theme tint on top of the physics (see themeLighting above). The
// exposure factor ramps from gain (day) to nightGain (night) with the
// same altitude ramp the terrain uses for its night cross-fade.
@@ -5054,6 +5197,33 @@ window.clouds3dRender = function(target) {
if (camY < CAM_MIN_Y) camY = CAM_MIN_Y;
else if (camY > CAM_MAX_Y) camY = CAM_MAX_Y;
var pitch = BOB_ON ? 0.45 - BOB_TILT * Math.cos(bob) : 0.45;
// ---- aim override: look at the moon, fly the same path -------------
// __currentMoonDir is a unit vector in the same world frame the view
// matrix uses, so the heading is just its horizontal part and the pitch
// is its altitude (negative pitch = above the horizon). Below the
// horizon the view holds the moon's azimuth at the horizon line instead
// of snapping back to the flight heading.
if (CAM_AIM_MOON) {
var _md = window.__currentMoonDir;
if (_md) {
var _hlen = Math.hypot(_md[0], _md[2]);
if (_hlen > 1e-4) {
var _alt = Math.asin(Math.max(-1, Math.min(1, _md[1])));
if (_alt < 0.02) _alt = 0.02;
else if (_alt > 1.40) _alt = 1.40;
var _tx = _md[0] / _hlen, _tz = _md[2] / _hlen, _tp = -_alt;
if (!aimReady) { aimHdX = _tx; aimHdZ = _tz; aimPitch = _tp; aimReady = true; }
var _k = 1 - CAM_AIM_EASE;
aimHdX += (_tx - aimHdX) * _k;
aimHdZ += (_tz - aimHdZ) * _k;
aimPitch += (_tp - aimPitch) * _k;
var _al = Math.hypot(aimHdX, aimHdZ) || 1;
hdX = aimHdX / _al;
hdZ = aimHdZ / _al;
pitch = aimPitch - (BOB_ON ? BOB_TILT * Math.cos(bob) * CAM_AIM_BOB : 0);
}
}
}
var aspect = canvas.width / canvas.height;
// The original cloud shader used a non-standard (wider) vertical fov
// so the top of the screen looked above the horizon (rd.y ~ 0.2 at
@@ -5139,6 +5309,9 @@ window.clouds3dRender = function(target) {
? -((window.__currentDayProgress || 0) * 6.2831853)
: 1.5707963267948966 - _sidPts);
gl.uniform1f(skyPts.u.sizeScale, canvas.height / 900);
var _mpt = window.__currentMoonDir || [0, 1, 0];
gl.uniform3f(skyPts.u.moonDir, _mpt[0], _mpt[1], _mpt[2]);
gl.uniform1f(skyPts.u.moonR, MOON_ANG_R);
gl.uniform1f(skyPts.u.night, starNight);
gl.uniform1f(skyPts.u.time, now);
gl.uniform1f(skyPts.u.bright, STAR_BRIGHT);
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB