提交 c87ca5d0 编写于 作者: I Ivo Herzig 提交者: Mr.doob

BVHLoader & example (#9188)

* Adds a BVH Loader & example #5524

* bVHLoader example fixed empty mesh errors.

* BVHLoader: replaced internal quaternions with THREE.Quaternion

* added BVHLoader example to example files

* removed arrow function notation

* BVHLoader example: replaced example animation.
上级 7b6f2076
......@@ -77,6 +77,7 @@ var files = {
"webgl_loader_assimp2json",
"webgl_loader_awd",
"webgl_loader_babylon",
"webgl_loader_bvh",
"webgl_loader_collada",
"webgl_loader_collada_keyframe",
"webgl_loader_collada_kinematics",
......
/**
* @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.load = function( url, onLoad, onProgress, onError ) {
var scope = this;
var loader = new THREE.XHRLoader( scope.manager );
loader.setResponseType( 'arraybuffer' );
loader.load( url, function( buffer ) {
onLoad( scope.parse( buffer ) );
}, onProgress, onError );
}
THREE.BVHLoader.prototype.parse = function( buffer ) {
// 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 = this._readBvh( lines );
var threeBones = [];
this._toTHREEBone( bones[ 0 ], threeBones );
var threeClip = this._toTHREEAnimation( bones );
return {
skeleton: new THREE.Skeleton( threeBones ),
clip: threeClip
};
}
/*
reads a string array (lines) from a BVH file
and outputs a skeleton structure including motion data
returns thee root node:
{ name: "", channels: [], children: [] }
*/
THREE.BVHLoader.prototype._readBvh = function( lines ) {
// read model structure
if ( this._nextLine( lines ) !== "HIERARCHY" ) {
throw "HIERARCHY expected";
}
var list = []; // collects flat array of all bones
var root = this._readNode( lines, this._nextLine( lines ), list );
// read motion data
if ( this._nextLine( lines ) != "MOTION" ) {
throw "MOTION expected";
}
// number of frames
var tokens = this._nextLine( lines ).split( /[\s]+/ );
var numFrames = parseInt( tokens[ 1 ] );
if ( isNaN( numFrames ) ) {
throw "Failed to read number of frames.";
}
// frame time
tokens = this._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 = this._nextLine( lines ).split( /[\s]+/ );
this._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.
*/
THREE.BVHLoader.prototype._readFrameData = function( 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 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":
var quat = new THREE.Quaternion();
quat.setFromAxisAngle( vx, parseFloat( data.shift().trim() ) * Math.PI / 180 );
keyframe.rotation.multiply( quat );
break;
case "Yrotation":
var quat = new THREE.Quaternion();
quat.setFromAxisAngle( vy, parseFloat( data.shift().trim() ) * Math.PI / 180 );
keyframe.rotation.multiply( quat );
break;
case "Zrotation":
var quat = new THREE.Quaternion();
quat.setFromAxisAngle( vz, parseFloat( data.shift().trim() ) * Math.PI / 180 );
keyframe.rotation.multiply( quat );
break;
default:
throw "invalid channel type";
break;
}
}
// parse child nodes
for ( var i = 0; i < bone.children.length; ++ i ) {
this._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
*/
THREE.BVHLoader.prototype._readNode = function( 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 ( this._nextLine( lines ) != "{" ) {
throw "Expected opening { after type & name";
}
// parse OFFSET
tokens = this._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 = this._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 = this._nextLine( lines );
if ( line === "}" ) {
return node;
} else {
node.children.push( this._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
*/
THREE.BVHLoader.prototype._toTHREEBone = function( 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( this._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
*/
THREE.BVHLoader.prototype._toTHREEAnimation = function( 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 ( this.animateBonePositions ) {
tracks.push( new THREE.VectorKeyframeTrack(
".bones[" + bone.name + "].position", times, positions ) );
}
if ( this.animateBoneRotations ) {
tracks.push( new THREE.QuaternionKeyframeTrack(
".bones[" + bone.name + "].quaternion", times, rotations ) );
}
}
var clip = new THREE.AnimationClip( "animation", - 1, tracks );
return clip;
}
/*
returns the next non-empty line in lines
*/
THREE.BVHLoader.prototype._nextLine = function( lines ) {
var line;
// skip empty lines
while ( ( line = lines.shift().trim() ).length === 0 ) { }
return line;
}
此差异已折叠。
<html>
<head>
<title>three.js webgl - BVHLoader</title>
<style>
body {
color: #fff;
font-family:Monospace;
font-size:13px;
text-align:center;
font-weight: bold;
background-color: #000;
margin: 0px;
overflow: hidden;
}
#info {
position: absolute;
padding: 10px;
width: 100%;
text-align: center;
color: #000000;
}
}
</style>
</head>
<body>
<div id="info">
<a href="http://threejs.org" target="_blank">three.js</a> - BVH Loader -
animation from <a href="http://mocap.cs.cmu.edu/">http://mocap.cs.cmu.edu/</a>
</div>
<script src="../build/three.js"></script>
<script src="js/controls/TrackballControls.js"></script>
<script src="js/loaders/BVHLoader.js"></script>
<script>
var clock = new THREE.Clock();
var mixer, skeletonHelper, camera, controls, scene, renderer;
var mesh;
init();
animate();
var loader = new THREE.BVHLoader();
loader.load( "models/bvh/pirouette.bvh", function( result ) {
skeletonHelper = new THREE.SkeletonHelper( result.skeleton.bones[ 0 ] );
skeletonHelper.skeleton = result.skeleton; // allow animation mixer to bind to SkeletonHelper directly
var boneContainer = new THREE.Object3D();
boneContainer.add( result.skeleton.bones[ 0 ] );
scene.add( skeletonHelper );
scene.add( boneContainer );
// play animation
mixer = new THREE.AnimationMixer( skeletonHelper );
mixer.clipAction( result.clip ).setEffectiveWeight( 1.0 ).play()
} );
function init() {
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 1000 );
controls = new THREE.TrackballControls( camera );
controls.rotateSpeed = 4.5;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;
scene = new THREE.Scene();
camera.position.z = 600;
camera.position.y = 200;
// renderer
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setClearColor( 0xeeeeee );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
controls.handleResize();
}
function animate() {
requestAnimationFrame( animate );
controls.update();
var delta = clock.getDelta();
if ( mixer )
mixer.update( delta );
if ( skeletonHelper )
skeletonHelper.update();
renderer.render( scene, camera );
}
</script>
</body>
</html>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册