Przeglądaj źródła

blendmesh example newly implemented

Jost Schmithals 8 lat temu
rodzic
commit
0a4f5b2d8e

+ 0 - 182
examples/js/BlendCharacter.js

@@ -1,182 +0,0 @@
-/**
- * @author Michael Guerrero / http://realitymeltdown.com
- */
-
-THREE.BlendCharacter = function () {
-
-	this.weightSchedule = [];
-	this.warpSchedule = [];
-
-	this.load = function ( url, onLoad ) {
-
-		var scope = this;
-
-		var loader = new THREE.ObjectLoader();
-		loader.load( url, function( loadedObject ) {
-
-			// The exporter does not currently allow exporting a skinned mesh by itself
-			// so we must fish it out of the hierarchy it is embedded in (scene)
-			loadedObject.traverse( function( object ) {
-
-				if ( object instanceof THREE.SkinnedMesh ) {
-
-					scope.skinnedMesh = object;
-
-				}
-
-			} );
-
-			THREE.SkinnedMesh.call( scope, scope.skinnedMesh.geometry, scope.skinnedMesh.material );
-
-			// If we didn't successfully find the mesh, bail out
-			if ( scope.skinnedMesh == undefined ) {
-
-				console.log( 'unable to find skinned mesh in ' + url );
-				return;
-
-			}
-
-			scope.material.skinning = true;
-
-			scope.mixer = new THREE.AnimationMixer( scope );
-
-			// Create the animations
-			for ( var i = 0; i < scope.geometry.animations.length; ++ i ) {
-
-				scope.mixer.clipAction( scope.geometry.animations[ i ] );
-
-			}
-
-			// Loading is complete, fire the callback
-			if ( onLoad !== undefined ) onLoad();
-
-		} );
-
-	};
-
-	this.loadJSON = function ( url, onLoad ) {
-
-		var scope = this;
-
-		var loader = new THREE.JSONLoader();
-		loader.load( url, function( geometry, materials ) {
-
-			var originalMaterial = materials[ 0 ];
-			originalMaterial.skinning = true;
-
-			THREE.SkinnedMesh.call( scope, geometry, originalMaterial );
-
-			var mixer = new THREE.AnimationMixer( scope );
-			scope.mixer = mixer;
-
-			// Create the animations
-			for ( var i = 0; i < geometry.animations.length; ++ i ) {
-
-				mixer.clipAction( geometry.animations[ i ] );
-
-			}
-
-			// Loading is complete, fire the callback
-			if ( onLoad !== undefined ) onLoad();
-
-		} );
-
-	};
-	
-	this.update = function( dt ) {
-
-		this.mixer.update( dt );
-
-	};
-
-	this.play = function( animName, weight ) {
-
-		//console.log("play('%s', %f)", animName, weight);
-		return this.mixer.clipAction( animName ).
-				setEffectiveWeight( weight ).play();
-	};
-
-	this.crossfade = function( fromAnimName, toAnimName, duration ) {
-
-		this.mixer.stopAllAction();
-
-		var fromAction = this.play( fromAnimName, 1 );
-		var toAction = this.play( toAnimName, 1 );
-
-		fromAction.crossFadeTo( toAction, duration, false );
-
-	};
-
-	this.warp = function( fromAnimName, toAnimName, duration ) {
-
-		this.mixer.stopAllAction();
-
-		var fromAction = this.play( fromAnimName, 1 );
-		var toAction = this.play( toAnimName, 1 );
-
-		fromAction.crossFadeTo( toAction, duration, true );
-
-	};
-
-	this.applyWeight = function( animName, weight ) {
-
-		this.mixer.clipAction( animName ).setEffectiveWeight( weight );
-
-	};
-
-	this.getWeight = function( animName ) {
-
-		return this.mixer.clipAction( animName ).getEffectiveWeight();
-
-	};
-
-	this.pauseAll = function() {
-
-		this.mixer.timeScale = 0;
-
-	};
-
-	this.unPauseAll = function() {
-
-		this.mixer.timeScale = 1;
-
-	};
-
-
-	this.stopAll = function() {
-
-		this.mixer.stopAllAction();
-
-	};
-
-	this.showModel = function( boolean ) {
-
-		this.visible = boolean;
-
-	};
-
-};
-
-
-THREE.BlendCharacter.prototype = Object.create( THREE.SkinnedMesh.prototype );
-THREE.BlendCharacter.prototype.constructor = THREE.BlendCharacter;
-
-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;
-
-	}
-
-};
-

+ 0 - 207
examples/js/BlendCharacterGui.js

@@ -1,207 +0,0 @@
-/**
- * @author Michael Guerrero / http://realitymeltdown.com
- */
-
-function BlendCharacterGui( blendMesh ) {
-
-	var controls = {
-
-		gui: null,
-		"Show Model": true,
-		"Show Skeleton": false,
-		"Time Scale": 1.0,
-		"Step Size": 0.016,
-		"Crossfade Time": 3.5,
-		"idle": 0.33,
-		"walk": 0.33,
-		"run": 0.33
-
-	};
-
-	var blendMesh = blendMesh;
-
-	this.showModel = function() {
-
-		return controls[ 'Show Model' ];
-
-	};
-
-	this.showSkeleton = function() {
-
-		return controls[ 'Show Skeleton' ];
-
-	};
-
-	this.getTimeScale = function() {
-
-		return controls[ 'Time Scale' ];
-
-	};
-
-	this.update = function( time ) {
-
-		controls[ 'idle' ] = blendMesh.getWeight( 'idle' );
-		controls[ 'walk' ] = blendMesh.getWeight( 'walk' );
-		controls[ 'run' ] = blendMesh.getWeight( 'run' );
-
-	};
-
-	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, "Show Model" ).onChange( controls.showModelChanged );
-		settings.add( controls, "Show Skeleton" ).onChange( controls.showSkeletonChanged );
-		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.showSkeletonChanged = function() {
-
-		var data = {
-			detail: {
-				shouldShow: controls[ 'Show Skeleton' ]
-			}
-		};
-
-		window.dispatchEvent( new CustomEvent( 'toggle-show-skeleton', data ) );
-
-	};
-
-
-	controls.showModelChanged = function() {
-
-		var data = {
-			detail: {
-				shouldShow: controls[ 'Show Model' ]
-			}
-		};
-
-		window.dispatchEvent( new CustomEvent( 'toggle-show-model', data ) );
-
-	};
-
-
-	init.call( this );
-
-}

+ 352 - 118
examples/webgl_animation_skinning_blending.html

@@ -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 );
 
 			}