Forráskód Böngészése

bvh animation to sea3d character and SkeletonUtils

sunag 7 éve
szülő
commit
055c3e8e23

+ 1 - 0
examples/files.js

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

+ 395 - 0
examples/js/utils/SkeletonUtils.js

@@ -0,0 +1,395 @@
+/**
+ * @author sunag / http://www.sunag.com.br
+ */
+
+'use strict';
+
+THREE.SkeletonUtils = {
+
+	retarget: function () {
+
+		var quat = new THREE.Quaternion(),
+			scale = new THREE.Vector3(),
+			bindBoneMatrix = new THREE.Matrix4(),
+			relativeMatrix = new THREE.Matrix4(),
+			globalMatrix = new THREE.Matrix4();
+
+		return function ( target, source, options ) {
+
+			options = options || {};
+			options.preserveMatrix = options.preserveMatrix !== undefined ? options.preserveMatrix : true;
+			options.preservePosition = options.preservePosition !== undefined ? options.preservePosition : true;
+			options.useTargetMatrix = options.useTargetMatrix !== undefined ? options.useTargetMatrix : false;
+			options.hip = options.hip !== undefined ? options.hip : "hip";
+			options.names = options.names || {};
+
+			var sourceBones = source.isObject3D ? source.skeleton.bones : this.getBones( source ),
+				bones = target.isObject3D ? target.skeleton.bones : this.getBones( target ),
+				bindBones,
+				bone, name, boneTo,
+				bonesPosition, i;
+
+			// reset bones
+
+			if ( target.isObject3D ) {
+
+				target.skeleton.pose();
+
+			} else {
+
+				options.useTargetMatrix = true;
+				options.preserveMatrix = false;
+
+			}
+
+			if ( options.preservePosition ) {
+
+				bonesPosition = [];
+
+				for ( i = 0; i < bones.length; i ++ ) {
+
+					bonesPosition.push( bones[ i ].position.clone() );
+
+				}
+
+			}
+
+			if ( options.preserveMatrix ) {
+
+				// reset matrix
+
+				target.updateMatrixWorld();
+
+				target.matrixWorld.identity();
+
+				// reset children matrix
+
+				for ( i = 0; i < target.children.length; ++ i ) {
+
+					target.children[ i ].updateMatrixWorld( true );
+
+				}
+
+			}
+
+			if ( options.offset ) {
+
+				bindBones = [];
+
+				for ( i = 0; i < bones.length; ++ i ) {
+
+					bone = bones[ i ];
+					name = options.names[ bone.name ] || bone.name;
+
+					if ( options.offset[ name ] ) {
+
+						bone.matrix.multiply( options.offset[ name ] );
+
+						bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
+
+						bone.updateMatrixWorld();
+
+					}
+
+					bindBones.push( bone.matrixWorld.clone() );
+
+				}
+
+			}
+
+			for ( i = 0; i < bones.length; ++ i ) {
+
+				bone = bones[ i ];
+				name = options.names[ bone.name ] || bone.name;
+
+				boneTo = this.getBoneByName( sourceBones, name );
+
+				globalMatrix.copy( bone.matrixWorld );
+
+				if ( boneTo ) {
+
+					boneTo.updateMatrixWorld();
+
+					if ( options.useTargetMatrix ) {
+
+						relativeMatrix.copy( boneTo.matrixWorld );
+
+					} else {
+
+						relativeMatrix.getInverse( target.matrixWorld );
+						relativeMatrix.multiply( boneTo.matrixWorld );
+
+					}
+
+					// ignore scale to extract rotation
+
+					scale.setFromMatrixScale( relativeMatrix );
+					relativeMatrix.scale( scale.set( 1 / scale.x, 1 / scale.y, 1 / scale.z ) );
+
+					// apply to global matrix
+
+					globalMatrix.makeRotationFromQuaternion( quat.setFromRotationMatrix( relativeMatrix ) );
+
+					if ( target.isObject3D ) {
+
+						var boneIndex = bones.indexOf( bone ),
+							wBindMatrix = bindBones ? bindBones[ boneIndex ] : bindBoneMatrix.getInverse( target.skeleton.boneInverses[ boneIndex ] );
+
+						globalMatrix.multiply( wBindMatrix );
+
+					}
+
+					globalMatrix.copyPosition( relativeMatrix );
+
+				}
+
+				if ( bone.parent && bone.parent.isBone ) {
+
+					bone.matrix.getInverse( bone.parent.matrixWorld );
+					bone.matrix.multiply( globalMatrix );
+
+				} else {
+
+					bone.matrix.copy( globalMatrix );
+
+				}
+
+				bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
+
+				bone.updateMatrixWorld();
+
+			}
+
+			if ( options.preservePosition ) {
+
+				for ( i = 0; i < bones.length; ++ i ) {
+
+					bone = bones[ i ];
+					name = options.names[ bone.name ] || bone.name;
+
+					if ( name !== options.hip ) {
+
+						bone.position.copy( bonesPosition[ i ] );
+
+					}
+
+				}
+
+			}
+
+			if ( options.preserveMatrix ) {
+
+				// restore matrix
+
+				target.updateMatrixWorld( true );
+
+			}
+
+		};
+
+	}(),
+
+	retargetClip: function ( target, source, clip, options ) {
+
+		options = options || {};
+		options.fps = options.fps !== undefined ? options.fps : 30;
+		options.names = options.names || [];
+
+		if ( ! source.isObject3D ) {
+
+			var skeleton = source;
+
+			source = new THREE.SkeletonHelper( skeleton.bones[ 0 ] );
+			source.skeleton = skeleton;
+
+		}
+
+		var numFrames = Math.round( clip.duration * ( options.fps / 1000 ) * 1000 ),
+			delta = 1 / options.fps,
+			convertedTracks = [],
+			mixer = new THREE.AnimationMixer( source ),
+			bones = this.getBones( target.skeleton ),
+			boneDatas = [],
+			bone, boneTo, boneData, i, j;
+
+		mixer.clipAction( clip ).play();
+		mixer.update( 0 );
+
+		//source.updateMatrixWorld();
+
+		for ( i = 0; i < numFrames; ++ i ) {
+
+			var time = i * delta;
+
+			this.retarget( target, source, options );
+
+			for ( j = 0; j < bones.length; ++ j ) {
+
+				boneTo = this.getBoneByName( source.skeleton, options.names[ bones[ j ].name ] || bones[ j ].name );
+
+				if ( boneTo ) {
+
+					bone = bones[ j ];
+					boneData = boneDatas[ j ] = boneDatas[ j ] || { bone: bone };
+
+					if ( options.hip === boneData.bone.name ) {
+
+						if ( ! boneData.pos ) {
+
+							boneData.pos = {
+								times: new Float32Array( numFrames ),
+								values: new Float32Array( numFrames * 3 )
+							};
+
+						}
+
+						boneData.pos.times[ i ] = time;
+
+						bone.position.toArray( boneData.pos.values, i * 3 );
+
+					}
+
+					if ( ! boneData.quat ) {
+
+						boneData.quat = {
+							times: new Float32Array( numFrames ),
+							values: new Float32Array( numFrames * 4 )
+						};
+
+					}
+
+					boneData.quat.times[ i ] = time;
+
+					bone.quaternion.toArray( boneData.quat.values, i * 4 );
+
+				}
+
+			}
+
+			mixer.update( delta );
+
+			//source.updateMatrixWorld();
+
+		}
+
+		for ( i = 0; i < boneDatas.length; ++ i ) {
+
+			boneData = boneDatas[ i ];
+
+			if ( boneData ) {
+
+				if ( boneData.pos ) {
+
+					convertedTracks.push( new THREE.VectorKeyframeTrack(
+						".bones[" + boneData.bone.name + "].position",
+						boneData.pos.times,
+						boneData.pos.values
+					) );
+
+				}
+
+				convertedTracks.push( new THREE.QuaternionKeyframeTrack(
+					".bones[" + boneData.bone.name + "].quaternion",
+					boneData.quat.times,
+					boneData.quat.values
+				) );
+
+			}
+
+		}
+
+		mixer.uncacheAction( clip );
+
+		return new THREE.AnimationClip( clip.name, - 1, convertedTracks );
+
+	},
+
+	rename: function ( skeleton, names ) {
+
+		var bones = this.getBones( skeleton );
+
+		for ( var i = 0; i < bones.length; ++ i ) {
+
+			var bone = bones[ i ];
+
+			if ( names[ bone.name ] ) {
+
+				bone.name = names[ bone.name ];
+
+			}
+
+		}
+
+		return this;
+
+	},
+
+	getBones: function ( skeleton ) {
+
+		return Array.isArray( skeleton ) ? skeleton : skeleton.bones;
+
+	},
+
+	getBoneByName: function ( skeleton, name ) {
+
+		for ( var i = 0, bones = this.getBones( skeleton ); i < bones.length; i ++ ) {
+
+			if ( name === bones[ i ].name )
+				return bones[ i ];
+
+		}
+
+	},
+
+	findBoneTrackData: function ( name, tracks ) {
+
+		var regexp = /\[(.*)\]\.(.*)/,
+			result = { name: name };
+
+		for ( var i = 0; i < tracks.length; ++ i ) {
+
+			// 1 is track name
+			// 2 is track type
+			var trackData = regexp.exec( tracks[ i ].name );
+
+			if ( trackData && name === trackData[ 1 ] ) {
+
+				result[ trackData[ 2 ] ] = i;
+
+			}
+
+		}
+
+		return result;
+
+	},
+
+	getEqualsBonesNames: function ( skeleton, targetSkeleton ) {
+
+		var sourceBones = this.getBones( skeleton ),
+			targetBones = this.getBones( targetSkeleton ),
+			bones = [];
+
+		search : for ( var i = 0; i < sourceBones.length; i ++ ) {
+
+			var boneName = sourceBones[ i ].name;
+
+			for ( var j = 0; j < targetBones.length; j ++ ) {
+
+				if ( boneName === targetBones[ j ].name ) {
+
+					bones.push( boneName );
+
+					continue search;
+
+				}
+
+			}
+
+		}
+
+		return bones;
+
+	}
+
+};

