BVHLoader.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. console.warn( "THREE.BVHLoader: As part of the transition to ES6 Modules, the files in 'examples/js' were deprecated in May 2020 (r117) and will be deleted in December 2020 (r124). You can find more information about developing using ES6 Modules in https://threejs.org/docs/index.html#manual/en/introduction/Import-via-modules." );
  2. /**
  3. * @author herzig / http://github.com/herzig
  4. * @author Mugen87 / https://github.com/Mugen87
  5. *
  6. * Description: reads BVH files and outputs a single THREE.Skeleton and an THREE.AnimationClip
  7. *
  8. * Currently only supports bvh files containing a single root.
  9. *
  10. */
  11. THREE.BVHLoader = function ( manager ) {
  12. THREE.Loader.call( this, manager );
  13. this.animateBonePositions = true;
  14. this.animateBoneRotations = true;
  15. };
  16. THREE.BVHLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
  17. constructor: THREE.BVHLoader,
  18. load: function ( url, onLoad, onProgress, onError ) {
  19. var scope = this;
  20. var loader = new THREE.FileLoader( scope.manager );
  21. loader.setPath( scope.path );
  22. loader.load( url, function ( text ) {
  23. try {
  24. onLoad( scope.parse( text ) );
  25. } catch ( e ) {
  26. if ( onError ) {
  27. onError( e );
  28. } else {
  29. console.error( e );
  30. }
  31. scope.manager.itemError( url );
  32. }
  33. }, onProgress, onError );
  34. },
  35. parse: function ( text ) {
  36. /*
  37. reads a string array (lines) from a BVH file
  38. and outputs a skeleton structure including motion data
  39. returns thee root node:
  40. { name: '', channels: [], children: [] }
  41. */
  42. function readBvh( lines ) {
  43. // read model structure
  44. if ( nextLine( lines ) !== 'HIERARCHY' ) {
  45. console.error( 'THREE.BVHLoader: HIERARCHY expected.' );
  46. }
  47. var list = []; // collects flat array of all bones
  48. var root = readNode( lines, nextLine( lines ), list );
  49. // read motion data
  50. if ( nextLine( lines ) !== 'MOTION' ) {
  51. console.error( 'THREE.BVHLoader: MOTION expected.' );
  52. }
  53. // number of frames
  54. var tokens = nextLine( lines ).split( /[\s]+/ );
  55. var numFrames = parseInt( tokens[ 1 ] );
  56. if ( isNaN( numFrames ) ) {
  57. console.error( 'THREE.BVHLoader: Failed to read number of frames.' );
  58. }
  59. // frame time
  60. tokens = nextLine( lines ).split( /[\s]+/ );
  61. var frameTime = parseFloat( tokens[ 2 ] );
  62. if ( isNaN( frameTime ) ) {
  63. console.error( 'THREE.BVHLoader: Failed to read frame time.' );
  64. }
  65. // read frame data line by line
  66. for ( var i = 0; i < numFrames; i ++ ) {
  67. tokens = nextLine( lines ).split( /[\s]+/ );
  68. readFrameData( tokens, i * frameTime, root );
  69. }
  70. return list;
  71. }
  72. /*
  73. Recursively reads data from a single frame into the bone hierarchy.
  74. The passed bone hierarchy has to be structured in the same order as the BVH file.
  75. keyframe data is stored in bone.frames.
  76. - data: splitted string array (frame values), values are shift()ed so
  77. this should be empty after parsing the whole hierarchy.
  78. - frameTime: playback time for this keyframe.
  79. - bone: the bone to read frame data from.
  80. */
  81. function readFrameData( data, frameTime, bone ) {
  82. // end sites have no motion data
  83. if ( bone.type === 'ENDSITE' ) return;
  84. // add keyframe
  85. var keyframe = {
  86. time: frameTime,
  87. position: new THREE.Vector3(),
  88. rotation: new THREE.Quaternion()
  89. };
  90. bone.frames.push( keyframe );
  91. var quat = new THREE.Quaternion();
  92. var vx = new THREE.Vector3( 1, 0, 0 );
  93. var vy = new THREE.Vector3( 0, 1, 0 );
  94. var vz = new THREE.Vector3( 0, 0, 1 );
  95. // parse values for each channel in node
  96. for ( var i = 0; i < bone.channels.length; i ++ ) {
  97. switch ( bone.channels[ i ] ) {
  98. case 'Xposition':
  99. keyframe.position.x = parseFloat( data.shift().trim() );
  100. break;
  101. case 'Yposition':
  102. keyframe.position.y = parseFloat( data.shift().trim() );
  103. break;
  104. case 'Zposition':
  105. keyframe.position.z = parseFloat( data.shift().trim() );
  106. break;
  107. case 'Xrotation':
  108. quat.setFromAxisAngle( vx, parseFloat( data.shift().trim() ) * Math.PI / 180 );
  109. keyframe.rotation.multiply( quat );
  110. break;
  111. case 'Yrotation':
  112. quat.setFromAxisAngle( vy, parseFloat( data.shift().trim() ) * Math.PI / 180 );
  113. keyframe.rotation.multiply( quat );
  114. break;
  115. case 'Zrotation':
  116. quat.setFromAxisAngle( vz, parseFloat( data.shift().trim() ) * Math.PI / 180 );
  117. keyframe.rotation.multiply( quat );
  118. break;
  119. default:
  120. console.warn( 'THREE.BVHLoader: Invalid channel type.' );
  121. }
  122. }
  123. // parse child nodes
  124. for ( var i = 0; i < bone.children.length; i ++ ) {
  125. readFrameData( data, frameTime, bone.children[ i ] );
  126. }
  127. }
  128. /*
  129. Recursively parses the HIERACHY section of the BVH file
  130. - lines: all lines of the file. lines are consumed as we go along.
  131. - firstline: line containing the node type and name e.g. 'JOINT hip'
  132. - list: collects a flat list of nodes
  133. returns: a BVH node including children
  134. */
  135. function readNode( lines, firstline, list ) {
  136. var node = { name: '', type: '', frames: [] };
  137. list.push( node );
  138. // parse node type and name
  139. var tokens = firstline.split( /[\s]+/ );
  140. if ( tokens[ 0 ].toUpperCase() === 'END' && tokens[ 1 ].toUpperCase() === 'SITE' ) {
  141. node.type = 'ENDSITE';
  142. node.name = 'ENDSITE'; // bvh end sites have no name
  143. } else {
  144. node.name = tokens[ 1 ];
  145. node.type = tokens[ 0 ].toUpperCase();
  146. }
  147. if ( nextLine( lines ) !== '{' ) {
  148. console.error( 'THREE.BVHLoader: Expected opening { after type & name' );
  149. }
  150. // parse OFFSET
  151. tokens = nextLine( lines ).split( /[\s]+/ );
  152. if ( tokens[ 0 ] !== 'OFFSET' ) {
  153. console.error( 'THREE.BVHLoader: Expected OFFSET but got: ' + tokens[ 0 ] );
  154. }
  155. if ( tokens.length !== 4 ) {
  156. console.error( 'THREE.BVHLoader: Invalid number of values for OFFSET.' );
  157. }
  158. var offset = new THREE.Vector3(
  159. parseFloat( tokens[ 1 ] ),
  160. parseFloat( tokens[ 2 ] ),
  161. parseFloat( tokens[ 3 ] )
  162. );
  163. if ( isNaN( offset.x ) || isNaN( offset.y ) || isNaN( offset.z ) ) {
  164. console.error( 'THREE.BVHLoader: Invalid values of OFFSET.' );
  165. }
  166. node.offset = offset;
  167. // parse CHANNELS definitions
  168. if ( node.type !== 'ENDSITE' ) {
  169. tokens = nextLine( lines ).split( /[\s]+/ );
  170. if ( tokens[ 0 ] !== 'CHANNELS' ) {
  171. console.error( 'THREE.BVHLoader: Expected CHANNELS definition.' );
  172. }
  173. var numChannels = parseInt( tokens[ 1 ] );
  174. node.channels = tokens.splice( 2, numChannels );
  175. node.children = [];
  176. }
  177. // read children
  178. while ( true ) {
  179. var line = nextLine( lines );
  180. if ( line === '}' ) {
  181. return node;
  182. } else {
  183. node.children.push( readNode( lines, line, list ) );
  184. }
  185. }
  186. }
  187. /*
  188. recursively converts the internal bvh node structure to a THREE.Bone hierarchy
  189. source: the bvh root node
  190. list: pass an empty array, collects a flat list of all converted THREE.Bones
  191. returns the root THREE.Bone
  192. */
  193. function toTHREEBone( source, list ) {
  194. var bone = new THREE.Bone();
  195. list.push( bone );
  196. bone.position.add( source.offset );
  197. bone.name = source.name;
  198. if ( source.type !== 'ENDSITE' ) {
  199. for ( var i = 0; i < source.children.length; i ++ ) {
  200. bone.add( toTHREEBone( source.children[ i ], list ) );
  201. }
  202. }
  203. return bone;
  204. }
  205. /*
  206. builds a THREE.AnimationClip from the keyframe data saved in each bone.
  207. bone: bvh root node
  208. returns: a THREE.AnimationClip containing position and quaternion tracks
  209. */
  210. function toTHREEAnimation( bones ) {
  211. var tracks = [];
  212. // create a position and quaternion animation track for each node
  213. for ( var i = 0; i < bones.length; i ++ ) {
  214. var bone = bones[ i ];
  215. if ( bone.type === 'ENDSITE' )
  216. continue;
  217. // track data
  218. var times = [];
  219. var positions = [];
  220. var rotations = [];
  221. for ( var j = 0; j < bone.frames.length; j ++ ) {
  222. var frame = bone.frames[ j ];
  223. times.push( frame.time );
  224. // the animation system animates the position property,
  225. // so we have to add the joint offset to all values
  226. positions.push( frame.position.x + bone.offset.x );
  227. positions.push( frame.position.y + bone.offset.y );
  228. positions.push( frame.position.z + bone.offset.z );
  229. rotations.push( frame.rotation.x );
  230. rotations.push( frame.rotation.y );
  231. rotations.push( frame.rotation.z );
  232. rotations.push( frame.rotation.w );
  233. }
  234. if ( scope.animateBonePositions ) {
  235. tracks.push( new THREE.VectorKeyframeTrack( '.bones[' + bone.name + '].position', times, positions ) );
  236. }
  237. if ( scope.animateBoneRotations ) {
  238. tracks.push( new THREE.QuaternionKeyframeTrack( '.bones[' + bone.name + '].quaternion', times, rotations ) );
  239. }
  240. }
  241. return new THREE.AnimationClip( 'animation', - 1, tracks );
  242. }
  243. /*
  244. returns the next non-empty line in lines
  245. */
  246. function nextLine( lines ) {
  247. var line;
  248. // skip empty lines
  249. while ( ( line = lines.shift().trim() ).length === 0 ) { }
  250. return line;
  251. }
  252. var scope = this;
  253. var lines = text.split( /[\r\n]+/g );
  254. var bones = readBvh( lines );
  255. var threeBones = [];
  256. toTHREEBone( bones[ 0 ], threeBones );
  257. var threeClip = toTHREEAnimation( bones );
  258. return {
  259. skeleton: new THREE.Skeleton( threeBones ),
  260. clip: threeClip
  261. };
  262. }
  263. } );