compare: interpolate between frames for smooth point-trajectory motion
Default is smooth (lerp each point between adjacent frames); a motion:step toggle preserves the snap-to-frame behaviour. Points missing in either adjacent frame are hidden during the transition to avoid pop-in.
This commit is contained in:
parent
fc6aad5516
commit
d0b026734a
@ -21,6 +21,7 @@ const playBtn = document.getElementById('cc-play');
|
||||
const scrub = document.getElementById('cc-scrub');
|
||||
const speedSel = document.getElementById('cc-speed');
|
||||
const syncSel = document.getElementById('cc-sync');
|
||||
const motionSel = document.getElementById('cc-motion');
|
||||
const timeAEl = document.getElementById('cc-time').querySelector('[data-role="time-a"]');
|
||||
const timeBEl = document.getElementById('cc-time').querySelector('[data-role="time-b"]');
|
||||
|
||||
@ -211,7 +212,43 @@ function createPanel({ slotId, panelEl, data }) {
|
||||
packedN = j;
|
||||
geo.attributes.position.needsUpdate = true;
|
||||
geo.setDrawRange(0, packedN);
|
||||
// If there was a highlighted id, reapply it so the overlay follows.
|
||||
applyHighlightForCurrentFrame();
|
||||
}
|
||||
|
||||
// Pack an interpolated frame. uLocal is a continuous index in [0, T-1].
|
||||
// Points missing in either adjacent frame are skipped for the duration of
|
||||
// that transition (no connect-back to the last-known position).
|
||||
function setFrameInterpolated(uLocal) {
|
||||
const T = data.frames.length;
|
||||
if (T === 0) return;
|
||||
if (uLocal <= 0) return setFrame(0);
|
||||
if (uLocal >= T - 1) return setFrame(T - 1);
|
||||
const f0 = Math.floor(uLocal);
|
||||
const f1 = f0 + 1;
|
||||
const t = uLocal - f0;
|
||||
const fr0 = data.frames[f0], fr1 = data.frames[f1];
|
||||
const x0 = fr0.x, y0 = fr0.y, x1 = fr1.x, y1 = fr1.y;
|
||||
const ptIds = data.point_ids;
|
||||
let j = 0;
|
||||
for (let i = 0; i < x0.length; i++) {
|
||||
const a = x0[i], b = x1[i], c = y0[i], d = y1[i];
|
||||
if (a === null || a === undefined || Number.isNaN(a)
|
||||
|| b === null || b === undefined || Number.isNaN(b)
|
||||
|| c === null || c === undefined || Number.isNaN(c)
|
||||
|| d === null || d === undefined || Number.isNaN(d)) continue;
|
||||
const xi = a + (b - a) * t;
|
||||
const yi = c + (d - c) * t;
|
||||
positions[j * 3 + 0] = xi;
|
||||
positions[j * 3 + 1] = yi;
|
||||
positions[j * 3 + 2] = 0;
|
||||
packedX[j] = xi;
|
||||
packedY[j] = yi;
|
||||
packedId[j] = ptIds[i];
|
||||
j++;
|
||||
}
|
||||
packedN = j;
|
||||
geo.attributes.position.needsUpdate = true;
|
||||
geo.setDrawRange(0, packedN);
|
||||
applyHighlightForCurrentFrame();
|
||||
}
|
||||
|
||||
@ -301,6 +338,7 @@ function createPanel({ slotId, panelEl, data }) {
|
||||
panelEl,
|
||||
data,
|
||||
setFrame,
|
||||
setFrameInterpolated,
|
||||
setBounds,
|
||||
setHighlight,
|
||||
resize,
|
||||
@ -400,13 +438,19 @@ async function main() {
|
||||
|
||||
function applyU(u) {
|
||||
u = Math.max(0, Math.min(1, u));
|
||||
const smooth = motionSel.value === 'smooth';
|
||||
for (const p of Object.values(panels)) {
|
||||
if (!p) continue;
|
||||
const T = framesOf(p);
|
||||
if (T <= 0) continue;
|
||||
const idx = Math.max(0, Math.min(T - 1, Math.round(u * (T - 1))));
|
||||
const uLocal = u * (T - 1);
|
||||
if (smooth) {
|
||||
p.setFrameInterpolated(uLocal);
|
||||
} else {
|
||||
const idx = Math.max(0, Math.min(T - 1, Math.round(uLocal)));
|
||||
p.setFrame(idx);
|
||||
}
|
||||
}
|
||||
timeAEl.textContent = timeLabelFor(panels.a, u);
|
||||
timeBEl.textContent = timeLabelFor(panels.b, u);
|
||||
}
|
||||
@ -476,6 +520,10 @@ async function main() {
|
||||
|
||||
speedSel.addEventListener('change', () => { lastTs = 0; });
|
||||
|
||||
motionSel.addEventListener('change', () => {
|
||||
applyU(parseFloat(scrub.value) / SCRUB_MAX);
|
||||
});
|
||||
|
||||
// ---- linked hover -----------------------------------------------------
|
||||
function wireHover(pA, pB) {
|
||||
if (!pA) return;
|
||||
|
||||
@ -1627,7 +1627,8 @@ button.submit:disabled { background: var(--faint); border-color: var(--faint); c
|
||||
.compare-controls .cc-time-b { color: var(--warm); }
|
||||
|
||||
.compare-controls .cc-speed-wrap,
|
||||
.compare-controls .cc-sync-wrap {
|
||||
.compare-controls .cc-sync-wrap,
|
||||
.compare-controls .cc-motion-wrap {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>embedding notebook · compare</title>
|
||||
<link rel="stylesheet" href="/static/style.css?v=21" />
|
||||
<link rel="stylesheet" href="/static/style.css?v=22" />
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
@ -91,6 +91,14 @@
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="cc-motion-wrap">
|
||||
<span class="cc-lbl">motion</span>
|
||||
<select class="cc-motion" id="cc-motion">
|
||||
<option value="smooth" selected>smooth</option>
|
||||
<option value="step">step</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="cc-sync-wrap">
|
||||
<span class="cc-lbl">axes</span>
|
||||
<select class="cc-sync" id="cc-sync">
|
||||
@ -103,6 +111,6 @@
|
||||
</section>
|
||||
|
||||
<script src="/static/theme.js?v=11"></script>
|
||||
<script type="module" src="/static/compare.js?v=1"></script>
|
||||
<script type="module" src="/static/compare.js?v=2"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>embedding notebook</title>
|
||||
<link rel="stylesheet" href="/static/style.css?v=21" />
|
||||
<link rel="stylesheet" href="/static/style.css?v=22" />
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||
<script type="importmap">
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user