SkeletonUtils.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. ( function () {
  2. class SkeletonUtils {
  3. static retarget( target, source, options = {} ) {
  4. const pos = new THREE.Vector3(),
  5. quat = new THREE.Quaternion(),
  6. scale = new THREE.Vector3(),
  7. bindBoneMatrix = new THREE.Matrix4(),
  8. relativeMatrix = new THREE.Matrix4(),
  9. globalMatrix = new THREE.Matrix4();
  10. options.preserveMatrix = options.preserveMatrix !== undefined ? options.preserveMatrix : true;
  11. options.preservePosition = options.preservePosition !== undefined ? options.preservePosition : true;
  12. options.preserveHipPosition = options.preserveHipPosition !== undefined ? options.preserveHipPosition : false;
  13. options.useTargetMatrix = options.useTargetMatrix !== undefined ? options.useTargetMatrix : false;
  14. options.hip = options.hip !== undefined ? options.hip : 'hip';
  15. options.names = options.names || {};
  16. const sourceBones = source.isObject3D ? source.skeleton.bones : this.getBones( source ),
  17. bones = target.isObject3D ? target.skeleton.bones : this.getBones( target );
  18. let bindBones, bone, name, boneTo, bonesPosition; // reset bones
  19. if ( target.isObject3D ) {
  20. target.skeleton.pose();
  21. } else {
  22. options.useTargetMatrix = true;
  23. options.preserveMatrix = false;
  24. }
  25. if ( options.preservePosition ) {
  26. bonesPosition = [];
  27. for ( let i = 0; i < bones.length; i ++ ) {
  28. bonesPosition.push( bones[ i ].position.clone() );
  29. }
  30. }
  31. if ( options.preserveMatrix ) {
  32. // reset matrix
  33. target.updateMatrixWorld();
  34. target.matrixWorld.identity(); // reset children matrix
  35. for ( let i = 0; i < target.children.length; ++ i ) {
  36. target.children[ i ].updateMatrixWorld( true );
  37. }
  38. }
  39. if ( options.offsets ) {
  40. bindBones = [];
  41. for ( let i = 0; i < bones.length; ++ i ) {
  42. bone = bones[ i ];
  43. name = options.names[ bone.name ] || bone.name;
  44. if ( options.offsets && options.offsets[ name ] ) {
  45. bone.matrix.multiply( options.offsets[ name ] );
  46. bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
  47. bone.updateMatrixWorld();
  48. }
  49. bindBones.push( bone.matrixWorld.clone() );
  50. }
  51. }
  52. for ( let i = 0; i < bones.length; ++ i ) {
  53. bone = bones[ i ];
  54. name = options.names[ bone.name ] || bone.name;
  55. boneTo = this.getBoneByName( name, sourceBones );
  56. globalMatrix.copy( bone.matrixWorld );
  57. if ( boneTo ) {
  58. boneTo.updateMatrixWorld();
  59. if ( options.useTargetMatrix ) {
  60. relativeMatrix.copy( boneTo.matrixWorld );
  61. } else {
  62. relativeMatrix.copy( target.matrixWorld ).invert();
  63. relativeMatrix.multiply( boneTo.matrixWorld );
  64. } // ignore scale to extract rotation
  65. scale.setFromMatrixScale( relativeMatrix );
  66. relativeMatrix.scale( scale.set( 1 / scale.x, 1 / scale.y, 1 / scale.z ) ); // apply to global matrix
  67. globalMatrix.makeRotationFromQuaternion( quat.setFromRotationMatrix( relativeMatrix ) );
  68. if ( target.isObject3D ) {
  69. const boneIndex = bones.indexOf( bone ),
  70. wBindMatrix = bindBones ? bindBones[ boneIndex ] : bindBoneMatrix.copy( target.skeleton.boneInverses[ boneIndex ] ).invert();
  71. globalMatrix.multiply( wBindMatrix );
  72. }
  73. globalMatrix.copyPosition( relativeMatrix );
  74. }
  75. if ( bone.parent && bone.parent.isBone ) {
  76. bone.matrix.copy( bone.parent.matrixWorld ).invert();
  77. bone.matrix.multiply( globalMatrix );
  78. } else {
  79. bone.matrix.copy( globalMatrix );
  80. }
  81. if ( options.preserveHipPosition && name === options.hip ) {
  82. bone.matrix.setPosition( pos.set( 0, bone.position.y, 0 ) );
  83. }
  84. bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
  85. bone.updateMatrixWorld();
  86. }
  87. if ( options.preservePosition ) {
  88. for ( let i = 0; i < bones.length; ++ i ) {
  89. bone = bones[ i ];
  90. name = options.names[ bone.name ] || bone.name;
  91. if ( name !== options.hip ) {
  92. bone.position.copy( bonesPosition[ i ] );
  93. }
  94. }
  95. }
  96. if ( options.preserveMatrix ) {
  97. // restore matrix
  98. target.updateMatrixWorld( true );
  99. }
  100. }
  101. static retargetClip( target, source, clip, options = {} ) {
  102. options.useFirstFramePosition = options.useFirstFramePosition !== undefined ? options.useFirstFramePosition : false;
  103. options.fps = options.fps !== undefined ? options.fps : 30;
  104. options.names = options.names || [];
  105. if ( ! source.isObject3D ) {
  106. source = this.getHelperFromSkeleton( source );
  107. }
  108. const numFrames = Math.round( clip.duration * ( options.fps / 1000 ) * 1000 ),
  109. delta = 1 / options.fps,
  110. convertedTracks = [],
  111. mixer = new THREE.AnimationMixer( source ),
  112. bones = this.getBones( target.skeleton ),
  113. boneDatas = [];
  114. let positionOffset, bone, boneTo, boneData, name;
  115. mixer.clipAction( clip ).play();
  116. mixer.update( 0 );
  117. source.updateMatrixWorld();
  118. for ( let i = 0; i < numFrames; ++ i ) {
  119. const time = i * delta;
  120. this.retarget( target, source, options );
  121. for ( let j = 0; j < bones.length; ++ j ) {
  122. name = options.names[ bones[ j ].name ] || bones[ j ].name;
  123. boneTo = this.getBoneByName( name, source.skeleton );
  124. if ( boneTo ) {
  125. bone = bones[ j ];
  126. boneData = boneDatas[ j ] = boneDatas[ j ] || {
  127. bone: bone
  128. };
  129. if ( options.hip === name ) {
  130. if ( ! boneData.pos ) {
  131. boneData.pos = {
  132. times: new Float32Array( numFrames ),
  133. values: new Float32Array( numFrames * 3 )
  134. };
  135. }
  136. if ( options.useFirstFramePosition ) {
  137. if ( i === 0 ) {
  138. positionOffset = bone.position.clone();
  139. }
  140. bone.position.sub( positionOffset );
  141. }
  142. boneData.pos.times[ i ] = time;
  143. bone.position.toArray( boneData.pos.values, i * 3 );
  144. }
  145. if ( ! boneData.quat ) {
  146. boneData.quat = {
  147. times: new Float32Array( numFrames ),
  148. values: new Float32Array( numFrames * 4 )
  149. };
  150. }
  151. boneData.quat.times[ i ] = time;
  152. bone.quaternion.toArray( boneData.quat.values, i * 4 );
  153. }
  154. }
  155. mixer.update( delta );
  156. source.updateMatrixWorld();
  157. }
  158. for ( let i = 0; i < boneDatas.length; ++ i ) {
  159. boneData = boneDatas[ i ];
  160. if ( boneData ) {
  161. if ( boneData.pos ) {
  162. convertedTracks.push( new THREE.VectorKeyframeTrack( '.bones[' + boneData.bone.name + '].position', boneData.pos.times, boneData.pos.values ) );
  163. }
  164. convertedTracks.push( new THREE.QuaternionKeyframeTrack( '.bones[' + boneData.bone.name + '].quaternion', boneData.quat.times, boneData.quat.values ) );
  165. }
  166. }
  167. mixer.uncacheAction( clip );
  168. return new THREE.AnimationClip( clip.name, - 1, convertedTracks );
  169. }
  170. static getHelperFromSkeleton( skeleton ) {
  171. const source = new THREE.SkeletonHelper( skeleton.bones[ 0 ] );
  172. source.skeleton = skeleton;
  173. return source;
  174. }
  175. static getSkeletonOffsets( target, source, options = {} ) {
  176. const targetParentPos = new THREE.Vector3(),
  177. targetPos = new THREE.Vector3(),
  178. sourceParentPos = new THREE.Vector3(),
  179. sourcePos = new THREE.Vector3(),
  180. targetDir = new THREE.Vector2(),
  181. sourceDir = new THREE.Vector2();
  182. options.hip = options.hip !== undefined ? options.hip : 'hip';
  183. options.names = options.names || {};
  184. if ( ! source.isObject3D ) {
  185. source = this.getHelperFromSkeleton( source );
  186. }
  187. const nameKeys = Object.keys( options.names ),
  188. nameValues = Object.values( options.names ),
  189. sourceBones = source.isObject3D ? source.skeleton.bones : this.getBones( source ),
  190. bones = target.isObject3D ? target.skeleton.bones : this.getBones( target ),
  191. offsets = [];
  192. let bone, boneTo, name, i;
  193. target.skeleton.pose();
  194. for ( i = 0; i < bones.length; ++ i ) {
  195. bone = bones[ i ];
  196. name = options.names[ bone.name ] || bone.name;
  197. boneTo = this.getBoneByName( name, sourceBones );
  198. if ( boneTo && name !== options.hip ) {
  199. const boneParent = this.getNearestBone( bone.parent, nameKeys ),
  200. boneToParent = this.getNearestBone( boneTo.parent, nameValues );
  201. boneParent.updateMatrixWorld();
  202. boneToParent.updateMatrixWorld();
  203. targetParentPos.setFromMatrixPosition( boneParent.matrixWorld );
  204. targetPos.setFromMatrixPosition( bone.matrixWorld );
  205. sourceParentPos.setFromMatrixPosition( boneToParent.matrixWorld );
  206. sourcePos.setFromMatrixPosition( boneTo.matrixWorld );
  207. targetDir.subVectors( new THREE.Vector2( targetPos.x, targetPos.y ), new THREE.Vector2( targetParentPos.x, targetParentPos.y ) ).normalize();
  208. sourceDir.subVectors( new THREE.Vector2( sourcePos.x, sourcePos.y ), new THREE.Vector2( sourceParentPos.x, sourceParentPos.y ) ).normalize();
  209. const laterialAngle = targetDir.angle() - sourceDir.angle();
  210. const offset = new THREE.Matrix4().makeRotationFromEuler( new THREE.Euler( 0, 0, laterialAngle ) );
  211. bone.matrix.multiply( offset );
  212. bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
  213. bone.updateMatrixWorld();
  214. offsets[ name ] = offset;
  215. }
  216. }
  217. return offsets;
  218. }
  219. static renameBones( skeleton, names ) {
  220. const bones = this.getBones( skeleton );
  221. for ( let i = 0; i < bones.length; ++ i ) {
  222. const bone = bones[ i ];
  223. if ( names[ bone.name ] ) {
  224. bone.name = names[ bone.name ];
  225. }
  226. }
  227. return this;
  228. }
  229. static getBones( skeleton ) {
  230. return Array.isArray( skeleton ) ? skeleton : skeleton.bones;
  231. }
  232. static getBoneByName( name, skeleton ) {
  233. for ( let i = 0, bones = this.getBones( skeleton ); i < bones.length; i ++ ) {
  234. if ( name === bones[ i ].name ) return bones[ i ];
  235. }
  236. }
  237. static getNearestBone( bone, names ) {
  238. while ( bone.isBone ) {
  239. if ( names.indexOf( bone.name ) !== - 1 ) {
  240. return bone;
  241. }
  242. bone = bone.parent;
  243. }
  244. }
  245. static findBoneTrackData( name, tracks ) {
  246. const regexp = /\[(.*)\]\.(.*)/,
  247. result = {
  248. name: name
  249. };
  250. for ( let i = 0; i < tracks.length; ++ i ) {
  251. // 1 is track name
  252. // 2 is track type
  253. const trackData = regexp.exec( tracks[ i ].name );
  254. if ( trackData && name === trackData[ 1 ] ) {
  255. result[ trackData[ 2 ] ] = i;
  256. }
  257. }
  258. return result;
  259. }
  260. static getEqualsBonesNames( skeleton, targetSkeleton ) {
  261. const sourceBones = this.getBones( skeleton ),
  262. targetBones = this.getBones( targetSkeleton ),
  263. bones = [];
  264. search: for ( let i = 0; i < sourceBones.length; i ++ ) {
  265. const boneName = sourceBones[ i ].name;
  266. for ( let j = 0; j < targetBones.length; j ++ ) {
  267. if ( boneName === targetBones[ j ].name ) {
  268. bones.push( boneName );
  269. continue search;
  270. }
  271. }
  272. }
  273. return bones;
  274. }
  275. static clone( source ) {
  276. const sourceLookup = new Map();
  277. const cloneLookup = new Map();
  278. const clone = source.clone();
  279. parallelTraverse( source, clone, function ( sourceNode, clonedNode ) {
  280. sourceLookup.set( clonedNode, sourceNode );
  281. cloneLookup.set( sourceNode, clonedNode );
  282. } );
  283. clone.traverse( function ( node ) {
  284. if ( ! node.isSkinnedMesh ) return;
  285. const clonedMesh = node;
  286. const sourceMesh = sourceLookup.get( node );
  287. const sourceBones = sourceMesh.skeleton.bones;
  288. clonedMesh.skeleton = sourceMesh.skeleton.clone();
  289. clonedMesh.bindMatrix.copy( sourceMesh.bindMatrix );
  290. clonedMesh.skeleton.bones = sourceBones.map( function ( bone ) {
  291. return cloneLookup.get( bone );
  292. } );
  293. clonedMesh.bind( clonedMesh.skeleton, clonedMesh.bindMatrix );
  294. } );
  295. return clone;
  296. }
  297. }
  298. function parallelTraverse( a, b, callback ) {
  299. callback( a, b );
  300. for ( let i = 0; i < a.children.length; i ++ ) {
  301. parallelTraverse( a.children[ i ], b.children[ i ], callback );
  302. }
  303. }
  304. THREE.SkeletonUtils = SkeletonUtils;
  305. } )();