diff --git a/background.html b/background.html
index 49ac6af..fedf31b 100644
--- a/background.html
+++ b/background.html
@@ -516,6 +516,21 @@ var noiseDataUri = 'data:image/png;base64,PLACEHOLDER';
// URL override for easy A/B testing: #renderer=clouds2d / #renderer=clouds3d
var _urlRenderer = (location.hash.match(/renderer=(\w+)/) || [])[1];
var renderer = _urlRenderer || (window.CONFIG && window.CONFIG.renderer) || 'vanta';
+
+// Flight direction for ground-tile corridor (N, NE, E, SE, S, SW, W, NW).
+// Default 'N' (= +Z world) so behaviour is unchanged when hash is absent.
+var _urlFlight = (location.hash.match(/flight=(\w+)/) || [])[1];
+var FLIGHT_DIRS = {
+ N: { cos: 0.0, sin: 1.0, dx: 0, dz: 1 },
+ NE: { cos: 0.7071068, sin: 0.7071068, dx: 0.7071068, dz: 0.7071068 },
+ E: { cos: 1.0, sin: 0.0, dx: 1, dz: 0 },
+ SE: { cos: 0.7071068, sin:-0.7071068, dx: 0.7071068, dz:-0.7071068 },
+ S: { cos: 0.0, sin:-1.0, dx: 0, dz:-1 },
+ SW: { cos:-0.7071068, sin:-0.7071068, dx:-0.7071068, dz:-0.7071068 },
+ W: { cos:-1.0, sin: 0.0, dx:-1, dz: 0 },
+ NW: { cos:-0.7071068, sin: 0.7071068, dx:-0.7071068, dz: 0.7071068 }
+};
+window.__flightDir = FLIGHT_DIRS[_urlFlight && FLIGHT_DIRS[_urlFlight.toUpperCase()] ? _urlFlight.toUpperCase() : 'N'];
var useVanta = (renderer !== 'clouds2d' && renderer !== 'clouds3d');
var effect;
if (useVanta && typeof VANTA !== 'undefined') {
diff --git a/vendor/clouds3d.js b/vendor/clouds3d.js
index c779ff5..e8e0fb4 100644
--- a/vendor/clouds3d.js
+++ b/vendor/clouds3d.js
@@ -139,7 +139,11 @@ var FRAG_SRC = [
'float drift = uTime * uSpeed * 0.12;',
// Fixed altitude well above the cloud ceiling (y=1.5).
'float camY = 2.2;',
- 'vec3 ro = vec3(0.0, camY, drift);',
+ // Camera position follows window.__flightDir (set from URL #flight=...).
+ // Default: drift along +Z (north). For east: drift along +X. For south: -Z, etc.
+ 'float fdx = (window.__flightDir ? window.__flightDir.dx : 0.0);',
+ 'float fdz = (window.__flightDir ? window.__flightDir.dz : 1.0);',
+ 'vec3 ro = vec3(drift * fdx, camY, drift * fdz);',
// Ray direction: tilt more downward so the visible ground fits
// within the mosaic. At uv.y = 0 the ray goes ~26° below
// horizontal, putting the visible ground at tGround < 5
@@ -281,10 +285,11 @@ function init(canvas) {
var GROUND_TILE_URL = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/';
var GROUND_TILE_Z = 11;
var GROUND_TILE_SIZE = 256;
-// Camera travels in +Z (forward) and looks in +Z (also forward).
-// The corridor is aligned with both motion and look direction.
-var GROUND_TRAVEL_COS = 0.0; // cos(90 deg)
-var GROUND_TRAVEL_SIN = 1.0; // sin(90 deg)
+// Camera flight direction. Set from window.__flightDir (URL: #flight=N|NE|E|...).
+// Default is N (= +Z forward). Each flight direction rotates the
+// corridor so its long axis follows the camera path.
+var GROUND_TRAVEL_COS = (window.__flightDir && window.__flightDir.cos) || 0.0;
+var GROUND_TRAVEL_SIN = (window.__flightDir && window.__flightDir.sin) || 1.0;
var GROUND_CORRIDOR_LONG = 25; // tiles along travel direction (~3.5 min of flight at uSpeed=1)
var GROUND_CORRIDOR_PERP = 5; // tiles perpendicular to travel
var GROUND_BUFFER_LONG = 11; // refetch when this close to forward edge
@@ -375,8 +380,13 @@ function rebuildGroundMosaic(centerTileX, centerTileY) {
// Called periodically from the render loop with the camera world
// position. Re-centres the mosaic if we're approaching its edge.
function maybeRefetchGround(cameraWorldX, cameraWorldZ, gl) {
- var cameraTileX = GROUND_BASE_X + Math.floor(cameraWorldX);
- var cameraTileY = GROUND_BASE_Y + Math.floor(cameraWorldZ);
+ var fdx = (window.__flightDir && window.__flightDir.dx) || 0;
+ var fdz = (window.__flightDir && window.__flightDir.dz) || 1;
+ // Camera position in world (X, Z) from the dominant flight direction
+ var camX = (fdx !== 0) ? cameraWorldX * fdx : 0;
+ var camZ = (fdz !== 0) ? cameraWorldZ * fdz : 0;
+ var cameraTileX = GROUND_BASE_X + Math.floor(camX);
+ var cameraTileY = GROUND_BASE_Y + Math.floor(camZ);
// Project camera offset onto the corridor's rotated axes.
// With GROUND_TRAVEL_COS/SIN = (0, 1) the corridor runs along
// +Z (camera's motion + look direction).
@@ -536,7 +546,7 @@ window.clouds3dRender = function(target) {
var drift = now * speed * 0.12;
if (performance.now() - groundLastCheck > 1500) {
groundLastCheck = performance.now();
- maybeRefetchGround(drift, drift * 0.7, gl);
+ maybeRefetchGround(drift, drift, gl);
}
gl.drawArrays(gl.TRIANGLES, 0, 3);