File size: 2,956 Bytes
6adbbb1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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};
  }
}