karate-wiener / _lab_viewer.mjs
polats's picture
Deploy Karate Wiener (kimodo kata maker)
7ab51fd verified
Raw
History Blame Contribute Delete
3.25 kB
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());