123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- /**
- * @author herzig / http://github.com/herzig
- *
- * Description: reads BVH files and outputs a single THREE.Skeleton and an THREE.AnimationClip
- *
- * Currently only supports bvh files containing a single root.
- *
- */
- THREE.BVHLoader = function( manager ) {
- this.animateBonePositions = true;
- this.animateBoneRotations = true;
- this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
- };
- THREE.BVHLoader.prototype = {
- constructor: THREE.BVHLoader,
- load: function ( url, onLoad, onProgress, onError ) {
- var scope = this;
- var loader = new THREE.FileLoader( scope.manager );
- loader.setResponseType( 'arraybuffer' );
- loader.load( url, function( buffer ) {
- onLoad( scope.parse( buffer ) );
- }, onProgress, onError );
- },
- parse: function ( buffer ) {
- /*
- reads a string array (lines) from a BVH file
- and outputs a skeleton structure including motion data
- returns thee root node:
- { name: "", channels: [], children: [] }
- */
- function readBvh( lines ) {
- // read model structure
- if ( nextLine( lines ) !== "HIERARCHY" ) {
- throw "HIERARCHY expected";
- }
- var list = []; // collects flat array of all bones
- var root = readNode( lines, nextLine( lines ), list );
- // read motion data
- if ( nextLine( lines ) != "MOTION" ) {
- throw "MOTION expected";
- }
- // number of frames
- var tokens = nextLine( lines ).split( /[\s]+/ );
- var numFrames = parseInt( tokens[ 1 ] );
- if ( isNaN( numFrames ) ) {
- throw "Failed to read number of frames.";
- }
- // frame time
- tokens = nextLine( lines ).split( /[\s]+/ );
- var frameTime = parseFloat( tokens[ 2 ] );
- if ( isNaN( frameTime ) ) {
- throw "Failed to read frame time.";
- }
- // read frame data line by line
- for ( var i = 0; i < numFrames; ++ i ) {
- tokens = nextLine( lines ).split( /[\s]+/ );
- readFrameData( tokens, i * frameTime, root, list );
- }
- return list;
- }
- /*
- Recursively reads data from a single frame into the bone hierarchy.
- The passed bone hierarchy has to be structured in the same order as the BVH file.
- keyframe data is stored in bone.frames.
- - data: splitted string array (frame values), values are shift()ed so
- this should be empty after parsing the whole hierarchy.
- - frameTime: playback time for this keyframe.
- - bone: the bone to read frame data from.
- */
- function readFrameData( data, frameTime, bone ) {
- // end sites have no motion data
- if ( bone.type === "ENDSITE" ) {
- return;
- }
- // add keyframe
- var keyframe = {
- time: frameTime,
- position: { x: 0, y: 0, z: 0 },
- rotation: new THREE.Quaternion(),
- };
- bone.frames.push( keyframe );
- var quat = new THREE.Quaternion();
- var vx = new THREE.Vector3( 1, 0, 0 );
- var vy = new THREE.Vector3( 0, 1, 0 );
- var vz = new THREE.Vector3( 0, 0, 1 );
- // parse values for each channel in node
- for ( var i = 0; i < bone.channels.length; ++ i ) {
- switch ( bone.channels[ i ] ) {
- case "Xposition":
- keyframe.position.x = parseFloat( data.shift().trim() );
- break;
- case "Yposition":
- keyframe.position.y = parseFloat( data.shift().trim() );
- break;
- case "Zposition":
- keyframe.position.z = parseFloat( data.shift().trim() );
- break;
- case "Xrotation":
- quat.setFromAxisAngle( vx, parseFloat( data.shift().trim() ) * Math.PI / 180 );
- keyframe.rotation.multiply( quat );
- break;
- case "Yrotation":
- quat.setFromAxisAngle( vy, parseFloat( data.shift().trim() ) * Math.PI / 180 );
- keyframe.rotation.multiply( quat );
- break;
- case "Zrotation":
- quat.setFromAxisAngle( vz, parseFloat( data.shift().trim() ) * Math.PI / 180 );
- keyframe.rotation.multiply( quat );
- break;
- default:
- throw "invalid channel type";
- }
- }
- // parse child nodes
- for ( var i = 0; i < bone.children.length; ++ i ) {
- readFrameData( data, frameTime, bone.children[ i ] );
- }
- }
- /*
- Recursively parses the HIERACHY section of the BVH file
- - lines: all lines of the file. lines are consumed as we go along.
- - firstline: line containing the node type and name e.g. "JOINT hip"
- - list: collects a flat list of nodes
- returns: a BVH node including children
- */
- function readNode( lines, firstline, list ) {
- var node = { name: "", type: "", frames: [] };
- list.push( node );
- // parse node type and name.
- var tokens = firstline.split( /[\s]+/ );
- if ( tokens[ 0 ].toUpperCase() === "END" && tokens[ 1 ].toUpperCase() === "SITE" ) {
- node.type = "ENDSITE";
- node.name = "ENDSITE"; // bvh end sites have no name
- } else {
- node.name = tokens[ 1 ];
- node.type = tokens[ 0 ].toUpperCase();
- }
- if ( nextLine( lines ) != "{" ) {
- throw "Expected opening { after type & name";
- }
- // parse OFFSET
- tokens = nextLine( lines ).split( /[\s]+/ );
- if ( tokens[ 0 ] !== "OFFSET" ) {
- throw "Expected OFFSET, but got: " + tokens[ 0 ];
- }
- if ( tokens.length != 4 ) {
- throw "OFFSET: Invalid number of values";
- }
- var offset = {
- x: parseFloat( tokens[ 1 ] ),
- y: parseFloat( tokens[ 2 ] ),
- z: parseFloat( tokens[ 3 ] )
- };
- if ( isNaN( offset.x ) || isNaN( offset.y ) || isNaN( offset.z ) ) {
- throw "OFFSET: Invalid values";
- }
- node.offset = offset;
- // parse CHANNELS definitions
- if ( node.type != "ENDSITE" ) {
- tokens = nextLine( lines ).split( /[\s]+/ );
- if ( tokens[ 0 ] != "CHANNELS" ) {
- throw "Expected CHANNELS definition";
- }
- var numChannels = parseInt( tokens[ 1 ] );
- node.channels = tokens.splice( 2, numChannels );
- node.children = [];
- }
- // read children
- while ( true ) {
- var line = nextLine( lines );
- if ( line === "}" ) {
- return node;
- } else {
- node.children.push( readNode( lines, line, list ) );
- }
- }
- }
- /*
- recursively converts the internal bvh node structure to a THREE.Bone hierarchy
- source: the bvh root node
- list: pass an empty array, collects a flat list of all converted THREE.Bones
- returns the root THREE.Bone
- */
- function toTHREEBone( source, list ) {
- var bone = new THREE.Bone();
- list.push( bone );
- bone.position.add( source.offset );
- bone.name = source.name;
- if ( source.type != "ENDSITE" ) {
- for ( var i = 0; i < source.children.length; ++ i ) {
- bone.add( toTHREEBone( source.children[ i ], list ) );
- }
- }
- return bone;
- }
- /*
- builds a THREE.AnimationClip from the keyframe data saved in each bone.
- bone: bvh root node
- returns: a THREE.AnimationClip containing position and quaternion tracks
- */
- function toTHREEAnimation( bones ) {
- var tracks = [];
- // create a position and quaternion animation track for each node
- for ( var i = 0; i < bones.length; ++ i ) {
- var bone = bones[ i ];
- if ( bone.type == "ENDSITE" )
- continue;
- // track data
- var times = [];
- var positions = [];
- var rotations = [];
- for ( var j = 0; j < bone.frames.length; ++ j ) {
- var frame = bone.frames[ j ];
- times.push( frame.time );
- // the animation system animates the position property,
- // so we have to add the joint offset to all values
- positions.push( frame.position.x + bone.offset.x );
- positions.push( frame.position.y + bone.offset.y );
- positions.push( frame.position.z + bone.offset.z );
- rotations.push( frame.rotation.x );
- rotations.push( frame.rotation.y );
- rotations.push( frame.rotation.z );
- rotations.push( frame.rotation.w );
- }
- if ( scope.animateBonePositions ) {
- tracks.push( new THREE.VectorKeyframeTrack(
- ".bones[" + bone.name + "].position", times, positions ) );
- }
- if ( scope.animateBoneRotations ) {
- tracks.push( new THREE.QuaternionKeyframeTrack(
- ".bones[" + bone.name + "].quaternion", times, rotations ) );
- }
- }
- return new THREE.AnimationClip( "animation", - 1, tracks );
- }
- /*
- returns the next non-empty line in lines
- */
- function nextLine( lines ) {
- var line;
- // skip empty lines
- while ( ( line = lines.shift().trim() ).length === 0 ) { }
- return line;
- }
- var scope = this;
- // convert buffer to ASCII string
- var text = "";
- var raw = new Uint8Array( buffer );
- for ( var i = 0; i < raw.length; ++ i ) {
- text += String.fromCharCode( raw[ i ] );
- }
- var lines = text.split( /[\r\n]+/g );
- var bones = readBvh( lines );
- var threeBones = [];
- toTHREEBone( bones[ 0 ], threeBones );
- var threeClip = toTHREEAnimation( bones );
- return {
- skeleton: new THREE.Skeleton( threeBones ),
- clip: threeClip
- };
- }
- };
|