|
import { Matrix4, Quaternion, Vector3, Object3D } from 'three' |
|
|
|
|
|
export interface IKS { |
|
effector: Object3D |
|
iteration: number |
|
links: Array<{ |
|
enabled: boolean |
|
index: Object3D |
|
limitation?: Vector3 |
|
rotationMin?: Vector3 |
|
rotationMax?: Vector3 |
|
}> |
|
minAngle: number |
|
maxAngle: number |
|
target: Object3D |
|
} |
|
|
|
const _q = new Quaternion() |
|
const _targetPos = new Vector3() |
|
const _targetVec = new Vector3() |
|
const _effectorPos = new Vector3() |
|
const _effectorVec = new Vector3() |
|
const _linkPos = new Vector3() |
|
const _invLinkQ = new Quaternion() |
|
const _linkScale = new Vector3() |
|
const _axis = new Vector3() |
|
const _vector = new Vector3() |
|
const _matrix = new Matrix4() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export class CCDIKSolver { |
|
iks: IKS[] |
|
constructor(iks: IKS[] = []) { |
|
this.iks = iks |
|
|
|
this._valid() |
|
} |
|
|
|
update() { |
|
const iks = this.iks |
|
|
|
for (let i = 0, il = iks.length; i < il; i++) { |
|
this.updateOne(iks[i]) |
|
} |
|
|
|
return this |
|
} |
|
|
|
updateOne(ik: IKS) { |
|
const bones = ik.links.map((i) => i.index) |
|
|
|
|
|
const math = Math |
|
|
|
const effector = ik.effector |
|
const target = ik.target |
|
|
|
|
|
|
|
_targetPos.setFromMatrixPosition(target.matrixWorld) |
|
|
|
const links = ik.links |
|
const iteration = ik.iteration !== undefined ? ik.iteration : 1 |
|
|
|
for (let i = 0; i < iteration; i++) { |
|
let rotated = false |
|
|
|
for (let j = 0, jl = links.length; j < jl; j++) { |
|
const link = bones[j] |
|
|
|
|
|
|
|
if (links[j].enabled === false) break |
|
|
|
const limitation = links[j].limitation |
|
const rotationMin = links[j].rotationMin |
|
const rotationMax = links[j].rotationMax |
|
|
|
|
|
|
|
link.matrixWorld.decompose(_linkPos, _invLinkQ, _linkScale) |
|
_invLinkQ.invert() |
|
_effectorPos.setFromMatrixPosition(effector.matrixWorld) |
|
|
|
|
|
_effectorVec.subVectors(_effectorPos, _linkPos) |
|
_effectorVec.applyQuaternion(_invLinkQ) |
|
_effectorVec.normalize() |
|
|
|
_targetVec.subVectors(_targetPos, _linkPos) |
|
_targetVec.applyQuaternion(_invLinkQ) |
|
_targetVec.normalize() |
|
|
|
let angle = _targetVec.dot(_effectorVec) |
|
|
|
if (angle > 1.0) { |
|
angle = 1.0 |
|
} else if (angle < -1.0) { |
|
angle = -1.0 |
|
} |
|
|
|
angle = math.acos(angle) |
|
|
|
|
|
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) |
|
|
|
|
|
if (limitation !== undefined) { |
|
let c = link.quaternion.w |
|
|
|
if (c > 1.0) c = 1.0 |
|
|
|
const 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( |
|
_vector.setFromEuler(link.rotation).max(rotationMin) |
|
) |
|
} |
|
|
|
if (rotationMax !== undefined) { |
|
link.rotation.setFromVector3( |
|
_vector.setFromEuler(link.rotation).min(rotationMax) |
|
) |
|
} |
|
|
|
link.updateMatrixWorld(true) |
|
|
|
rotated = true |
|
} |
|
|
|
if (!rotated) break |
|
} |
|
|
|
return this |
|
} |
|
|
|
|
|
|
|
_valid() { |
|
const iks = this.iks |
|
|
|
for (let i = 0, il = iks.length; i < il; i++) { |
|
const ik = iks[i] |
|
|
|
const effector = ik.effector |
|
const links = ik.links |
|
let link0, link1 |
|
|
|
link0 = effector |
|
|
|
for (let j = 0, jl = links.length; j < jl; j++) { |
|
link1 = links[j].index |
|
|
|
if (link0.parent !== link1) { |
|
console.warn( |
|
'THREE.CCDIKSolver: bone ' + |
|
link0.name + |
|
' is not the child of bone ' + |
|
link1.name |
|
) |
|
} |
|
|
|
link0 = link1 |
|
} |
|
} |
|
} |
|
} |
|
|