Browse Source

Update CCDIKSolver

Takahiro 7 years ago
parent
commit
847ecb4d67
1 changed files with 258 additions and 246 deletions
  1. 258 246
      examples/js/animation/CCDIKSolver.js

+ 258 - 246
examples/js/animation/CCDIKSolver.js

@@ -2,416 +2,428 @@
  * @author takahiro / https://github.com/takahirox
  *
  * CCD Algorithm
- *  https://sites.google.com/site/auraliusproject/ccd-algorithm
- *
- * mesh.geometry needs to have iks array.
+ *  - https://sites.google.com/site/auraliusproject/ccd-algorithm
  *
  * // ik parameter example
  * //
- * // target, effector, index in links are bone index in skeleton.
+ * // target, effector, index in links are bone index in skeleton.bones.
  * // the bones relation should be
  * // <-- parent                                  child -->
  * // links[ n ], links[ n - 1 ], ..., links[ 0 ], effector
- * ik = {
+ * iks = [ {
  *	target: 1,
  *	effector: 2,
  *	links: [ { index: 5, limitation: new THREE.Vector3( 1, 0, 0 ) }, { index: 4, enabled: false }, { index : 3 } ],
  *	iteration: 10,
  *	minAngle: 0.0,
  *	maxAngle: 1.0,
- * };
+ * } ];
  */
 
