-
Notifications
You must be signed in to change notification settings - Fork 2
/
loadMixamoAnimation.js
97 lines (67 loc) · 3.2 KB
/
loadMixamoAnimation.js
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import * as THREE from 'three';
import { FBXLoader } from 'three/addons/loaders/FBXLoader.js';
import { mixamoVRMRigMap } from './mixamoVRMRigMap.js';
/**
* Load Mixamo animation, convert for three-vrm use, and return it.
*
* @param {string} url A url of mixamo animation data
* @param {VRM} vrm A target VRM
* @returns {Promise<THREE.AnimationClip>} The converted AnimationClip
*/
export function loadMixamoAnimation( url, vrm ) {
const loader = new FBXLoader(); // A loader which loads FBX
return loader.loadAsync( url ).then( ( asset ) => {
const clip = THREE.AnimationClip.findByName( asset.animations, 'mixamo.com' ); // extract the AnimationClip
const tracks = []; // KeyframeTracks compatible with VRM will be added here
const restRotationInverse = new THREE.Quaternion();
const parentRestWorldRotation = new THREE.Quaternion();
const _quatA = new THREE.Quaternion();
const _vec3 = new THREE.Vector3();
// Adjust with reference to hips height.
const motionHipsHeight = asset.getObjectByName( 'mixamorigHips' ).position.y;
const vrmHipsY = vrm.humanoid?.getNormalizedBoneNode( 'hips' ).getWorldPosition( _vec3 ).y;
const vrmRootY = vrm.scene.getWorldPosition( _vec3 ).y;
const vrmHipsHeight = Math.abs( vrmHipsY - vrmRootY );
const hipsPositionScale = vrmHipsHeight / motionHipsHeight;
clip.tracks.forEach( ( track ) => {
// Convert each tracks for VRM use, and push to `tracks`
const trackSplitted = track.name.split( '.' );
const mixamoRigName = trackSplitted[ 0 ];
const vrmBoneName = mixamoVRMRigMap[ mixamoRigName ];
const vrmNodeName = vrm.humanoid?.getNormalizedBoneNode( vrmBoneName )?.name;
const mixamoRigNode = asset.getObjectByName( mixamoRigName );
if ( vrmNodeName != null ) {
const propertyName = trackSplitted[ 1 ];
// Store rotations of rest-pose.
mixamoRigNode.getWorldQuaternion( restRotationInverse ).invert();
mixamoRigNode.parent.getWorldQuaternion( parentRestWorldRotation );
if ( track instanceof THREE.QuaternionKeyframeTrack ) {
// Retarget rotation of mixamoRig to NormalizedBone.
for ( let i = 0; i < track.values.length; i += 4 ) {
const flatQuaternion = track.values.slice( i, i + 4 );
_quatA.fromArray( flatQuaternion );
// 親のレスト時ワールド回転 * トラックの回転 * レスト時ワールド回転の逆
_quatA
.premultiply( parentRestWorldRotation )
.multiply( restRotationInverse );
_quatA.toArray( flatQuaternion );
flatQuaternion.forEach( ( v, index ) => {
track.values[ index + i ] = v;
} );
}
tracks.push(
new THREE.QuaternionKeyframeTrack(
`${vrmNodeName}.${propertyName}`,
track.times,
track.values.map( ( v, i ) => ( vrm.meta?.metaVersion === '0' && i % 2 === 0 ? - v : v ) ),
),
);
} else if ( track instanceof THREE.VectorKeyframeTrack ) {
const value = track.values.map( ( v, i ) => ( vrm.meta?.metaVersion === '0' && i % 3 !== 1 ? - v : v ) * hipsPositionScale );
tracks.push( new THREE.VectorKeyframeTrack( `${vrmNodeName}.${propertyName}`, track.times, value ) );
}
}
} );
return new THREE.AnimationClip( 'vrmAnimation', clip.duration, tracks );
} );
}