diff --git a/steps/src/dom.js b/steps/src/dom.js index e9d663b..915dc29 100644 --- a/steps/src/dom.js +++ b/steps/src/dom.js @@ -126,6 +126,7 @@ export function resetUIForNewLoad(isNewVideo = true) { // Reset the FPS counter state to prevent incorrect calculations on reload appState.fps = 0; + appState.lastOverlayUpdateTime = 0; // --- Conditional Video Reset --- if (isNewVideo || !videoPlayer.src) { @@ -374,11 +375,21 @@ export function updatePersistentOverlays(currentMediaTime) { targetMsPerBlock = Math.min(40, Math.max(10, maxWindowIFT / 10)); } + // --- START: Frame-Rate Independent Smoothing --- + // We use performance.now() to calculate a delta time for smooth animations + // across different monitor refresh rates. + const now = performance.now(); + const dt = appState.lastOverlayUpdateTime ? now - appState.lastOverlayUpdateTime : 16.67; + appState.lastOverlayUpdateTime = now; + // Smooth Interpolation (Lerp) // Move current scale towards the target. // If playing, use 0.1 (fast). If stopped, use 0.033 (slow, ~3x slower). - const smoothingFactor = appState.isPlaying ? 0.1 : 0.033; - appState.currentGraphScale += (targetMsPerBlock - appState.currentGraphScale) * smoothingFactor; + const baseSmoothing = appState.isPlaying ? 0.1 : 0.033; + const adjustedSmoothing = 1 - Math.pow(1 - baseSmoothing, dt / (1000 / 60)); + + appState.currentGraphScale += (targetMsPerBlock - appState.currentGraphScale) * adjustedSmoothing; + // --- END: Frame-Rate Independent Smoothing --- // If the scale hasn't converged yet and we are NOT playing (main loop not running), // request another frame to continue the smoothing animation. diff --git a/steps/src/p5/radarSketch.js b/steps/src/p5/radarSketch.js index 3b6b326..0fbf9b2 100644 --- a/steps/src/p5/radarSketch.js +++ b/steps/src/p5/radarSketch.js @@ -209,8 +209,11 @@ export const radarSketch = function (p) { if (framesDrawn === 10 || appState.fps === 0) { appState.fps = currentFps; } else { - const smoothingFactor = 0.95; - appState.fps = appState.fps * smoothingFactor + currentFps * (1 - smoothingFactor); + // --- START: Frame-Rate Independent FPS Smoothing --- + const baseFactor = 0.05; // Smoothing factor at 60 FPS + const adjustedFactor = 1 - Math.pow(1 - baseFactor, delta / (1000 / 60)); + appState.fps = p.lerp(appState.fps, currentFps, adjustedFactor); + // --- END: Frame-Rate Independent FPS Smoothing --- } } lastFrameTime = currentTime; @@ -375,13 +378,16 @@ export const radarSketch = function (p) { isFirstFrame = false; } - // The smoothing factor. A smaller value (e.g., 0.1) means more smoothing. - // This can be adjusted to feel more or less responsive. - const smoothingFactor = 0.5; + // --- START: Frame-Rate Independent Smoothing --- + // We use p.deltaTime to adjust the smoothing factor so that the animation + // speed remains consistent across different monitor refresh rates. + const baseSmoothing = 0.5; // Target smoothing at 60 FPS + const adjustedSmoothing = 1 - Math.pow(1 - baseSmoothing, p.deltaTime / (1000 / 60)); // Linearly interpolate the smoothed position towards the actual mouse position. - smoothedMouseX = p.lerp(smoothedMouseX, p.mouseX, smoothingFactor); - smoothedMouseY = p.lerp(smoothedMouseY, p.mouseY, smoothingFactor); + smoothedMouseX = p.lerp(smoothedMouseX, p.mouseX, adjustedSmoothing); + smoothedMouseY = p.lerp(smoothedMouseY, p.mouseY, adjustedSmoothing); + // --- END: Frame-Rate Independent Smoothing --- // Use the smoothed coordinates for all subsequent zoom-related calculations. const hoveredItems = handleCloseUpDisplay(p, plotScales, smoothedMouseX, smoothedMouseY); diff --git a/steps/src/p5/zoomSketch.js b/steps/src/p5/zoomSketch.js index 9082119..f4142f3 100644 --- a/steps/src/p5/zoomSketch.js +++ b/steps/src/p5/zoomSketch.js @@ -219,7 +219,6 @@ export const zoomSketch = function (p) { appState.zoomFactor = 4; // Set a default zoom factor in the global state p.setup = function () { - p.frameRate(60); // We enable looping so the lerp smoothing can animate between frames p.loop(); }; @@ -272,9 +271,14 @@ export const zoomSketch = function (p) { smoothedAvgX = targetAvgX; smoothedAvgY = targetAvgY; } else { - const smoothingFactor = 0.05; // Tweak this for more/less lag - smoothedAvgX = p.lerp(smoothedAvgX, targetAvgX, smoothingFactor); - smoothedAvgY = p.lerp(smoothedAvgY, targetAvgY, smoothingFactor); + // --- START: Frame-Rate Independent Smoothing --- + // We use p.deltaTime to adjust the smoothing factor so that the animation + // speed remains consistent across different monitor refresh rates. + const baseSmoothing = 0.05; // Target smoothing at 60 FPS + const adjustedSmoothing = 1 - Math.pow(1 - baseSmoothing, p.deltaTime / (1000 / 60)); + smoothedAvgX = p.lerp(smoothedAvgX, targetAvgX, adjustedSmoothing); + smoothedAvgY = p.lerp(smoothedAvgY, targetAvgY, adjustedSmoothing); + // --- END: Frame-Rate Independent Smoothing --- } } else { smoothedAvgX = null; diff --git a/steps/src/state.js b/steps/src/state.js index b0b976d..805ecac 100644 --- a/steps/src/state.js +++ b/steps/src/state.js @@ -53,6 +53,7 @@ export const appState = { lastFrameRenderTime: 0, lastVideoFrameTime: 0, videoFrameRenderTime: 0, + lastOverlayUpdateTime: 0, // Track time between overlay updates for smoothing useCustomTtcScheme: false, // Flag to switch between default and custom customTtcScheme: { // Default values match the UI