Spaces:
Running on Zero
Running on Zero
| import * as THREE from './x.js'; | |
| import { OrbitControls } from './y.js'; | |
| const M = JSON.parse(document.getElementById('motion').textContent); | |
| const P = M.joints; // [T][22][3] | |
| const PARENTS = M.parents; | |
| const T = P.length, J = P[0].length; | |
| const scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(0x101013); | |
| const cam = new THREE.PerspectiveCamera(45, innerWidth/innerHeight, 0.05, 100); | |
| const renderer = new THREE.WebGLRenderer({antialias:true}); | |
| renderer.setSize(innerWidth, innerHeight); | |
| document.body.appendChild(renderer.domElement); | |
| scene.add(new THREE.HemisphereLight(0xffffff, 0x334, 1.1)); | |
| const grid = new THREE.GridHelper(8, 16, 0x3a3a40, 0x26262c); | |
| scene.add(grid); | |
| // bounding box across ALL frames -> camera + center | |
| let mn=[1e9,1e9,1e9], mx=[-1e9,-1e9,-1e9]; | |
| for (const f of P) for (const p of f) for (let i=0;i<3;i++){ | |
| mn[i]=Math.min(mn[i],p[i]); mx[i]=Math.max(mx[i],p[i]); } | |
| const cx=(mn[0]+mx[0])/2, cy=(mn[1]+mx[1])/2, cz=(mn[2]+mx[2])/2; | |
| const R = Math.max(mx[0]-mn[0], mx[1]-mn[1], mx[2]-mn[2]); | |
| cam.position.set(cx+R*1.6, cy+R*0.8, cz+R*1.8); | |
| const controls = new OrbitControls(cam, renderer.domElement); | |
| controls.target.set(cx, cy, cz); | |
| const jmat = new THREE.MeshStandardMaterial({color:0xf3ead8}); | |
| const hmat = new THREE.MeshStandardMaterial({color:0xbba76f}); | |
| const spheres = []; | |
| for (let j=0;j<J;j++){ | |
| const s = new THREE.Mesh(new THREE.SphereGeometry(j===15?0.055:0.028, 12, 10), | |
| j===15?hmat:jmat); | |
| scene.add(s); spheres.push(s); | |
| } | |
| const bonesGeo = new THREE.BufferGeometry(); | |
| const bonePairs = []; | |
| for (let j=0;j<J;j++) if (PARENTS[j]>=0) bonePairs.push([PARENTS[j], j]); | |
| const pos = new Float32Array(bonePairs.length*6); | |
| bonesGeo.setAttribute('position', new THREE.BufferAttribute(pos, 3)); | |
| scene.add(new THREE.LineSegments(bonesGeo, | |
| new THREE.LineBasicMaterial({color:0x9fc2ff}))); | |
| const scrub = document.getElementById('scrub'); | |
| const info = document.getElementById('info'); | |
| const playBtn = document.getElementById('play'); | |
| scrub.max = T-1; | |
| let frame = 0, playing = true, acc = 0, last = performance.now(); | |
| playBtn.onclick = () => { playing = !playing; playBtn.textContent = playing?'⏸':'▶'; }; | |
| scrub.oninput = () => { frame = +scrub.value; playing = false; playBtn.textContent='▶'; }; | |
| function setFrame(f){ | |
| const fr = P[f]; | |
| for (let j=0;j<J;j++) spheres[j].position.set(fr[j][0], fr[j][1], fr[j][2]); | |
| let k=0; | |
| for (const [a,b] of bonePairs){ | |
| pos[k++]=fr[a][0]; pos[k++]=fr[a][1]; pos[k++]=fr[a][2]; | |
| pos[k++]=fr[b][0]; pos[k++]=fr[b][1]; pos[k++]=fr[b][2]; | |
| } | |
| bonesGeo.attributes.position.needsUpdate = true; | |
| scrub.value = f; | |
| info.textContent = 'frame ' + f + ' / ' + (T-1) + ' · ' + M.fps + 'fps'; | |
| } | |
| function tick(now){ | |
| requestAnimationFrame(tick); | |
| if (playing){ | |
| acc += (now-last); | |
| const step = 1000 / M.fps; | |
| while (acc > step){ acc -= step; frame = (frame+1) % T; } | |
| } | |
| last = now; | |
| setFrame(frame); | |
| controls.update(); | |
| renderer.render(scene, cam); | |
| } | |
| addEventListener('resize', () => { cam.aspect=innerWidth/innerHeight; | |
| cam.updateProjectionMatrix(); renderer.setSize(innerWidth, innerHeight); }); | |
| tick(performance.now()); | |