diff --git a/app/demo/index.html b/app/demo/index.html
index f3da360..2178436 100644
--- a/app/demo/index.html
+++ b/app/demo/index.html
@@ -490,8 +490,12 @@ function createScene(container, dataset) {
trajectories: null,
numFrames: 0,
snapshotMs: 1000 / 12,
- // random phase so the three scenes don't loop in lockstep
- cycleStartMs: performance.now() - Math.random() * 2000,
+ // holdMs pads the end of each cycle at rest, so the pulse actually
+ // 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.numFrames = numFrames;
- s.cycleStartMs = performance.now() - Math.random() * s.snapshotMs * numFrames;
}
function sizeScene(s) {
@@ -630,9 +633,11 @@ async function main() {
const fInputs = document.querySelectorAll('input[name="f"]');
function applyF(n) {
numFrames = n;
+ // Shared start time keeps all three scenes in lockstep.
+ const start = performance.now();
for (const s of scenes) {
s.numFrames = n;
- s.cycleStartMs = performance.now() - Math.random() * s.snapshotMs * n;
+ s.cycleStartMs = start;
}
}
fInputs.forEach(input => {
@@ -691,14 +696,9 @@ async function main() {
for (const s of scenes) {
const N = s.numFrames;
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 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 drawCount = s.geometry.drawRange.count;
@@ -706,13 +706,25 @@ async function main() {
const limit = visibleN * 3;
const pos = s.geometry.attributes.position.array;
const base = s.basePositions;
- const tr = s.trajectories;
- // Jitter is applied in normalized space so σ means the same thing
- // across datasets — independent of the raw feature magnitudes.
- const scale = jitterScale;
- const u = 1 - interpT;
- for (let i = 0; i < limit; i++) {
- pos[i] = base[i] + (tr[aOff + i] * u + tr[bOff + i] * interpT) * scale;
+
+ 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;
+ // Jitter is applied in normalized space so σ means the same thing
+ // across datasets — independent of the raw feature magnitudes.
+ const scale = jitterScale;
+ const u = 1 - interpT;
+ for (let i = 0; i < limit; i++) {
+ pos[i] = base[i] + (tr[aOff + i] * u + tr[bOff + i] * interpT) * scale;
+ }
}
s.geometry.attributes.position.needsUpdate = true;