| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394 | ( function () {	/** * Description: reads BVH files and outputs a single THREE.Skeleton and an THREE.AnimationClip * * Currently only supports bvh files containing a single root. * */	class BVHLoader extends THREE.Loader {		constructor( manager ) {			super( manager );			this.animateBonePositions = true;			this.animateBoneRotations = true;		}		load( url, onLoad, onProgress, onError ) {			const scope = this;			const loader = new THREE.FileLoader( scope.manager );			loader.setPath( scope.path );			loader.setRequestHeader( scope.requestHeader );			loader.setWithCredentials( scope.withCredentials );			loader.load( url, function ( text ) {				try {					onLoad( scope.parse( text ) );				} catch ( e ) {					if ( onError ) {						onError( e );					} else {						console.error( e );					}					scope.manager.itemError( url );				}			}, onProgress, onError );		}		parse( text ) {			/*    	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' ) {					console.error( 'THREE.BVHLoader: HIERARCHY expected.' );				}				const list = []; // collects flat array of all bones				const root = readNode( lines, nextLine( lines ), list ); // read motion data				if ( nextLine( lines ) !== 'MOTION' ) {					console.error( 'THREE.BVHLoader: MOTION expected.' );				} // number of frames				let tokens = nextLine( lines ).split( /[\s]+/ );				const numFrames = parseInt( tokens[ 1 ] );				if ( isNaN( numFrames ) ) {					console.error( 'THREE.BVHLoader: Failed to read number of frames.' );				} // frame time				tokens = nextLine( lines ).split( /[\s]+/ );				const frameTime = parseFloat( tokens[ 2 ] );				if ( isNaN( frameTime ) ) {					console.error( 'THREE.BVHLoader: Failed to read frame time.' );				} // read frame data line by line				for ( let i = 0; i < numFrames; i ++ ) {					tokens = nextLine( lines ).split( /[\s]+/ );					readFrameData( tokens, i * frameTime, root );				}				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				const keyframe = {					time: frameTime,					position: new THREE.Vector3(),					rotation: new THREE.Quaternion()				};				bone.frames.push( keyframe );				const quat = new THREE.Quaternion();				const vx = new THREE.Vector3( 1, 0, 0 );				const vy = new THREE.Vector3( 0, 1, 0 );				const vz = new THREE.Vector3( 0, 0, 1 ); // parse values for each channel in node				for ( let 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:							console.warn( 'THREE.BVHLoader: Invalid channel type.' );					}				} // parse child nodes				for ( let 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 ) {				const node = {					name: '',					type: '',					frames: []				};				list.push( node ); // parse node type and name				let 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 ) !== '{' ) {					console.error( 'THREE.BVHLoader: Expected opening { after type & name' );				} // parse OFFSET				tokens = nextLine( lines ).split( /[\s]+/ );				if ( tokens[ 0 ] !== 'OFFSET' ) {					console.error( 'THREE.BVHLoader: Expected OFFSET but got: ' + tokens[ 0 ] );				}				if ( tokens.length !== 4 ) {					console.error( 'THREE.BVHLoader: Invalid number of values for OFFSET.' );				}				const offset = new THREE.Vector3( parseFloat( tokens[ 1 ] ), parseFloat( tokens[ 2 ] ), parseFloat( tokens[ 3 ] ) );				if ( isNaN( offset.x ) || isNaN( offset.y ) || isNaN( offset.z ) ) {					console.error( 'THREE.BVHLoader: Invalid values of OFFSET.' );				}				node.offset = offset; // parse CHANNELS definitions				if ( node.type !== 'ENDSITE' ) {					tokens = nextLine( lines ).split( /[\s]+/ );					if ( tokens[ 0 ] !== 'CHANNELS' ) {						console.error( 'THREE.BVHLoader: Expected CHANNELS definition.' );					}					const numChannels = parseInt( tokens[ 1 ] );					node.channels = tokens.splice( 2, numChannels );					node.children = [];				} // read children				while ( true ) {					const 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 ) {				const bone = new THREE.Bone();				list.push( bone );				bone.position.add( source.offset );				bone.name = source.name;				if ( source.type !== 'ENDSITE' ) {					for ( let 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 ) {				const tracks = []; // create a position and quaternion animation track for each node				for ( let i = 0; i < bones.length; i ++ ) {					const bone = bones[ i ];					if ( bone.type === 'ENDSITE' ) continue; // track data					const times = [];					const positions = [];					const rotations = [];					for ( let j = 0; j < bone.frames.length; j ++ ) {						const 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 ) {				let line; // skip empty lines				while ( ( line = lines.shift().trim() ).length === 0 ) {}				return line;			}			const scope = this;			const lines = text.split( /[\r\n]+/g );			const bones = readBvh( lines );			const threeBones = [];			toTHREEBone( bones[ 0 ], threeBones );			const threeClip = toTHREEAnimation( bones );			return {				skeleton: new THREE.Skeleton( threeBones ),				clip: threeClip			};		}	}	THREE.BVHLoader = BVHLoader;} )();
 |