Browse Source

JSM: Added module and TS file for CCDIKSolver.

Mugen87 6 years ago
parent
commit
3628c898ae

+ 7 - 0
docs/manual/en/introduction/Import-via-modules.html

@@ -79,6 +79,7 @@
 				<li>animation
 					<ul>
 						<li>AnimationClipCreator</li>
+						<li>CCDIKSolver</li>
 						<li>TimelinerController</li>
 					</ul>
 				</li>
@@ -201,6 +202,11 @@
 						<li>SimplexNoise</li>
 					</ul>
 				</li>
+				<li>misc
+					<ul>
+						<li>Ocean</li>
+					</ul>
+				</li>
 				<li>modifiers
 					<ul>
 						<li>ExplodeModifier</li>
@@ -299,6 +305,7 @@
 						<li>LuminosityShader</li>
 						<li>MirrorShader</li>
 						<li>NormalMapShader</li>
+						<li>OceanShaders</li>
 						<li>ParallaxShader</li>
 						<li>PixelShader</li>
 						<li>RGBShiftShader</li>

+ 5 - 5
examples/js/animation/CCDIKSolver.js

@@ -65,7 +65,7 @@ THREE.CCDIKSolver = ( function () {
 				// for reference overhead reduction in loop
 				var math = Math;
 
-				for ( var i = 0, il = iks.length; i < il; i++ ) {
+				for ( var i = 0, il = iks.length; i < il; i ++ ) {
 
 					var ik = iks[ i ];
 					var effector = bones[ ik.effector ];
@@ -78,11 +78,11 @@ THREE.CCDIKSolver = ( function () {
 					var links = ik.links;
 					var iteration = ik.iteration !== undefined ? ik.iteration : 1;
 
-					for ( var j = 0; j < iteration; j++ ) {
+					for ( var j = 0; j < iteration; j ++ ) {
 
 						var rotated = false;
 
-						for ( var k = 0, kl = links.length; k < kl; k++ ) {
+						for ( var k = 0, kl = links.length; k < kl; k ++ ) {
 
 							var link = bones[ links[ k ].index ];
 
@@ -115,9 +115,9 @@ THREE.CCDIKSolver = ( function () {
 
 								angle = 1.0;
 
-							} else if ( angle < -1.0 ) {
+							} else if ( angle < - 1.0 ) {
 
-								angle = -1.0;
+								angle = - 1.0;
 
 							}
 

+ 25 - 0
examples/jsm/animation/CCDIKSolver.d.ts

@@ -0,0 +1,25 @@
+import {
+  SkinnedMesh,
+} from '../../../src/Three';
+
+export interface IKS {
+  effector: number;
+  iteration: number;
+  links: {
+    enabled: boolean;
+    index: number;
+  }
+  maxAngle: number;
+  target: number;
+}
+
+export class CCDIKSolver {
+  constructor(mesh: SkinnedMesh, iks: IKS[]);
+
+  update(): this;
+  createHelper(): CCDIKHelper;
+}
+
+export class CCDIKHelper {
+  constructor(mesh: SkinnedMesh, iks: IKS[]);
+}

+ 472 - 0
examples/jsm/animation/CCDIKSolver.js

@@ -0,0 +1,472 @@
+/**
+ * @author takahiro / https://github.com/takahirox
+ *
+ * CCD Algorithm
+ *  - https://sites.google.com/site/auraliusproject/ccd-algorithm
+ *
+ * // ik parameter example
+ * //
+ * // 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
+ * iks = [ {
+ *	target: 1,
+ *	effector: 2,
+ *	links: [ { index: 5, limitation: new Vector3( 1, 0, 0 ) }, { index: 4, enabled: false }, { index : 3 } ],
+ *	iteration: 10,
+ *	minAngle: 0.0,
+ *	maxAngle: 1.0,
+ * } ];
+ */
+
+import {
+	BufferAttribute,
+	BufferGeometry,
+	Color,
+	Line,
+	LineBasicMaterial,
+	Matrix4,
+	Mesh,
+	MeshBasicMaterial,
+	Object3D,
+	Quaternion,
+	SphereBufferGeometry,
+	Vector3
+} from "../../../build/three.module.js";
+
+var CCDIKSolver = ( function () {
+
+	/**
+	 * @param {THREE.SkinnedMesh} mesh
+	 * @param {Array<Object>} iks
+	 */
+	function CCDIKSolver( mesh, iks ) {
+
+		this.mesh = mesh;
+		this.iks = iks || [];
+
+		this._valid();
+
+	}
+
+	CCDIKSolver.prototype = {
+
+		constructor: CCDIKSolver,
+
+		/**
+		 * Update IK bones.
+		 *
+		 * @return {CCDIKSolver}
+		 */
+		update: function () {
+
+			var q = new Quaternion();
+			var targetPos = new Vector3();
+			var targetVec = new Vector3();
+			var effectorPos = new Vector3();
+			var effectorVec = new Vector3();
+			var linkPos = new Vector3();
+			var invLinkQ = new Quaternion();
+			var linkScale = new Vector3();
+			var axis = new Vector3();
+			var vector = new Vector3();
+
+			return function update() {
+
+				var bones = this.mesh.skeleton.bones;
+				var iks = this.iks;
+
+				// for reference overhead reduction in loop
+				var math = Math;
+
+				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;
+
+					for ( var j = 0; j < iteration; j ++ ) {
+
+						var rotated = false;
+
+						for ( var k = 0, kl = links.length; k < kl; k ++ ) {
+
+							var link = bones[ links[ k ].index ];
+
+							// skip this link and following links.
+							// this skip is used for MMD performance optimization.
+							if ( links[ k ].enabled === false ) break;
+
+							var limitation = links[ k ].limitation;
+							var rotationMin = links[ k ].rotationMin;
+							var rotationMax = links[ k ].rotationMax;
+
+							// 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();
+
+							targetVec.subVectors( targetPos, linkPos );
+							targetVec.applyQuaternion( invLinkQ );
+							targetVec.normalize();
+
+							var angle = targetVec.dot( effectorVec );
+
+							if ( angle > 1.0 ) {
+
+								angle = 1.0;
+
+							} else if ( angle < - 1.0 ) {
+
+								angle = - 1.0;
+
+							}
+
+							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;
+
+							if ( ik.minAngle !== undefined && angle < ik.minAngle ) {
+
+								angle = ik.minAngle;
+
+							}
+
+							if ( ik.maxAngle !== undefined && angle > ik.maxAngle ) {
+
+								angle = ik.maxAngle;
+
+							}
+
+							axis.crossVectors( effectorVec, targetVec );
+							axis.normalize();
+
+							q.setFromAxisAngle( axis, angle );
+							link.quaternion.multiply( q );
+
+							// TODO: re-consider the limitation specification
+							if ( limitation !== undefined ) {
+
+								var c = link.quaternion.w;
+
+								if ( c > 1.0 ) c = 1.0;
+
+								var c2 = math.sqrt( 1 - c * c );
+								link.quaternion.set( limitation.x * c2,
+								                     limitation.y * c2,
+								                     limitation.z * c2,
+								                     c );
+
+							}
+
+							if ( rotationMin !== undefined ) {
+
+								link.rotation.setFromVector3(
+									link.rotation
+										.toVector3( vector )
+										.max( rotationMin ) );
+
+							}
+
+							if ( rotationMax !== undefined ) {
+
+								link.rotation.setFromVector3(
+									link.rotation
+										.toVector3( vector )
+										.min( rotationMax ) );
+
+							}
+
+							link.updateMatrixWorld( true );
+
+							rotated = true;
+
+						}
+
+						if ( ! rotated ) break;
+
+					}
+
+				}
+
+				return this;
+
+			};
+
+		}(),
+
+		/**
+		 * Creates Helper
+		 *
+		 * @return {CCDIKHelper}
+		 */
+		createHelper: function () {
+
+			return new CCDIKHelper( this.mesh, this.mesh.geometry.userData.MMD.iks );
+
+		},
+
+		// private methods
+
+		_valid: function () {
+
+			var iks = this.iks;
+			var bones = this.mesh.skeleton.bones;
+
+			for ( var i = 0, il = iks.length; i < il; i ++ ) {
+
+				var ik = iks[ i ];
+				var effector = bones[ ik.effector ];
+				var links = ik.links;
+				var link0, link1;
+
+				link0 = effector;
+
+				for ( var j = 0, jl = links.length; j < jl; j ++ ) {
+
+					link1 = bones[ links[ j ].index ];
+
+					if ( link0.parent !== link1 ) {
+
+						console.warn( 'THREE.CCDIKSolver: bone ' + link0.name + ' is not the child of bone ' + link1.name );
+
+					}
+
+					link0 = link1;
+
+				}
+
+			}
+
+		}
+
+	};
+
+	/**
+	 * Visualize IK bones
+	 *
+	 * @param {SkinnedMesh} mesh
+	 * @param {Array<Object>} iks
+	 */
+	function CCDIKHelper( mesh, iks ) {
+
+		Object3D.call( this );
+
+		this.root = mesh;
+		this.iks = iks || [];
+
+		this.matrix.copy( mesh.matrixWorld );
+		this.matrixAutoUpdate = false;
+
+		this.sphereGeometry = new SphereBufferGeometry( 0.25, 16, 8 );
+
+		this.targetSphereMaterial = new MeshBasicMaterial( {
+			color: new Color( 0xff8888 ),
+			depthTest: false,
+			depthWrite: false,
+			transparent: true
+		} );
+
+		this.effectorSphereMaterial = new MeshBasicMaterial( {
+			color: new Color( 0x88ff88 ),
+			depthTest: false,
+			depthWrite: false,
+			transparent: true
+		} );
+
+		this.linkSphereMaterial = new MeshBasicMaterial( {
+			color: new Color( 0x8888ff ),
+			depthTest: false,
+			depthWrite: false,
+			transparent: true
+		} );
+
+		this.lineMaterial = new LineBasicMaterial( {
+			color: new Color( 0xff0000 ),
+			depthTest: false,
+			depthWrite: false,
+			transparent: true
+		} );
+
+		this._init();
+
+	}
+
+	CCDIKHelper.prototype = Object.assign( Object.create( Object3D.prototype ), {
+
+		constructor: CCDIKHelper,
+
+		/**
+		 * Updates IK bones visualization.
+		 */
+		updateMatrixWorld: function () {
+
+			var matrix = new Matrix4();
+			var vector = new Vector3();
+
+			function getPosition( bone, matrixWorldInv ) {
+
+				return vector
+					.setFromMatrixPosition( bone.matrixWorld )
+					.applyMatrix4( matrixWorldInv );
+
+			}
+
+			function setPositionOfBoneToAttributeArray( array, index, bone, matrixWorldInv ) {
+
+				var v = getPosition( bone, matrixWorldInv );
+
+				array[ index * 3 + 0 ] = v.x;
+				array[ index * 3 + 1 ] = v.y;
+				array[ index * 3 + 2 ] = v.z;
+
+			}
+
+			return function updateMatrixWorld( force ) {
+
+				var mesh = this.root;
+
+				if ( this.visible ) {
+
+					var offset = 0;
+
+					var iks = this.iks;
+					var bones = mesh.skeleton.bones;
+
+					matrix.getInverse( mesh.matrixWorld );
+
+					for ( var i = 0, il = iks.length; i < il; i ++ ) {
+
+						var ik = iks[ i ];
+
+						var targetBone = bones[ ik.target ];
+						var effectorBone = bones[ ik.effector ];
+
+						var targetMesh = this.children[ offset ++ ];
+						var effectorMesh = this.children[ offset ++ ];
+
+						targetMesh.position.copy( getPosition( targetBone, matrix ) );
+						effectorMesh.position.copy( getPosition( effectorBone, matrix ) );
+
+						for ( var j = 0, jl = ik.links.length; j < jl; j ++ ) {
+
+							var link = ik.links[ j ];
+							var linkBone = bones[ link.index ];
+
+							var linkMesh = this.children[ offset ++ ];
+
+							linkMesh.position.copy( getPosition( linkBone, matrix ) );
+
+						}
+
+						var line = this.children[ offset ++ ];
+						var array = line.geometry.attributes.position.array;
+
+						setPositionOfBoneToAttributeArray( array, 0, targetBone, matrix );
+						setPositionOfBoneToAttributeArray( array, 1, effectorBone, matrix );
+
+						for ( var j = 0, jl = ik.links.length; j < jl; j ++ ) {
+
+							var link = ik.links[ j ];
+							var linkBone = bones[ link.index ];
+							setPositionOfBoneToAttributeArray( array, j + 2, linkBone, matrix );
+
+						}
+
+						line.geometry.attributes.position.needsUpdate = true;
+
+					}
+
+				}
+
+				this.matrix.copy( mesh.matrixWorld );
+
+				Object3D.prototype.updateMatrixWorld.call( this, force );
+
+			};
+
+		}(),
+
+		// private method
+
+		_init: function () {
+
+			var self = this;
+			var iks = this.iks;
+
+			function createLineGeometry( ik ) {
+
+				var geometry = new BufferGeometry();
+				var vertices = new Float32Array( ( 2 + ik.links.length ) * 3 );
+				geometry.addAttribute( 'position', new BufferAttribute( vertices, 3 ) );
+
+				return geometry;
+
+			}
+
+			function createTargetMesh() {
+
+				return new Mesh( self.sphereGeometry, self.targetSphereMaterial );
+
+			}
+
+			function createEffectorMesh() {
+
+				return new Mesh( self.sphereGeometry, self.effectorSphereMaterial );
+
+			}
+
+			function createLinkMesh() {
+
+				return new Mesh( self.sphereGeometry, self.linkSphereMaterial );
+
+			}
+
+			function createLine( ik ) {
+
+				return new Line( createLineGeometry( ik ), self.lineMaterial );
+
+			}
+
+			for ( var i = 0, il = iks.length; i < il; i ++ ) {
+
+				var ik = iks[ i ];
+
+				this.add( createTargetMesh() );
+				this.add( createEffectorMesh() );
+
+				for ( var j = 0, jl = ik.links.length; j < jl; j ++ ) {
+
+					this.add( createLinkMesh() );
+
+				}
+
+				this.add( createLine( ik ) );
+
+			}
+
+		}
+
+	} );
+
+	return CCDIKSolver;
+
+} )();
+
+export { CCDIKSolver };

+ 1 - 0
utils/modularize.js

@@ -11,6 +11,7 @@ var dstFolder = __dirname + '/../examples/jsm/';
 
 var files = [
 	{ path: 'animation/AnimationClipCreator.js', dependencies: [], ignoreList: [] },
+	{ path: 'animation/CCDIKSolver.js', dependencies: [], ignoreList: [ 'SkinnedMesh' ] },
 	{ path: 'animation/TimelinerController.js', dependencies: [], ignoreList: [] },
 
 	{ path: 'cameras/CinematicCamera.js', dependencies: [ { name: 'BokehShader', path: 'shaders/BokehShader2.js' }, { name: 'BokehDepthShader', path: 'shaders/BokehShader2.js' } ], ignoreList: [] },