motion / src /profile-controller.js
harshthedev's picture
SuperHQ Motion experiment: locomotion and action checkpoints, runtime, clips, evaluation
6adbbb1 verified
Raw History Blame Contribute Delete
2.96 kB
import {DenseNetwork} from './network.js';
import {MotionProfileClock} from './profile-clock.js';
import {decodeMotionPose} from './controller.js';
/** Experimental profile-conditioned controller. Host engine owns physics.
* This checkpoint supports the profiles and synthetic transitions in config.
*/
export class MotionProfileController {
constructor(manifest,weights,config) {
if(config?.format!=='superhq-motion-profile-controller-v1'||config.dt!==1/60||
manifest.layers[0]?.inputs!==34||manifest.layers.at(-1)?.outputs!==143||
!Array.isArray(config.rig?.end_sites)||config.rig.end_sites.filter(v=>!v).length!==23||
!config.profiles?.Idle_Loop||!Array.isArray(config.transitions))throw Error('Incompatible profile controller.');
this.network=new DenseNetwork(manifest,weights);this.config=config;this.rig=config.rig;this.reset();
}
static async load(baseURL,{fetch:fetcher=globalThis.fetch}={}) {
const base=baseURL instanceof URL?baseURL:new URL(baseURL,globalThis.location?.href);
const request=async(path,binary=false)=>{const r=await fetcher(new URL(path,base));if(!r.ok)throw Error(`Cannot load ${path}: HTTP ${r.status}`);return binary?r.arrayBuffer():r.json();};
const [manifest,config]=await Promise.all([request('network.json'),request('profile-controller.json')]);
if(manifest.weights!=='weights.bin')throw Error('Unexpected weight filename.');
const weights=await request(manifest.weights,true);
if(!globalThis.crypto?.subtle)throw Error('Weight verification requires HTTPS or localhost.');
const digest=Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256',weights)),v=>v.toString(16).padStart(2,'0')).join('');
if(digest!==manifest.sha256)throw Error('Motion weight checksum mismatch.');
return new MotionProfileController(manifest,weights,config);
}
reset({profile='Idle_Loop',rate=1,phase=0,position=[0,0,0]}={}) {
const value=this.config.profiles[profile];if(!value)throw Error('Unknown motion profile.');
const clock=new MotionProfileClock(value,{rate});clock.reset(value,{rate,phase,position});
this.clock=clock;this.profile=profile;
}
setProfile(name,{rate=1}={}) {
if(!this.config.profiles[name])throw Error('Unknown motion profile.');
if(this.clock.time-this.clock.start<this.clock.transitionSeconds-1e-8)throw Error('Finish the current profile transition before requesting another.');
const allowed=name===this.profile||this.config.transitions.some(([a,b])=>(a===name&&b===this.profile)||(b===name&&a===this.profile));
if(!allowed)throw Error('This transition has not been validated; use reset to select a separate profile.');
this.clock.setProfile(this.config.profiles[name],{rate});this.profile=name;
}
step() {
const state=this.clock.step(),output=this.network.forward(state.input);
return {...decodeMotionPose(output,this.rig,state.simulationPosition),...state,profile:this.profile};
}
}