Browse Source

optimizations and added USCCharacterMixer (mixer-based USCCharacter).

Ben Houston 10 years ago
parent
commit
cf343180ab

+ 142 - 0
examples/js/UCSCharacterMixer.js

@@ -0,0 +1,142 @@
+THREE.UCSCharacterMixer = function() {
+
+	var scope = this;
+	
+	var mesh;
+
+	this.scale = 1;
+
+	this.root = new THREE.Object3D();
+	
+	this.numSkins;
+	this.numMorphs;
+	
+	this.skins = [];
+	this.materials = [];
+	this.morphs = [];
+
+	this.mixer = new THREE.AnimationMixer( this.root );
+
+	this.onLoadComplete = function () {};
+	
+	this.loadCounter = 0;
+
+	this.loadParts = function ( config ) {
+		
+		this.numSkins = config.skins.length;
+		this.numMorphs = config.morphs.length;
+		
+		// Character geometry + number of skins
+		this.loadCounter = 1 + config.skins.length;
+		
+		// SKINS
+		this.skins = loadTextures( config.baseUrl + "skins/", config.skins );
+		this.materials = createMaterials( this.skins );
+		
+		// MORPHS
+		this.morphs = config.morphs;
+		
+		// CHARACTER
+		var loader = new THREE.JSONLoader();
+		console.log( config.baseUrl + config.character );
+		loader.load( config.baseUrl + config.character, function( geometry ) {
+
+			geometry.computeBoundingBox();
+			geometry.computeVertexNormals();
+			
+			mesh = new THREE.SkinnedMesh( geometry, new THREE.MeshFaceMaterial() );
+			mesh.name = config.character;
+			scope.root.add( mesh );
+			
+			var bb = geometry.boundingBox;
+			scope.root.scale.set( config.s, config.s, config.s );
+			scope.root.position.set( config.x, config.y - bb.min.y * config.s, config.z );
+
+			mesh.castShadow = true;
+			mesh.receiveShadow = true;
+
+			var clipBones = THREE.AnimationClip.FromJSONLoaderAnimation( geometry, mesh.uuid );
+
+			scope.mixer.addAction( new THREE.AnimationAction( clipBones, 0, 1, 1, true ) );
+			
+			scope.setSkin( 0 );
+			
+			scope.checkLoadComplete();
+
+		} );
+
+	};
+	
+	this.setSkin = function( index ) {
+
+		if ( mesh && scope.materials ) {
+
+			mesh.material = scope.materials[ index ];
+
+		}
+
+	};
+	
+	this.updateMorphs = function( influences ) {
+
+		if ( mesh ) {
+
+			for ( var i = 0; i < scope.numMorphs; i ++ ) {
+
+				mesh.morphTargetInfluences[ i ] = influences[ scope.morphs[ i ] ] / 100;
+
+			}
+
+		}
+
+	};
+	
+	function loadTextures( baseUrl, textureUrls ) {
+
+		var mapping = THREE.UVMapping;
+		var textures = [];
+
+		for ( var i = 0; i < textureUrls.length; i ++ ) {
+
+			textures[ i ] = THREE.ImageUtils.loadTexture( baseUrl + textureUrls[ i ], mapping, scope.checkLoadComplete );
+			textures[ i ].name = textureUrls[ i ];
+
+		}
+
+		return textures;
+
+	}
+
+	function createMaterials( skins ) {
+
+		var materials = [];
+		
+		for ( var i = 0; i < skins.length; i ++ ) {
+
+			materials[ i ] = new THREE.MeshLambertMaterial( {
+				color: 0xeeeeee,
+				specular: 10.0,
+				map: skins[ i ],
+				skinning: true,
+				morphTargets: true
+			} );
+
+		}
+		
+		return materials;
+
+	}
+
+	this.checkLoadComplete = function () {
+
+		scope.loadCounter -= 1;
+
+		if ( scope.loadCounter === 0 ) {
+
+			scope.onLoadComplete();
+
+		}
+
+	}
+
+};

+ 236 - 0
examples/webgl_morphtargets_human_mixer.html

