Commit Graph
177 Commits
Author SHA1 Message Date
Richard Zink de471dcb8c Fix rebuildGroundMosaic .then(): WebGL constants via groundGl
Inside the Promise.all().then() callback, the WebGL constants
(gl.TEXTURE_2D, gl.RGBA, gl.UNSIGNED_BYTE) were still bare 'gl.',
not groundGl.*. Fixed.
2026-10-07 20:51:31 +02:00
Richard Zink 447ff824af Fix initGroundSystem: WebGL constants must use groundGl, not gl
Last commit fixed gl calls inside the .then() but missed the
calls in the body of initGroundSystem itself. Replaced every
gl.X reference with groundGl.X in the new code.
2026-10-07 20:50:54 +02:00
Richard Zink 19139b01c7 Fix runtime tile system: gl out of scope in rebuildGroundMosaic
The function used bare 'gl' but it's a module-level closure, so
gl wasn't defined inside the .then() callback. Added groundGl
module variable, set it in initGroundSystem, used it everywhere
in rebuildGroundMosaic instead of bare 'gl'.
2026-10-07 20:49:59 +02:00
Richard Zink 979134102d Runtime tile fetching for ground texture (ESRI)
Replace static ground.jpg with a dynamic tile-based system that
fetches new tiles from ESRI World Imagery as the camera moves
through the world.

- GROUND_TILE_Z = 11 (~13 km per tile)
- GROUND_MOSAIC_SIZE = 9 (9x9 = 81 tiles, 2304x2304 canvas)
- GROUND_BUFFER_TILES = 2 (refetch when within 2 tiles of edge)
- Passau tile at zoom 11: x=1102, y=710

Shader:
- Two new uniforms: uGroundCenter (mosaic centre in world units),
  uGroundSize (mosaic side length = 9)
- gUv = (hitPoint - uGroundCenter) / uGroundSize + 0.5 so the
  mosaic centre stays at UV (0.5, 0.5) as the camera drifts

Runtime:
- maybeRefetchGround() called every 1.5s with current camera
  position; if camera is in the buffer zone, mosaic centre is
  shifted and missing tiles re-fetched in parallel
- Canvas re-uploaded to GPU after each rebuild

Camera can now roam indefinitely — every time we approach the
mosaic edge, a fresh 9x9 of tiles is fetched.
2026-10-07 20:48:25 +02:00
Richard Zink 052bbfbf49 Push ground horizon lower in frame
gmask smoothstep end -0.10 -> -0.30: ground only fully fills when
ray Y is below -0.30 (about 17° below horizon). The horizon line
sits higher in the frame, more sky visible above.
2026-10-07 20:44:30 +02:00
Richard Zink 4e035c9c35 Fix clouds3d: same JS-comment-in-FRAG_SRC-array bug as before
Wrapped the multi-line horizon comment into a single quoted
string. Same bug as the earlier fix but in the new ground code.
Going forward I'll make sure all multi-line comments inside
FRAG_SRC are wrapped in quotes.
2026-10-07 20:42:00 +02:00
Richard Zink 2ad51141c6 Ground now uses real ray cast onto y=0 plane
Instead of mapping rd.x/rd.y to UV directly (which gave a flat
orthographic look), compute where the ray actually intersects
the ground plane and sample the satellite texture at that world
(x,z) coordinate. As the camera drifts forward (uTime), the
ground scrolls, giving the illusion of flight over real terrain.

- tGround = -ro.y / rd.y (with rd.y < 0 guard)
- gUv = (hitPoint.x, hitPoint.z) * 0.04 + 0.5
- Atmospheric perspective: ground fades toward sky with distance
  (fog starts at hit-distance 0, full at 30 world units)
- gmask 1.0 only when tGround > 0 (camera above ground AND ray
  pointing down), 0 otherwise
2026-10-07 20:40:46 +02:00
Richard Zink c9159a8876 Fix clouds3d: temporal dead zone on 's' variable
loadImageTexture(s.gl, ...) referenced 's' before its 'var s =
canvas.__state' declaration later in the function. Used
canvas.__state.gl directly since __state was just assigned.
2026-10-07 20:38:49 +02:00
Richard Zink 40fcea0548 Fix clouds3d: JS line comments inside FRAG_SRC array broke parsing
When I added the ground-texture code I left the comment as
multiple JS-line-comments ('// Horizon...') between strings.
Those are NOT inside any string, so JS sees:
  '// Horizon: ... satellite    <- opens string, never closes
  // ground texture ...           <- JS thinks these are line comments
  ...
The string stays open until the next ',, then the next string
line parses as a continuation of an unterminated string and the
whole file is rejected. Wrap the comment into one quoted string.
2026-10-07 20:37:58 +02:00
Richard Zink 6323beccf9 Add satellite ground texture (ESRI World Imagery, Passau)
Pulled a 5x4 tile mosaic of the Passau region (zoom 10) from
ESRI's free ArcGIS World Imagery service, stitched + resized to
1024x768 JPEG (277 KB). Added to Dockerfile as ground.jpg.

