File size: 2,393 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 45 46 47 48 49 50 51 | const clamp=(v,a,b)=>Math.max(a,Math.min(b,v));
const lerp=(a,b,u)=>a+(b-a)*u;
export function slerp(a,b,u) {
let dot=a.reduce((s,v,i)=>s+v*b[i],0);
if(dot<0){b=b.map(v=>-v);dot=-dot;}
dot=clamp(dot,-1,1);
const angle=Math.acos(dot),s=Math.sin(angle);
const q=dot>.9995?a.map((v,i)=>lerp(v,b[i],u)):
a.map((v,i)=>(Math.sin((1-u)*angle)*v+Math.sin(u*angle)*b[i])/s);
const length=Math.hypot(...q);return q.map(v=>v/length);
}
export function blendPoses(a,b,u) {
return {rootPosition:a.rootPosition.map((v,i)=>lerp(v,b.rootPosition[i],u)),
localQuaternions:a.localQuaternions.map((q,i)=>slerp(q,b.localQuaternions[i],u)),
contacts:a.contacts.map((v,i)=>lerp(v,b.contacts[i],u))};
}
/** Forward kinematics, shared by the raw experiment and its metrics. */
export function worldPositions(pose,rig) {
const mul=(a,b)=>[
a[3]*b[0]+a[0]*b[3]+a[1]*b[2]-a[2]*b[1],
a[3]*b[1]-a[0]*b[2]+a[1]*b[3]+a[2]*b[0],
a[3]*b[2]+a[0]*b[1]-a[1]*b[0]+a[2]*b[3],
a[3]*b[3]-a[0]*b[0]-a[1]*b[1]-a[2]*b[2]];
const rotate=(v,q)=>mul(mul(q,[...v,0]),[-q[0],-q[1],-q[2],q[3]]).slice(0,3);
const positions=[],rotations=[];
for(let i=0;i<rig.names.length;i++) {
const parent=rig.parents[i],q=pose.localQuaternions[i];
if(parent<0){positions.push(pose.rootPosition.slice());rotations.push(q);}
else{const o=rotate(rig.offsets[i],rotations[parent]);positions.push(o.map((v,j)=>v+positions[parent][j]));rotations.push(mul(rotations[parent],q));}
}
return positions;
}
/** The game checkpoints' canonical Head frame was derived from the neck-to-head
* vector during source conversion, so a level source head appears pitched
* 10.9 degrees forward (measured on the Quaternius A_TPose; see
* docs/motion-one-shot-actions-and-character.md). Correct it before display.
* Locomotion, actions and clips share the bias; poses are otherwise unchanged.
*/
export const HEAD_PITCH_BIAS_DEGREES=10.9;
export function correctHeadPitch(pose,degrees=HEAD_PITCH_BIAS_DEGREES) {
const h=-degrees*Math.PI/360,r=[Math.sin(h),0,0,Math.cos(h)],q=pose.localQuaternions[6];
const head=[q[3]*r[0]+q[0]*r[3]+q[1]*r[2]-q[2]*r[1],q[3]*r[1]-q[0]*r[2]+q[1]*r[3]+q[2]*r[0],
q[3]*r[2]+q[0]*r[1]-q[1]*r[0]+q[2]*r[3],q[3]*r[3]-q[0]*r[0]-q[1]*r[1]-q[2]*r[2]];
const localQuaternions=pose.localQuaternions.slice();localQuaternions[6]=head;
return {...pose,localQuaternions};
}
|