-THREE.CCDIKSolver = function ( mesh ) {
-
-	this.mesh = mesh;
-
-	this._valid();
-
-};
-
-THREE.CCDIKSolver.prototype = {
-
-	constructor: THREE.CCDIKSolver,
-
-	_valid: function () {
-
-		var iks = this.mesh.geometry.iks;
-		var bones = this.mesh.skeleton.bones;
+THREE.CCDIKSolver = ( function () {
 
-		for ( var i = 0, il = iks.length; i < il; i ++ ) {
+	/**
+	 * @param {THREE.SkinnedMesh} mesh
+	 * @param {Array<Object>} iks
+	 */
+	function CCDIKSolver( mesh, iks ) {
 
-			var ik = iks[ i ];
+		this.mesh = mesh;
+		this.iks = iks || [];
 
-			var effector = bones[ ik.effector ];
+		this._valid();
 
-			var links = ik.links;
+	}
 
-			var link0, link1;
+	CCDIKSolver.prototype = {
 
-			link0 = effector;
+		constructor: CCDIKSolver,
 
-			for ( var j = 0, jl = links.length; j < jl; j ++ ) {
+		/**
+		 * Update IK bones.
+		 *
+		 * @return {THREE.CCDIKSolver}
+		 */
+		update: function () {
 
-				link1 = bones[ links[ j ].index ];
+			var q = new THREE.Quaternion();
+			var targetPos = new THREE.Vector3();
+			var targetVec = new THREE.Vector3();
+			var effectorPos = new THREE.Vector3();
+			var effectorVec = new THREE.Vector3();
+			var linkPos = new THREE.Vector3();
+			var invLinkQ = new THREE.Quaternion();
+			var linkScale = new THREE.Vector3();
+			var axis = new THREE.Vector3();
 
-				if ( link0.parent !== link1 ) {
+			return function update() {
 
-					console.warn( 'THREE.CCDIKSolver: bone ' + link0.name + ' is not the child of bone ' + link1.name );
+				var bones = this.mesh.skeleton.bones;
+				var iks = this.iks;
 
-				}
+				// for reference overhead reduction in loop
+				var math = Math;
 
-				link0 = link1;
+				for ( var i = 0, il = iks.length; i < il; i++ ) {
 
-			}
+					var ik = iks[ i ];
+					var effector = bones[ ik.effector ];
+					var target = bones[ ik.target ];
 
-		}
+					// don't use getWorldPosition() here for the performance
+					// because it calls updateMatrixWorld( true ) inside.
+					targetPos.setFromMatrixPosition( target.matrixWorld );
 
-	},
+					var links = ik.links;
+					var iteration = ik.iteration !== undefined ? ik.iteration : 1;
 
-	/*
-	 * save the bone matrices before solving IK.
-	 * they're used for generating VMD and VPD.
-	 */
-	_saveOriginalBonesInfo: function () {
+					for ( var j = 0; j < iteration; j++ ) {
 
-		var bones = this.mesh.skeleton.bones;
+						var rotated = false;
 
-		for ( var i = 0, il = bones.length; i < il; i ++ ) {
+						for ( var k = 0, kl = links.length; k < kl; k++ ) {
 
-			var bone = bones[ i ];
+							var link = bones[ links[ k ].index ];
 
-			if ( bone.userData.ik === undefined ) bone.userData.ik = {};
+							// skip this link and following links.
+							// this skip is used for MMD performance optimization.
+							if ( links[ k ].enabled === false ) break;
 
-			bone.userData.ik.originalMatrix = bone.matrix.toArray();
+							var limitation = links[ k ].limitation;
 
-		}
+							// don't use getWorldPosition/Quaternion() here for the performance
+							// because they call updateMatrixWorld( true ) inside.
+							link.matrixWorld.decompose( linkPos, invLinkQ, linkScale );
+							invLinkQ.inverse();
+							effectorPos.setFromMatrixPosition( effector.matrixWorld );
 
-	},
+							// work in link world
+							effectorVec.subVectors( effectorPos, linkPos );
+							effectorVec.applyQuaternion( invLinkQ );
+							effectorVec.normalize();
 
-	update: function ( saveOriginalBones ) {
+							targetVec.subVectors( targetPos, linkPos );
+							targetVec.applyQuaternion( invLinkQ );
+							targetVec.normalize();
 
-		var q = new THREE.Quaternion();
+							var angle = targetVec.dot( effectorVec );
 
-		var targetPos = new THREE.Vector3();
-		var targetVec = new THREE.Vector3();
-		var effectorPos = new THREE.Vector3();
-		var effectorVec = new THREE.Vector3();
-		var linkPos = new THREE.Vector3();
-		var invLinkQ = new THREE.Quaternion();
-		var linkScale = new THREE.Vector3();
-		var axis = new THREE.Vector3();
+							if ( angle > 1.0 ) {
 
-		var bones = this.mesh.skeleton.bones;
-		var iks = this.mesh.geometry.iks;
+								angle = 1.0;
 
-		var boneParams = this.mesh.geometry.bones;
+							} else if ( angle < -1.0 ) {
 
-		// for reference overhead reduction in loop
-		var math = Math;
+								angle = -1.0;
 
-		this.mesh.updateMatrixWorld( true );
+							}
 
-		if ( saveOriginalBones === true ) this._saveOriginalBonesInfo();
+							angle = math.acos( angle );
 
-		for ( var i = 0, il = iks.length; i < il; i++ ) {
+							// skip if changing angle is too small to prevent vibration of bone
+							// Refer to http://www20.atpages.jp/katwat/three.js_r58/examples/mytest37/mmd.three.js
+							if ( angle < 1e-5 ) continue;
 
-			var ik = iks[ i ];
-			var effector = bones[ ik.effector ];
-			var target = bones[ ik.target ];
+							if ( ik.minAngle !== undefined && angle < ik.minAngle ) {
 
-			// don't use getWorldPosition() here for the performance
-			// because it calls updateMatrixWorld( true ) inside.
-			targetPos.setFromMatrixPosition( target.matrixWorld );
+								angle = ik.minAngle;
 
-			var links = ik.links;
-			var iteration = ik.iteration !== undefined ? ik.iteration : 1;
+							}
 
-			for ( var j = 0; j < iteration; j++ ) {
+							if ( ik.maxAngle !== undefined && angle > ik.maxAngle ) {
 
-				var rotated = false;
+								angle = ik.maxAngle;
 
-				for ( var k = 0, kl = links.length; k < kl; k++ ) {
+							}
 
-					var link = bones[ links[ k ].index ];
+							axis.crossVectors( effectorVec, targetVec );
+							axis.normalize();
 
-					// skip this link and following links.
-					// this skip is used for MMD performance optimization.
-					if ( links[ k ].enabled === false ) break;
+							q.setFromAxisAngle( axis, angle );
+							link.quaternion.multiply( q );
 
-					var limitation = links[ k ].limitation;
+							// TODO: re-consider the limitation specification
+							if ( limitation !== undefined ) {
 
-					// don't use getWorldPosition/Quaternion() here for the performance
-					// because they call updateMatrixWorld( true ) inside.
-					link.matrixWorld.decompose( linkPos, invLinkQ, linkScale );
-					invLinkQ.inverse();
-					effectorPos.setFromMatrixPosition( effector.matrixWorld );
+								var c = link.quaternion.w;
 
-					// work in link world
-					effectorVec.subVectors( effectorPos, linkPos );
-					effectorVec.applyQuaternion( invLinkQ );
-					effectorVec.normalize();
+								if ( c > 1.0 ) c = 1.0;
 
-					targetVec.subVectors( targetPos, linkPos );
-					targetVec.applyQuaternion( invLinkQ );
-					targetVec.normalize();
+								var c2 = math.sqrt( 1 - c * c );
+								link.quaternion.set( limitation.x * c2,
+								                     limitation.y * c2,
+								                     limitation.z * c2,
+								                     c );
 
-					var angle = targetVec.dot( effectorVec );
+							}
 
-					if ( angle > 1.0 ) {
+							link.updateMatrixWorld( true );
 
-						angle = 1.0;
+							rotated = true;
 
-					} else if ( angle < -1.0 ) {
+						}
 
-						angle = -1.0;
+						if ( ! rotated ) break;
 
 					}
 
-					angle = math.acos( angle );
+				}
 
-					// skip if changing angle is too small to prevent vibration of bone
-					// Refer to http://www20.atpages.jp/katwat/three.js_r58/examples/mytest37/mmd.three.js
-					if ( angle < 1e-5 ) continue;
+				return this;
 
-					if ( ik.minAngle !== undefined && angle < ik.minAngle ) {
+			}
 
-						angle = ik.minAngle;
+		}(),
 
-					}
+		/**
+		 * Creates Helper
+		 *
+		 * @return {CCDIKHelper}
+		 */
+		createHelper: function () {
 
-					if ( ik.maxAngle !== undefined && angle > ik.maxAngle ) {
+			return new CCDIKHelper( this.mesh, this.mesh.geometry.iks );
 
-						angle = ik.maxAngle;
+		},
 
-					}
+		// private methods
 
-					axis.crossVectors( effectorVec, targetVec );
-					axis.normalize();
+		_valid: function () {
 
-					q.setFromAxisAngle( axis, angle );
-					link.quaternion.multiply( q );
+			var iks = this.iks;
+			var bones = this.mesh.skeleton.bones;
 
-					// TODO: re-consider the limitation specification
-					if ( limitation !== undefined ) {
+			for ( var i = 0, il = iks.length; i < il; i ++ ) {
 
-						var c = link.quaternion.w;
+				var ik = iks[ i ];
+				var effector = bones[ ik.effector ];
+				var links = ik.links;
+				var link0, link1;
 
-						if ( c > 1.0 ) {
+				link0 = effector;
 
-							c = 1.0;
+				for ( var j = 0, jl = links.length; j < jl; j ++ ) {
 
-						}
+					link1 = bones[ links[ j ].index ];
+
+					if ( link0.parent !== link1 ) {
 
-						var c2 = math.sqrt( 1 - c * c );
-						link.quaternion.set( limitation.x * c2,
-						                     limitation.y * c2,
-						                     limitation.z * c2,
-						                     c );
+						console.warn( 'THREE.CCDIKSolver: bone ' + link0.name + ' is not the child of bone ' + link1.name );
 
 					}
 
-					link.updateMatrixWorld( true );
-					rotated = true;
+					link0 = link1;
 
 				}
 
-				if ( ! rotated ) break;
-
 			}
 
 		}
 
-		// just in case
-		this.mesh.updateMatrixWorld( true );
+	};
 
-	}
+	/**
+	 * Visualize IK bones
+	 *
+	 * @param {SkinnedMesh} mesh
+	 * @param {Array<Object>} iks
+	 */
+	function CCDIKHelper( mesh, iks ) {
 
-};
+		THREE.Object3D.call( this );
 
+		this.root = mesh;
+		this.iks = iks || [];
 
-THREE.CCDIKHelper = function ( mesh ) {
+		this.matrix = mesh.matrixWorld;
+		this.matrixAutoUpdate = false;
 
-	if ( mesh.geometry.iks === undefined || mesh.skeleton === undefined ) {
+		this.sphereGeometry = new THREE.SphereBufferGeometry( 0.25, 16, 8 );
 
-		throw 'THREE.CCDIKHelper requires iks in mesh.geometry and skeleton in mesh.';
+		this.targetSphereMaterial = new THREE.MeshBasicMaterial( {
+			color: new THREE.Color( 0xff8888 ),
+			depthTest: false,
+			depthWrite: false,
+			transparent: true
+		} );
 
-	}
+		this.effectorSphereMaterial = new THREE.MeshBasicMaterial( {
+			color: new THREE.Color( 0x88ff88 ),
+			depthTest: false,
+			depthWrite: false,
+			transparent: true
+		} );
 
-	THREE.Object3D.call( this );
+		this.linkSphereMaterial = new THREE.MeshBasicMaterial( {
+			color: new THREE.Color( 0x8888ff ),
+			depthTest: false,
+			depthWrite: false,
+			transparent: true
+		} );
 
-	this.root = mesh;
+		this.lineMaterial = new THREE.LineBasicMaterial( {
+			color: new THREE.Color( 0xff0000 ),
+			depthTest: false,
+			depthWrite: false,
+			transparent: true
+		} );
 
-	this.matrix = mesh.matrixWorld;
-	this.matrixAutoUpdate = false;
+		this._init();
 
-	this.sphereGeometry = new THREE.SphereBufferGeometry( 0.25, 16, 8 );
+	}
 
-	this.targetSphereMaterial = new THREE.MeshBasicMaterial( {
-		color: new THREE.Color( 0xff8888 ),
-		depthTest: false,
-		depthWrite: false,
-		transparent: true
-	} );
+	CCDIKHelper.prototype = Object.assign( Object.create( THREE.Object3D.prototype ), {
 
-	this.effectorSphereMaterial = new THREE.MeshBasicMaterial( {
-		color: new THREE.Color( 0x88ff88 ),
-		depthTest: false,
-		depthWrite: false,
-		transparent: true
-	} );
+		constructor: CCDIKHelper,
 
-	this.linkSphereMaterial = new THREE.MeshBasicMaterial( {
-		color: new THREE.Color( 0x8888ff ),
-		depthTest: false,
-		depthWrite: false,
-		transparent: true
-	} );
+		/**
+		 * Updates IK bones visualization.
+		 *
+		 * @return {CCDIKHelper}
+		 */
+		update: function () {
 
-	this.lineMaterial = new THREE.LineBasicMaterial( {
-		color: new THREE.Color( 0xff0000 ),
-		depthTest: false,
-		depthWrite: false,
-		transparent: true
-	} );
+			var matrix = new THREE.Matrix4();
+			var vector = new THREE.Vector3();
 
-	this._init();
-	this.update();
+			function getPosition( bone, matrixWorldInv ) {
 
-};
+				vector.setFromMatrixPosition( bone.matrixWorld );
+				vector.applyMatrix4( matrixWorldInv );
 
-THREE.CCDIKHelper.prototype = Object.create( THREE.Object3D.prototype );
-THREE.CCDIKHelper.prototype.constructor = THREE.CCDIKHelper;
+				return vector;
 
-THREE.CCDIKHelper.prototype._init = function () {
+			}
 
-	var self = this;
-	var mesh = this.root;
-	var iks = mesh.geometry.iks;
+			function setPositionOfBoneToAttributeArray( array, index, bone, matrixWorldInv ) {
 
-	function createLineGeometry( ik ) {
+				var v = getPosition( bone, matrixWorldInv );
 
-		var geometry = new THREE.BufferGeometry();
-		var vertices = new Float32Array( ( 2 + ik.links.length ) * 3 );
-		geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
+				array[ index * 3 + 0 ] = v.x;
+				array[ index * 3 + 1 ] = v.y;
+				array[ index * 3 + 2 ] = v.z;
 
-		return geometry;
+			}
 
-	}
+			return function update() {
 
-	function createTargetMesh() {
+				var offset = 0;
 
-		return new THREE.Mesh( self.sphereGeometry, self.targetSphereMaterial );
+				var mesh = this.root;
+				var iks = this.iks;
+				var bones = mesh.skeleton.bones;
 
-	}
+				matrix.getInverse( mesh.matrixWorld );
 
-	function createEffectorMesh() {
+				for ( var i = 0, il = iks.length; i < il; i ++ ) {
 
-		return new THREE.Mesh( self.sphereGeometry, self.effectorSphereMaterial );
+					var ik = iks[ i ];
 
-	}
+					var targetBone = bones[ ik.target ];
+					var effectorBone = bones[ ik.effector ];
 
-	function createLinkMesh() {
+					var targetMesh = this.children[ offset ++ ];
+					var effectorMesh = this.children[ offset ++ ];
 
-		return new THREE.Mesh( self.sphereGeometry, self.linkSphereMaterial );
+					targetMesh.position.copy( getPosition( targetBone, matrix ) );
+					effectorMesh.position.copy( getPosition( effectorBone, matrix ) );
 
-	}
+					for ( var j = 0, jl = ik.links.length; j < jl; j ++ ) {
 
-	function createLine( ik ) {
+						var link = ik.links[ j ];
+						var linkBone = bones[ link.index ];
 
-		return new THREE.Line( createLineGeometry( ik ), self.lineMaterial );
+						var linkMesh = this.children[ offset ++ ];
 
-	}
+						linkMesh.position.copy( getPosition( linkBone, matrix ) );
 
-	for ( var i = 0, il = iks.length; i < il; i ++ ) {
+					}
 
-		var ik = iks[ i ];
+					var line = this.children[ offset ++ ];
+					var array = line.geometry.attributes.position.array;
 
-		this.add( createTargetMesh() );
-		this.add( createEffectorMesh() );
+					setPositionOfBoneToAttributeArray( array, 0, targetBone, matrix );
+					setPositionOfBoneToAttributeArray( array, 1, effectorBone, matrix );
 
-		for ( var j = 0, jl = ik.links.length; j < jl; j ++ ) {
+					for ( var j = 0, jl = ik.links.length; j < jl; j ++ ) {
 
-			this.add( createLinkMesh() );
+						var link = ik.links[ j ];
+						var linkBone = bones[ link.index ];
+						setPositionOfBoneToAttributeArray( array, j + 2, linkBone, matrix );
 
-		}
+					}
 
-		this.add( createLine( ik ) );
+					line.geometry.attributes.position.needsUpdate = true;
 
-	}
+				}
 
-};
+			}
 
-THREE.CCDIKHelper.prototype.update = function () {
+		}(),
 
-	var offset = 0;
+		// private method
 
-	var mesh = this.root;
-	var iks = mesh.geometry.iks;
-	var bones = mesh.skeleton.bones;
+		_init: function () {
 
-	var matrixWorldInv = new THREE.Matrix4().getInverse( mesh.matrixWorld );
-	var vector = new THREE.Vector3();
+			var self = this;
+			var mesh = this.root;
+			var iks = this.iks;
 
-	function getPosition( bone ) {
+			function createLineGeometry( ik ) {
 
-		vector.setFromMatrixPosition( bone.matrixWorld );
-		vector.applyMatrix4( matrixWorldInv );
+				var geometry = new THREE.BufferGeometry();
+				var vertices = new Float32Array( ( 2 + ik.links.length ) * 3 );
+				geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
 
-		return vector;
+				return geometry;
 
-	}
+			}
 
-	function setPositionOfBoneToAttributeArray( array, index, bone ) {
+			function createTargetMesh() {
 
-		var v = getPosition( bone );
+				return new THREE.Mesh( self.sphereGeometry, self.targetSphereMaterial );
 
-		array[ index * 3 + 0 ] = v.x;
-		array[ index * 3 + 1 ] = v.y;
-		array[ index * 3 + 2 ] = v.z;
+			}
 
-	}
+			function createEffectorMesh() {
+
+				return new THREE.Mesh( self.sphereGeometry, self.effectorSphereMaterial );
+
+			}
 
-	for ( var i = 0, il = iks.length; i < il; i ++ ) {
+			function createLinkMesh() {
 
-		var ik = iks[ i ];
+				return new THREE.Mesh( self.sphereGeometry, self.linkSphereMaterial );
 
-		var targetBone = bones[ ik.target ];
-		var effectorBone = bones[ ik.effector ];
+			}
 
-		var targetMesh = this.children[ offset ++ ];
-		var effectorMesh = this.children[ offset ++ ];
+			function createLine( ik ) {
 
-		targetMesh.position.copy( getPosition( targetBone ) );
-		effectorMesh.position.copy( getPosition( effectorBone ) );
+				return new THREE.Line( createLineGeometry( ik ), self.lineMaterial );
 
-		for ( var j = 0, jl = ik.links.length; j < jl; j ++ ) {
+			}
 
-			var link = ik.links[ j ];
-			var linkBone = bones[ link.index ];
+			for ( var i = 0, il = iks.length; i < il; i ++ ) {
 
-			var linkMesh = this.children[ offset ++ ];
+				var ik = iks[ i ];
 
-			linkMesh.position.copy( getPosition( linkBone ) );
+				this.add( createTargetMesh() );
+				this.add( createEffectorMesh() );
 
-		}
+				for ( var j = 0, jl = ik.links.length; j < jl; j ++ ) {
 
-		var line = this.children[ offset ++ ];
-		var array = line.geometry.attributes.position.array;
+					this.add( createLinkMesh() );
 
-		setPositionOfBoneToAttributeArray( array, 0, targetBone );
-		setPositionOfBoneToAttributeArray( array, 1, effectorBone );
+				}
 
-		for ( var j = 0, jl = ik.links.length; j < jl; j ++ ) {
+				this.add( createLine( ik ) );
 
-			var link = ik.links[ j ];
-			var linkBone = bones[ link.index ];
-			setPositionOfBoneToAttributeArray( array, j + 2, linkBone );
+			}
 
 		}
 
-		line.geometry.attributes.position.needsUpdate = true;
+	} );
 
-	}
+	return CCDIKSolver;
 
-};
+} )();