Shader side:
- New uniform uGround (texture unit 1)
- horizonMask: rd.y in [-0.20, -0.02] -> ground fades in from 0
  to 1 as we look further down
- gUv derived from rd.xz so looking forward/down scrolls across
  the satellite image (parallax-like feel)
- Distant ground fades toward uBg via a 'fog' factor so the horizon
  blends naturally with the sky gradient

Loading is async — clouds render before ground texture arrives.
Falls back to a black sample if ground.jpg fails to load.
2026-10-07 20:15:53 +02:00
Richard Zink 2e745c26ad Soften night sky: 55% bright (was 85%), wider star band
- skyDarkness 0.85 -> 0.55 so night sky is deep but not pitch
  black, leaving room for stars to show through
- skyMask widened (-0.10 -> -0.20 lower bound, 0.30 -> 0.40 upper)
  so stars appear closer to the horizon in the cockpit view
- Star tint multiplier 0.9 -> 1.0 (full bright) for clearer dots
2026-10-07 20:12:32 +02:00
Richard Zink f567ebf768 Reduce cloud structure: 2 layers, 3 octaves
- fbm3: 4 -> 3 octaves
- cloudDensity: 4 layers (base + mid + fine1 + fine2) -> 2 layers
  (base + mid at 20%)
Cost per pixel: 16 noise3 -> 6 noise3 per step, ~62% cheaper.
Visual: clouds still have secondary detail but the fine wispy
edges are gone — back to the cleaner silhouette.
2026-10-07 20:11:13 +02:00
Richard Zink 7cb04686c7 Fix clouds3d: declare outer cloud variable
'cloud' was referenced outside the for-loop where it was
defined. Use 1.0 - trans (transmittance) as a cloud mask
instead — same information, properly scoped.
2026-10-07 20:09:56 +02:00
Richard Zink 48160b730f Dark night sky + slow star twinkle + moonlit clouds
clouds3d:
- Star twinkle: 3 Hz ±45% -> 0.5 Hz ±15% (gentle scintillation
  instead of static-like flickering)
- Night darkening: applied only to sky pixels via the cloud mask,
  not to clouds. The darkening factor jumped 0.45 -> 0.85 so the
  night sky really does go black around moonlit clouds. Clouds
  keep their uLight-tinted colour so they remain visible.

clouds2d:
- Same darkening approach but via multiplicative factor (no cloud
  mask in the 2D shader): col *= 1 - nightFactor * 0.7
2026-10-07 20:09:08 +02:00
Richard Zink 595b22aac1 More cloud structure: 4 fBm layers, 4 octaves each
Density reverted to original 0.20/0.70/1.00/0.45 but structure
denser than ever:
- fbm3: 3 -> 4 octaves (more detail at fine scales)
- cloudDensity: 4 layers (base + mid 35% + fine1 18% + fine2 10%)
- smoothstep 0.24 -> 0.20 for tighter cloud edges
Cost: 4 fbm * 4 octaves = 16 noise3 samples per step * 18 steps
= 288 samples per pixel. Watch FPS on low-end GPUs.
2026-10-07 20:07:20 +02:00
Richard Zink 42c078a407 Permanent above-clouds cruise + more structure
- Remove dive component from camY. Was mix(2.0, 1.2, diveT)
  (occasional dips into cloud top); now fixed at 2.2, always above
  the y=1.5 cloud ceiling. Cloud deck always visible below.
- WEATHER_DENSITY bumped again: sunny 0.55->0.65, cloudy 0.85->0.90,
  night 0.65->0.75. Even 'sunny' is now a partly-cloudy sky.
- clouds3d: third fBm detail layer at 3.2x frequency (was 2 layers).
  Contributions: base 1.0, mid 0.30, fine 0.12. Combined with the
  tighter smoothstep (0.18 -> 0.16) the silhouettes have wispier,
  more interesting edges instead of soft blobs.
- Same density bump in clouds2d for consistency.
2026-10-07 20:05:39 +02:00
Richard Zink 9a933b3dc4 More clouds + finer structure, switch default back to clouds3d
- WEATHER_DENSITY bumped: sunny 0.20->0.55, cloudy 0.70->0.85,
  night 0.45->0.65. Stormy stays at 1.0 (already maxed).
  Even 'sunny' now has visible cloud structure, not just clear sky.
- clouds3d: add a second fBm layer at 4x frequency for fine detail,
  blended at 18% into the base. Combined with the tighter smoothstep
  (0.24 -> 0.18) the cloud silhouettes have more interesting edges
  instead of soft blob shapes.
- Same density bump applied to clouds2d so swapping renderers
  doesn't change the visual weather.