+ 281 - 0
examples/webgl_loader_sea3d_bvh.html

@@ -0,0 +1,281 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - sea3d / 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 {
+				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 convertion 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;
+
+			var bvhSkeletonHelper, bvhMixer;
+
+			// Initialize Three.JS
+
+			init();
+
+			//
+			// SEA3D Loader
+			//
+
+			loader = new THREE.SEA3D( {
+
+				autoPlay: true, // 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' );
+
+			//
+
+			function bvhToSEA3D( result ) {
+
+				var clip = THREE.SkeletonUtils.retargetClip( player, result.skeleton, result.clip, {
+					hip: "Base HumanPelvis",
+					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"
+					},
+					offset: {
+						"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 )
+							)
+						)
+					}
+				} );
+
+				clip = THREE.SEA3D.AnimationClip.fromClip( clip );
+
+				player.addAnimation( new THREE.SEA3D.Animation( clip ) );
+
+				player.play( "animation" );
+
+
+			}
+
+			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.y = - 100;
+
+					scene.add( bvhSkeletonHelper );
+					scene.add( boneContainer );
+
+					// play animation
+					bvhMixer = new THREE.AnimationMixer( bvhSkeletonHelper );
+					bvhMixer.clipAction( result.clip ).setEffectiveWeight( 1.0 ).play();
+
+					bvhToSEA3D( result );
+
+				} );
+
+			}
+
+			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 );
+
+				render( delta );
+
+				stats.update();
+
+			}
+
+			function render( dlt ) {
+
+				//renderer.render( scene, camera );
+				composer.render( dlt );
+
+			}
+
+		</script>
+
+	</body>
+</html>