camera: bank the view when the flight path curves

This commit is contained in:
Richard Zink committed 2026-10-12 21:23:54 +02:00
1 parent 1fe214f403
commit e8ff7ee714
2 files changed
+53 -3

No files matched your search

+12 -1
View File
@@ -480,7 +480,18 @@ window.CONFIG = {
aim: 'off', // 'off' | 'moon' aim: 'off', // 'off' | 'moon'
// aim: 'moon' // look at the moon while flying the path // aim: 'moon' // look at the moon while flying the path
aimEase: 0.94, // 0 = instant turn, 0.99 = very slow aimEase: 0.94, // 0 = instant turn, 0.99 = very slow
aimBob: 0.25 // share of the altitude bob kept while aiming aimBob: 0.25, // share of the altitude bob kept while aiming
// Banking when the flight path curves. The camera keeps flying the
// same path; roll only rotates the view around the forward axis.
roll: {
enabled: true,
gain: 12, // turn-rate to bank-angle gain
maxDeg: 12, // maximum bank angle
ease: 0.92, // 0 = instant bank, 0.99 = very slow
sign: -1 // flip to +1 if the bank direction feels inverted
// roll: { enabled: false }
// roll: { gain: 20, maxDeg: 18 }
}
// camera: { aim: 'moon' } // look at the moon // camera: { aim: 'moon' } // look at the moon
}, },
+41 -2
View File
@@ -1428,6 +1428,13 @@ var CAM_CFG = (window.CONFIG && window.CONFIG.camera) || {};
var CAM_AIM_MOON = (CAM_CFG.aim === 'moon'); var CAM_AIM_MOON = (CAM_CFG.aim === 'moon');
var CAM_AIM_EASE = (typeof CAM_CFG.aimEase === 'number') ? Math.max(0, Math.min(0.999, CAM_CFG.aimEase)) : 0.94; var CAM_AIM_EASE = (typeof CAM_CFG.aimEase === 'number') ? Math.max(0, Math.min(0.999, CAM_CFG.aimEase)) : 0.94;
var CAM_AIM_BOB = (typeof CAM_CFG.aimBob === 'number') ? Math.max(0, Math.min(1, CAM_CFG.aimBob)) : 0.25; var CAM_AIM_BOB = (typeof CAM_CFG.aimBob === 'number') ? Math.max(0, Math.min(1, CAM_CFG.aimBob)) : 0.25;
var CAM_ROLL_CFG = CAM_CFG.roll || {};
var CAM_ROLL_ON = CAM_ROLL_CFG.enabled !== false;
var CAM_ROLL_GAIN = (typeof CAM_ROLL_CFG.gain === 'number') ? Math.max(0, CAM_ROLL_CFG.gain) : 12;
var CAM_ROLL_MAX = (typeof CAM_ROLL_CFG.maxDeg === 'number') ? Math.max(0, Math.min(35, CAM_ROLL_CFG.maxDeg)) * Math.PI / 180 : 12 * Math.PI / 180;
var CAM_ROLL_EASE = (typeof CAM_ROLL_CFG.ease === 'number') ? Math.max(0, Math.min(0.999, CAM_ROLL_CFG.ease)) : 0.92;
var CAM_ROLL_SIGN = (typeof CAM_ROLL_CFG.sign === 'number') ? CAM_ROLL_CFG.sign : -1;
var lastCamBearing = null, lastCamTime = null, camRoll = 0;
var aimHdX = 0, aimHdZ = 1, aimPitch = 0.45, aimReady = false; var aimHdX = 0, aimHdZ = 1, aimPitch = 0.45, aimReady = false;
// CONFIG.clouds = { top: ..., bottom: ... } overrides the defaults; the // CONFIG.clouds = { top: ..., bottom: ... } overrides the defaults; the
// deck must stay below the camera and keep a minimum thickness. // deck must stay below the camera and keep a minimum thickness.
@@ -6243,7 +6250,29 @@ window.clouds3dRender = function(target) {
// View direction as compass bearing (world +X = east, +Z = north, // View direction as compass bearing (world +X = east, +Z = north,
// matching the sun-vector convention): 0 = north, clockwise. // matching the sun-vector convention): 0 = north, clockwise.
// The clock's compass needle reads this. // The clock's compass needle reads this.
window.__flightBearing = Math.atan2(hdX, hdZ); var flightBearing = Math.atan2(hdX, hdZ);
window.__flightBearing = flightBearing;
var viewRoll = 0;
if (CAM_ROLL_ON) {
if (lastCamBearing !== null && lastCamTime !== null) {
var _dt = Math.max(0.001, now - lastCamTime);
var _db = flightBearing - lastCamBearing;
while (_db > Math.PI) _db -= 2 * Math.PI;
while (_db < -Math.PI) _db += 2 * Math.PI;
var _turnRate = _db / _dt;
var _target = CAM_ROLL_SIGN * _turnRate * CAM_ROLL_GAIN;
if (_target > CAM_ROLL_MAX) _target = CAM_ROLL_MAX;
else if (_target < -CAM_ROLL_MAX) _target = -CAM_ROLL_MAX;
camRoll += (_target - camRoll) * (1 - CAM_ROLL_EASE);
}
lastCamBearing = flightBearing;
lastCamTime = now;
viewRoll = camRoll;
} else {
lastCamBearing = null;
lastCamTime = null;
camRoll = 0;
}
// Periodic check: when the camera approaches the corridor edge, // Periodic check: when the camera approaches the corridor edge,
// re-centre the mosaic (whole-tile shift of the canvas content + // re-centre the mosaic (whole-tile shift of the canvas content +
// fetch the newly exposed columns). At uSpeed=1 the camera moves // fetch the newly exposed columns). At uSpeed=1 the camera moves
@@ -6309,10 +6338,20 @@ window.clouds3dRender = function(target) {
// Debug override: point at world origin so the mesh is always in view. // Debug override: point at world origin so the mesh is always in view.
var fwdLen = Math.hypot(hdX, pitch, hdZ) || 1; var fwdLen = Math.hypot(hdX, pitch, hdZ) || 1;
var fwdX = hdX / fwdLen, fwdY = -pitch / fwdLen, fwdZ = hdZ / fwdLen; var fwdX = hdX / fwdLen, fwdY = -pitch / fwdLen, fwdZ = hdZ / fwdLen;
var upX = 0, upY = 1, upZ = 0;
if (Math.abs(viewRoll) > 1e-5) {
var _rc = Math.cos(viewRoll), _rs = Math.sin(viewRoll);
// Rodrigues rotation of the world-up vector around the forward axis.
upX = -fwdZ * _rs;
upY = _rc;
upZ = fwdX * _rs;
var _ul = Math.hypot(upX, upY, upZ) || 1;
upX /= _ul; upY /= _ul; upZ /= _ul;
}
var view = mat4LookAt( var view = mat4LookAt(
[camX, camY, camZ], [camX, camY, camZ],
[camX + fwdX * 5, camY + fwdY * 5, camZ + fwdZ * 5], [camX + fwdX * 5, camY + fwdY * 5, camZ + fwdZ * 5],
[0, 1, 0] [upX, upY, upZ]
); );
// Inverse of (proj * view) is uploaded to the cloud/sky shader so // Inverse of (proj * view) is uploaded to the cloud/sky shader so
// both can reconstruct the world ray from gl_FragCoord identically // both can reconstruct the world ray from gl_FragCoord identically