Replace square mosaic with corridor elongated in travel direction
The camera moves primarily in +X (drift = uTime * uSpeed * 0.45) with a slight +Z component (drift * 0.7). A square 9x9 mosaic wastes half its tiles behind the camera. A corridor shape with 13 tiles along travel (X) and 5 tiles perpendicular (Z) uses 65 tiles to cover the same useful area — and refetches happen only when the camera approaches the forward edge, not all four sides. - GROUND_CORRIDOR_X = 13 (≈13s of forward travel at uSpeed=1) - GROUND_CORRIDOR_Z = 5 - GROUND_BUFFER_X = 2, GROUND_BUFFER_Z = 1 - Shader uniform uGroundSize is now vec2 (sizeX, sizeZ) - UV mapping: gUv = (hitPoint - centre) / size + 0.5 with per-axis size
This commit is contained in:
1 parent
e80a270b05
commit
47f2c16eba
1 file changed
+35
-21
Vendored
+35
-21
@@ -55,7 +55,7 @@ var FRAG_SRC = [
|
|||||||
'uniform float uTime;',
|
'uniform float uTime;',
|
||||||
'uniform sampler2D uGround;',
|
'uniform sampler2D uGround;',
|
||||||
'uniform vec2 uGroundCenter;', // current mosaic centre (world units)
|
'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 uSky;',
|
||||||
'uniform vec3 uCloud;',
|
'uniform vec3 uCloud;',
|
||||||
'uniform vec3 uCloudShadow;',
|
'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_URL = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/';
|
||||||
var GROUND_TILE_Z = 11;
|
var GROUND_TILE_Z = 11;
|
||||||
var GROUND_TILE_SIZE = 256;
|
var GROUND_TILE_SIZE = 256;
|
||||||
var GROUND_MOSAIC_SIZE = 9; // 9x9 tiles around the camera (~81 tiles, fast initial load)
|
// Camera travels at uTime * uSpeed * 0.45 in +X (and +X*0.7 in +Z).
|
||||||
var GROUND_BUFFER_TILES = 2; // refetch when this close to edge
|
// 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
|
// Passau tile at zoom 11
|
||||||
var GROUND_BASE_X = 1099;
|
var GROUND_BASE_X = 1099;
|
||||||
var GROUND_BASE_Y = 706;
|
var GROUND_BASE_Y = 706;
|
||||||
@@ -296,22 +301,25 @@ function fetchTileImage(tileX, tileY) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawTileAt(tileX, tileY, centerTile) {
|
function drawTileAt(dx, dz, centerTileX, centerTileY) {
|
||||||
var r = Math.floor(GROUND_MOSAIC_SIZE / 2);
|
var tileX = centerTileX + dx;
|
||||||
var dx = (tileX - centerTile.x + r) * GROUND_TILE_SIZE;
|
var tileY = centerTileY + dz;
|
||||||
var dy = (tileY - centerTile.y + r) * GROUND_TILE_SIZE;
|
var halfX = Math.floor(GROUND_CORRIDOR_X / 2);
|
||||||
if (dx < 0 || dx >= groundCanvas.width || dy < 0 || dy >= groundCanvas.height) {
|
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 Promise.resolve(false);
|
||||||
}
|
}
|
||||||
return fetchTileImage(tileX, tileY).then(function(img) {
|
return fetchTileImage(tileX, tileY).then(function(img) {
|
||||||
groundCtx.drawImage(img, dx, dy);
|
groundCtx.drawImage(img, canvasX, canvasY);
|
||||||
groundLoadedTiles[tileX + '_' + tileY] = true;
|
groundLoadedTiles[tileX + '_' + tileY] = true;
|
||||||
return true;
|
return true;
|
||||||
}).catch(function() {
|
}).catch(function() {
|
||||||
// On failure draw a neutral gray so the gap is at least
|
// On failure draw a neutral gray so the gap is at least
|
||||||
// not the bright fog colour
|
// not the bright fog colour
|
||||||
groundCtx.fillStyle = '#3a4458';
|
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;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -320,14 +328,15 @@ function drawTileAt(tileX, tileY, centerTile) {
|
|||||||
// and loads every tile in the new range.
|
// and loads every tile in the new range.
|
||||||
function rebuildGroundMosaic(centerTileX, centerTileY) {
|
function rebuildGroundMosaic(centerTileX, centerTileY) {
|
||||||
groundCenterTile = { x: centerTileX, y: 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
|
// Clear with fog colour so the texture is never undefined
|
||||||
groundCtx.fillStyle = '#7c9aa1';
|
groundCtx.fillStyle = '#7c9aa1';
|
||||||
groundCtx.fillRect(0, 0, groundCanvas.width, groundCanvas.height);
|
groundCtx.fillRect(0, 0, groundCanvas.width, groundCanvas.height);
|
||||||
var promises = [];
|
var promises = [];
|
||||||
for (var x = centerTileX - r; x <= centerTileX + r; x++) {
|
for (var dx = -halfX; dx <= halfX; dx++) {
|
||||||
for (var y = centerTileY - r; y <= centerTileY + r; y++) {
|
for (var dz = -halfZ; dz <= halfZ; dz++) {
|
||||||
promises.push(drawTileAt(x, y, groundCenterTile));
|
promises.push(drawTileAt(dx, dz, centerTileX, centerTileY));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.all(promises).then(function() {
|
return Promise.all(promises).then(function() {
|
||||||
@@ -343,10 +352,15 @@ function rebuildGroundMosaic(centerTileX, centerTileY) {
|
|||||||
function maybeRefetchGround(cameraWorldX, cameraWorldZ, gl) {
|
function maybeRefetchGround(cameraWorldX, cameraWorldZ, gl) {
|
||||||
var cameraTileX = GROUND_BASE_X + Math.floor(cameraWorldX);
|
var cameraTileX = GROUND_BASE_X + Math.floor(cameraWorldX);
|
||||||
var cameraTileY = GROUND_BASE_Y + Math.floor(cameraWorldZ);
|
var cameraTileY = GROUND_BASE_Y + Math.floor(cameraWorldZ);
|
||||||
var r = Math.floor(GROUND_MOSAIC_SIZE / 2);
|
var halfX = Math.floor(GROUND_CORRIDOR_X / 2);
|
||||||
var dx = Math.abs(cameraTileX - groundCenterTile.x);
|
var halfZ = Math.floor(GROUND_CORRIDOR_Z / 2);
|
||||||
var dy = Math.abs(cameraTileY - groundCenterTile.y);
|
var dx = cameraTileX - groundCenterTile.x;
|
||||||
if (dx > r - GROUND_BUFFER_TILES || dy > r - GROUND_BUFFER_TILES) {
|
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;
|
if (groundUpdatePending) return;
|
||||||
groundUpdatePending = true;
|
groundUpdatePending = true;
|
||||||
rebuildGroundMosaic(cameraTileX, cameraTileY).then(function() {
|
rebuildGroundMosaic(cameraTileX, cameraTileY).then(function() {
|
||||||
@@ -361,8 +375,8 @@ function initGroundSystem(glCtx) {
|
|||||||
if (groundCanvas) return; // already initialised
|
if (groundCanvas) return; // already initialised
|
||||||
groundGl = glCtx;
|
groundGl = glCtx;
|
||||||
groundCanvas = document.createElement('canvas');
|
groundCanvas = document.createElement('canvas');
|
||||||
groundCanvas.width = GROUND_TILE_SIZE * GROUND_MOSAIC_SIZE;
|
groundCanvas.width = GROUND_TILE_SIZE * GROUND_CORRIDOR_X;
|
||||||
groundCanvas.height = GROUND_TILE_SIZE * GROUND_MOSAIC_SIZE;
|
groundCanvas.height = GROUND_TILE_SIZE * GROUND_CORRIDOR_Z;
|
||||||
groundCtx = groundCanvas.getContext('2d');
|
groundCtx = groundCanvas.getContext('2d');
|
||||||
// Pre-fill with fog colour so partially-loaded mosaic still
|
// Pre-fill with fog colour so partially-loaded mosaic still
|
||||||
// shows the right base colour
|
// shows the right base colour
|
||||||
@@ -487,7 +501,7 @@ window.clouds3dRender = function(target) {
|
|||||||
y: groundCenterTile.y - GROUND_BASE_Y
|
y: groundCenterTile.y - GROUND_BASE_Y
|
||||||
};
|
};
|
||||||
gl.uniform2f(s.uGroundCenter, centerWorld.x, centerWorld.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;
|
var drift = now * speed * 0.45;
|
||||||
if (performance.now() - groundLastCheck > 1500) {
|
if (performance.now() - groundLastCheck > 1500) {
|
||||||
|
|||||||
Reference in new issue
Block a user