diff --git a/vendor/clouds3d.js b/vendor/clouds3d.js index 3ed57be..aecee64 100644 --- a/vendor/clouds3d.js +++ b/vendor/clouds3d.js @@ -55,7 +55,7 @@ var FRAG_SRC = [ 'uniform float uTime;', 'uniform sampler2D uGround;', 'uniform vec2 uGroundCenter;', // current mosaic centre (world units) - 'uniform float uGroundSize;', // mosaic side length (world units) + 'uniform vec2 uGroundSize;', // mosaic side extents in X and Z (world units) 'uniform vec3 uSky;', 'uniform vec3 uCloud;', 'uniform vec3 uCloudShadow;', @@ -270,8 +270,13 @@ 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; -var GROUND_MOSAIC_SIZE = 9; // 9x9 tiles around the camera (~81 tiles, fast initial load) -var GROUND_BUFFER_TILES = 2; // refetch when this close to edge +// Camera travels at uTime * uSpeed * 0.45 in +X (and +X*0.7 in +Z). +// A corridor elongated in X gives more forward coverage per tile. +// 13 tiles along X (≈13s of travel) x 5 tiles wide in Z = 65 tiles. +var GROUND_CORRIDOR_X = 13; // tiles along travel direction +var GROUND_CORRIDOR_Z = 5; // tiles perpendicular +var GROUND_BUFFER_X = 2; // refetch when this close to forward edge +var GROUND_BUFFER_Z = 1; // refetch when this close to side edge // Passau tile at zoom 11 var GROUND_BASE_X = 1099; var GROUND_BASE_Y = 706; @@ -296,22 +301,25 @@ function fetchTileImage(tileX, tileY) { }); } -function drawTileAt(tileX, tileY, centerTile) { - var r = Math.floor(GROUND_MOSAIC_SIZE / 2); - var dx = (tileX - centerTile.x + r) * GROUND_TILE_SIZE; - var dy = (tileY - centerTile.y + r) * GROUND_TILE_SIZE; - if (dx < 0 || dx >= groundCanvas.width || dy < 0 || dy >= groundCanvas.height) { +function drawTileAt(dx, dz, centerTileX, centerTileY) { + var tileX = centerTileX + dx; + var tileY = centerTileY + dz; + var halfX = Math.floor(GROUND_CORRIDOR_X / 2); + var halfZ = Math.floor(GROUND_CORRIDOR_Z / 2); + var canvasX = (dx + halfX) * GROUND_TILE_SIZE; + var canvasY = (dz + halfZ) * GROUND_TILE_SIZE; + if (canvasX < 0 || canvasX >= groundCanvas.width || canvasY < 0 || canvasY >= groundCanvas.height) { return Promise.resolve(false); } return fetchTileImage(tileX, tileY).then(function(img) { - groundCtx.drawImage(img, dx, dy); + groundCtx.drawImage(img, canvasX, canvasY); groundLoadedTiles[tileX + '_' + tileY] = true; return true; }).catch(function() { // On failure draw a neutral gray so the gap is at least // not the bright fog colour groundCtx.fillStyle = '#3a4458'; - groundCtx.fillRect(dx, dy, GROUND_TILE_SIZE, GROUND_TILE_SIZE); + groundCtx.fillRect(canvasX, canvasY, GROUND_TILE_SIZE, GROUND_TILE_SIZE); return false; }); } @@ -320,14 +328,15 @@ function drawTileAt(tileX, tileY, centerTile) { // and loads every tile in the new range. function rebuildGroundMosaic(centerTileX, centerTileY) { groundCenterTile = { x: centerTileX, y: centerTileY }; - var r = Math.floor(GROUND_MOSAIC_SIZE / 2); + var halfX = Math.floor(GROUND_CORRIDOR_X / 2); + var halfZ = Math.floor(GROUND_CORRIDOR_Z / 2); // Clear with fog colour so the texture is never undefined groundCtx.fillStyle = '#7c9aa1'; groundCtx.fillRect(0, 0, groundCanvas.width, groundCanvas.height); var promises = []; - for (var x = centerTileX - r; x <= centerTileX + r; x++) { - for (var y = centerTileY - r; y <= centerTileY + r; y++) { - promises.push(drawTileAt(x, y, groundCenterTile)); + for (var dx = -halfX; dx <= halfX; dx++) { + for (var dz = -halfZ; dz <= halfZ; dz++) { + promises.push(drawTileAt(dx, dz, centerTileX, centerTileY)); } } return Promise.all(promises).then(function() { @@ -343,10 +352,15 @@ function rebuildGroundMosaic(centerTileX, centerTileY) { function maybeRefetchGround(cameraWorldX, cameraWorldZ, gl) { var cameraTileX = GROUND_BASE_X + Math.floor(cameraWorldX); var cameraTileY = GROUND_BASE_Y + Math.floor(cameraWorldZ); - var r = Math.floor(GROUND_MOSAIC_SIZE / 2); - var dx = Math.abs(cameraTileX - groundCenterTile.x); - var dy = Math.abs(cameraTileY - groundCenterTile.y); - if (dx > r - GROUND_BUFFER_TILES || dy > r - GROUND_BUFFER_TILES) { + var halfX = Math.floor(GROUND_CORRIDOR_X / 2); + var halfZ = Math.floor(GROUND_CORRIDOR_Z / 2); + var dx = cameraTileX - groundCenterTile.x; + var dy = cameraTileY - groundCenterTile.y; + // Only refetch when camera approaches the FRONT of the corridor + // (the only direction the camera moves toward). Side and back + // buffers keep refetches minimal. + if (dx > halfX - GROUND_BUFFER_X || dx < -halfX + GROUND_BUFFER_X || + dy > halfZ - GROUND_BUFFER_Z || dy < -halfZ + GROUND_BUFFER_Z) { if (groundUpdatePending) return; groundUpdatePending = true; rebuildGroundMosaic(cameraTileX, cameraTileY).then(function() { @@ -361,8 +375,8 @@ function initGroundSystem(glCtx) { if (groundCanvas) return; // already initialised groundGl = glCtx; groundCanvas = document.createElement('canvas'); - groundCanvas.width = GROUND_TILE_SIZE * GROUND_MOSAIC_SIZE; - groundCanvas.height = GROUND_TILE_SIZE * GROUND_MOSAIC_SIZE; + groundCanvas.width = GROUND_TILE_SIZE * GROUND_CORRIDOR_X; + groundCanvas.height = GROUND_TILE_SIZE * GROUND_CORRIDOR_Z; groundCtx = groundCanvas.getContext('2d'); // Pre-fill with fog colour so partially-loaded mosaic still // shows the right base colour @@ -487,7 +501,7 @@ window.clouds3dRender = function(target) { y: groundCenterTile.y - GROUND_BASE_Y }; gl.uniform2f(s.uGroundCenter, centerWorld.x, centerWorld.y); - gl.uniform1f(s.uGroundSize, GROUND_MOSAIC_SIZE); + gl.uniform2f(s.uGroundSize, GROUND_CORRIDOR_X, GROUND_CORRIDOR_Z); var drift = now * speed * 0.45; if (performance.now() - groundLastCheck > 1500) {