sync + pause animation btwn cycles

This commit is contained in:
Michael Pilosov 2026-04-21 18:50:31 -06:00
parent 3d3e1e62ee
commit 6b103cd95f

View File

@ -490,8 +490,12 @@ function createScene(container, dataset) {
trajectories: null, trajectories: null,
numFrames: 0, numFrames: 0,
snapshotMs: 1000 / 12, snapshotMs: 1000 / 12,
// random phase so the three scenes don't loop in lockstep // holdMs pads the end of each cycle at rest, so the pulse actually
cycleStartMs: performance.now() - Math.random() * 2000, // lands (otherwise frame-0-as-rest is zero-duration and never visible).
holdMs: 200,
// shared reference so all scenes stay in lockstep; applyF() sets this
// to the same value across scenes on every n-frames toggle.
cycleStartMs: 0,
}; };
} }
@ -515,7 +519,6 @@ function buildTrajectories(s, numFrames) {
} }
s.trajectories = buf; s.trajectories = buf;
s.numFrames = numFrames; s.numFrames = numFrames;
s.cycleStartMs = performance.now() - Math.random() * s.snapshotMs * numFrames;
} }
function sizeScene(s) { function sizeScene(s) {
@ -630,9 +633,11 @@ async function main() {
const fInputs = document.querySelectorAll('input[name="f"]'); const fInputs = document.querySelectorAll('input[name="f"]');
function applyF(n) { function applyF(n) {
numFrames = n; numFrames = n;
// Shared start time keeps all three scenes in lockstep.
const start = performance.now();
for (const s of scenes) { for (const s of scenes) {
s.numFrames = n; s.numFrames = n;
s.cycleStartMs = performance.now() - Math.random() * s.snapshotMs * n; s.cycleStartMs = start;
} }
} }
fInputs.forEach(input => { fInputs.forEach(input => {
@ -691,14 +696,9 @@ async function main() {
for (const s of scenes) { for (const s of scenes) {
const N = s.numFrames; const N = s.numFrames;
const n = s.basePositions.length; const n = s.basePositions.length;
const cycleMs = N * s.snapshotMs; const walkMs = N * s.snapshotMs;
const cycleMs = walkMs + s.holdMs;
const elapsed = ((now - s.cycleStartMs) % cycleMs + cycleMs) % cycleMs; const elapsed = ((now - s.cycleStartMs) % cycleMs + cycleMs) % cycleMs;
const frameF = elapsed / s.snapshotMs;
const frameIdx = Math.floor(frameF);
const interpT = frameF - frameIdx;
const nextIdx = (frameIdx + 1) % N;
const aOff = frameIdx * n;
const bOff = nextIdx * n;
const total = n / 3; const total = n / 3;
const drawCount = s.geometry.drawRange.count; const drawCount = s.geometry.drawRange.count;
@ -706,6 +706,17 @@ async function main() {
const limit = visibleN * 3; const limit = visibleN * 3;
const pos = s.geometry.attributes.position.array; const pos = s.geometry.attributes.position.array;
const base = s.basePositions; const base = s.basePositions;
if (elapsed >= walkMs) {
// Hold zone: snap-back has landed, sit at rest until next cycle.
for (let i = 0; i < limit; i++) pos[i] = base[i];
} else {
const frameF = elapsed / s.snapshotMs;
const frameIdx = Math.floor(frameF);
const interpT = frameF - frameIdx;
const nextIdx = (frameIdx + 1) % N;
const aOff = frameIdx * n;
const bOff = nextIdx * n;
const tr = s.trajectories; const tr = s.trajectories;
// Jitter is applied in normalized space so σ means the same thing // Jitter is applied in normalized space so σ means the same thing
// across datasets — independent of the raw feature magnitudes. // across datasets — independent of the raw feature magnitudes.
@ -714,6 +725,7 @@ async function main() {
for (let i = 0; i < limit; i++) { for (let i = 0; i < limit; i++) {
pos[i] = base[i] + (tr[aOff + i] * u + tr[bOff + i] * interpT) * scale; pos[i] = base[i] + (tr[aOff + i] * u + tr[bOff + i] * interpT) * scale;
} }
}
s.geometry.attributes.position.needsUpdate = true; s.geometry.attributes.position.needsUpdate = true;
s.controls.update(); s.controls.update();