compare: pad-to-match time mapping + fix stalled play at small du
- applyU now maps u to a shared global frame index uGlobal in [0, maxT-1]; each panel clamps to its own (T-1), so a shorter timeline pads its last frame while the longer one finishes — both advance at the same wall-clock tempo instead of rescaling their timelines. - tick() keeps u as a float closure variable; reading it back from the integer-step scrubber was quantizing du to 0 at slow tempo + high T (1600ms/frame, T=24: du ≈ 4e-4 → round to 0 on scrub), stalling playback after one frame.
This commit is contained in:
parent
89401e3aee
commit
d3f5088233
@ -459,27 +459,34 @@ async function main() {
|
||||
}
|
||||
|
||||
// ---- time mapping -----------------------------------------------------
|
||||
// Scrubber is 0..1000. Each panel picks round(u * (T-1)) as its frame idx.
|
||||
// Scrubber is 0..1000, mapped to a shared global frame index uGlobal in
|
||||
// [0, maxT-1]. Each panel clamps uGlobal to its own (T-1) — so a shorter
|
||||
// timeline pads its last frame until the longer timeline finishes, and
|
||||
// both panels advance at the same wall-clock tempo.
|
||||
const SCRUB_MAX = 1000;
|
||||
|
||||
function framesOf(p) { return p ? p.data.frames.length : 0; }
|
||||
|
||||
function timeLabelFor(p, u) {
|
||||
function timeLabelForAtLocal(p, uLocal) {
|
||||
if (!p) return '—';
|
||||
const T = framesOf(p);
|
||||
if (T <= 0) return '—';
|
||||
const idx = Math.max(0, Math.min(T - 1, Math.round(u * (T - 1))));
|
||||
const idx = Math.max(0, Math.min(T - 1, Math.round(uLocal)));
|
||||
return p.data.times?.[idx] ?? String(idx);
|
||||
}
|
||||
|
||||
function applyU(u) {
|
||||
u = Math.max(0, Math.min(1, u));
|
||||
const smooth = motionSel.value === 'smooth';
|
||||
for (const p of Object.values(panels)) {
|
||||
const tMax = maxT();
|
||||
const uGlobal = u * (tMax - 1);
|
||||
let uLocalA = 0, uLocalB = 0;
|
||||
for (const [slot, p] of Object.entries(panels)) {
|
||||
if (!p) continue;
|
||||
const T = framesOf(p);
|
||||
if (T <= 0) continue;
|
||||
const uLocal = u * (T - 1);
|
||||
const uLocal = Math.min(uGlobal, T - 1);
|
||||
if (slot === 'a') uLocalA = uLocal; else uLocalB = uLocal;
|
||||
if (smooth) {
|
||||
p.setFrameInterpolated(uLocal);
|
||||
} else {
|
||||
@ -487,8 +494,8 @@ async function main() {
|
||||
p.setFrame(idx);
|
||||
}
|
||||
}
|
||||
timeAEl.textContent = timeLabelFor(panels.a, u);
|
||||
timeBEl.textContent = timeLabelFor(panels.b, u);
|
||||
timeAEl.textContent = timeLabelForAtLocal(panels.a, uLocalA);
|
||||
timeBEl.textContent = timeLabelForAtLocal(panels.b, uLocalB);
|
||||
}
|
||||
|
||||
// ---- axes sync mode ---------------------------------------------------
|
||||
@ -523,22 +530,25 @@ async function main() {
|
||||
}
|
||||
function baseMsPerFrame() { return 1600 / parseFloat(speedSel.value || '1'); }
|
||||
|
||||
// u is the authoritative playhead (float in [0, 1]). The scrubber mirrors
|
||||
// it for display; reading u back from the scrubber would quantize to the
|
||||
// scrubber's integer step and stall the loop at small du.
|
||||
let u = 0;
|
||||
|
||||
function tick(ts) {
|
||||
requestAnimationFrame(tick);
|
||||
for (const p of Object.values(panels)) p?.render();
|
||||
if (!playing) { lastTs = ts; return; }
|
||||
if (!lastTs) lastTs = ts;
|
||||
if (!lastTs) { lastTs = ts; return; }
|
||||
const dt = ts - lastTs;
|
||||
lastTs = ts;
|
||||
if (dt <= 0) return;
|
||||
const perFrame = baseMsPerFrame();
|
||||
const T = maxT();
|
||||
const du = dt / (perFrame * (T - 1));
|
||||
if (du > 0) {
|
||||
let u = parseFloat(scrub.value) / SCRUB_MAX + du;
|
||||
u += dt / (perFrame * (T - 1));
|
||||
if (u >= 1) u -= Math.floor(u); // wrap 0..1
|
||||
scrub.value = String(Math.round(u * SCRUB_MAX));
|
||||
applyU(u);
|
||||
lastTs = ts;
|
||||
}
|
||||
}
|
||||
requestAnimationFrame(tick);
|
||||
|
||||
@ -551,7 +561,8 @@ async function main() {
|
||||
playBtn.addEventListener('click', () => setPlaying(!playing));
|
||||
|
||||
scrub.addEventListener('input', () => {
|
||||
applyU(parseFloat(scrub.value) / SCRUB_MAX);
|
||||
u = parseFloat(scrub.value) / SCRUB_MAX;
|
||||
applyU(u);
|
||||
});
|
||||
|
||||
speedSel.addEventListener('change', () => { lastTs = 0; });
|
||||
|
||||
@ -111,6 +111,6 @@
|
||||
</section>
|
||||
|
||||
<script src="/static/theme.js?v=11"></script>
|
||||
<script type="module" src="/static/compare.js?v=5"></script>
|
||||
<script type="module" src="/static/compare.js?v=7"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user