提交 3522d8fc 编写于 作者: M Michael Guerrero

Added an example to showcase skeletal animation blending.

上级 e95ff7d7
/**
* @author Michael Guerrero / http://realitymeltdown.com
*/
//==============================================================================
THREE.BlendCharacter = function () {
var self = this;
this.animations = {};
this.boneHelpers = [];
this.weightSchedule = [];
this.warpSchedule = [];
// ---------------------------------------------------------------------------
this.load = function(url, loadedCB) {
var loader = new THREE.JSONLoader();
loader.load( url, function( geometry, materials ) {
var originalMaterial = materials[ 0 ];
originalMaterial.skinning = true;
originalMaterial.transparent = true;
originalMaterial.alphaTest = 0.75;
THREE.SkinnedMesh.call( self, geometry, originalMaterial );
for ( var i = 0; i < geometry.animations.length; ++i ) {
THREE.AnimationHandler.add( geometry.animations[ i ] );
// Create the animation object and set a default weight
var animName = geometry.animations[ i ].name;
self.animations[ animName ] = new THREE.Animation( self, animName );
}
for ( var i = 0; i < self.skeleton.bones.length; ++i ) {
var helper = new THREE.BoneAxisHelper( self.skeleton.bones[i], 2, 1 );
helper.update();
self.add( helper );
self.boneHelpers.push( helper );
}
self.toggleShowBones( false );
// Loading is complete, fire the callback
loadedCB && loadedCB();
} );
};
// ---------------------------------------------------------------------------
this.updateWeights = function( dt ) {
for ( var i = this.weightSchedule.length - 1; i >= 0; --i ) {
var data = this.weightSchedule[ i ];
data.timeElapsed += dt;
if ( data.timeElapsed > data.duration ) {
data.anim.weight = data.endWeight;
this.weightSchedule.splice( i, 1 );
if ( data.anim.weight == 0 ) {
data.anim.stop( 0 );
}
} else {
data.anim.weight = data.startWeight + (data.endWeight - data.startWeight) * data.timeElapsed / data.duration;
}
}
this.updateWarps( dt );
};
// ---------------------------------------------------------------------------
this.updateWarps = function( dt ) {
for ( var i = this.warpSchedule.length - 1; i >= 0; --i ) {
var data = this.warpSchedule[ i ];
data.timeElapsed += dt;
if ( data.timeElapsed > data.duration ) {
data.to.weight = 1;
data.to.timeScale = 1;
data.from.weight = 0;
data.from.timeScale = 1;
data.from.stop( 0 );
this.warpSchedule.splice( i, 1 );
} else {
var alpha = data.timeElapsed / data.duration;
var fromLength = data.from.data.length;
var toLength = data.to.data.length;
var fromToRatio = fromLength / toLength;
var toFromRatio = toLength / fromLength;
// scale from each time proportionally to the other animation
data.from.timeScale = ( 1 - alpha ) + fromToRatio * alpha;
data.to.timeScale = alpha + toFromRatio * ( 1 - alpha );
data.from.weight = 1 - alpha;
data.to.weight = alpha;
}
}
}
// ---------------------------------------------------------------------------
this.updateBoneHelpers = function() {
for ( var i = 0; i < this.boneHelpers.length; ++i ) {
this.boneHelpers[ i ].update();
}
};
// ---------------------------------------------------------------------------
this.play = function(animName, weight) {
this.animations[ animName ].play( 0, weight );
};
// ---------------------------------------------------------------------------
this.crossfade = function( fromAnimName, toAnimName, duration ) {
var fromAnim = this.animations[ fromAnimName ];
var toAnim = this.animations[ toAnimName ];
fromAnim.play( 0, 1 );
toAnim.play( 0, 0 );
this.weightSchedule.push( {
anim: fromAnim,
startWeight: 1,
endWeight: 0,
timeElapsed: 0,
duration: duration
} );
this.weightSchedule.push( {
anim: toAnim,
startWeight: 0,
endWeight: 1,
timeElapsed: 0,
duration: duration
} );
};
// ---------------------------------------------------------------------------
this.warp = function( fromAnimName, toAnimName, duration ) {
var fromAnim = this.animations[ fromAnimName ];
var toAnim = this.animations[ toAnimName ];
fromAnim.play( 0, 1 );
toAnim.play( 0, 0 );
this.warpSchedule.push( {
from: fromAnim,
to: toAnim,
timeElapsed: 0,
duration: duration
} );
};
// ---------------------------------------------------------------------------
this.applyWeight = function(animName, weight) {
this.animations[animName].weight = weight;
};
// ---------------------------------------------------------------------------
this.pauseAll = function() {
for ( var a in this.animations ) {
if ( this.animations[ a ].isPlaying ) {
this.animations[ a ].pause();
}
}
};
// ---------------------------------------------------------------------------
this.unPauseAll = function() {
for ( var a in this.animations ) {
if ( this.animations[ a ].isPaused ) {
this.animations[ a ].pause();
}
}
};
// ---------------------------------------------------------------------------
this.stopAll = function() {
for (a in this.animations) {
if ( this.animations[ a ].isPlaying ) {
this.animations[ a ].stop(0);
}
this.animations[ a ].weight = 0;
}
this.weightSchedule.length = 0;
this.warpSchedule.length = 0;
}
// ---------------------------------------------------------------------------
this.toggleShowBones = function( shouldShow ) {
this.visible = !shouldShow;
for ( var i = 0; i < self.boneHelpers.length; ++i ) {
self.boneHelpers[ i ].traverse( function( obj ) {
obj.visible = shouldShow;
} );
}
}
};
//==============================================================================
THREE.BlendCharacter.prototype = Object.create( THREE.SkinnedMesh.prototype );
THREE.BlendCharacter.prototype.getForward = function() {
var forward = new THREE.Vector3();
return function() {
// pull the character's forward basis vector out of the matrix
forward.set(
-this.matrix.elements[ 8 ],
-this.matrix.elements[ 9 ],
-this.matrix.elements[ 10 ]
);
return forward;
}
}
/**
* @author Michael Guerrero / http://realitymeltdown.com
*/
function BlendCharacterGui(animations) {
var controls = {
gui: null,
"Lock Camera": false,
"Show Bones": false,
"Time Scale": 1.0,
"Step Size": 0.016,
"Crossfade Time": 3.5,
"idle": 0.33,
"walk": 0.33,
"run": 0.33
};
var animations = animations;
this.shouldShowBones = function() {
return controls['Show Bones'];
};
this.getTimeScale = function() {
return controls['Time Scale'];
};
this.update = function() {
controls[ 'idle'] = animations[ 'idle' ].weight;
controls[ 'walk'] = animations[ 'walk' ].weight;
controls[ 'run'] = animations[ 'run' ].weight;
};
var init = function() {
controls.gui = new dat.GUI();
var settings = controls.gui.addFolder( 'Settings' );
var playback = controls.gui.addFolder( 'Playback' );
var blending = controls.gui.addFolder( 'Blend Tuning' );
settings.add( controls, "Lock Camera" ).onChange( controls.lockCameraChanged );
settings.add( controls, "Show Bones" ).onChange( controls.showBonesChanged );
settings.add( controls, "Time Scale", 0, 1, 0.01 );
settings.add( controls, "Step Size", 0.01, 0.1, 0.01 );
settings.add( controls, "Crossfade Time", 0.1, 6.0, 0.05 );
// These controls execute functions
playback.add( controls, "start" );
playback.add( controls, "pause" );
playback.add( controls, "step" );
playback.add( controls, "idle to walk" );
playback.add( controls, "walk to run" );
playback.add( controls, "warp walk to run" );
blending.add( controls, "idle", 0, 1, 0.01).listen().onChange( controls.weight );
blending.add( controls, "walk", 0, 1, 0.01).listen().onChange( controls.weight );
blending.add( controls, "run", 0, 1, 0.01).listen().onChange( controls.weight );
settings.open();
playback.open();
blending.open();
}
var getAnimationData = function() {
return {
detail: {
anims: [ "idle", "walk", "run" ],
weights: [ controls['idle'],
controls['walk'],
controls['run'] ]
}
};
}
controls.start = function() {
var startEvent = new CustomEvent( 'start-animation', getAnimationData() );
window.dispatchEvent(startEvent);
};
controls.stop = function() {
var stopEvent = new CustomEvent( 'stop-animation' );
window.dispatchEvent( stopEvent );
};
controls.pause = function() {
var pauseEvent = new CustomEvent( 'pause-animation' );
window.dispatchEvent( pauseEvent );
};
controls.step = function() {
var stepData = { detail: { stepSize: controls['Step Size'] } };
window.dispatchEvent( new CustomEvent('step-animation', stepData ));
};
controls.weight = function() {
// renormalize
var sum = controls['idle'] + controls['walk'] + controls['run'];
controls['idle'] /= sum;
controls['walk'] /= sum;
controls['run'] /= sum;
var weightEvent = new CustomEvent( 'weight-animation', getAnimationData() );
window.dispatchEvent(weightEvent);
};
controls.crossfade = function( from, to ) {
var fadeData = getAnimationData();
fadeData.detail.from = from;
fadeData.detail.to = to;
fadeData.detail.time = controls[ "Crossfade Time" ];
window.dispatchEvent( new CustomEvent( 'crossfade', fadeData ) );
}
controls.warp = function( from, to ) {
var warpData = getAnimationData();
warpData.detail.from = 'walk';
warpData.detail.to = 'run';
warpData.detail.time = controls[ "Crossfade Time" ];
window.dispatchEvent( new CustomEvent( 'warp', warpData ) );
}
controls['idle to walk'] = function() {
controls.crossfade( 'idle', 'walk' );
};
controls['walk to run'] = function() {
controls.crossfade( 'walk', 'run' );
};
controls['warp walk to run'] = function() {
controls.warp( 'walk', 'run' );
};
controls.lockCameraChanged = function() {
var data = {
detail: {
shouldLock: controls['Lock Camera']
}
}
window.dispatchEvent( new CustomEvent( 'toggle-lock-camera', data ) );
}
controls.showBonesChanged = function() {
var data = {
detail: {
shouldShow: controls['Show Bones']
}
}
window.dispatchEvent( new CustomEvent( 'toggle-show-bones', data ) );
}
init.call(this);
}
\ No newline at end of file
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - animation - skinning</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
body {
color: #000;
font-family:Monospace;
font-size:13px;
text-align:center;
background-color: #fff;
margin: 0px;
overflow: hidden;
}
#info {
position: absolute;
top: 0px; width: 100%;
padding: 5px;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="../build/Three.js"></script>
<script src="js/Detector.js"></script>
<script src="js/Controls/OrbitControls.js"></script>
<script src="js/BlendCharacter.js"></script>
<script src="js/BlendCharacterGui.js"></script>
<script src="js/libs/dat.gui.min.js"></script>
<script>
if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
var container;
var camera, scene, renderer, controls;
var light;
var blendMesh = null;
var clock = new THREE.Clock();
var gui = null;
var isFrameStepping = false;
var timeToStep = 0;
init();
function init() {
container = document.getElementById( 'container' );
scene = new THREE.Scene();
scene.add ( new THREE.AmbientLight( 0xaaaaaa ) );
light = new THREE.DirectionalLight( 0xffffff, 1.5 );
light.position = new THREE.Vector3( 0, 0, 1000.0 );
scene.add( light );
renderer = new THREE.WebGLRenderer( { antialias: true, alpha: false } );
renderer.setClearColor( '#777777', 1 );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.autoClear = false;
container.appendChild( renderer.domElement );
window.addEventListener( 'resize', onWindowResize, false );
window.addEventListener( 'start-animation', onStartAnimation );
window.addEventListener( 'stop-animation', onStopAnimation );
window.addEventListener( 'pause-animation', onPauseAnimation );
window.addEventListener( 'step-animation', onStepAnimation );
window.addEventListener( 'weight-animation', onWeightAnimation );
window.addEventListener( 'crossfade', onCrossfade );
window.addEventListener( 'warp', onWarp );
window.addEventListener( 'toggle-lock-camera', onLockCameraToggle );
window.addEventListener( 'toggle-show-bones', onShowBonesToggle );
blendMesh = new THREE.BlendCharacter();
blendMesh.load( "models/skinned/marine/marine_anims.js", start );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function onStartAnimation( event ) {
var data = event.detail;
blendMesh.stopAll();
// the blend mesh will combine 1 or more animations
for ( var i = 0; i < data.anims.length; ++i ) {
blendMesh.play(data.anims[i], data.weights[i]);
}
isFrameStepping = false;
}
function onStopAnimation( event ) {
blendMesh.stopAll();
isFrameStepping = false;
}
function onPauseAnimation( event ) {
( isFrameStepping ) ? blendMesh.unPauseAll(): blendMesh.pauseAll();
isFrameStepping = false;
}
function onStepAnimation( event ) {
blendMesh.unPauseAll();
isFrameStepping = true;
timeToStep = event.detail.stepSize;
}
function onWeightAnimation(event) {
var data = event.detail;
for ( var i = 0; i < data.anims.length; ++i ) {
blendMesh.applyWeight(data.anims[i], data.weights[i]);
}
}
function onCrossfade(event) {
var data = event.detail;
blendMesh.stopAll();
blendMesh.crossfade( data.from, data.to, data.time );
isFrameStepping = false;
}
function onWarp( event ) {
var data = event.detail;
blendMesh.stopAll();
blendMesh.warp( data.from, data.to, data.time );
isFrameStepping = false;
}
function onLockCameraToggle( event ) {
var shouldLock = event.detail.shouldLock;
controls.enabled = !shouldLock;
}
function onShowBonesToggle( event ) {
var shouldShow = event.detail.shouldShow;
blendMesh.toggleShowBones( shouldShow );
}
function start() {
blendMesh.rotation.y = Math.PI * -135 / 180;
scene.add( blendMesh );
var aspect = window.innerWidth / window.innerHeight;
var radius = blendMesh.geometry.boundingSphere.radius;
camera = new THREE.PerspectiveCamera( 45, aspect, 1, 10000 );
camera.position.set( 0.0, radius, radius * 3.5 );
controls = new THREE.OrbitControls( camera );
controls.target = new THREE.Vector3( 0, radius, 0 );
controls.update();
// Set default weights
blendMesh.animations[ 'idle' ].weight = 1 / 3;
blendMesh.animations[ 'walk' ].weight = 1 / 3;
blendMesh.animations[ 'run' ].weight = 1 / 3;
gui = new BlendCharacterGui(blendMesh.animations);
animate();
}
function animate() {
requestAnimationFrame( animate, renderer.domElement );
var scale = gui.getTimeScale();
var delta = clock.getDelta();
var stepSize = (!isFrameStepping) ? delta * scale: timeToStep;
blendMesh.updateWeights( stepSize );
gui.update();
THREE.AnimationHandler.update( stepSize );
blendMesh.updateBoneHelpers();
timeToStep = 0;
renderer.clear();
renderer.render( scene, camera );
}
</script>
</body>
</html>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册