diff --git a/background.html b/background.html
index fedf31b..6ce828a 100644
--- a/background.html
+++ b/background.html
@@ -513,24 +513,45 @@ window.__clouds2dStarted = true;
try {
window.__noiseDataUri = 'data:image/png;base64,PLACEHOLDER';
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];
+// Parse URL hash params: #key=value&key=value...
+// Supported: #renderer=clouds2d|clouds3d|vanta, #flight=N|NE|E|SE|S|SW|W|NW,
+// #fetch=N|NE|E|SE|S|SW|W|NW
+function _parseHashParams(hash) {
+ var out = {};
+ hash = (hash || '').replace(/^#/, '');
+ var pairs = hash.split(/[&;]/);
+ for (var i = 0; i < pairs.length; i++) {
+ var eq = pairs[i].indexOf('=');
+ if (eq > 0) {
+ out[pairs[i].slice(0, eq).toLowerCase()] = pairs[i].slice(eq + 1);
+ }
+ }
+ return out;
+}
+var _hashParams = _parseHashParams(location.hash);
+
+// Renderer override
+var _urlRenderer = _hashParams.renderer;
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];
+// Compass directions for flight and fetch (independent). Each direction
+// has cos/sin (corridor rotation matrix) and dx/dz (camera motion unit vec).
var FLIGHT_DIRS = {
- N: { cos: 0.0, sin: 1.0, dx: 0, dz: 1 },
+ 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 },
+ 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 },
+ 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 },
+ 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'];
+function _pickDir(key) {
+ var raw = (_hashParams[key] || 'N').toUpperCase();
+ return FLIGHT_DIRS[raw] || FLIGHT_DIRS.N;
+}
+window.__flightDir = _pickDir('flight');
+window.__fetchDir = _pickDir('fetch');
var useVanta = (renderer !== 'clouds2d' && renderer !== 'clouds3d');
var effect;
if (useVanta && typeof VANTA !== 'undefined') {
diff --git a/vendor/clouds3d.js b/vendor/clouds3d.js
index e8e0fb4..a57cb5a 100644
--- a/vendor/clouds3d.js
+++ b/vendor/clouds3d.js
@@ -285,11 +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 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;
+// Fetch direction: which way the tile corridor is oriented.
+// Independent of flight direction (set via #fetch=N|NE|E|...).
+// Default N (= +Z, canvas Y = +Z).
+var GROUND_TRAVEL_COS = (window.__fetchDir && window.__fetchDir.cos) || 0.0;
+var GROUND_TRAVEL_SIN = (window.__fetchDir && window.__fetchDir.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