Kaynağa Gözat

MMDPhysics improvement (#9989)

* MMDPhysics improvement

* Add property defined check
Takahiro 8 yıl önce
ebeveyn
işleme
544770e512

+ 237 - 25
examples/js/animation/MMDPhysics.js

@@ -4,37 +4,36 @@
  * Dependencies
  *  - Ammo.js https://github.com/kripken/ammo.js
  *
- *
  * MMD specific Physics class.
  *
  * See THREE.MMDLoader for the passed parameter list of RigidBody/Constraint.
  *
+ * Requirement:
+ *  - don't change object's scale from (1,1,1) after setting physics to object
  *
  * TODO
+ *  - optimize for the performance
  *  - use Physijs http://chandlerprall.github.io/Physijs/
  *    and improve the performance by making use of Web worker.
  *  - if possible, make this class being non-MMD specific.
+ *  - object scale change support
  */
 
 THREE.MMDPhysics = function ( mesh, params ) {
 
+	if ( params === undefined ) params = {};
+
 	this.mesh = mesh;
-	this.helper = new THREE.MMDPhysics.PhysicsHelper();
+	this.helper = new THREE.MMDPhysics.ResourceHelper();
 
-	/*
-	 * Note: Default 1 / 65 unit step is a workaround that
-	 *       some bones go wrong at near 60fps if it's 1 / 60.
-	 *       Be careful that small unitStep could cause heavy
-	 *       physics calculation.
-	 */
-	this.unitStep = ( params && params.unitStep ) ? params.unitStep : 1 / 65;
-	this.maxStepNum = ( params && params.maxStepNum ) ? params.maxStepNum : 3;
+	this.unitStep = ( params.unitStep !== undefined ) ? params.unitStep : 1 / 60;
+	this.maxStepNum = ( params.maxStepNum !== undefined ) ? params.maxStepNum : 3;
 
 	this.world = null;
 	this.bodies = [];
 	this.constraints = [];
 
-	this.init();
+	this.init( mesh );
 
 };
 
@@ -42,11 +41,42 @@ THREE.MMDPhysics.prototype = {
 
 	constructor: THREE.MMDPhysics,
 
-	init: function () {
+	init: function ( mesh ) {
+
+		var parent = mesh.parent;
+
+		if ( parent !== null ) {
+
+			parent.remove( mesh );
+
+		}
+
+		var currentPosition = mesh.position.clone();
+		var currentRotation = mesh.rotation.clone();
+		var currentScale = mesh.scale.clone();
+
+		mesh.position.set( 0, 0, 0 );
+		mesh.rotation.set( 0, 0, 0 );
+		mesh.scale.set( 1, 1, 1 );
+
+		mesh.updateMatrixWorld( true );
 
 		this.initWorld();
 		this.initRigidBodies();
 		this.initConstraints();
+
+		if ( parent !== null ) {
+
+			parent.add( mesh );
+
+		}
+
+		mesh.position.copy( currentPosition );
+		mesh.rotation.copy( currentRotation );
+		mesh.scale.copy( currentScale );
+
+		mesh.updateMatrixWorld( true );
+
 		this.reset();
 
 	},
@@ -58,7 +88,7 @@ THREE.MMDPhysics.prototype = {
 		var cache = new Ammo.btDbvtBroadphase();
 		var solver = new Ammo.btSequentialImpulseConstraintSolver();
 		var world = new Ammo.btDiscreteDynamicsWorld( dispatcher, cache, solver, config );
-		world.setGravity( new Ammo.btVector3( 0, -10 * 10, 0 ) );
+		world.setGravity( new Ammo.btVector3( 0, -9.8 * 10, 0 ) );
 		this.world = world;
 
 	},
@@ -99,6 +129,13 @@ THREE.MMDPhysics.prototype = {
 		var stepTime = delta;
 		var maxStepNum = ( ( delta / unitStep ) | 0 ) + 1;
 
+		if ( stepTime < unitStep ) {
+
+			stepTime = unitStep;
+			maxStepNum = 1;
+
+		}
+
 		if ( maxStepNum > this.maxStepNum ) {
 
 			maxStepNum = this.maxStepNum;
@@ -145,7 +182,7 @@ THREE.MMDPhysics.prototype = {
 
 		for ( var i = 0; i < cycles; i++ ) {
 
-			this.update( 1 );
+			this.update( 1 / 60 );
 
 		}
 
@@ -162,12 +199,13 @@ THREE.MMDPhysics.prototype = {
  *
  * 2. provide simple Ammo object operations.
  */
-THREE.MMDPhysics.PhysicsHelper = function () {
+THREE.MMDPhysics.ResourceHelper = function () {
 
 	// for Three.js
 	this.threeVector3s = [];
 	this.threeMatrix4s = [];
 	this.threeQuaternions = [];
+	this.threeEulers = [];
 
 	// for Ammo.js
 	this.transforms = [];
@@ -176,7 +214,7 @@ THREE.MMDPhysics.PhysicsHelper = function () {
 
 };
 
-THREE.MMDPhysics.PhysicsHelper.prototype = {
+THREE.MMDPhysics.ResourceHelper.prototype = {
 
 	allocThreeVector3: function () {
 
@@ -214,6 +252,18 @@ THREE.MMDPhysics.PhysicsHelper.prototype = {
 
 	},
 
+	allocThreeEuler: function () {
+
+		return ( this.threeEulers.length > 0 ) ? this.threeEulers.pop() : new THREE.Euler();
+
+	},
+
+	freeThreeEuler: function ( e ) {
+
+		this.threeEulers.push( e );
+
+	},
+
 	allocTransform: function () {
 
 		return ( this.transforms.length > 0 ) ? this.transforms.pop() : new Ammo.btTransform();
@@ -314,7 +364,13 @@ THREE.MMDPhysics.PhysicsHelper.prototype = {
 
 	setBasisFromArray3: function ( t, a ) {
 
-		t.getBasis().setEulerZYX( a[ 0 ], a[ 1 ], a[ 2 ] );
+		var thQ = this.allocThreeQuaternion();
+		var thE = this.allocThreeEuler();
+		thE.set( a[ 0 ], a[ 1 ], a[ 2 ] );
+		this.setBasisFromArray4( t, thQ.setFromEuler( thE ).toArray() );
+
+		this.freeThreeEuler( thE );
+		this.freeThreeQuaternion( thQ );
 
 	},
 
@@ -598,7 +654,7 @@ THREE.MMDPhysics.RigidBody.prototype = {
 
 	init: function () {
 
-		function generateShape ( p ) {
+		function generateShape( p ) {
 
 			switch( p.shapeType ) {
 
@@ -648,13 +704,19 @@ THREE.MMDPhysics.RigidBody.prototype = {
 
 		var info = new Ammo.btRigidBodyConstructionInfo( weight, state, shape, localInertia );
 		info.set_m_friction( params.friction );
-		info.set_m_restitution( params.restriction );
+		info.set_m_restitution( params.restitution );
 
 		var body = new Ammo.btRigidBody( info );
 
 		if ( params.type === 0 ) {
 
 			body.setCollisionFlags( body.getCollisionFlags() | 2 );
+
+			/*
+			 * It'd be better to comment out this line though in general I should call this method
+			 * because I'm not sure why but physics will be more like MMD's
+			 * if I comment out.
+			 */
 			body.setActivationState( 4 );
 
 		}
@@ -695,12 +757,6 @@ THREE.MMDPhysics.RigidBody.prototype = {
 
 		}
 
-		if ( this.params.type === 2 ) {
-
-			this.setPositionFromBone();
-
-		}
-
 	},
 
 	updateBone: function () {
@@ -721,6 +777,12 @@ THREE.MMDPhysics.RigidBody.prototype = {
 
 		this.bone.updateMatrixWorld( true );
 
+		if ( this.params.type === 2 ) {
+
+			this.setPositionFromBone();
+
+		}
+
 	},
 
 	getBoneTransform: function () {
@@ -932,6 +994,23 @@ THREE.MMDPhysics.Constraint.prototype = {
 
 		}
 
+		/*
+		 * Currently(10/31/2016) official ammo.js doesn't support
+		 * btGeneric6DofSpringConstraint.setParam method.
+		 * You need custom ammo.js (add the method into idl) if you wanna use.
+		 * By setting this parameter, physics will be more like MMD's
+		 */
+		if ( constraint.setParam !== undefined ) {
+
+			for ( var i = 0; i < 6; i ++ ) {
+
+				// this parameter is from http://www20.atpages.jp/katwat/three.js_r58/examples/mytest37/mmd.three.js
+				constraint.setParam( 2, 0.475, i );
+
+			}
+
+		}
+
 		this.world.addConstraint( constraint, true );
 		this.constraint = constraint;
 
@@ -950,3 +1029,136 @@ THREE.MMDPhysics.Constraint.prototype = {
 	}
 
 };
+
+
+/*
+ * This helper displays rigid bodies of mesh for debug.
+ * It should be under Scene because it just sets world position/roration to meshes
+ * for simplicity.
+ */
+THREE.MMDPhysicsHelper = function ( mesh ) {
+
+	if ( mesh.physics === undefined || mesh.geometry.rigidBodies === undefined ) {
+
+		throw 'THREE.MMDPhysicsHelper requires physics in mesh and rigidBodies in mesh.geometry.';
+
+	}
+
+	THREE.Object3D.call( this );
+
+	this.mesh = mesh;
+
+	this._init();
+
+};
+
+THREE.MMDPhysicsHelper.prototype = Object.create( THREE.Object3D.prototype );
+THREE.MMDPhysicsHelper.prototype.constructor = THREE.MMDPhysicsHelper;
+
+THREE.MMDPhysicsHelper.prototype._init = function () {
+
+	function createGeometry( param ) {
+
+		switch ( param.shapeType ) {
+
+			case 0:
+				return new THREE.SphereBufferGeometry( param.width, 16, 8 );
+
+			case 1:
+				return new THREE.BoxBufferGeometry( param.width * 2, param.height * 2, param.depth * 2, 8, 8, 8);
+
+			case 2:
+				return new createCapsuleGeometry( param.width, param.height, 16, 8 );
+
+			default:
+				return null;
+
+		}
+
+	}
+
+	// copy from http://www20.atpages.jp/katwat/three.js_r58/examples/mytest37/mytest37.js?ver=20160815
+	function createCapsuleGeometry( radius, cylinderHeight, segmentsRadius, segmentsHeight ) {
+
+		var geometry = new THREE.CylinderBufferGeometry( radius, radius, cylinderHeight, segmentsRadius, segmentsHeight, true );
+		var upperSphere = new THREE.Mesh( new THREE.SphereBufferGeometry( radius, segmentsRadius, segmentsHeight, 0, Math.PI * 2, 0, Math.PI / 2 ) );
+		var lowerSphere = new THREE.Mesh( new THREE.SphereBufferGeometry( radius, segmentsRadius, segmentsHeight, 0, Math.PI * 2, Math.PI / 2, Math.PI / 2 ) );
+
+		upperSphere.position.set( 0, cylinderHeight / 2, 0 );
+		lowerSphere.position.set( 0, -cylinderHeight / 2, 0 );
+
+		upperSphere.updateMatrix();
+		lowerSphere.updateMatrix();
+
+		geometry.merge( upperSphere.geometry, upperSphere.matrix );
+		geometry.merge( lowerSphere.geometry, lowerSphere.matrix );
+
+		return geometry;
+
+	}
+
+
+	function createMaterial( param ) {
+
+		var color;
+
+		switch ( param.type ) {
+
+			case 0:
+				color = 0xff8888;
+				break;
+
+			case 1:
+				color = 0x88ff88;
+				break;
+
+			case 2:
+				color = 0x8888ff;
+				break;
+
+			default:
+				color = 0x000000;
+				break;
+
+		}
+
+		return new THREE.MeshBasicMaterial( {
+			color: new THREE.Color( color ),
+			wireframe: true,
+			depthTest: false,
+			depthWrite: false,
+			opacity: 0.25,
+			transparent: true
+		} );
+
+	}
+
+	for ( var i = 0, il = this.mesh.geometry.rigidBodies.length; i < il; i ++ ) {
+
+		var param = this.mesh.geometry.rigidBodies[ i ];
+
+		var mesh = new THREE.Mesh( createGeometry( param ), createMaterial( param ) );
+		this.add( mesh );
+
+	}
+
+};
+
+THREE.MMDPhysicsHelper.prototype.update = function () {
+
+	for ( var i = 0, il = this.mesh.geometry.rigidBodies.length; i < il; i ++ ) {
+
+		var body = this.mesh.physics.bodies[ i ].body;
+		var mesh = this.children[ i ];
+
+		var tr = body.getCenterOfMassTransform();
+
+		var o = tr.getOrigin();
+		var r = tr.getRotation();
+
+		mesh.position.set( o.x(), o.y(), o.z() );
+		mesh.quaternion.set( r.x(), r.y(), r.z(), r.w() );
+
+	}
+
+};

+ 43 - 8
examples/js/loaders/MMDLoader.js

@@ -780,7 +780,7 @@ THREE.MMDLoader.prototype.parsePmd = function ( buffer ) {
 			p.weight = dv.getFloat32();
 			p.positionDamping = dv.getFloat32();
 			p.rotationDamping = dv.getFloat32();
-			p.restriction = dv.getFloat32();
+			p.restitution = dv.getFloat32();
 			p.friction = dv.getFloat32();
 			p.type = dv.getUint8();
 			return p;
@@ -1334,7 +1334,7 @@ THREE.MMDLoader.prototype.parsePmx = function ( buffer ) {
 			p.weight = dv.getFloat32();
 			p.positionDamping = dv.getFloat32();
 			p.rotationDamping = dv.getFloat32();
-			p.restriction = dv.getFloat32();
+			p.restitution = dv.getFloat32();
 			p.friction = dv.getFloat32();
 			p.type = dv.getUint8();
 			return p;
@@ -3851,10 +3851,12 @@ THREE.MMDHelper.prototype = {
 
 		}
 
-		mesh.mixer = null;
-		mesh.ikSolver = null;
-		mesh.grantSolver = null;
-		mesh.physics = null;
+		if ( mesh.mixer === undefined ) mesh.mixer = null;
+		if ( mesh.ikSolver === undefined ) mesh.ikSolver = null;
+		if ( mesh.grantSolver === undefined ) mesh.grantSolver = null;
+		if ( mesh.physics === undefined ) mesh.physics = null;
+		if ( mesh.looped === undefined ) mesh.looped = false;
+
 		this.meshes.push( mesh );
 
 		// workaround until I make IK and Physics Animation plugin
@@ -3904,8 +3906,22 @@ THREE.MMDHelper.prototype = {
 
 	setPhysics: function ( mesh, params ) {
 
-		mesh.physics = new THREE.MMDPhysics( mesh, params );
-		mesh.physics.warmup( 10 );
+		if ( params === undefined ) params = {};
+
+		var warmup = params.warmup !== undefined ? params.warmup : 60;
+
+		var physics = new THREE.MMDPhysics( mesh, params );
+
+		if ( mesh.mixer !== null && mesh.mixer !== undefined && this.doAnimation === true && params.preventAnimationWarmup !== false ) {
+
+			this.animateOneMesh( 0, mesh );
+			physics.reset();
+
+		}
+
+		physics.warmup( warmup );
+
+		mesh.physics = physics;
 
 	},
 
@@ -3925,6 +3941,17 @@ THREE.MMDHelper.prototype = {
 
 			mesh.mixer = new THREE.AnimationMixer( mesh );
 
+			// TODO: find a workaround not to access (seems like) private properties
+			//       the name of them begins with "_".
+			mesh.mixer.addEventListener( 'loop', function ( e ) {
+
+				if ( e.action._clip.tracks[ 0 ].name.indexOf( '.bones' ) !== 0 ) return;
+
+				var mesh = e.target._root;
+				mesh.looped = true;
+
+			} );
+
 			var foundAnimation = false;
 			var foundMorphAnimation = false;
 
@@ -4143,6 +4170,14 @@ THREE.MMDHelper.prototype = {
 
 		}
 
+		if ( mesh.looped === true ) {
+
+			if ( physics !== null ) physics.reset();
+
+			mesh.looped = false;
+
+		}
+
 		if ( physics !== null && this.doPhysics === true ) {
 
 			physics.update( delta );

+ 7 - 3
examples/webgl_loader_mmd.html

@@ -52,7 +52,7 @@
 			var container, stats;
 
 			var mesh, camera, scene, renderer;
-			var helper;
+			var helper, physicsHelper;
 
 			var mouseX = 0, mouseY = 0;
 
@@ -120,14 +120,17 @@
 					helper.setAnimation( mesh );
 
 					/*
-					 * Note: You must set Physics
-					 *       before you add mesh to scene or any other 3D object.
+					 * Note: You're recommended to call helper.setPhysics()
+					 *       after calling helper.setAnimation().
 					 * Note: Physics calculation is pretty heavy.
 					 *       It may not be acceptable for most mobile devices yet.
 			 		 */
 					if ( ! isMobileDevice() ) {
 
 						helper.setPhysics( mesh );
+						physicsHelper = new THREE.MMDPhysicsHelper( mesh );
+						physicsHelper.visible = false;  // comment out this line for Physics debug
+						scene.add( physicsHelper );
 
 					}
 
@@ -183,6 +186,7 @@
 				if ( mesh ) {
 
 					helper.animate( clock.getDelta() );
+					if ( physicsHelper !== undefined ) physicsHelper.update();
 					helper.render( scene, camera );
 
 				} else {

+ 2 - 2
examples/webgl_loader_mmd_audio.html

@@ -127,8 +127,8 @@
 					helper.setAnimation( mesh );
 
 					/*
-					 * Note: You must set Physics
-					 *       before you add mesh to scene or any other 3D object.
+					 * Note: You're recommended to call helper.setPhysics()
+					 *       after calling helper.setAnimation().
 					 * Note: Physics calculation is pretty heavy.
 					 *       It may not be acceptable for most mobile devices yet.
 			 		 */