|
@@ -10,25 +10,32 @@
|
|
|
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;
|
|
|
}
|
|
|
+ .ac {
|
|
|
+ -webkit-user-select: none;
|
|
|
+ -moz-user-select: none;
|
|
|
+ -ms-user-select: none;
|
|
|
+ user-select: none;
|
|
|
+ }
|
|
|
+ a {
|
|
|
+ color: #bbb;
|
|
|
+ }
|
|
|
</style>
|
|
|
</head>
|
|
|
<body>
|
|
|
<div id="container"></div>
|
|
|
<div id="info">
|
|
|
<a href="http://threejs.org" target="_blank">three.js</a> - Skeletal Animation Blending
|
|
|
- <br><br> Adjust blend weights to affect the animations that are currently playing.
|
|
|
- <br> Cross fades (and warping) blend between 2 animations and end with a single animation.
|
|
|
+ (model from <a href="http://realitymeltdown.com" target="_blank">realitymeltdown.com</a>)
|
|
|
+ <br><br>- camera orbit/zoom/pan with left/middle/right mouse button -
|
|
|
</div>
|
|
|
|
|
|
<script src="../build/three.js"></script>
|
|
@@ -36,220 +43,447 @@
|
|
|
<script src="js/Detector.js"></script>
|
|
|
<script src="js/libs/stats.min.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, stats;
|
|
|
+ var container = document.getElementById( 'container' );
|
|
|
+
|
|
|
+ var scene, renderer, camera, controls, stats;
|
|
|
+ var mesh, skeleton, mixer;
|
|
|
|
|
|
- var blendMesh, helper, camera, scene, renderer, controls;
|
|
|
+ var idleAction, walkAction, runAction;
|
|
|
+ var actions;
|
|
|
+ var settings;
|
|
|
|
|
|
var clock = new THREE.Clock();
|
|
|
- var gui = null;
|
|
|
|
|
|
- var isFrameStepping = false;
|
|
|
- var timeToStep = 0;
|
|
|
+ var singleStepMode = false;
|
|
|
+ var sizeOfNextStep = 0;
|
|
|
|
|
|
- init();
|
|
|
+ var url = 'models/skinned/marine/marine_anims_core.json';
|
|
|
|
|
|
- function init() {
|
|
|
|
|
|
- container = document.getElementById( 'container' );
|
|
|
+ // Initialize stats (fps display)
|
|
|
|
|
|
- scene = new THREE.Scene();
|
|
|
- scene.add ( new THREE.AmbientLight( 0xffffff ) );
|
|
|
+ stats = new Stats();
|
|
|
+ container.appendChild( stats.dom );
|
|
|
|
|
|
- renderer = new THREE.WebGLRenderer( { antialias: true, alpha: false } );
|
|
|
- renderer.setClearColor( 0x777777 );
|
|
|
- renderer.setPixelRatio( window.devicePixelRatio );
|
|
|
- renderer.setSize( window.innerWidth, window.innerHeight );
|
|
|
- renderer.autoClear = true;
|
|
|
|
|
|
- container.appendChild( renderer.domElement );
|
|
|
+ // Initialize scene, light and renderer
|
|
|
+
|
|
|
+ scene = new THREE.Scene();
|
|
|
+ scene.add( new THREE.AmbientLight( 0xffffff ) );
|
|
|
+
|
|
|
+ renderer = new THREE.WebGLRenderer( { antialias: true } );
|
|
|
+ renderer.setClearColor( 0x333333 );
|
|
|
+ renderer.setPixelRatio( window.devicePixelRatio );
|
|
|
+ renderer.setSize( window.innerWidth, window.innerHeight );
|
|
|
+
|
|
|
+ container.appendChild( renderer.domElement );
|
|
|
+
|
|
|
+
|
|
|
+ // Load skinned mesh
|
|
|
+
|
|
|
+ new THREE.ObjectLoader().load( url, function ( loadedObject ) {
|
|
|
+
|
|
|
+ loadedObject.traverse( function ( child ) {
|
|
|
+
|
|
|
+ if ( child instanceof THREE.SkinnedMesh ) {
|
|
|
+
|
|
|
+ mesh = child;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ } );
|
|
|
+
|
|
|
+ if ( mesh === undefined ) {
|
|
|
+
|
|
|
+ alert( 'Unable to find a SkinnedMesh in this place:\n\n' + url + '\n\n' );
|
|
|
+ return;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // Add mesh and skeleton helper to scene
|
|
|
+
|
|
|
+ mesh.rotation.y = - 135 * Math.PI / 180;
|
|
|
+ scene.add( mesh );
|
|
|
+
|
|
|
+ skeleton = new THREE.SkeletonHelper( mesh );
|
|
|
+ skeleton.visible = false;
|
|
|
+ scene.add( skeleton );
|
|
|
+
|
|
|
+
|
|
|
+ // Initialize camera and camera controls
|
|
|
+
|
|
|
+ var radius = mesh.geometry.boundingSphere.radius;
|
|
|
+
|
|
|
+ var aspect = window.innerWidth / window.innerHeight;
|
|
|
+ camera = new THREE.PerspectiveCamera( 45, aspect, 1, 10000 );
|
|
|
+ camera.position.set( 0.0, radius, radius * 3.5 );
|
|
|
+
|
|
|
+ controls = new THREE.OrbitControls( camera, renderer.domElement );
|
|
|
+ controls.target.set( 0, radius, 0 );
|
|
|
+ controls.update();
|
|
|
+
|
|
|
|
|
|
- //
|
|
|
+ // Create the control panel
|
|
|
|
|
|
- stats = new Stats();
|
|
|
- container.appendChild( stats.dom );
|
|
|
+ createPanel();
|
|
|
|
|
|
- //
|
|
|
+
|
|
|
+ // Initialize mixer and clip actions
|
|
|
+
|
|
|
+ mixer = new THREE.AnimationMixer( mesh );
|
|
|
+
|
|
|
+ idleAction = mixer.clipAction( 'idle' );
|
|
|
+ walkAction = mixer.clipAction( 'walk' );
|
|
|
+ runAction = mixer.clipAction( 'run' );
|
|
|
+ actions = [ idleAction, walkAction, runAction ];
|
|
|
+
|
|
|
+ activateAllActions();
|
|
|
+
|
|
|
+
|
|
|
+ // Listen on window resizing and start the render loop
|
|
|
|
|
|
window.addEventListener( 'resize', onWindowResize, false );
|
|
|
+ animate();
|
|
|
|
|
|
- // listen for messages from the gui
|
|
|
- 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-show-skeleton', onShowSkeleton );
|
|
|
- window.addEventListener( 'toggle-show-model', onShowModel );
|
|
|
|
|
|
- blendMesh = new THREE.BlendCharacter();
|
|
|
- blendMesh.load( "models/skinned/marine/marine_anims_core.json", start );
|
|
|
+ } );
|
|
|
+
|
|
|
+
|
|
|
+ function createPanel() {
|
|
|
+
|
|
|
+ var panel = new dat.GUI( { width: 310 } );
|
|
|
+
|
|
|
+ var folder1 = panel.addFolder( 'Visibility' );
|
|
|
+ var folder2 = panel.addFolder( 'Activation/Deactivation' );
|
|
|
+ var folder3 = panel.addFolder( 'Pausing/Stepping' );
|
|
|
+ var folder4 = panel.addFolder( 'Crossfading' );
|
|
|
+ var folder5 = panel.addFolder( 'Blend Weights' );
|
|
|
+ var folder6 = panel.addFolder( 'General Speed' );
|
|
|
+
|
|
|
+ settings = {
|
|
|
+ 'show model': true,
|
|
|
+ 'show skeleton': false,
|
|
|
+ 'deactivate all': deactivateAllActions,
|
|
|
+ 'activate all': activateAllActions,
|
|
|
+ 'pause/continue': pauseContinue,
|
|
|
+ 'make single step': toSingleStepMode,
|
|
|
+ 'modify step size': 0.05,
|
|
|
+ 'from walk to idle': function() { prepareCrossFade( walkAction, idleAction, runAction, 1.0 ) },
|
|
|
+ 'from idle to walk': function() { prepareCrossFade( idleAction, walkAction, runAction, 0.5 ) },
|
|
|
+ 'from walk to run': function() { prepareCrossFade( walkAction, runAction, idleAction, 2.5 ) },
|
|
|
+ 'from run to walk': function() { prepareCrossFade( runAction, walkAction, idleAction, 5.0 ) },
|
|
|
+ 'use default duration': true,
|
|
|
+ 'set custom duration': 3.5,
|
|
|
+ 'modify idle weight': 0.0,
|
|
|
+ 'modify walk weight': 1.0,
|
|
|
+ 'modify run weight': 0.0,
|
|
|
+ 'modify time scale': 1.0
|
|
|
+ };
|
|
|
+
|
|
|
+ folder1.add( settings, 'show model' ).onChange( showModel );
|
|
|
+ folder1.add( settings, 'show skeleton' ).onChange( showSkeleton );
|
|
|
+ folder2.add( settings, 'deactivate all' );
|
|
|
+ folder2.add( settings, 'activate all' );
|
|
|
+ folder3.add( settings, 'pause/continue' );
|
|
|
+ folder3.add( settings, 'make single step' );
|
|
|
+ folder3.add( settings, 'modify step size', 0.01, 0.1, 0.001 );
|
|
|
+ folder4.add( settings, 'from walk to idle' );
|
|
|
+ folder4.add( settings, 'from idle to walk' );
|
|
|
+ folder4.add( settings, 'from walk to run' );
|
|
|
+ folder4.add( settings, 'from run to walk' );
|
|
|
+ folder4.add( settings, 'use default duration' );
|
|
|
+ folder4.add( settings, 'set custom duration', 0, 10, 0.01 );
|
|
|
+ folder5.add( settings, 'modify idle weight', 0.0, 1.0, 0.01 ).listen().onChange( function(weight) { setWeight( idleAction, weight ) } );
|
|
|
+ folder5.add( settings, 'modify walk weight', 0.0, 1.0, 0.01 ).listen().onChange( function(weight) { setWeight( walkAction, weight ) } );
|
|
|
+ folder5.add( settings, 'modify run weight', 0.0, 1.0, 0.01 ).listen().onChange( function(weight) { setWeight( runAction, weight ) } );
|
|
|
+ folder6.add( settings, 'modify time scale', 0.0, 1.5, 0.01 ).onChange( modifyTimeScale );
|
|
|
+
|
|
|
+ folder1.open();
|
|
|
+ folder2.open();
|
|
|
+ folder3.open();
|
|
|
+ folder4.open();
|
|
|
+ folder5.open();
|
|
|
+ folder6.open();
|
|
|
|
|
|
}
|
|
|
|
|
|
- function onWindowResize() {
|
|
|
|
|
|
- camera.aspect = window.innerWidth / window.innerHeight;
|
|
|
- camera.updateProjectionMatrix();
|
|
|
+ function showModel( visibility ) {
|
|
|
|
|
|
- renderer.setSize( window.innerWidth, window.innerHeight );
|
|
|
+ mesh.visible = visibility;
|
|
|
|
|
|
}
|
|
|
|
|
|
- function onStartAnimation( event ) {
|
|
|
|
|
|
- var data = event.detail;
|
|
|
+ function showSkeleton( visibility ) {
|
|
|
|
|
|
- blendMesh.stopAll();
|
|
|
- blendMesh.unPauseAll();
|
|
|
+ skeleton.visible = visibility;
|
|
|
|
|
|
- // 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]);
|
|
|
|
|
|
- }
|
|
|
+ function modifyTimeScale( speed ) {
|
|
|
|
|
|
- isFrameStepping = false;
|
|
|
+ mixer.timeScale = speed;
|
|
|
|
|
|
}
|
|
|
|
|
|
- function onStopAnimation( event ) {
|
|
|
|
|
|
- blendMesh.stopAll();
|
|
|
- isFrameStepping = false;
|
|
|
+ function deactivateAllActions() {
|
|
|
+
|
|
|
+ actions.forEach( function ( action ) {
|
|
|
+
|
|
|
+ action.stop();
|
|
|
+
|
|
|
+ } );
|
|
|
|
|
|
}
|
|
|
|
|
|
- function onPauseAnimation( event ) {
|
|
|
|
|
|
- ( isFrameStepping ) ? blendMesh.unPauseAll(): blendMesh.pauseAll();
|
|
|
+ function activateAllActions() {
|
|
|
|
|
|
- isFrameStepping = false;
|
|
|
+ setWeight( idleAction, settings[ 'modify idle weight' ] );
|
|
|
+ setWeight( walkAction, settings[ 'modify walk weight' ] );
|
|
|
+ setWeight( runAction, settings[ 'modify run weight' ] );
|
|
|
|
|
|
- }
|
|
|
+ actions.forEach( function ( action ) {
|
|
|
+
|
|
|
+ action.play();
|
|
|
|
|
|
- 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 ) {
|
|
|
+ function pauseContinue() {
|
|
|
|
|
|
- blendMesh.applyWeight( data.anims[ i ], data.weights[ i ] );
|
|
|
+ if ( singleStepMode ) {
|
|
|
+
|
|
|
+ singleStepMode = false;
|
|
|
+ unPauseAllActions();
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ if ( idleAction.paused ) {
|
|
|
+
|
|
|
+ unPauseAllActions();
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ pauseAllActions();
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
- function onCrossfade(event) {
|
|
|
|
|
|
- var data = event.detail;
|
|
|
+ function pauseAllActions() {
|
|
|
|
|
|
- blendMesh.stopAll();
|
|
|
- blendMesh.crossfade( data.from, data.to, data.time );
|
|
|
+ actions.forEach( function ( action ) {
|
|
|
|
|
|
- isFrameStepping = false;
|
|
|
+ action.paused = true;
|
|
|
+
|
|
|
+ } );
|
|
|
|
|
|
}
|
|
|
|
|
|
- function onWarp( event ) {
|
|
|
|
|
|
- var data = event.detail;
|
|
|
+ function unPauseAllActions() {
|
|
|
+
|
|
|
+ actions.forEach( function ( action ) {
|
|
|
|
|
|
- blendMesh.stopAll();
|
|
|
- blendMesh.warp( data.from, data.to, data.time );
|
|
|
+ action.paused = false;
|
|
|
|
|
|
- isFrameStepping = false;
|
|
|
+ } );
|
|
|
|
|
|
}
|
|
|
|
|
|
- function onShowSkeleton( event ) {
|
|
|
|
|
|
- var shouldShow = event.detail.shouldShow;
|
|
|
- helper.visible = shouldShow;
|
|
|
+ function toSingleStepMode() {
|
|
|
+
|
|
|
+ unPauseAllActions();
|
|
|
+
|
|
|
+ singleStepMode = true;
|
|
|
+ sizeOfNextStep = settings[ 'modify step size' ];
|
|
|
|
|
|
}
|
|
|
|
|
|
- function onShowModel( event ) {
|
|
|
|
|
|
- var shouldShow = event.detail.shouldShow;
|
|
|
- blendMesh.showModel( shouldShow );
|
|
|
+ function prepareCrossFade( startAction, endAction, otherAction, defaultDuration ) {
|
|
|
+
|
|
|
+ var duration;
|
|
|
+
|
|
|
+ // If the current weight values don't allow the choosen crossfade type,
|
|
|
+ // display a message only, and modify the blend weights accordingly; else go on
|
|
|
+
|
|
|
+ if ( startAction.getEffectiveWeight() !== 1 ||
|
|
|
+ endAction.getEffectiveWeight() !== 0 ||
|
|
|
+ otherAction.getEffectiveWeight() !== 0 ) {
|
|
|
+
|
|
|
+ displayMessage();
|
|
|
+ prepareWeightsForCrossfade( startAction, endAction, otherAction );
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ // Switch default / custom crossfade duration (according to the user's choice)
|
|
|
+
|
|
|
+ var duration = setCrossFadeDuration( defaultDuration );
|
|
|
+
|
|
|
+ // Make sure that we don't go on in singleStepMode, and that all actions are unpaused
|
|
|
+
|
|
|
+ singleStepMode = false;
|
|
|
+ unPauseAllActions();
|
|
|
+
|
|
|
+ // If the current action is 'idle' (duration 4 sec), execute the crossfade immediately;
|
|
|
+ // else wait until the current action has finished its current loop
|
|
|
+
|
|
|
+ if ( startAction === idleAction ) {
|
|
|
+
|
|
|
+ executeCrossFade( startAction, endAction, duration );
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ synchronizeCrossFade( startAction, endAction, duration );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
}
|
|
|
|
|
|
- function start() {
|
|
|
|
|
|
- blendMesh.rotation.y = Math.PI * -135 / 180;
|
|
|
- scene.add( blendMesh );
|
|
|
+ function displayMessage() {
|
|
|
|
|
|
- var aspect = window.innerWidth / window.innerHeight;
|
|
|
- var radius = blendMesh.geometry.boundingSphere.radius;
|
|
|
+ alert( 'Crossfading is not useful if its start animation isn\'t already running before (or if it is not the only one at this moment).\n\n' +
|
|
|
+ 'Thus the initial blend weights are now modified according to your crossfade choice.\n\n' +
|
|
|
+ 'That being done you can try the same crossfade again by clicking its button!\n\n' );
|
|
|
|
|
|
- camera = new THREE.PerspectiveCamera( 45, aspect, 1, 10000 );
|
|
|
- camera.position.set( 0.0, radius, radius * 3.5 );
|
|
|
+ }
|
|
|
|
|
|
- controls = new THREE.OrbitControls( camera );
|
|
|
- controls.target.set( 0, radius, 0 );
|
|
|
- controls.update();
|
|
|
|
|
|
- // Set default weights
|
|
|
- blendMesh.applyWeight( 'idle', 1 / 3 );
|
|
|
- blendMesh.applyWeight( 'walk', 1 / 3 );
|
|
|
- blendMesh.applyWeight( 'run', 1 / 3 );
|
|
|
+ function prepareWeightsForCrossfade( startAction, endAction, otherAction ) {
|
|
|
|
|
|
- gui = new BlendCharacterGui(blendMesh);
|
|
|
+ setWeight( startAction, 1 );
|
|
|
+ setWeight( endAction, 0 );
|
|
|
+ setWeight( otherAction, 0 );
|
|
|
|
|
|
- // Create the debug visualization
|
|
|
+ }
|
|
|
|
|
|
- helper = new THREE.SkeletonHelper( blendMesh );
|
|
|
- helper.material.linewidth = 3;
|
|
|
- scene.add( helper );
|
|
|
|
|
|
- helper.visible = false;
|
|
|
+ function setCrossFadeDuration( defaultDuration ) {
|
|
|
+
|
|
|
+ // Switch default crossfade duration <-> custom crossfade duration
|
|
|
+
|
|
|
+ if ( settings[ 'use default duration' ] ) {
|
|
|
+
|
|
|
+ return defaultDuration;
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ return settings[ 'set custom duration' ];
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ function synchronizeCrossFade( startAction, endAction, duration ) {
|
|
|
+
|
|
|
+ mixer.addEventListener( 'loop', onLoopFinished );
|
|
|
+
|
|
|
+ function onLoopFinished( event ) {
|
|
|
+
|
|
|
+ if ( event.action === startAction ) {
|
|
|
+
|
|
|
+ mixer.removeEventListener( 'loop', onLoopFinished );
|
|
|
+
|
|
|
+ executeCrossFade( startAction, endAction, duration );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ function executeCrossFade( startAction, endAction, duration ) {
|
|
|
+
|
|
|
+ // Not only the start action, but also the end action must get a weight of 1 before fading
|
|
|
+ // (concerning the start action this is already guaranteed in this place)
|
|
|
+
|
|
|
+ setWeight( endAction, 1 );
|
|
|
+ endAction.time = 0;
|
|
|
+
|
|
|
+ // Crossfade with warping - you can also try without warping by setting the third parameter to false
|
|
|
+
|
|
|
+ startAction.crossFadeTo( endAction, duration, true );
|
|
|
|
|
|
- animate();
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+ // This function is needed, since animationAction.crossFadeTo() disables its start action and sets
|
|
|
+ // the start action's timeScale to ((start animation's duration) / (end animation's duration))
|
|
|
+
|
|
|
+ function setWeight( action, weight ) {
|
|
|
+
|
|
|
+ action.enabled = true;
|
|
|
+ action.setEffectiveTimeScale( 1 );
|
|
|
+ action.setEffectiveWeight( weight );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ function onWindowResize() {
|
|
|
+
|
|
|
+ camera.aspect = window.innerWidth / window.innerHeight;
|
|
|
+ camera.updateProjectionMatrix();
|
|
|
+
|
|
|
+ renderer.setSize( window.innerWidth, window.innerHeight );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
function animate() {
|
|
|
|
|
|
- requestAnimationFrame( animate, renderer.domElement );
|
|
|
+ // Render loop
|
|
|
|
|
|
- stats.begin();
|
|
|
+ requestAnimationFrame( animate );
|
|
|
|
|
|
- // step forward in time based on whether we're stepping and scale
|
|
|
+ // Update the panel values if weights are modified from "outside" (by crossfadings)
|
|
|
|
|
|
- var scale = gui.getTimeScale();
|
|
|
- var delta = clock.getDelta();
|
|
|
- var stepSize = (!isFrameStepping) ? delta * scale: timeToStep;
|
|
|
+ settings[ 'modify idle weight' ] = idleAction.getEffectiveWeight();
|
|
|
+ settings[ 'modify walk weight' ] = walkAction.getEffectiveWeight();
|
|
|
+ settings[ 'modify run weight' ] = runAction.getEffectiveWeight();
|
|
|
|
|
|
- // modify blend weights
|
|
|
+ // Get the time elapsed since the last frame, used for mixer update (if not in single step mode)
|
|
|
|
|
|
- blendMesh.update( stepSize );
|
|
|
- helper.update();
|
|
|
- gui.update( blendMesh.mixer.time );
|
|
|
+ var mixerUpdateDelta = clock.getDelta();
|
|
|
|
|
|
- renderer.render( scene, camera );
|
|
|
- stats.end();
|
|
|
+ // If in single step mode, make one step and then do nothing (until the user clicks again)
|
|
|
+
|
|
|
+ if ( singleStepMode ) {
|
|
|
+
|
|
|
+ mixerUpdateDelta = sizeOfNextStep;
|
|
|
+ sizeOfNextStep = 0;
|
|
|
|
|
|
- // if we are stepping, consume time
|
|
|
- // ( will equal step size next time a single step is desired )
|
|
|
+ }
|
|
|
+
|
|
|
+ // Update the animation mixer, the skeleton and the stats panel, and render this frame
|
|
|
|
|
|
- timeToStep = 0;
|
|
|
+ mixer.update( mixerUpdateDelta );
|
|
|
+ skeleton.update();
|
|
|
+ stats.update();
|
|
|
+
|
|
|
+ renderer.render( scene, camera );
|
|
|
|
|
|
}
|
|
|
|