| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437 | import {	AnimationClip,	Bone,	FileLoader,	Loader,	Quaternion,	QuaternionKeyframeTrack,	Skeleton,	Vector3,	VectorKeyframeTrack} from '../../../build/three.module.js';/** * Description: reads BVH files and outputs a single Skeleton and an AnimationClip * * Currently only supports bvh files containing a single root. * */class BVHLoader extends Loader {	constructor( manager ) {		super( manager );		this.animateBonePositions = true;		this.animateBoneRotations = true;	}	load( url, onLoad, onProgress, onError ) {		const scope = this;		const loader = new 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 Vector3(),				rotation: new Quaternion()			};			bone.frames.push( keyframe );			const quat = new Quaternion();			const vx = new Vector3( 1, 0, 0 );			const vy = new Vector3( 0, 1, 0 );			const vz = new 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 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 Bone hierarchy			source: the bvh root node			list: pass an empty array, collects a flat list of all converted THREE.Bones			returns the root Bone		*/		function toTHREEBone( source, list ) {			const bone = new 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 AnimationClip from the keyframe data saved in each bone.			bone: bvh root node			returns: a 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 VectorKeyframeTrack( '.bones[' + bone.name + '].position', times, positions ) );				}				if ( scope.animateBoneRotations ) {					tracks.push( new QuaternionKeyframeTrack( '.bones[' + bone.name + '].quaternion', times, rotations ) );				}			}			return new 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 Skeleton( threeBones ),			clip: threeClip		};	}}export { BVHLoader };
 |