Smooth ground scroll: redraw corridor every frame

Replaced refetch-based updates with continuous per-frame redraw.
The corridor now tracks the camera's continuous world position
(floating point, not floored to a tile) so the tile content
scrolls smoothly underneath the camera. No more 'jump by 1 tile'
at every refetch boundary.

Tradeoff: every frame draws ~65 images and does one texImage2D
upload. Browser caches the Image elements, so the cost is mainly
compositing + upload (~5MB at 6400x1280). Should still hit 30+ fps.

This is closer to how real flight sims work: the world content
scrolls continuously, not in tile-size jumps.
This commit is contained in:
Richard Zink committed 2026-10-08 07:59:11 +02:00
1 parent 23327a1cba
commit 4f8fd4b62a
1 file changed
+52 -4
+52 -4
View File
@@ -307,6 +307,8 @@ var groundCenterTile = { x: GROUND_BASE_X, y: GROUND_BASE_Y };
var groundLoadedTiles = {}; // "x_y" -> true
var groundLastCheck = 0;
var groundUpdatePending = false;
var groundCameraX = 0;
var groundCameraZ = 0;
function fetchTileImage(tileX, tileY) {
return new Promise(function(resolve, reject) {
@@ -416,6 +418,44 @@ 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).
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;
var camZ = (fdz !== 0) ? groundCameraZ * fdz : 0;
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);
}
});
}
// 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) {
@@ -586,10 +626,18 @@ window.clouds3dRender = function(target) {
gl.uniform2f(s.uFlightDir, flightDir.dx, flightDir.dz);
var drift = now * speed * 0.12;
if (performance.now() - groundLastCheck > 1500) {
groundLastCheck = performance.now();
maybeRefetchGround(drift, drift, gl);
}
// Track continuous camera world position (X and Z independently,
// since flight direction is independent from fetch direction).
var fdx2 = (window.__flightDir && window.__flightDir.dx) || 0;
var fdz2 = (window.__flightDir && window.__flightDir.dz) || 1;
groundCameraX = (fdx2 !== 0) ? drift * fdx2 : 0;
groundCameraZ = (fdz2 !== 0) ? drift * fdz2 : 0;
// 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();
gl.drawArrays(gl.TRIANGLES, 0, 3);
};