- config.renderer: clouds2d -> clouds3d (user request)
2026-10-07 20:01:52 +02:00
Richard Zink 673ed6cf0f Fix night sky: visible clouds, real moon-driven darkening
Three coordinated changes:
1. nightPaletteNew.cloud: void -> raised (slightly lighter than
   sky) so clouds have contrast against the new-moon sky
2. nightPaletteFull.cloudShadow: panel -> void so cloud undersides
   fade into the night sky, while the lit topsides (border) stand
   out clearly
3. getNightColor() now keeps 'light' (moonlight) bright and only
   partially darkens sky/bg. The previous version squashed
   everything toward black, killing cloud structure.

Shader side:
- max(0.15, uDayProgress) keeps a baseline moonlight tint on the
  lit side of clouds even when uDayProgress happens to be 0
- The final darken pass is now driven by nightFactor (sunAlt-based)
  instead of the unreliable uDayProgress
2026-10-07 19:25:50 +02:00
Richard Zink 65af0bf2f0 clouds3d: anti-flicker dither + softer smoothstep
The cloud horizon flickered when the camera was far from the
deck because the regular raymarch steps aligned with the cloud
boundary. Two cheap fixes:
- Per-pixel jitter (hash21 of gl_FragCoord + uTime) shifts each
  ray's start by up to one stepSize; the brain averages the
  random offsets into a smooth edge
- Wider smoothstep (0.18 -> 0.24) softens the binary cloud/no-cloud
  boundary the jitter is now dithering across
2026-10-07 19:16:54 +02:00
Richard Zink 5e66cdfa2b clouds3d: closer cruise, sun-alt-driven stars
- Cruise altitude 2.6 -> 2.0 (just above cloud ceiling y=1.5,
  gives the 'just above the clouds' airliner feel)
- Expose __currentSunAlt (radians) from animate() so the
  renderer can tell night from day without misreading
  realDayProgress (which mixes pre-dawn + post-sunset hours)
- Replace (1 - uDayProgress) star gate by smoothstep over uSunAlt:
  nightFactor = 1 when sun is well below horizon, 0 when above
- Widen sky band so stars are visible from the horizon upward
  (cockpit-perspective frame is mostly below rd.y=0.3)
2026-10-07 19:14:46 +02:00
Richard Zink d55f2dc1d4 Tighten cruise altitude and shallow out dives
- Cloud ceiling lowered from y=1.8 to y=1.5 (thinner deck)
- Cruise raised from y=2.0 to y=2.6 (clear margin above clouds)
- Dive endpoint raised from y=0.9 to y=1.2 (skims top, never deep)
- Dive exponent 5 -> 8 (much rarer dives)
- Early-out and ceiling bounds updated to match new y=1.5
2026-10-07 19:10:54 +02:00
Richard Zink 47ce467c4a Fix clouds3d: remove orphan closing brace
A leftover '},' from the previous patch was producing an extra
closing brace at line 55 of the joined GLSL, causing '}' syntax
errors on every frame.
2026-10-07 19:08:48 +02:00
Richard Zink 468e66058e Fix clouds3d: move starField out of main() (GLSL syntax)
GLSL does not allow nested function definitions; the starField()
helper must live at top-level alongside hash3/noise3/fbm3.
2026-10-07 19:04:50 +02:00
Richard Zink ee821dd724 Airliner cockpit view + night stars
- Cruise altitude lowered to y=2.0 (just above cloud top at y=1.8)
- Look direction nearly level (rd.y ~ -0.05 at horizon line)
- Stars rendered as 3D hash-grid sphere, only above horizon and
  only when uDayProgress < 1 (i.e. at night). Subtle sin twinkle
  keeps them from looking like static dither.
2026-10-07 19:02:06 +02:00
Richard Zink e80d255d58 Fly mostly above the clouds with brief dips
Camera altitude is now driven by pow(sin,5) so the view stays at
y=2.6 most of the time and only briefly dips to y=0.9 to skim the
top of the cloud deck. Marching also skips the empty gap between
camera and cloud ceiling when looking down from above.
2026-10-07 18:57:55 +02:00
Richard Zink 433d743446 Tune clouds3d for low-end GPUs: 18 steps, 3 octaves
Reduce raymarch iterations from 28 to 18 and fBm octaves from
4 to 3 (with step size 0.55->0.85 to preserve cloud coverage).
Per-pixel cost drops by ~55% while visual density stays similar.
2026-10-07 18:56:40 +02:00
Richard Zink 0648676974 Add volumetric clouds3d renderer
Raymarches a horizontal cloud layer (28 steps, 4-octave 3D fBm).
Camera path is sinusoidal in altitude so the view dips into the
cloud deck at intervals for a 'fly through the edges' feel.
Density threshold from weather: sunny -> sparse, stormy -> overcast.
Renderer is selected via CONFIG.renderer ('clouds2d' or 'clouds3d').
2026-10-07 18:54:58 +02:00