UNPKG

10.1 kBJavaScriptView Raw
1/**
2 * @author takahiro / https://github.com/takahirox
3 *
4 * CCD Algorithm
5 * - https://sites.google.com/site/auraliusproject/ccd-algorithm
6 *
7 * // ik parameter example
8 * //
9 * // target, effector, index in links are bone index in skeleton.bones.
10 * // the bones relation should be
11 * // <-- parent child -->
12 * // links[ n ], links[ n - 1 ], ..., links[ 0 ], effector
13 * iks = [ {
14 * target: 1,
15 * effector: 2,
16 * links: [ { index: 5, limitation: new THREE.Vector3( 1, 0, 0 ) }, { index: 4, enabled: false }, { index : 3 } ],
17 * iteration: 10,
18 * minAngle: 0.0,
19 * maxAngle: 1.0,
20 * } ];
21 */
22
23THREE.CCDIKSolver = ( function () {
24
25 /**
26 * @param {THREE.SkinnedMesh} mesh
27 * @param {Array<Object>} iks
28 */
29 function CCDIKSolver( mesh, iks ) {
30
31 this.mesh = mesh;
32 this.iks = iks || [];
33
34 this._valid();
35
36 }
37
38 CCDIKSolver.prototype = {
39
40 constructor: CCDIKSolver,
41
42 /**
43 * Update IK bones.
44 *
45 * @return {THREE.CCDIKSolver}
46 */
47 update: function () {
48
49 var q = new THREE.Quaternion();
50 var targetPos = new THREE.Vector3();
51 var targetVec = new THREE.Vector3();
52 var effectorPos = new THREE.Vector3();
53 var effectorVec = new THREE.Vector3();
54 var linkPos = new THREE.Vector3();
55 var invLinkQ = new THREE.Quaternion();
56 var linkScale = new THREE.Vector3();
57 var axis = new THREE.Vector3();
58 var vector = new THREE.Vector3();
59
60 return function update() {
61
62 var bones = this.mesh.skeleton.bones;
63 var iks = this.iks;
64
65 // for reference overhead reduction in loop
66 var math = Math;
67
68 for ( var i = 0, il = iks.length; i < il; i ++ ) {
69
70 var ik = iks[ i ];
71 var effector = bones[ ik.effector ];
72 var target = bones[ ik.target ];
73
74 // don't use getWorldPosition() here for the performance
75 // because it calls updateMatrixWorld( true ) inside.
76 targetPos.setFromMatrixPosition( target.matrixWorld );
77
78 var links = ik.links;
79 var iteration = ik.iteration !== undefined ? ik.iteration : 1;
80
81 for ( var j = 0; j < iteration; j ++ ) {
82
83 var rotated = false;
84
85 for ( var k = 0, kl = links.length; k < kl; k ++ ) {
86
87 var link = bones[ links[ k ].index ];
88
89 // skip this link and following links.
90 // this skip is used for MMD performance optimization.
91 if ( links[ k ].enabled === false ) break;
92
93 var limitation = links[ k ].limitation;
94 var rotationMin = links[ k ].rotationMin;
95 var rotationMax = links[ k ].rotationMax;
96
97 // don't use getWorldPosition/Quaternion() here for the performance
98 // because they call updateMatrixWorld( true ) inside.
99 link.matrixWorld.decompose( linkPos, invLinkQ, linkScale );
100 invLinkQ.inverse();
101 effectorPos.setFromMatrixPosition( effector.matrixWorld );
102
103 // work in link world
104 effectorVec.subVectors( effectorPos, linkPos );
105 effectorVec.applyQuaternion( invLinkQ );
106 effectorVec.normalize();
107
108 targetVec.subVectors( targetPos, linkPos );
109 targetVec.applyQuaternion( invLinkQ );
110 targetVec.normalize();
111
112 var angle = targetVec.dot( effectorVec );
113
114 if ( angle > 1.0 ) {
115
116 angle = 1.0;
117
118 } else if ( angle < - 1.0 ) {
119
120 angle = - 1.0;
121
122 }
123
124 angle = math.acos( angle );
125
126 // skip if changing angle is too small to prevent vibration of bone
127 // Refer to http://www20.atpages.jp/katwat/three.js_r58/examples/mytest37/mmd.three.js
128 if ( angle < 1e-5 ) continue;
129
130 if ( ik.minAngle !== undefined && angle < ik.minAngle ) {
131
132 angle = ik.minAngle;
133
134 }
135
136 if ( ik.maxAngle !== undefined && angle > ik.maxAngle ) {
137
138 angle = ik.maxAngle;
139
140 }
141
142 axis.crossVectors( effectorVec, targetVec );
143 axis.normalize();
144
145 q.setFromAxisAngle( axis, angle );
146 link.quaternion.multiply( q );
147
148 // TODO: re-consider the limitation specification
149 if ( limitation !== undefined ) {
150
151 var c = link.quaternion.w;
152
153 if ( c > 1.0 ) c = 1.0;
154
155 var c2 = math.sqrt( 1 - c * c );
156 link.quaternion.set( limitation.x * c2,
157 limitation.y * c2,
158 limitation.z * c2,
159 c );
160
161 }
162
163 if ( rotationMin !== undefined ) {
164
165 link.rotation.setFromVector3(
166 link.rotation
167 .toVector3( vector )
168 .max( rotationMin ) );
169
170 }
171
172 if ( rotationMax !== undefined ) {
173
174 link.rotation.setFromVector3(
175 link.rotation
176 .toVector3( vector )
177 .min( rotationMax ) );
178
179 }
180
181 link.updateMatrixWorld( true );
182
183 rotated = true;
184
185 }
186
187 if ( ! rotated ) break;
188
189 }
190
191 }
192
193 return this;
194
195 };
196
197 }(),
198
199 /**
200 * Creates Helper
201 *
202 * @return {CCDIKHelper}
203 */
204 createHelper: function () {
205
206 return new CCDIKHelper( this.mesh, this.mesh.geometry.userData.MMD.iks );
207
208 },
209
210 // private methods
211
212 _valid: function () {
213
214 var iks = this.iks;
215 var bones = this.mesh.skeleton.bones;
216
217 for ( var i = 0, il = iks.length; i < il; i ++ ) {
218
219 var ik = iks[ i ];
220 var effector = bones[ ik.effector ];
221 var links = ik.links;
222 var link0, link1;
223
224 link0 = effector;
225
226 for ( var j = 0, jl = links.length; j < jl; j ++ ) {
227
228 link1 = bones[ links[ j ].index ];
229
230 if ( link0.parent !== link1 ) {
231
232 console.warn( 'THREE.CCDIKSolver: bone ' + link0.name + ' is not the child of bone ' + link1.name );
233
234 }
235
236 link0 = link1;
237
238 }
239
240 }
241
242 }
243
244 };
245
246 /**
247 * Visualize IK bones
248 *
249 * @param {SkinnedMesh} mesh
250 * @param {Array<Object>} iks
251 */
252 function CCDIKHelper( mesh, iks ) {
253
254 THREE.Object3D.call( this );
255
256 this.root = mesh;
257 this.iks = iks || [];
258
259 this.matrix.copy( mesh.matrixWorld );
260 this.matrixAutoUpdate = false;
261
262 this.sphereGeometry = new THREE.SphereBufferGeometry( 0.25, 16, 8 );
263
264 this.targetSphereMaterial = new THREE.MeshBasicMaterial( {
265 color: new THREE.Color( 0xff8888 ),
266 depthTest: false,
267 depthWrite: false,
268 transparent: true
269 } );
270
271 this.effectorSphereMaterial = new THREE.MeshBasicMaterial( {
272 color: new THREE.Color( 0x88ff88 ),
273 depthTest: false,
274 depthWrite: false,
275 transparent: true
276 } );
277
278 this.linkSphereMaterial = new THREE.MeshBasicMaterial( {
279 color: new THREE.Color( 0x8888ff ),
280 depthTest: false,
281 depthWrite: false,
282 transparent: true
283 } );
284
285 this.lineMaterial = new THREE.LineBasicMaterial( {
286 color: new THREE.Color( 0xff0000 ),
287 depthTest: false,
288 depthWrite: false,
289 transparent: true
290 } );
291
292 this._init();
293
294 }
295
296 CCDIKHelper.prototype = Object.assign( Object.create( THREE.Object3D.prototype ), {
297
298 constructor: CCDIKHelper,
299
300 /**
301 * Updates IK bones visualization.
302 */
303 updateMatrixWorld: function () {
304
305 var matrix = new THREE.Matrix4();
306 var vector = new THREE.Vector3();
307
308 function getPosition( bone, matrixWorldInv ) {
309
310 return vector
311 .setFromMatrixPosition( bone.matrixWorld )
312 .applyMatrix4( matrixWorldInv );
313
314 }
315
316 function setPositionOfBoneToAttributeArray( array, index, bone, matrixWorldInv ) {
317
318 var v = getPosition( bone, matrixWorldInv );
319
320 array[ index * 3 + 0 ] = v.x;
321 array[ index * 3 + 1 ] = v.y;
322 array[ index * 3 + 2 ] = v.z;
323
324 }
325
326 return function updateMatrixWorld( force ) {
327
328 var mesh = this.root;
329
330 if ( this.visible ) {
331
332 var offset = 0;
333
334 var iks = this.iks;
335 var bones = mesh.skeleton.bones;
336
337 matrix.getInverse( mesh.matrixWorld );
338
339 for ( var i = 0, il = iks.length; i < il; i ++ ) {
340
341 var ik = iks[ i ];
342
343 var targetBone = bones[ ik.target ];
344 var effectorBone = bones[ ik.effector ];
345
346 var targetMesh = this.children[ offset ++ ];
347 var effectorMesh = this.children[ offset ++ ];
348
349 targetMesh.position.copy( getPosition( targetBone, matrix ) );
350 effectorMesh.position.copy( getPosition( effectorBone, matrix ) );
351
352 for ( var j = 0, jl = ik.links.length; j < jl; j ++ ) {
353
354 var link = ik.links[ j ];
355 var linkBone = bones[ link.index ];
356
357 var linkMesh = this.children[ offset ++ ];
358
359 linkMesh.position.copy( getPosition( linkBone, matrix ) );
360
361 }
362
363 var line = this.children[ offset ++ ];
364 var array = line.geometry.attributes.position.array;
365
366 setPositionOfBoneToAttributeArray( array, 0, targetBone, matrix );
367 setPositionOfBoneToAttributeArray( array, 1, effectorBone, matrix );
368
369 for ( var j = 0, jl = ik.links.length; j < jl; j ++ ) {
370
371 var link = ik.links[ j ];
372 var linkBone = bones[ link.index ];
373 setPositionOfBoneToAttributeArray( array, j + 2, linkBone, matrix );
374
375 }
376
377 line.geometry.attributes.position.needsUpdate = true;
378
379 }
380
381 }
382
383 this.matrix.copy( mesh.matrixWorld );
384
385 THREE.Object3D.prototype.updateMatrixWorld.call( this, force );
386
387 };
388
389 }(),
390
391 // private method
392
393 _init: function () {
394
395 var self = this;
396 var iks = this.iks;
397
398 function createLineGeometry( ik ) {
399
400 var geometry = new THREE.BufferGeometry();
401 var vertices = new Float32Array( ( 2 + ik.links.length ) * 3 );
402 geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
403
404 return geometry;
405
406 }
407
408 function createTargetMesh() {
409
410 return new THREE.Mesh( self.sphereGeometry, self.targetSphereMaterial );
411
412 }
413
414 function createEffectorMesh() {
415
416 return new THREE.Mesh( self.sphereGeometry, self.effectorSphereMaterial );
417
418 }
419
420 function createLinkMesh() {
421
422 return new THREE.Mesh( self.sphereGeometry, self.linkSphereMaterial );
423
424 }
425
426 function createLine( ik ) {
427
428 return new THREE.Line( createLineGeometry( ik ), self.lineMaterial );
429
430 }
431
432 for ( var i = 0, il = iks.length; i < il; i ++ ) {
433
434 var ik = iks[ i ];
435
436 this.add( createTargetMesh() );
437 this.add( createEffectorMesh() );
438
439 for ( var j = 0, jl = ik.links.length; j < jl; j ++ ) {
440
441 this.add( createLinkMesh() );
442
443 }
444
445 this.add( createLine( ik ) );
446
447 }
448
449 }
450
451 } );
452
453 return CCDIKSolver;
454
455} )();