BVHLoader.js 9.0 KB

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