@@ -0,0 +1,236 @@
+<!doctype html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - morph target - human</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;
+			}
+
+			a {
+				color: #0af;
+			}
+		</style>
+	</head>
+
+	<body>
+		
+		<div id="container"></div>
+
+		<div id="info">
+		<a href="http://github.com/mrdoob/three.js" target="_blank">three.js</a> webgl - morph targets - human</a>
+		</div>
+
+		<script src="../build/three.min.js"></script>
+		
+		<script src="js/UCSCharacterMixer.js"></script>
+
+		<script src="js/Detector.js"></script>
+		
+		<script src='js/libs/dat.gui.min.js'></script>
+				
+		<script src="js/controls/OrbitControls.js"></script>
+		
+		<script>
+			
+			var SCREEN_WIDTH = window.innerWidth;
+			var SCREEN_HEIGHT = window.innerHeight;
+
+			var container;
+
+			var camera, scene;
+			var renderer;
+			
+			var mesh;
+
+			var mouseX = 0, mouseY = 0;
+
+			var windowHalfX = window.innerWidth / 2;
+			var windowHalfY = window.innerHeight / 2;
+
+			var clock = new THREE.Clock();
+			
+			var gui, skinConfig, morphConfig;
+				
+			document.addEventListener( 'mousemove', onDocumentMouseMove, false );
+
+			init();
+			animate();
+
+			function init() {
+
+				container = document.getElementById( 'container' );
+
+				camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 100000 );
+				camera.position.set( 2000, 5000, 5000 );
+
+				scene = new THREE.Scene();
+
+				// LIGHTS
+
+				var light = new THREE.DirectionalLight( 0xffffff, 1 );
+				light.position.set( 0, 140, 500 );
+				light.position.multiplyScalar( 1.1 );
+				light.color.setHSL( 0.6, 0.075, 1 );
+				scene.add( light );
+
+				//
+
+				var light = new THREE.DirectionalLight( 0xffffff, 1 );
+				light.position.set( 0, -1, 0 );
+				scene.add( light );
+
+				// RENDERER
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setClearColor( 0xffffff );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( SCREEN_WIDTH, SCREEN_HEIGHT );
+				container.appendChild( renderer.domElement );
+
+				// CHARACTER
+
+				character = new THREE.UCSCharacterMixer();
+				character.onLoadComplete = function() {
+					console.log( "Load Complete" );
+					console.log( character.numSkins + " skins and " + character.numMorphs + " morphtargets loaded." );
+					gui = new dat.GUI();
+					setupSkinsGUI();
+					setupMorphsGUI();
+					gui.width = 300;
+					gui.open();
+				};
+				
+				var loader = new THREE.XHRLoader();
+				loader.load("models/skinned/UCS_config.json", function ( text ) {
+
+					var config = JSON.parse( text );
+					character.loadParts( config );
+					scene.add( character.root );
+
+				} );
+
+				window.addEventListener( 'resize', onWindowResize, false );
+				
+				controls = new THREE.OrbitControls( camera, renderer.domElement );
+				controls.center.set( 0, 3000, 0);
+
+				controls.addEventListener( 'change', render );
+
+			}
+			
+			function setupSkinsGUI() {
+			
+				var skinGui = gui.addFolder( "Skins" );
+				
+				skinConfig = {
+					wireframe: false
+				};
+				
+				var skinCallback = function( index ) {
+					return function () {
+						character.setSkin( index );
+					};
+				};
+
+				for ( var i = 0; i < character.numSkins; i++ ) {
+					var name = character.skins[ i ].name;
+					skinConfig[ name ] = skinCallback( i );
+				}
+				
+				for ( var i = 0; i < character.numSkins; i++ ) {
+					skinGui.add( skinConfig, character.skins[i].name );
+				}
+				
+				skinGui.open();
+
+			}
+			
+			function setupMorphsGUI() {
+				
+				var morphGui = gui.addFolder( "Morphs" );
+				
+				morphConfig = {
+				};
+				
+				var morphCallback = function( index ) {
+					return function () {
+						character.updateMorphs( morphConfig );
+					}
+				};
+				
+				for ( var i = 0; i < character.numMorphs; i ++ ) {
+					var morphName = character.morphs[ i ];
+					morphConfig[ morphName ] = 0;
+				}
+				
+				for ( var i = 0; i < character.numMorphs; i ++ ) {
+					morphGui.add( morphConfig, character.morphs[ i ] ).min( 0 ).max( 100 ).onChange( morphCallback( i ) );
+				}
+				
+				morphGui.open();
+			
+			}
+
+			function onWindowResize() {
+
+				windowHalfX = window.innerWidth / 2;
+				windowHalfY = window.innerHeight / 2;
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function onDocumentMouseMove( event ) {
+
+				mouseX = ( event.clientX - windowHalfX ) * 10;
+				mouseY = ( event.clientY - windowHalfY ) * 10;
+
+			}
+
+			//
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				controls.update();
+
+				render();
+
+			}
+
+			function render() {
+
+				var delta = 0.75 * clock.getDelta();
+
+				// update skinning
+
+				character.mixer.update( delta );
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 3 - 3
src/animation/AnimationClip.js

@@ -81,7 +81,7 @@ THREE.AnimationClip.prototype = {
                     },
 */
 
-THREE.AnimationClip.FromJSONLoaderAnimation = function( jsonLoader ) {
+THREE.AnimationClip.FromJSONLoaderAnimation = function( jsonLoader, nodeName ) {
 
 	var animation = jsonLoader.animation;
 	if( ! animation ) {
@@ -165,14 +165,14 @@ THREE.AnimationClip.FromJSONLoaderAnimation = function( jsonLoader ) {
 				
 				}
 
-				tracks.push( new THREE.KeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', keys ) );
+				tracks.push( new THREE.KeyframeTrack( nodeName + '.morphTargetInfluence[' + morphTargetName + ']', keys ) );
 
 			}
 
 		}
 		else {
 
-			var boneName = '.bones[' + boneList[ h ].name + ']';
+			var boneName = nodeName + '.bones[' + boneList[ h ].name + ']';
 			//console.log( 'boneName', boneName );
 		
 			// track contains positions...

+ 24 - 1
src/animation/KeyframeTrack.js

@@ -17,6 +17,8 @@ THREE.KeyframeTrack = function ( name, keys ) {
 	this.result = THREE.AnimationUtils.clone( this.keys[0].value );
 	//console.log( 'constructor result', this.result )
 
+	this.lastIndex = -1;
+
 	this.sort();
 	this.validate();
 	this.optimize();
@@ -32,7 +34,26 @@ THREE.KeyframeTrack.prototype = {
 		//console.log( 'KeyframeTrack[' + this.name + '].getAt( ' + time + ' )' );
 
 		if( this.keys.length == 0 ) throw new Error( "no keys in track named " + this.name );
-		
+
+		// fast early exit	
+		if( this.lastIndex >= 0 ) {
+			if( ( time <= this.keys[this.lastIndex].time ) && ( this.keys[this.lastIndex-1].time < time ) ) {
+
+				var i = this.lastIndex;
+
+				var alpha = ( time - this.keys[ i - 1 ].time ) / ( this.keys[ i ].time - this.keys[ i - 1 ].time );
+
+				this.setResult( this.keys[ i - 1 ].value );
+
+				this.result = this.lerp( this.result, this.keys[ i ].value, alpha );
+
+				return this.result;
+
+			}
+
+			this.lastIndex = -1;
+		}
+
 		//console.log( "keys", this.keys );
 		// before the start of the track, return the first key value
 		if( this.keys[0].time >= time ) {
@@ -80,6 +101,8 @@ THREE.KeyframeTrack.prototype = {
 					value1: this.keys[ i ].value
 				} );*/
 
+				this.lastIndex = i;
+
 				return this.result;
 
 			}

+ 1 - 1
src/animation/PropertyBinding.js

@@ -253,7 +253,7 @@ THREE.PropertyBinding.parseTrackName = function( trackName ) {
 	//	  .bone[Armature.DEF_cog].position
 	// created and tested via https://regex101.com/#javascript
 
-	var re = /^(([\w]+\/)*)([\w-]+)?(\.([\w]+)(\[([\w.]+)\])?)?(\.([\w.]+)(\[([\w]+)\])?)$/; 
+	var re = /^(([\w]+\/)*)([\w-\d]+)?(\.([\w]+)(\[([\w\d\[\]\_. ]+)\])?)?(\.([\w.]+)(\[([\w\d\[\]\_. ]+)\])?)$/; 
 	var matches = re.exec(trackName);
 
 	if( ! matches ) {