Browse Source

auto skeleton offset :tada: and retarget example

sunag 7 years ago
parent
commit
0da5df32d1

+ 1 - 0
examples/files.js

@@ -118,6 +118,7 @@ var files = {
 		"webgl_loader_prwm",
 		"webgl_loader_prwm",
 		"webgl_loader_sea3d",
 		"webgl_loader_sea3d",
 		"webgl_loader_sea3d_bvh",
 		"webgl_loader_sea3d_bvh",
+		"webgl_loader_sea3d_bvh_retarget",
 		"webgl_loader_sea3d_hierarchy",
 		"webgl_loader_sea3d_hierarchy",
 		"webgl_loader_sea3d_keyframe",
 		"webgl_loader_sea3d_keyframe",
 		"webgl_loader_sea3d_morph",
 		"webgl_loader_sea3d_morph",

+ 139 - 14
examples/js/utils/SkeletonUtils.js

@@ -8,8 +8,10 @@ THREE.SkeletonUtils = {
 
 
 	retarget: function () {
 	retarget: function () {
 
 
-		var quat = new THREE.Quaternion(),
+		var pos = new THREE.Vector3(),
+			quat = new THREE.Quaternion(),
 			scale = new THREE.Vector3(),
 			scale = new THREE.Vector3(),
+			hipPosition = new THREE.Vector3(),
 			bindBoneMatrix = new THREE.Matrix4(),
 			bindBoneMatrix = new THREE.Matrix4(),
 			relativeMatrix = new THREE.Matrix4(),
 			relativeMatrix = new THREE.Matrix4(),
 			globalMatrix = new THREE.Matrix4();
 			globalMatrix = new THREE.Matrix4();
@@ -19,6 +21,7 @@ THREE.SkeletonUtils = {
 			options = options || {};
 			options = options || {};
 			options.preserveMatrix = options.preserveMatrix !== undefined ? options.preserveMatrix : true;
 			options.preserveMatrix = options.preserveMatrix !== undefined ? options.preserveMatrix : true;
 			options.preservePosition = options.preservePosition !== undefined ? options.preservePosition : true;
 			options.preservePosition = options.preservePosition !== undefined ? options.preservePosition : true;
+			options.preserveHipPosition = options.preserveHipPosition !== undefined ? options.preserveHipPosition : false;
 			options.useTargetMatrix = options.useTargetMatrix !== undefined ? options.useTargetMatrix : false;
 			options.useTargetMatrix = options.useTargetMatrix !== undefined ? options.useTargetMatrix : false;
 			options.hip = options.hip !== undefined ? options.hip : "hip";
 			options.hip = options.hip !== undefined ? options.hip : "hip";
 			options.names = options.names || {};
 			options.names = options.names || {};
@@ -81,7 +84,7 @@ THREE.SkeletonUtils = {
 					bone = bones[ i ];
 					bone = bones[ i ];
 					name = options.names[ bone.name ] || bone.name;
 					name = options.names[ bone.name ] || bone.name;
 
 
-					if ( options.offsets[ name ] ) {
+					if ( options.offsets && options.offsets[ name ] ) {
 
 
 						bone.matrix.multiply( options.offsets[ name ] );
 						bone.matrix.multiply( options.offsets[ name ] );
 
 
@@ -90,7 +93,7 @@ THREE.SkeletonUtils = {
 						bone.updateMatrixWorld();
 						bone.updateMatrixWorld();
 
 
 					}
 					}
-
+					
 					bindBones.push( bone.matrixWorld.clone() );
 					bindBones.push( bone.matrixWorld.clone() );
 
 
 				}
 				}
@@ -102,14 +105,14 @@ THREE.SkeletonUtils = {
 				bone = bones[ i ];
 				bone = bones[ i ];
 				name = options.names[ bone.name ] || bone.name;
 				name = options.names[ bone.name ] || bone.name;
 
 
-				boneTo = this.getBoneByName( sourceBones, name );
+				boneTo = this.getBoneByName( name, sourceBones );
 
 
 				globalMatrix.copy( bone.matrixWorld );
 				globalMatrix.copy( bone.matrixWorld );
 
 
 				if ( boneTo ) {
 				if ( boneTo ) {
 
 
 					boneTo.updateMatrixWorld();
 					boneTo.updateMatrixWorld();
-
+					
 					if ( options.useTargetMatrix ) {
 					if ( options.useTargetMatrix ) {
 
 
 						relativeMatrix.copy( boneTo.matrixWorld );
 						relativeMatrix.copy( boneTo.matrixWorld );
@@ -154,6 +157,12 @@ THREE.SkeletonUtils = {
 
 
 				}
 				}
 
 
+				if ( options.preserveHipPosition && name === options.hip ) {
+					
+					bone.matrix.setPosition( pos.set( 0, bone.position.y, 0 ) );
+					
+				}
+				
 				bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
 				bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
 
 
 				bone.updateMatrixWorld();
 				bone.updateMatrixWorld();
@@ -198,10 +207,7 @@ THREE.SkeletonUtils = {
 
 
 		if ( ! source.isObject3D ) {
 		if ( ! source.isObject3D ) {
 
 
-			var skeleton = source;
-
-			source = new THREE.SkeletonHelper( skeleton.bones[ 0 ] );
-			source.skeleton = skeleton;
+			source = this.getHelperFromSkeleton( source );
 
 
 		}
 		}
 
 
@@ -212,7 +218,8 @@ THREE.SkeletonUtils = {
 			bones = this.getBones( target.skeleton ),
 			bones = this.getBones( target.skeleton ),
 			boneDatas = [],
 			boneDatas = [],
 			positionOffset,
 			positionOffset,
-			bone, boneTo, boneData, i, j;
+			bone, boneTo, boneData, 
+			name, i, j;
 
 
 		mixer.clipAction( clip ).play();
 		mixer.clipAction( clip ).play();
 		mixer.update( 0 );
 		mixer.update( 0 );
@@ -227,14 +234,16 @@ THREE.SkeletonUtils = {
 
 
 			for ( j = 0; j < bones.length; ++ j ) {
 			for ( j = 0; j < bones.length; ++ j ) {
 
 
-				boneTo = this.getBoneByName( source.skeleton, options.names[ bones[ j ].name ] || bones[ j ].name );
+				name = options.names[ bones[ j ].name ] || bones[ j ].name;
+				
+				boneTo = this.getBoneByName( name, source.skeleton );
 
 
 				if ( boneTo ) {
 				if ( boneTo ) {
 
 
 					bone = bones[ j ];
 					bone = bones[ j ];
 					boneData = boneDatas[ j ] = boneDatas[ j ] || { bone: bone };
 					boneData = boneDatas[ j ] = boneDatas[ j ] || { bone: bone };
 
 
-					if ( options.hip === boneData.bone.name ) {
+					if ( options.hip === name ) {
 
 
 						if ( ! boneData.pos ) {
 						if ( ! boneData.pos ) {
 
 
@@ -317,8 +326,107 @@ THREE.SkeletonUtils = {
 		return new THREE.AnimationClip( clip.name, - 1, convertedTracks );
 		return new THREE.AnimationClip( clip.name, - 1, convertedTracks );
 
 
 	},
 	},
+	
+	getHelperFromSkeleton: function( skeleton ) {
+		
+		var source = new THREE.SkeletonHelper( skeleton.bones[ 0 ] );
+		source.skeleton = skeleton;
+		
+		return source;
+		
+	},
+	
+	getSkeletonOffsets: function () {
+
+		var targetParentPos = new THREE.Vector3(),
+			targetPos = new THREE.Vector3(),
+			sourceParentPos = new THREE.Vector3(),
+			sourcePos = new THREE.Vector3(),
+			targetDir = new THREE.Vector2(),
+			sourceDir = new THREE.Vector2();
+
+		return function ( target, source, options ) {
 
 
-	rename: function ( skeleton, names ) {
+			options = options || {};
+			options.hip = options.hip !== undefined ? options.hip : "hip";
+			options.names = options.names || {};
+
+			if ( ! source.isObject3D ) {
+
+				source = this.getHelperFromSkeleton( source );
+
+			}
+			
+			var nameKeys = Object.keys( options.names ),
+				nameValues = Object.values( options.names ),
+				sourceBones = source.isObject3D ? source.skeleton.bones : this.getBones( source ),
+				bones = target.isObject3D ? target.skeleton.bones : this.getBones( target ),
+				offsets = [],
+				bone, boneTo, 
+				name, i;
+			
+			target.skeleton.pose();
+			
+			for ( i = 0; i < bones.length; ++ i ) {
+
+				bone = bones[ i ];
+				name = options.names[ bone.name ] || bone.name;
+
+				boneTo = this.getBoneByName( name, sourceBones );
+				
+				if ( boneTo && name !== options.hip ) {
+				
+					var boneParent = this.getNearestBone( bone.parent, nameKeys ),
+						boneToParent = this.getNearestBone( boneTo.parent, nameValues );
+				
+					boneParent.updateMatrixWorld();
+					boneToParent.updateMatrixWorld();
+				
+					targetParentPos.setFromMatrixPosition( boneParent.matrixWorld  );
+					targetPos.setFromMatrixPosition( bone.matrixWorld );
+					
+					sourceParentPos.setFromMatrixPosition( boneToParent.matrixWorld );
+					sourcePos.setFromMatrixPosition( boneTo.matrixWorld );
+					
+					targetDir.subVectors( 
+						new THREE.Vector2( targetPos.x, targetPos.y ),
+						new THREE.Vector2( targetParentPos.x, targetParentPos.y ), 
+					).normalize();
+					
+					sourceDir.subVectors( 
+						new THREE.Vector2( sourcePos.x, sourcePos.y ),
+						new THREE.Vector2( sourceParentPos.x, sourceParentPos.y ),
+					).normalize();
+					
+					var laterialAngle = targetDir.angle() - sourceDir.angle();
+					
+					var offset = new THREE.Matrix4().makeRotationFromEuler(
+						new THREE.Euler(
+							0,
+							0,
+							laterialAngle
+						)
+					);
+					
+					bone.matrix.multiply( offset );
+
+					bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
+
+					bone.updateMatrixWorld();
+					
+					offsets[ name ] = offset;
+					
+				}
+				
+			}
+			
+			return offsets;
+			
+		}
+
+	}(),
+
+	renameBones: function ( skeleton, names ) {
 
 
 		var bones = this.getBones( skeleton );
 		var bones = this.getBones( skeleton );
 
 
@@ -344,16 +452,33 @@ THREE.SkeletonUtils = {
 
 
 	},
 	},
 
 
-	getBoneByName: function ( skeleton, name ) {
+	getBoneByName: function ( name, skeleton ) {
 
 
 		for ( var i = 0, bones = this.getBones( skeleton ); i < bones.length; i ++ ) {
 		for ( var i = 0, bones = this.getBones( skeleton ); i < bones.length; i ++ ) {
 
 
 			if ( name === bones[ i ].name )
 			if ( name === bones[ i ].name )
+				
 				return bones[ i ];
 				return bones[ i ];
 
 
 		}
 		}
 
 
 	},
 	},
+	
+	getNearestBone: function ( bone, names ) {
+
+		while( bone.isBone ) {
+			
+			if ( names.indexOf( bone.name ) !== -1 ) {
+				
+				return bone;
+				
+			}
+			
+			bone = bone.parent;
+			
+		}
+
+	},
 
 
 	findBoneTrackData: function ( name, tracks ) {
 	findBoneTrackData: function ( name, tracks ) {
 
 

+ 43 - 34
examples/webgl_loader_sea3d_bvh.html

@@ -114,10 +114,10 @@
 
 
 			function bvhToSEA3D( result ) {
 			function bvhToSEA3D( result ) {
 
 
-				var clip = THREE.SkeletonUtils.retargetClip( player, result.skeleton, result.clip, {
-					preservePosition: false,
+				var options = {
 					useFirstFramePosition: true,
 					useFirstFramePosition: true,
-					hip: "Base HumanPelvis",
+					preserveHipPosition: false,
+					hip: "hip",
 					// left is SEA3D bone names and right BVH bone names
 					// left is SEA3D bone names and right BVH bone names
 					names: {
 					names: {
 						"Base HumanPelvis": "hip",
 						"Base HumanPelvis": "hip",
@@ -141,38 +141,47 @@
 						"Base HumanLCalf1": "lShin",
 						"Base HumanLCalf1": "lShin",
 						"Base HumanLFoot": "lFoot"
 						"Base HumanLFoot": "lFoot"
 					},
 					},
-					// compensates the difference in skeletons ( T-Pose )
-					offsets: {
-						"lShldr": new THREE.Matrix4().makeRotationFromEuler(
-							new THREE.Euler(
-								0,
-								THREE.Math.degToRad( - 45 ),
-								THREE.Math.degToRad( - 80 )
-							)
-						),
-						"rShldr": new THREE.Matrix4().makeRotationFromEuler(
-							new THREE.Euler(
-								0,
-								THREE.Math.degToRad( 45 ),
-								THREE.Math.degToRad( 80 )
-							)
-						),
-						"lFoot": new THREE.Matrix4().makeRotationFromEuler(
-							new THREE.Euler(
-								0,
-								THREE.Math.degToRad( 15 ),
-								0
-							)
-						),
-						"rFoot": new THREE.Matrix4().makeRotationFromEuler(
-							new THREE.Euler(
-								0,
-								THREE.Math.degToRad( 15 ),
-								0
-							)
+					
+				};
+			
+				// Automatic offset: get offsets when it is in T-Pose
+				options.offsets = THREE.SkeletonUtils.getSkeletonOffsets( player, bvhSkeletonHelper, options );
+			
+				// Manual offsets: compensates the difference in skeletons ( T-Pose )
+				/*
+				options.offsets = {
+					"lShldr": new THREE.Matrix4().makeRotationFromEuler(
+						new THREE.Euler(
+							0,
+							THREE.Math.degToRad( - 45 ),
+							THREE.Math.degToRad( - 80 )
 						)
 						)
-					}
-				} );
+					),
+					"rShldr": new THREE.Matrix4().makeRotationFromEuler(
+						new THREE.Euler(
+							0,
+							THREE.Math.degToRad( 45 ),
+							THREE.Math.degToRad( 80 )
+						)
+					),
+					"lFoot": new THREE.Matrix4().makeRotationFromEuler(
+						new THREE.Euler(
+							0,
+							THREE.Math.degToRad( 15 ),
+							0
+						)
+					),
+					"rFoot": new THREE.Matrix4().makeRotationFromEuler(
+						new THREE.Euler(
+							0,
+							THREE.Math.degToRad( 15 ),
+							0
+						)
+					)
+				};
+				*/
+			
+				var clip = THREE.SkeletonUtils.retargetClip( player, result.skeleton, result.clip, options );
 
 
 				clip.name = "dance";
 				clip.name = "dance";
 
 

+ 266 - 0
examples/webgl_loader_sea3d_bvh_retarget.html

@@ -0,0 +1,266 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - sea3d / bvh / retarget</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 {
+				font-family: Monospace;
+				background-color: #000;
+				margin: 0px;
+				overflow: hidden;
+			}
+
+			#info {
+				color: #fff;
+				position: absolute;
+				top: 10px;
+				width: 100%;
+				text-align: center;
+				z-index: 100;
+				display:block;
+
+			}
+
+			a { color: white }
+		</style>
+	</head>
+	<body>
+		<div id="info">
+			<a href="http://threejs.org" target="_blank" rel="noopener">Three.JS</a> - Exported by <a href="https://github.com/sunag/sea3d" style="color:#FFFFFF" target="_blank" rel="noopener">SEA3D Exporter</a>. Asset by <a href="http://www.turbosquid.com/3d-models/soccer-player-max-free/307330" style="color:#FFFFFF" target="_blank" rel="noopener">Trivision</a>
+			<br/>
+			<br/>Runtime retarget of BVH Animation to SEA3D Skeleton Animation
+		</div>
+
+		<script src="../build/three.js"></script>
+
+		<script src="js/controls/OrbitControls.js"></script>
+
+		<script src="js/postprocessing/EffectComposer.js"></script>
+		<script src="js/postprocessing/RenderPass.js"></script>
+		<script src="js/postprocessing/ShaderPass.js"></script>
+		<script src="js/postprocessing/MaskPass.js"></script>
+		<script src="js/shaders/CopyShader.js"></script>
+		<script src="js/shaders/ColorCorrectionShader.js"></script>
+		<script src="js/shaders/VignetteShader.js"></script>
+
+		<script src="js/loaders/sea3d/SEA3D.js"></script>
+		<script src="js/loaders/sea3d/SEA3DLZMA.js"></script>
+		<script src="js/loaders/sea3d/SEA3DLoader.js"></script>
+
+		<script src="js/loaders/BVHLoader.js"></script>
+		<script src="js/utils/SkeletonUtils.js"></script>
+		
+		<script src="js/Detector.js"></script>
+		<script src="js/libs/stats.min.js"></script>
+
+		<script>
+
+			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
+
+			console.log( "Visit https://github.com/sunag/sea3d to all codes and builds under development." );
+
+			var container, stats;
+
+			var camera, scene, renderer, composer, player, hat;
+
+			var loader, options;
+
+			var bvhSkeletonHelper, bvhMixer, bvhOffsets;
+
+			// Initialize Three.JS
+
+			init();
+
+			//
+			// SEA3D Loader
+			//
+
+			loader = new THREE.SEA3D( {
+
+				autoPlay: false, // Auto play animations
+				container: scene, // Container to add models
+				multiplier: .6 // Light multiplier
+
+			} );
+
+			loader.onComplete = function ( ) {
+
+				// Get the first camera from SEA3D Studio
+				// use loader.get... to get others objects
+
+				var cam = loader.cameras[ 0 ];
+				camera.position.copy( cam.position );
+				camera.rotation.copy( cam.rotation );
+
+				new THREE.OrbitControls( camera );
+
+				// get meshes
+				player = loader.getMesh( "Player" );
+				hat = loader.getMesh( "Hat" );
+
+				hat.visible = false;
+
+				loadBVH();
+
+				animate();
+
+			};
+
+			loader.load( './models/sea3d/skin.tjs.sea' );
+
+			//
+			
+			options = {
+				hip: "hip",
+				// left is SEA3D bone names and right BVH bone names
+				names: {
+					"Base HumanPelvis": "hip",
+					"Base HumanSpine3": "abdomen",
+					"Base HumanRibcage": "chest",
+					"Base HumanHead": "head",
+
+					"Base HumanRUpperarm": "rShldr",
+					"Base HumanRForearm1": "rForeArm",
+					"Base HumanRPalm": "rHand",
+
+					"Base HumanLUpperarm": "lShldr",
+					"Base HumanLForearm1": "lForeArm",
+					"Base HumanLPalm": "lHand",
+
+					"Base HumanRThigh": "rThigh",
+					"Base HumanRCalf1": "rShin",
+					"Base HumanRFoot": "rFoot",
+
+					"Base HumanLThigh": "lThigh",
+					"Base HumanLCalf1": "lShin",
+					"Base HumanLFoot": "lFoot"
+				}
+			};
+			
+			//
+
+			function loadBVH() {
+
+				var loader = new THREE.BVHLoader();
+				loader.load( "models/bvh/pirouette.bvh", function ( result ) {
+
+					bvhSkeletonHelper = new THREE.SkeletonHelper( result.skeleton.bones[ 0 ] );
+					bvhSkeletonHelper.skeleton = result.skeleton; // allow animation mixer to bind to SkeletonHelper directly
+
+					var boneContainer = new THREE.Group();
+					boneContainer.add( result.skeleton.bones[ 0 ] );
+					boneContainer.position.z = -100;
+					boneContainer.position.y = - 100;
+
+					scene.add( bvhSkeletonHelper );
+					scene.add( boneContainer );
+
+					// play animation
+					bvhMixer = new THREE.AnimationMixer( bvhSkeletonHelper );
+					
+					// get offsets when it is in T-Pose
+					options.offsets = THREE.SkeletonUtils.getSkeletonOffsets( player, bvhSkeletonHelper, options );
+					
+					bvhMixer.clipAction( result.clip ).setEffectiveWeight( 1.0 ).play();
+
+				} );
+
+			}
+
+			function init() {
+
+				scene = new THREE.Scene();
+				scene.background = new THREE.Color( 0x333333 );
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 20000 );
+				camera.position.set( 1000, - 300, 1000 );
+
+				renderer = new THREE.WebGLRenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				container.appendChild( renderer.domElement );
+
+				stats = new Stats();
+				container.appendChild( stats.dom );
+
+				// post-processing
+
+				composer = new THREE.EffectComposer( renderer );
+
+				var renderPass = new THREE.RenderPass( scene, camera );
+				var copyPass = new THREE.ShaderPass( THREE.CopyShader );
+				composer.addPass( renderPass );
+
+				var vh = 1.4, vl = 1.2;
+
+				var colorCorrectionPass = new THREE.ShaderPass( THREE.ColorCorrectionShader );
+				colorCorrectionPass.uniforms[ "powRGB" ].value = new THREE.Vector3( vh, vh, vh );
+				colorCorrectionPass.uniforms[ "mulRGB" ].value = new THREE.Vector3( vl, vl, vl );
+				composer.addPass( colorCorrectionPass );
+
+				var vignettePass = new THREE.ShaderPass( THREE.VignetteShader );
+				vignettePass.uniforms[ "darkness" ].value = 1.0;
+				composer.addPass( vignettePass );
+
+				composer.addPass( copyPass );
+				copyPass.renderToScreen = true;
+
+				// events
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				composer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			//
+
+			var clock = new THREE.Clock();
+
+			function animate() {
+
+				var delta = clock.getDelta();
+
+				requestAnimationFrame( animate );
+
+				// Update SEA3D Animations
+				THREE.SEA3D.AnimationHandler.update( delta );
+
+				if ( bvhMixer ) {
+				
+					bvhMixer.update( delta );
+					
+					THREE.SkeletonUtils.retarget( player, bvhSkeletonHelper, options );
+					
+				}
+				
+				render( delta );
+
+				stats.update();
+
+			}
+
+			function render( dlt ) {
+
+				//renderer.render( scene, camera );
+				composer.render( dlt );
+
+			}
+
+		</script>
+
+	</body>
+</html>