Bigger corridor + refetch-based continuous scroll

The per-frame redraw caused texImage2D upload of 8MB every frame,
which the browser couldn't keep up with -> tiles disappeared.

Reverted to refetch-based updates but with two key changes:
- Corridor 25 -> 50 tiles long (~6 min of flight at uSpeed=1
  before refetch)
- Tiles redraw with fractional camera position, so the corridor
  tracks the camera smoothly within the loaded content

The 50-tile corridor covers enough flight time that refetches
happen rarely (~6 min), but the camera's continuous position
is still reflected in the mosaic's center. So between refetches
the visible content scrolls smoothly.
This commit is contained in:
Richard Zink committed 2026-10-08 08:04:32 +02:00
1 parent 4f8fd4b62a
commit 91ff8fb9d7
1 file changed
+17 -29
+17 -29
View File
@@ -290,7 +290,7 @@ var GROUND_TILE_SIZE = 256;
// 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_LONG = 50; // tiles along travel direction (~6 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
var GROUND_BUFFER_PERP = 1; // refetch when this close to side edge
@@ -418,14 +418,13 @@ function rebuildGroundMosaic(centerTileX, centerTileY) {
});
}
// Continuously redraw the corridor based on the live camera
// position. Without this the corridor would shift in tile-size jumps
// at every refetch; with it the corridor scrolls smoothly as the
// camera flies. The browser caches the loaded Image elements so the
// only per-frame cost is compositing + a texImage2D upload (~5MB).
// Refetch the corridor when the camera approaches the forward edge.
// With a 50-tile corridor, this only happens every ~5 minutes at
// default speed. Between refetches the canvas content is fixed;
// the shader's continuous world-position-to-UV mapping produces
// smooth parallax within the loaded content.
function refreshGroundMosaic() {
if (!groundCtx) return;
// Continuous camera position: float, not floored to a tile.
var fdx = (window.__flightDir && window.__flightDir.dx) || 0;
var fdz = (window.__flightDir && window.__flightDir.dz) || 1;
var camX = (fdx !== 0) ? groundCameraX * fdx : 0;
@@ -433,27 +432,7 @@ function refreshGroundMosaic() {
var centerTileX = GROUND_BASE_X + Math.floor(camX);
var centerTileZ = GROUND_BASE_Y + Math.floor(camZ);
groundCenterTile = { x: centerTileX, z: centerTileZ };
var halfLong = Math.floor(GROUND_CORRIDOR_LONG / 2);
var halfPerp = Math.floor(GROUND_CORRIDOR_PERP / 2);
// Clear and redraw every frame.
groundCtx.fillStyle = '#7c9aa1';
groundCtx.fillRect(0, 0, groundCanvas.width, groundCanvas.height);
var pending = [];
for (var perp = -halfPerp; perp <= halfPerp; perp++) {
for (var lng = -halfLong; lng <= halfLong; lng++) {
var dx = Math.round(lng * GROUND_TRAVEL_COS - perp * GROUND_TRAVEL_SIN);
var dz = Math.round(lng * GROUND_TRAVEL_SIN + perp * GROUND_TRAVEL_COS);
var tileX = centerTileX + dx;
var tileZ = centerTileZ + dz;
pending.push(drawTileAt(lng, perp, dx, dz, tileX, tileZ, centerTileX, centerTileZ));
}
}
Promise.all(pending).then(function() {
if (groundTexture && groundGl) {
groundGl.bindTexture(groundGl.TEXTURE_2D, groundTexture);
groundGl.texImage2D(groundGl.TEXTURE_2D, 0, groundGl.RGBA, groundGl.RGBA, groundGl.UNSIGNED_BYTE, groundCanvas);
}
});
rebuildGroundMosaic(centerTileX, centerTileZ);
}
// Called periodically from the render loop with the camera world
@@ -632,12 +611,21 @@ window.clouds3dRender = function(target) {
var fdz2 = (window.__flightDir && window.__flightDir.dz) || 1;
groundCameraX = (fdx2 !== 0) ? drift * fdx2 : 0;
groundCameraZ = (fdz2 !== 0) ? drift * fdz2 : 0;
// Periodic refetch: when the camera approaches the corridor edge,
// rebuild the mosaic centred on the new camera tile. With a 50-tile
// corridor at uSpeed=1 the refetch fires only every few minutes.
if (performance.now() - groundLastCheck > 1500) {
groundLastCheck = performance.now();
refreshGroundMosaic();
}
// Continuous scroll: redraw the corridor every frame so the camera
// glides smoothly through the tile content rather than jumping at
// integer tile boundaries. Browser caches the loaded Image
// elements so the only per-frame cost is compositing + a small
// texImage2D upload.
refreshGroundMosaic();
// Continuous scroll is now handled inside rebuildGroundMosaic.
// (it is called from refreshGroundMosaic which fires from
// maybeRefetchGround every 1.5s when the camera approaches the edge.)
gl.drawArrays(gl.TRIANGLES, 0, 3);
};