271 lines
8.9 KiB
JavaScript
271 lines
8.9 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
const THREE = require("three");
|
|
const _q = /* @__PURE__ */ new THREE.Quaternion();
|
|
const _targetPos = /* @__PURE__ */ new THREE.Vector3();
|
|
const _targetVec = /* @__PURE__ */ new THREE.Vector3();
|
|
const _effectorPos = /* @__PURE__ */ new THREE.Vector3();
|
|
const _effectorVec = /* @__PURE__ */ new THREE.Vector3();
|
|
const _linkPos = /* @__PURE__ */ new THREE.Vector3();
|
|
const _invLinkQ = /* @__PURE__ */ new THREE.Quaternion();
|
|
const _linkScale = /* @__PURE__ */ new THREE.Vector3();
|
|
const _axis = /* @__PURE__ */ new THREE.Vector3();
|
|
const _vector = /* @__PURE__ */ new THREE.Vector3();
|
|
const _matrix = /* @__PURE__ */ new THREE.Matrix4();
|
|
class CCDIKSolver {
|
|
/**
|
|
* @param {THREE.SkinnedMesh} mesh
|
|
* @param {Array<Object>} iks
|
|
*/
|
|
constructor(mesh, iks = []) {
|
|
this.mesh = mesh;
|
|
this.iks = iks;
|
|
this._valid();
|
|
}
|
|
/**
|
|
* Update all IK bones.
|
|
*
|
|
* @return {CCDIKSolver}
|
|
*/
|
|
update() {
|
|
const iks = this.iks;
|
|
for (let i = 0, il = iks.length; i < il; i++) {
|
|
this.updateOne(iks[i]);
|
|
}
|
|
return this;
|
|
}
|
|
/**
|
|
* Update one IK bone
|
|
*
|
|
* @param {Object} ik parameter
|
|
* @return {CCDIKSolver}
|
|
*/
|
|
updateOne(ik) {
|
|
const bones = this.mesh.skeleton.bones;
|
|
const math = Math;
|
|
const effector = bones[ik.effector];
|
|
const target = bones[ik.target];
|
|
_targetPos.setFromMatrixPosition(target.matrixWorld);
|
|
const links = ik.links;
|
|
const iteration = ik.iteration !== void 0 ? 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[links[j].index];
|
|
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) {
|
|
angle = 1;
|
|
} else if (angle < -1) {
|
|
angle = -1;
|
|
}
|
|
angle = math.acos(angle);
|
|
if (angle < 1e-5)
|
|
continue;
|
|
if (ik.minAngle !== void 0 && angle < ik.minAngle) {
|
|
angle = ik.minAngle;
|
|
}
|
|
if (ik.maxAngle !== void 0 && angle > ik.maxAngle) {
|
|
angle = ik.maxAngle;
|
|
}
|
|
_axis.crossVectors(_effectorVec, _targetVec);
|
|
_axis.normalize();
|
|
_q.setFromAxisAngle(_axis, angle);
|
|
link.quaternion.multiply(_q);
|
|
if (limitation !== void 0) {
|
|
let c = link.quaternion.w;
|
|
if (c > 1)
|
|
c = 1;
|
|
const c2 = math.sqrt(1 - c * c);
|
|
link.quaternion.set(limitation.x * c2, limitation.y * c2, limitation.z * c2, c);
|
|
}
|
|
if (rotationMin !== void 0) {
|
|
link.rotation.setFromVector3(_vector.setFromEuler(link.rotation).max(rotationMin));
|
|
}
|
|
if (rotationMax !== void 0) {
|
|
link.rotation.setFromVector3(_vector.setFromEuler(link.rotation).min(rotationMax));
|
|
}
|
|
link.updateMatrixWorld(true);
|
|
rotated = true;
|
|
}
|
|
if (!rotated)
|
|
break;
|
|
}
|
|
return this;
|
|
}
|
|
/**
|
|
* Creates Helper
|
|
*
|
|
* @return {CCDIKHelper}
|
|
*/
|
|
createHelper() {
|
|
return new CCDIKHelper(this.mesh, this.iks);
|
|
}
|
|
// private methods
|
|
_valid() {
|
|
const iks = this.iks;
|
|
const bones = this.mesh.skeleton.bones;
|
|
for (let i = 0, il = iks.length; i < il; i++) {
|
|
const ik = iks[i];
|
|
const effector = bones[ik.effector];
|
|
const links = ik.links;
|
|
let link0, link1;
|
|
link0 = effector;
|
|
for (let 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function getPosition(bone, matrixWorldInv) {
|
|
return _vector.setFromMatrixPosition(bone.matrixWorld).applyMatrix4(matrixWorldInv);
|
|
}
|
|
function setPositionOfBoneToAttributeArray(array, index, bone, matrixWorldInv) {
|
|
const v = getPosition(bone, matrixWorldInv);
|
|
array[index * 3 + 0] = v.x;
|
|
array[index * 3 + 1] = v.y;
|
|
array[index * 3 + 2] = v.z;
|
|
}
|
|
class CCDIKHelper extends THREE.Object3D {
|
|
constructor(mesh, iks = [], sphereSize = 0.25) {
|
|
super();
|
|
this.root = mesh;
|
|
this.iks = iks;
|
|
this.matrix.copy(mesh.matrixWorld);
|
|
this.matrixAutoUpdate = false;
|
|
this.sphereGeometry = new THREE.SphereGeometry(sphereSize, 16, 8);
|
|
this.targetSphereMaterial = new THREE.MeshBasicMaterial({
|
|
color: new THREE.Color(16746632),
|
|
depthTest: false,
|
|
depthWrite: false,
|
|
transparent: true
|
|
});
|
|
this.effectorSphereMaterial = new THREE.MeshBasicMaterial({
|
|
color: new THREE.Color(8978312),
|
|
depthTest: false,
|
|
depthWrite: false,
|
|
transparent: true
|
|
});
|
|
this.linkSphereMaterial = new THREE.MeshBasicMaterial({
|
|
color: new THREE.Color(8947967),
|
|
depthTest: false,
|
|
depthWrite: false,
|
|
transparent: true
|
|
});
|
|
this.lineMaterial = new THREE.LineBasicMaterial({
|
|
color: new THREE.Color(16711680),
|
|
depthTest: false,
|
|
depthWrite: false,
|
|
transparent: true
|
|
});
|
|
this._init();
|
|
}
|
|
/**
|
|
* Updates IK bones visualization.
|
|
*/
|
|
updateMatrixWorld(force) {
|
|
const mesh = this.root;
|
|
if (this.visible) {
|
|
let offset = 0;
|
|
const iks = this.iks;
|
|
const bones = mesh.skeleton.bones;
|
|
_matrix.copy(mesh.matrixWorld).invert();
|
|
for (let i = 0, il = iks.length; i < il; i++) {
|
|
const ik = iks[i];
|
|
const targetBone = bones[ik.target];
|
|
const effectorBone = bones[ik.effector];
|
|
const targetMesh = this.children[offset++];
|
|
const effectorMesh = this.children[offset++];
|
|
targetMesh.position.copy(getPosition(targetBone, _matrix));
|
|
effectorMesh.position.copy(getPosition(effectorBone, _matrix));
|
|
for (let j = 0, jl = ik.links.length; j < jl; j++) {
|
|
const link = ik.links[j];
|
|
const linkBone = bones[link.index];
|
|
const linkMesh = this.children[offset++];
|
|
linkMesh.position.copy(getPosition(linkBone, _matrix));
|
|
}
|
|
const line = this.children[offset++];
|
|
const array = line.geometry.attributes.position.array;
|
|
setPositionOfBoneToAttributeArray(array, 0, targetBone, _matrix);
|
|
setPositionOfBoneToAttributeArray(array, 1, effectorBone, _matrix);
|
|
for (let j = 0, jl = ik.links.length; j < jl; j++) {
|
|
const link = ik.links[j];
|
|
const linkBone = bones[link.index];
|
|
setPositionOfBoneToAttributeArray(array, j + 2, linkBone, _matrix);
|
|
}
|
|
line.geometry.attributes.position.needsUpdate = true;
|
|
}
|
|
}
|
|
this.matrix.copy(mesh.matrixWorld);
|
|
super.updateMatrixWorld(force);
|
|
}
|
|
/**
|
|
* Frees the GPU-related resources allocated by this instance. Call this method whenever this instance is no longer used in your app.
|
|
*/
|
|
dispose() {
|
|
this.sphereGeometry.dispose();
|
|
this.targetSphereMaterial.dispose();
|
|
this.effectorSphereMaterial.dispose();
|
|
this.linkSphereMaterial.dispose();
|
|
this.lineMaterial.dispose();
|
|
const children = this.children;
|
|
for (let i = 0; i < children.length; i++) {
|
|
const child = children[i];
|
|
if (child.isLine)
|
|
child.geometry.dispose();
|
|
}
|
|
}
|
|
// private method
|
|
_init() {
|
|
const scope = this;
|
|
const iks = this.iks;
|
|
function createLineGeometry(ik) {
|
|
const geometry = new THREE.BufferGeometry();
|
|
const vertices = new Float32Array((2 + ik.links.length) * 3);
|
|
geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3));
|
|
return geometry;
|
|
}
|
|
function createTargetMesh() {
|
|
return new THREE.Mesh(scope.sphereGeometry, scope.targetSphereMaterial);
|
|
}
|
|
function createEffectorMesh() {
|
|
return new THREE.Mesh(scope.sphereGeometry, scope.effectorSphereMaterial);
|
|
}
|
|
function createLinkMesh() {
|
|
return new THREE.Mesh(scope.sphereGeometry, scope.linkSphereMaterial);
|
|
}
|
|
function createLine(ik) {
|
|
return new THREE.Line(createLineGeometry(ik), scope.lineMaterial);
|
|
}
|
|
for (let i = 0, il = iks.length; i < il; i++) {
|
|
const ik = iks[i];
|
|
this.add(createTargetMesh());
|
|
this.add(createEffectorMesh());
|
|
for (let j = 0, jl = ik.links.length; j < jl; j++) {
|
|
this.add(createLinkMesh());
|
|
}
|
|
this.add(createLine(ik));
|
|
}
|
|
}
|
|
}
|
|
exports.CCDIKHelper = CCDIKHelper;
|
|
exports.CCDIKSolver = CCDIKSolver;
|
|
//# sourceMappingURL=CCDIKSolver.cjs.map
|