BVHLoader.js 9.1 KB

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