// Injected before the page loads. It puts the page on a clock we step by hand, and uses three.js's // devtools hook to find the renderer and the camera it draws with. Only the clock and the camera are // touched: the frames are measured from pixels, never from the scene's animation data. (() => { let now = 0; let callbacks = []; let nextId = 1; const realDateNow = Date.now.bind(Date); const epoch = realDateNow(); performance.now = () => now; Date.now = () => epoch + now; window.requestAnimationFrame = (cb) => { const id = nextId++; callbacks.push({ id, cb }); return id; }; window.cancelAnimationFrame = (id) => { callbacks = callbacks.filter((c) => c.id !== id); }; // The camera placement to force on every render: a point the camera orbits, its distance, and the // yaw/pitch it looks from (degrees; yaw 0 looks at the model's front). let view = null; let lastFrame = null; const capture = { renderers: [], camera: null, /** Advances the clock by ms and runs one animation frame. Returns the frame as a PNG data URL. */ step(ms, grab) { now += ms; lastFrame = null; const due = callbacks; callbacks = []; for (const { cb } of due) { try { cb(now); } catch (e) { console.error(e); } } return grab ? lastFrame : null; }, time() { return now; }, setView(v) { view = v; }, camera_info() { const cam = capture.camera; return cam && { fov: cam.fov, aspect: cam.aspect, near: cam.near, far: cam.far, position: cam.position.toArray(), quaternion: cam.quaternion.toArray() }; }, }; window.__capture = capture; function place(camera) { const toRad = Math.PI / 180; const yaw = view.yaw * toRad; const pitch = view.pitch * toRad; const [tx, ty, tz] = view.target; camera.position.set( tx + view.distance * Math.cos(pitch) * Math.sin(yaw), ty + view.distance * Math.sin(pitch), tz + view.distance * Math.cos(pitch) * Math.cos(yaw)); camera.up.set(0, 1, 0); camera.lookAt(tx, ty, tz); if (view.fov) { camera.fov = view.fov; } camera.updateProjectionMatrix(); camera.updateMatrixWorld(true); } const hook = new EventTarget(); hook.addEventListener('observe', (event) => { const obj = event.detail; if (obj && obj.isWebGLRenderer && !capture.renderers.includes(obj)) { capture.renderers.push(obj); const render = obj.render.bind(obj); obj.render = (scene, camera) => { capture.camera = camera; if (view) { place(camera); } const result = render(scene, camera); // Read the drawing buffer in the same task as the draw, before the browser clears it. lastFrame = obj.domElement.toDataURL('image/png'); return result; }; } }); window.__THREE_DEVTOOLS__ = hook; })();