@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
270 lines • 10.9 kB
JavaScript
import { Vector3, Quaternion, Matrix } from "../Maths/math.vector.js";
import { Logger } from "../Misc/logger.js";
/**
* Class used to apply inverse kinematics to bones
* @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/bonesSkeletons#boneikcontroller
*/
export class BoneIKController {
/**
* Gets or sets maximum allowed angle
*/
get maxAngle() {
return this._maxAngle;
}
set maxAngle(value) {
this._setMaxAngle(value);
}
/**
* Creates a new BoneIKController
* @param mesh defines the TransformNode to control
* @param bone defines the bone to control. The bone needs to have a parent bone. It also needs to have a length greater than 0 or a children we can use to infer its length.
* @param options defines options to set up the controller
* @param options.targetMesh
* @param options.poleTargetMesh
* @param options.poleTargetBone
* @param options.poleTargetLocalOffset
* @param options.poleAngle
* @param options.bendAxis
* @param options.maxAngle
* @param options.slerpAmount
*/
constructor(mesh, bone, options) {
/**
* Gets or sets the target position
*/
this.targetPosition = Vector3.Zero();
/**
* Gets or sets the pole target position
*/
this.poleTargetPosition = Vector3.Zero();
/**
* Gets or sets the pole target local offset
*/
this.poleTargetLocalOffset = Vector3.Zero();
/**
* Gets or sets the pole angle
*/
this.poleAngle = 0;
/**
* The amount to slerp (spherical linear interpolation) to the target. Set this to a value between 0 and 1 (a value of 1 disables slerp)
*/
this.slerpAmount = 1;
this._bone1Quat = Quaternion.Identity();
this._bone1Mat = Matrix.Identity();
this._bone2Ang = Math.PI;
this._maxAngle = Math.PI;
this._rightHandedSystem = false;
this._bendAxis = Vector3.Right();
this._slerping = false;
this._adjustRoll = 0;
this._notEnoughInformation = false;
this._bone2 = bone;
const bone1 = bone.getParent();
if (!bone1) {
this._notEnoughInformation = true;
Logger.Error("BoneIKController: bone must have a parent for IK to work.");
return;
}
this._bone1 = bone1;
if (this._bone2.children.length === 0 && !this._bone2.length) {
this._notEnoughInformation = true;
Logger.Error("BoneIKController: bone must not be a leaf or it should have a length for IK to work.");
return;
}
this.mesh = mesh;
bone.getSkeleton().computeAbsoluteMatrices();
const bonePos = bone.getPosition();
if (bone.getAbsoluteMatrix().determinant() > 0) {
this._rightHandedSystem = true;
this._bendAxis.x = 0;
this._bendAxis.y = 0;
this._bendAxis.z = -1;
if (bonePos.x > bonePos.y && bonePos.x > bonePos.z) {
this._adjustRoll = Math.PI * 0.5;
this._bendAxis.z = 1;
}
}
if (this._bone1.length && this._bone2.length) {
const boneScale1 = this._bone1.getScale();
const boneScale2 = this._bone2.getScale();
this._bone1Length = this._bone1.length * boneScale1.y * this.mesh.scaling.y;
this._bone2Length = this._bone2.length * boneScale2.y * this.mesh.scaling.y;
}
else if (this._bone2.children[0]) {
mesh.computeWorldMatrix(true);
const pos1 = this._bone2.children[0].getAbsolutePosition(mesh);
const pos2 = this._bone2.getAbsolutePosition(mesh);
const pos3 = this._bone1.getAbsolutePosition(mesh);
this._bone2Length = Vector3.Distance(pos1, pos2);
this._bone1Length = Vector3.Distance(pos2, pos3);
}
else {
mesh.computeWorldMatrix(true);
const boneScale2 = this._bone2.getScale();
this._bone2Length = this._bone2.length * boneScale2.y * this.mesh.scaling.y;
const pos2 = this._bone2.getAbsolutePosition(mesh);
const pos3 = this._bone1.getAbsolutePosition(mesh);
this._bone1Length = Vector3.Distance(pos2, pos3);
}
this._bone1.getRotationMatrixToRef(1 /* Space.WORLD */, mesh, this._bone1Mat);
this.maxAngle = Math.PI;
if (options) {
if (options.targetMesh) {
this.targetMesh = options.targetMesh;
this.targetMesh.computeWorldMatrix(true);
}
if (options.poleTargetMesh) {
this.poleTargetMesh = options.poleTargetMesh;
this.poleTargetMesh.computeWorldMatrix(true);
}
else if (options.poleTargetBone) {
this.poleTargetBone = options.poleTargetBone;
}
else if (this._bone1.getParent()) {
this.poleTargetBone = this._bone1.getParent();
}
if (options.poleTargetLocalOffset) {
this.poleTargetLocalOffset.copyFrom(options.poleTargetLocalOffset);
}
if (options.poleAngle) {
this.poleAngle = options.poleAngle;
}
if (options.bendAxis) {
this._bendAxis.copyFrom(options.bendAxis);
}
if (options.maxAngle) {
this.maxAngle = options.maxAngle;
}
if (options.slerpAmount) {
this.slerpAmount = options.slerpAmount;
}
}
}
_setMaxAngle(ang) {
if (ang < 0) {
ang = 0;
}
if (ang > Math.PI || ang == undefined) {
ang = Math.PI;
}
this._maxAngle = ang;
const a = this._bone1Length;
const b = this._bone2Length;
this._maxReach = Math.sqrt(a * a + b * b - 2 * a * b * Math.cos(ang));
}
/**
* Force the controller to update the bones
*/
update() {
if (this._notEnoughInformation) {
return;
}
const target = this.targetPosition;
const poleTarget = this.poleTargetPosition;
const mat1 = BoneIKController._TmpMats[0];
const mat2 = BoneIKController._TmpMats[1];
if (this.targetMesh) {
target.copyFrom(this.targetMesh.getAbsolutePosition());
}
if (this.poleTargetBone) {
this.poleTargetBone.getAbsolutePositionFromLocalToRef(this.poleTargetLocalOffset, this.mesh, poleTarget);
}
else if (this.poleTargetMesh) {
Vector3.TransformCoordinatesToRef(this.poleTargetLocalOffset, this.poleTargetMesh.getWorldMatrix(), poleTarget);
}
const bonePos = BoneIKController._TmpVecs[0];
const zaxis = BoneIKController._TmpVecs[1];
const xaxis = BoneIKController._TmpVecs[2];
const yaxis = BoneIKController._TmpVecs[3];
const upAxis = BoneIKController._TmpVecs[4];
const tmpQuat = BoneIKController._TmpQuat;
this._bone1.getAbsolutePositionToRef(this.mesh, bonePos);
poleTarget.subtractToRef(bonePos, upAxis);
if (upAxis.x == 0 && upAxis.y == 0 && upAxis.z == 0) {
upAxis.y = 1;
}
else {
upAxis.normalize();
}
target.subtractToRef(bonePos, yaxis);
yaxis.normalize();
Vector3.CrossToRef(yaxis, upAxis, zaxis);
zaxis.normalize();
Vector3.CrossToRef(yaxis, zaxis, xaxis);
xaxis.normalize();
Matrix.FromXYZAxesToRef(xaxis, yaxis, zaxis, mat1);
const a = this._bone1Length;
const b = this._bone2Length;
let c = Vector3.Distance(bonePos, target);
if (this._maxReach > 0) {
c = Math.min(this._maxReach, c);
}
let acosa = (b * b + c * c - a * a) / (2 * b * c);
let acosb = (c * c + a * a - b * b) / (2 * c * a);
if (acosa > 1) {
acosa = 1;
}
if (acosb > 1) {
acosb = 1;
}
if (acosa < -1) {
acosa = -1;
}
if (acosb < -1) {
acosb = -1;
}
const angA = Math.acos(acosa);
const angB = Math.acos(acosb);
let angC = -angA - angB;
if (this._rightHandedSystem) {
Matrix.RotationYawPitchRollToRef(0, 0, this._adjustRoll, mat2);
mat2.multiplyToRef(mat1, mat1);
Matrix.RotationAxisToRef(this._bendAxis, angB, mat2);
mat2.multiplyToRef(mat1, mat1);
}
else {
const _tmpVec = BoneIKController._TmpVecs[5];
_tmpVec.copyFrom(this._bendAxis);
_tmpVec.x *= -1;
Matrix.RotationAxisToRef(_tmpVec, -angB, mat2);
mat2.multiplyToRef(mat1, mat1);
}
if (this.poleAngle) {
Matrix.RotationAxisToRef(yaxis, this.poleAngle, mat2);
mat1.multiplyToRef(mat2, mat1);
}
if (this._bone1) {
if (this.slerpAmount < 1) {
if (!this._slerping) {
Quaternion.FromRotationMatrixToRef(this._bone1Mat, this._bone1Quat);
}
Quaternion.FromRotationMatrixToRef(mat1, tmpQuat);
Quaternion.SlerpToRef(this._bone1Quat, tmpQuat, this.slerpAmount, this._bone1Quat);
angC = this._bone2Ang * (1.0 - this.slerpAmount) + angC * this.slerpAmount;
this._bone1.setRotationQuaternion(this._bone1Quat, 1 /* Space.WORLD */, this.mesh);
this._slerping = true;
}
else {
this._bone1.setRotationMatrix(mat1, 1 /* Space.WORLD */, this.mesh);
this._bone1Mat.copyFrom(mat1);
this._slerping = false;
}
this._updateLinkedTransformRotation(this._bone1);
}
this._bone2.setAxisAngle(this._bendAxis, angC, 0 /* Space.LOCAL */);
this._updateLinkedTransformRotation(this._bone2);
this._bone2Ang = angC;
}
_updateLinkedTransformRotation(bone) {
if (bone._linkedTransformNode) {
if (!bone._linkedTransformNode.rotationQuaternion) {
bone._linkedTransformNode.rotationQuaternion = new Quaternion();
}
bone.getRotationQuaternionToRef(0 /* Space.LOCAL */, null, bone._linkedTransformNode.rotationQuaternion);
}
}
}
BoneIKController._TmpVecs = [Vector3.Zero(), Vector3.Zero(), Vector3.Zero(), Vector3.Zero(), Vector3.Zero(), Vector3.Zero()];
BoneIKController._TmpQuat = Quaternion.Identity();
BoneIKController._TmpMats = [Matrix.Identity(), Matrix.Identity()];
//# sourceMappingURL=boneIKController.js.map