Spaces:
Running
Running
| <html><head><meta charset="utf-8"><title>player clips</title> | |
| <style>html,body{margin:0;height:100%;background:#202028;overflow:hidden} | |
| #hud{position:fixed;top:0;left:0;color:#0f0;font:12px monospace;z-index:9;padding:6px;background:#000a;white-space:pre}</style> | |
| <script type="importmap">{"imports":{ | |
| "three":"https://unpkg.com/three@0.160.0/build/three.module.js", | |
| "three/addons/":"https://unpkg.com/three@0.160.0/examples/jsm/", | |
| "fflate":"https://unpkg.com/fflate@0.8.2/esm/browser.js" | |
| }}</script></head> | |
| <body><div id="hud">loading…</div> | |
| <script type="module"> | |
| import * as THREE from 'three'; | |
| import {FBXLoader} from 'three/addons/loaders/FBXLoader.js'; | |
| const hud=document.getElementById('hud'); | |
| const renderer=new THREE.WebGLRenderer({antialias:true}); | |
| renderer.setPixelRatio(Math.min(devicePixelRatio,1.5));renderer.setSize(innerWidth,innerHeight); | |
| renderer.shadowMap.enabled=true;document.body.appendChild(renderer.domElement); | |
| const scene=new THREE.Scene();scene.background=new THREE.Color(0x202028); | |
| scene.add(new THREE.HemisphereLight(0xffffff,0x445,1.3)); | |
| const dl=new THREE.DirectionalLight(0xffffff,2.2);dl.position.set(3,8,6);scene.add(dl); | |
| const cam=new THREE.PerspectiveCamera(45,innerWidth/innerHeight,0.05,100); | |
| scene.overrideMaterial=new THREE.MeshStandardMaterial({color:0xbfc4cc,roughness:.7}); | |
| const fbx=new FBXLoader(); | |
| let mixer,clips,root,cur=0,action; | |
| fbx.load('assets/chars/player.fbx',obj=>{ | |
| root=obj;clips=obj.animations; | |
| const box=new THREE.Box3().setFromObject(obj);const s=new THREE.Vector3();box.getSize(s); | |
| const sc=1.8/s.y;obj.scale.setScalar(sc);obj.position.y=-box.min.y*sc; | |
| scene.add(obj); | |
| cam.position.set(0,1.1,3.4);cam.lookAt(0,1.0,0); | |
| mixer=new THREE.AnimationMixer(obj); | |
| window.__clip=(i)=>{cur=i;if(action)action.stop();action=mixer.clipAction(clips[i]);action.reset().play(); | |
| return {i,name:clips[i].name,dur:+clips[i].duration.toFixed(2)};}; | |
| window.__list=()=>clips.map((c,i)=>({i,name:c.name,dur:+c.duration.toFixed(2)})); | |
| window.__seek=(t)=>{action.time=t;mixer.update(0);return action.time;}; | |
| const bn={};obj.traverse(o=>{if(o.isBone)bn[o.name]=o;}); | |
| const H=bn['mixamorigHips'],HEAD=bn['mixamorigHead'],RH=bn['mixamorigRightHand'],LH=bn['mixamorigLeftHand'],RF=bn['mixamorigRightFoot']; | |
| const wp=(b)=>b.getWorldPosition(new THREE.Vector3()); | |
| window.__analyze=()=>{ | |
| const res=[]; | |
| for(let i=0;i<clips.length;i++){ | |
| const c=clips[i];if(c.duration<0.1){res.push({i,dur:+c.duration.toFixed(2),skip:1});continue;} | |
| const a=mixer.clipAction(c);mixer.stopAllAction();a.reset().play(); | |
| let hipXZ=[1e9,-1e9,1e9,-1e9],hipY=[1e9,-1e9],headY=[1e9,-1e9],handAbove=-1e9,footY=[1e9,-1e9],tiltMax=0; | |
| const N=24; | |
| for(let k=0;k<=N;k++){a.time=c.duration*k/N;mixer.update(0);obj.updateMatrixWorld(true); | |
| const h=wp(H),hd=wp(HEAD),rh=wp(RH),lh=wp(LH),rf=wp(RF); | |
| hipXZ[0]=Math.min(hipXZ[0],h.x);hipXZ[1]=Math.max(hipXZ[1],h.x);hipXZ[2]=Math.min(hipXZ[2],h.z);hipXZ[3]=Math.max(hipXZ[3],h.z); | |
| hipY[0]=Math.min(hipY[0],h.y);hipY[1]=Math.max(hipY[1],h.y); | |
| headY[0]=Math.min(headY[0],hd.y);headY[1]=Math.max(headY[1],hd.y); | |
| handAbove=Math.max(handAbove,rh.y-h.y,lh.y-h.y); | |
| footY[0]=Math.min(footY[0],rf.y);footY[1]=Math.max(footY[1],rf.y); | |
| const tv=hd.clone().sub(h);const tilt=Math.acos(Math.max(-1,Math.min(1,tv.y/tv.length())));tiltMax=Math.max(tiltMax,tilt); | |
| } | |
| res.push({i,dur:+c.duration.toFixed(2), | |
| rootXZ:+Math.max(hipXZ[1]-hipXZ[0],hipXZ[3]-hipXZ[2]).toFixed(2), | |
| hipYr:+(hipY[1]-hipY[0]).toFixed(2), | |
| headMin:+headY[0].toFixed(2), | |
| handAbove:+handAbove.toFixed(2), | |
| footYr:+(footY[1]-footY[0]).toFixed(2), | |
| tiltDeg:+(tiltMax*180/Math.PI).toFixed(0)}); | |
| } | |
| mixer.stopAllAction();window.__clip(cur); | |
| return res; | |
| }; | |
| window.__clip(0); | |
| hud.textContent='ready: '+clips.length+' clips. __clip(i) __seek(t)'; | |
| let last=performance.now(); | |
| (function loop(now){requestAnimationFrame(loop);const dt=(now-last)/1000;last=now; | |
| if(mixer)mixer.update(dt);renderer.render(scene,cam);})(performance.now()); | |
| },undefined,e=>hud.textContent='ERR '+(e.message||e)); | |
| </script></body></html> | |