Buckets:
ktongue/docker_container / simsite /frontend /node_modules /three /examples /jsm /loaders /BVHLoader.js
| import { | |
| AnimationClip, | |
| Bone, | |
| FileLoader, | |
| Loader, | |
| Quaternion, | |
| QuaternionKeyframeTrack, | |
| Skeleton, | |
| Vector3, | |
| VectorKeyframeTrack | |
| } from 'three'; | |
| /** | |
| * 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( bone.name + '.position', times, positions ) ); | |
| } | |
| if ( scope.animateBoneRotations ) { | |
| tracks.push( new QuaternionKeyframeTrack( 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 }; | |
Xet Storage Details
- Size:
- 9.02 kB
- Xet hash:
- d135bcd2dace96956e2773900311645d4da1e163db0466e3ec7b582b936a1483
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.