BlendCharacter.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. /**
  2. * @author Michael Guerrero / http://realitymeltdown.com
  3. */
  4. THREE.BlendCharacter = function () {
  5. this.animations = {};
  6. this.weightSchedule = [];
  7. this.warpSchedule = [];
  8. this.load = function ( url, onLoad ) {
  9. var scope = this;
  10. var loader = new THREE.JSONLoader();
  11. loader.load( url, function( geometry, materials ) {
  12. var originalMaterial = materials[ 0 ];
  13. originalMaterial.skinning = true;
  14. THREE.SkinnedMesh.call( scope, geometry, originalMaterial );
  15. // Create the animations
  16. for ( var i = 0; i < geometry.animations.length; ++ i ) {
  17. var animName = geometry.animations[ i ].name;
  18. scope.animations[ animName ] = new THREE.Animation( scope, geometry.animations[ i ] );
  19. }
  20. // Loading is complete, fire the callback
  21. if ( onLoad !== undefined ) onLoad();
  22. } );
  23. };
  24. this.update = function( dt ) {
  25. for ( var i = this.weightSchedule.length - 1; i >= 0; -- i ) {
  26. var data = this.weightSchedule[ i ];
  27. data.timeElapsed += dt;
  28. // If the transition is complete, remove it from the schedule
  29. if ( data.timeElapsed > data.duration ) {
  30. data.anim.weight = data.endWeight;
  31. this.weightSchedule.splice( i, 1 );
  32. // If we've faded out completely, stop the animation
  33. if ( data.anim.weight == 0 ) {
  34. data.anim.stop( 0 );
  35. }
  36. } else {
  37. // interpolate the weight for the current time
  38. data.anim.weight = data.startWeight + ( data.endWeight - data.startWeight ) * data.timeElapsed / data.duration;
  39. }
  40. }
  41. this.updateWarps( dt );
  42. };
  43. this.updateWarps = function( dt ) {
  44. // Warping modifies the time scale over time to make 2 animations of different
  45. // lengths match. This is useful for smoothing out transitions that get out of
  46. // phase such as between a walk and run cycle
  47. for ( var i = this.warpSchedule.length - 1; i >= 0; -- i ) {
  48. var data = this.warpSchedule[ i ];
  49. data.timeElapsed += dt;
  50. if ( data.timeElapsed > data.duration ) {
  51. data.to.weight = 1;
  52. data.to.timeScale = 1;
  53. data.from.weight = 0;
  54. data.from.timeScale = 1;
  55. data.from.stop( 0 );
  56. this.warpSchedule.splice( i, 1 );
  57. } else {
  58. var alpha = data.timeElapsed / data.duration;
  59. var fromLength = data.from.data.length;
  60. var toLength = data.to.data.length;
  61. var fromToRatio = fromLength / toLength;
  62. var toFromRatio = toLength / fromLength;
  63. // scale from each time proportionally to the other animation
  64. data.from.timeScale = ( 1 - alpha ) + fromToRatio * alpha;
  65. data.to.timeScale = alpha + toFromRatio * ( 1 - alpha );
  66. data.from.weight = 1 - alpha;
  67. data.to.weight = alpha;
  68. }
  69. }
  70. };
  71. this.play = function( animName, weight ) {
  72. this.animations[ animName ].play( 0, weight );
  73. };
  74. this.crossfade = function( fromAnimName, toAnimName, duration ) {
  75. var fromAnim = this.animations[ fromAnimName ];
  76. var toAnim = this.animations[ toAnimName ];
  77. fromAnim.play( 0, 1 );
  78. toAnim.play( 0, 0 );
  79. this.weightSchedule.push( {
  80. anim: fromAnim,
  81. startWeight: 1,
  82. endWeight: 0,
  83. timeElapsed: 0,
  84. duration: duration
  85. } );
  86. this.weightSchedule.push( {
  87. anim: toAnim,
  88. startWeight: 0,
  89. endWeight: 1,
  90. timeElapsed: 0,
  91. duration: duration
  92. } );
  93. };
  94. this.warp = function( fromAnimName, toAnimName, duration ) {
  95. var fromAnim = this.animations[ fromAnimName ];
  96. var toAnim = this.animations[ toAnimName ];
  97. fromAnim.play( 0, 1 );
  98. toAnim.play( 0, 0 );
  99. this.warpSchedule.push( {
  100. from: fromAnim,
  101. to: toAnim,
  102. timeElapsed: 0,
  103. duration: duration
  104. } );
  105. };
  106. this.applyWeight = function( animName, weight ) {
  107. this.animations[ animName ].weight = weight;
  108. };
  109. this.pauseAll = function() {
  110. for ( var a in this.animations ) {
  111. if ( this.animations[ a ].isPlaying ) {
  112. this.animations[ a ].stop();
  113. }
  114. }
  115. };
  116. this.unPauseAll = function() {
  117. for ( var a in this.animations ) {
  118. if ( this.animations[ a ].isPlaying && this.animations[ a ].isPaused ) {
  119. this.animations[ a ].pause();
  120. }
  121. }
  122. };
  123. this.stopAll = function() {
  124. for ( a in this.animations ) {
  125. if ( this.animations[ a ].isPlaying ) {
  126. this.animations[ a ].stop( 0 );
  127. }
  128. this.animations[ a ].weight = 0;
  129. }
  130. this.weightSchedule.length = 0;
  131. this.warpSchedule.length = 0;
  132. };
  133. this.showModel = function( boolean ) {
  134. this.visible = boolean;
  135. }
  136. };
  137. THREE.BlendCharacter.prototype = Object.create( THREE.SkinnedMesh.prototype );
  138. THREE.BlendCharacter.prototype.constructor = THREE.BlendCharacter;
  139. THREE.BlendCharacter.prototype.getForward = function() {
  140. var forward = new THREE.Vector3();
  141. return function() {
  142. // pull the character's forward basis vector out of the matrix
  143. forward.set(
  144. - this.matrix.elements[ 8 ],
  145. - this.matrix.elements[ 9 ],
  146. - this.matrix.elements[ 10 ]
  147. );
  148. return forward;
  149. }
  150. };