Spaces:
Running
Running
/** | |
* @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 THREE.Vector3( 1, 0, 0 ) }, { index: 4, enabled: false }, { index : 3 } ], | |
* iteration: 10, | |
* minAngle: 0.0, | |
* maxAngle: 1.0, | |
* } ]; | |
*/ | |
THREE.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 {THREE.CCDIKSolver} | |
*/ | |
update: function () { | |
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(); | |
var vector = new THREE.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 ) { | |
THREE.Object3D.call( this ); | |
this.root = mesh; | |
this.iks = iks || []; | |
this.matrix.copy( mesh.matrixWorld ); | |
this.matrixAutoUpdate = false; | |
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 | |
} ); | |
this.effectorSphereMaterial = new THREE.MeshBasicMaterial( { | |
color: new THREE.Color( 0x88ff88 ), | |
depthTest: false, | |
depthWrite: false, | |
transparent: true | |
} ); | |
this.linkSphereMaterial = new THREE.MeshBasicMaterial( { | |
color: new THREE.Color( 0x8888ff ), | |
depthTest: false, | |
depthWrite: false, | |
transparent: true | |
} ); | |
this.lineMaterial = new THREE.LineBasicMaterial( { | |
color: new THREE.Color( 0xff0000 ), | |
depthTest: false, | |
depthWrite: false, | |
transparent: true | |
} ); | |
this._init(); | |
} | |
CCDIKHelper.prototype = Object.assign( Object.create( THREE.Object3D.prototype ), { | |
constructor: CCDIKHelper, | |
/** | |
* Updates IK bones visualization. | |
*/ | |
updateMatrixWorld: function () { | |
var matrix = new THREE.Matrix4(); | |
var vector = new THREE.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 ); | |
THREE.Object3D.prototype.updateMatrixWorld.call( this, force ); | |
}; | |
}(), | |
// private method | |
_init: function () { | |
var self = this; | |
var iks = this.iks; | |
function createLineGeometry( ik ) { | |
var geometry = new THREE.BufferGeometry(); | |
var vertices = new Float32Array( ( 2 + ik.links.length ) * 3 ); | |
geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); | |
return geometry; | |
} | |
function createTargetMesh() { | |
return new THREE.Mesh( self.sphereGeometry, self.targetSphereMaterial ); | |
} | |
function createEffectorMesh() { | |
return new THREE.Mesh( self.sphereGeometry, self.effectorSphereMaterial ); | |
} | |
function createLinkMesh() { | |
return new THREE.Mesh( self.sphereGeometry, self.linkSphereMaterial ); | |
} | |
function createLine( ik ) { | |
return new THREE.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; | |
} )(); | |