Spaces:
Running on Zero
Running on Zero
Viewer: wall-clock playback that holds while waiting for frames
Browse files
viewer.py
CHANGED
|
@@ -122,7 +122,8 @@ CSS = """
|
|
| 122 |
JS = """
|
| 123 |
const view = element.querySelector('.f3a-view');
|
| 124 |
const labelEl = element.querySelector('.f3a-label');
|
| 125 |
-
const st = { ready: false, run: null, frames: [], labels: [],
|
|
|
|
| 126 |
let THREE = null;
|
| 127 |
let renderer, scene, camera, controls, root, dynamicRoot;
|
| 128 |
|
|
@@ -186,25 +187,38 @@ async function buildScene(msg) {
|
|
| 186 |
function handle(msg) {
|
| 187 |
if (!msg || !st.ready) return;
|
| 188 |
if (msg.kind === 'reset') {
|
| 189 |
-
st.run = msg.run; st.frames = []; st.labels = []; st.
|
| 190 |
st.building = true;
|
| 191 |
buildScene(msg).then(() => { st.building = false; labelEl.textContent = msg.label || 'ready'; });
|
| 192 |
return;
|
| 193 |
}
|
|
|
|
| 194 |
if (msg.kind === 'frames' && msg.run === st.run) {
|
| 195 |
msg.poses.forEach((p, i) => { st.frames[msg.start + i] = p; st.labels[msg.start + i] = msg.labels[i]; });
|
| 196 |
if (msg.done) st.done = true;
|
| 197 |
}
|
| 198 |
}
|
| 199 |
|
| 200 |
-
function
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
requestAnimationFrame(tick);
|
| 202 |
if (!renderer) return;
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
st.
|
| 207 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
}
|
| 209 |
controls.update();
|
| 210 |
renderer.render(scene, camera);
|
|
@@ -218,7 +232,7 @@ function resize() {
|
|
| 218 |
camera.updateProjectionMatrix();
|
| 219 |
}
|
| 220 |
|
| 221 |
-
element.querySelector('.f3a-replay').addEventListener('click', () => { st.
|
| 222 |
|
| 223 |
(async () => {
|
| 224 |
THREE = await import('https://esm.sh/three@0.160.0');
|
|
|
|
| 122 |
JS = """
|
| 123 |
const view = element.querySelector('.f3a-view');
|
| 124 |
const labelEl = element.querySelector('.f3a-label');
|
| 125 |
+
const st = { ready: false, run: null, frames: [], labels: [], shown: -1, playT0: 0, done: false, msgs: 0, bodyGroups: {}, meshCache: {}, meshUrl: null };
|
| 126 |
+
element.f3aState = st;
|
| 127 |
let THREE = null;
|
| 128 |
let renderer, scene, camera, controls, root, dynamicRoot;
|
| 129 |
|
|
|
|
| 187 |
function handle(msg) {
|
| 188 |
if (!msg || !st.ready) return;
|
| 189 |
if (msg.kind === 'reset') {
|
| 190 |
+
st.run = msg.run; st.frames = []; st.labels = []; st.shown = -1; st.playT0 = performance.now(); st.done = false;
|
| 191 |
st.building = true;
|
| 192 |
buildScene(msg).then(() => { st.building = false; labelEl.textContent = msg.label || 'ready'; });
|
| 193 |
return;
|
| 194 |
}
|
| 195 |
+
st.msgs += 1;
|
| 196 |
if (msg.kind === 'frames' && msg.run === st.run) {
|
| 197 |
msg.poses.forEach((p, i) => { st.frames[msg.start + i] = p; st.labels[msg.start + i] = msg.labels[i]; });
|
| 198 |
if (msg.done) st.done = true;
|
| 199 |
}
|
| 200 |
}
|
| 201 |
|
| 202 |
+
function available() {
|
| 203 |
+
let n = st.shown;
|
| 204 |
+
while (st.frames[n + 1]) n += 1;
|
| 205 |
+
return n;
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
function tick() {
|
| 209 |
requestAnimationFrame(tick);
|
| 210 |
if (!renderer) return;
|
| 211 |
+
const now = performance.now();
|
| 212 |
+
if (!st.building) {
|
| 213 |
+
const avail = available();
|
| 214 |
+
const due = Math.floor((now - st.playT0) * 0.03);
|
| 215 |
+
if (due > avail) st.playT0 = now - (avail + 1) / 0.03;
|
| 216 |
+
const target = Math.min(due, avail);
|
| 217 |
+
if (target > st.shown) {
|
| 218 |
+
applyPose(st.frames[target]);
|
| 219 |
+
labelEl.textContent = st.labels[target] || '';
|
| 220 |
+
st.shown = target;
|
| 221 |
+
}
|
| 222 |
}
|
| 223 |
controls.update();
|
| 224 |
renderer.render(scene, camera);
|
|
|
|
| 232 |
camera.updateProjectionMatrix();
|
| 233 |
}
|
| 234 |
|
| 235 |
+
element.querySelector('.f3a-replay').addEventListener('click', () => { st.shown = -1; st.playT0 = performance.now(); });
|
| 236 |
|
| 237 |
(async () => {
|
| 238 |
THREE = await import('https://esm.sh/three@0.160.0');
|