@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.
472 lines • 20.1 kB
JavaScript
import { BuildArray } from "../Misc/arrayTools.js";
import { Vector3, Quaternion, Matrix } from "../Maths/math.vector.js";
import { Axis } from "../Maths/math.axis.js";
/**
* Class used to make a bone look toward a point in space
* @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/bonesSkeletons#bonelookcontroller
*/
export class BoneLookController {
/**
* Gets or sets the minimum yaw angle that the bone can look to
*/
get minYaw() {
return this._minYaw;
}
set minYaw(value) {
this._minYaw = value;
this._minYawSin = Math.sin(value);
this._minYawCos = Math.cos(value);
if (this._maxYaw != null) {
this._midYawConstraint = this._getAngleDiff(this._minYaw, this._maxYaw) * 0.5 + this._minYaw;
this._yawRange = this._maxYaw - this._minYaw;
}
}
/**
* Gets or sets the maximum yaw angle that the bone can look to
*/
get maxYaw() {
return this._maxYaw;
}
set maxYaw(value) {
this._maxYaw = value;
this._maxYawSin = Math.sin(value);
this._maxYawCos = Math.cos(value);
if (this._minYaw != null) {
this._midYawConstraint = this._getAngleDiff(this._minYaw, this._maxYaw) * 0.5 + this._minYaw;
this._yawRange = this._maxYaw - this._minYaw;
}
}
/**
* Gets or sets the minimum pitch angle that the bone can look to
*/
get minPitch() {
return this._minPitch;
}
set minPitch(value) {
this._minPitch = value;
this._minPitchTan = Math.tan(value);
}
/**
* Gets or sets the maximum pitch angle that the bone can look to
*/
get maxPitch() {
return this._maxPitch;
}
set maxPitch(value) {
this._maxPitch = value;
this._maxPitchTan = Math.tan(value);
}
/**
* Create a BoneLookController
* @param mesh the TransformNode that the bone belongs to
* @param bone the bone that will be looking to the target
* @param target the target Vector3 to look at
* @param options optional settings:
* * maxYaw: the maximum angle the bone will yaw to
* * minYaw: the minimum angle the bone will yaw to
* * maxPitch: the maximum angle the bone will pitch to
* * minPitch: the minimum angle the bone will yaw to
* * slerpAmount: set the between 0 and 1 to make the bone slerp to the target.
* * upAxis: the up axis of the coordinate system
* * upAxisSpace: the space that the up axis is in - Space.BONE, Space.LOCAL (default), or Space.WORLD.
* * yawAxis: set yawAxis if the bone does not yaw on the y axis
* * pitchAxis: set pitchAxis if the bone does not pitch on the x axis
* * adjustYaw: used to make an adjustment to the yaw of the bone
* * adjustPitch: used to make an adjustment to the pitch of the bone
* * adjustRoll: used to make an adjustment to the roll of the bone
* @param options.maxYaw
* @param options.minYaw
* @param options.maxPitch
* @param options.minPitch
* @param options.slerpAmount
* @param options.upAxis
* @param options.upAxisSpace
* @param options.yawAxis
* @param options.pitchAxis
* @param options.adjustYaw
* @param options.adjustPitch
* @param options.adjustRoll
**/
constructor(mesh, bone, target, options) {
/**
* The up axis of the coordinate system that is used when the bone is rotated
*/
this.upAxis = Vector3.Up();
/**
* The space that the up axis is in - Space.BONE, Space.LOCAL (default), or Space.WORLD
*/
this.upAxisSpace = 0 /* Space.LOCAL */;
/**
* Used to make an adjustment to the yaw of the bone
*/
this.adjustYaw = 0;
/**
* Used to make an adjustment to the pitch of the bone
*/
this.adjustPitch = 0;
/**
* Used to make an adjustment to the roll of the bone
*/
this.adjustRoll = 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._boneQuat = Quaternion.Identity();
this._slerping = false;
this._firstFrameSkipped = false;
this._fowardAxis = Vector3.Forward();
/**
* Use the absolute value for yaw when checking the min/max constraints
*/
this.useAbsoluteValueForYaw = false;
this.mesh = mesh;
this.bone = bone;
this.target = target;
if (options) {
if (options.adjustYaw) {
this.adjustYaw = options.adjustYaw;
}
if (options.adjustPitch) {
this.adjustPitch = options.adjustPitch;
}
if (options.adjustRoll) {
this.adjustRoll = options.adjustRoll;
}
if (options.maxYaw != null) {
this.maxYaw = options.maxYaw;
}
else {
this.maxYaw = Math.PI;
}
if (options.minYaw != null) {
this.minYaw = options.minYaw;
}
else {
this.minYaw = -Math.PI;
}
if (options.maxPitch != null) {
this.maxPitch = options.maxPitch;
}
else {
this.maxPitch = Math.PI;
}
if (options.minPitch != null) {
this.minPitch = options.minPitch;
}
else {
this.minPitch = -Math.PI;
}
if (options.slerpAmount != null) {
this.slerpAmount = options.slerpAmount;
}
if (options.upAxis != null) {
this.upAxis = options.upAxis;
}
if (options.upAxisSpace != null) {
this.upAxisSpace = options.upAxisSpace;
}
if (options.yawAxis != null || options.pitchAxis != null) {
let newYawAxis = Axis.Y;
let newPitchAxis = Axis.X;
if (options.yawAxis != null) {
newYawAxis = options.yawAxis.clone();
newYawAxis.normalize();
}
if (options.pitchAxis != null) {
newPitchAxis = options.pitchAxis.clone();
newPitchAxis.normalize();
}
const newRollAxis = Vector3.Cross(newPitchAxis, newYawAxis);
this._transformYawPitch = Matrix.Identity();
Matrix.FromXYZAxesToRef(newPitchAxis, newYawAxis, newRollAxis, this._transformYawPitch);
this._transformYawPitchInv = this._transformYawPitch.clone();
this._transformYawPitch.invert();
}
if (options.useAbsoluteValueForYaw !== undefined) {
this.useAbsoluteValueForYaw = options.useAbsoluteValueForYaw;
}
}
if (!bone.getParent() && this.upAxisSpace == 2 /* Space.BONE */) {
this.upAxisSpace = 0 /* Space.LOCAL */;
}
}
/**
* Update the bone to look at the target. This should be called before the scene is rendered (use scene.registerBeforeRender())
*/
update() {
//skip the first frame when slerping so that the TransformNode rotation is correct
if (this.slerpAmount < 1 && !this._firstFrameSkipped) {
this._firstFrameSkipped = true;
return;
}
const bone = this.bone;
const bonePos = BoneLookController._TmpVecs[0];
bone.getAbsolutePositionToRef(this.mesh, bonePos);
let target = this.target;
const _tmpMat1 = BoneLookController._TmpMats[0];
const _tmpMat2 = BoneLookController._TmpMats[1];
const mesh = this.mesh;
const parentBone = bone.getParent();
const upAxis = BoneLookController._TmpVecs[1];
upAxis.copyFrom(this.upAxis);
if (this.upAxisSpace == 2 /* Space.BONE */ && parentBone) {
if (this._transformYawPitch) {
Vector3.TransformCoordinatesToRef(upAxis, this._transformYawPitchInv, upAxis);
}
parentBone.getDirectionToRef(upAxis, this.mesh, upAxis);
}
else if (this.upAxisSpace == 0 /* Space.LOCAL */) {
mesh.getDirectionToRef(upAxis, upAxis);
if (mesh.scaling.x != 1 || mesh.scaling.y != 1 || mesh.scaling.z != 1) {
upAxis.normalize();
}
}
let checkYaw = false;
let checkPitch = false;
if (this._maxYaw != Math.PI || this._minYaw != -Math.PI) {
checkYaw = true;
}
if (this._maxPitch != Math.PI || this._minPitch != -Math.PI) {
checkPitch = true;
}
if (checkYaw || checkPitch) {
const spaceMat = BoneLookController._TmpMats[2];
const spaceMatInv = BoneLookController._TmpMats[3];
if (this.upAxisSpace == 2 /* Space.BONE */ && upAxis.y == 1 && parentBone) {
parentBone.getRotationMatrixToRef(1 /* Space.WORLD */, this.mesh, spaceMat);
}
else if (this.upAxisSpace == 0 /* Space.LOCAL */ && upAxis.y == 1 && !parentBone) {
spaceMat.copyFrom(mesh.getWorldMatrix());
}
else {
let forwardAxis = BoneLookController._TmpVecs[2];
forwardAxis.copyFrom(this._fowardAxis);
if (this._transformYawPitch) {
Vector3.TransformCoordinatesToRef(forwardAxis, this._transformYawPitchInv, forwardAxis);
}
if (parentBone) {
parentBone.getDirectionToRef(forwardAxis, this.mesh, forwardAxis);
}
else {
mesh.getDirectionToRef(forwardAxis, forwardAxis);
}
const rightAxis = Vector3.Cross(upAxis, forwardAxis);
rightAxis.normalize();
forwardAxis = Vector3.Cross(rightAxis, upAxis);
Matrix.FromXYZAxesToRef(rightAxis, upAxis, forwardAxis, spaceMat);
}
spaceMat.invertToRef(spaceMatInv);
let xzlen = null;
if (checkPitch) {
const localTarget = BoneLookController._TmpVecs[3];
target.subtractToRef(bonePos, localTarget);
Vector3.TransformCoordinatesToRef(localTarget, spaceMatInv, localTarget);
xzlen = Math.sqrt(localTarget.x * localTarget.x + localTarget.z * localTarget.z);
const pitch = Math.atan2(localTarget.y, xzlen);
let newPitch = pitch;
if (pitch > this._maxPitch) {
localTarget.y = this._maxPitchTan * xzlen;
newPitch = this._maxPitch;
}
else if (pitch < this._minPitch) {
localTarget.y = this._minPitchTan * xzlen;
newPitch = this._minPitch;
}
if (pitch != newPitch) {
Vector3.TransformCoordinatesToRef(localTarget, spaceMat, localTarget);
localTarget.addInPlace(bonePos);
target = localTarget;
}
}
if (checkYaw) {
const localTarget = BoneLookController._TmpVecs[4];
target.subtractToRef(bonePos, localTarget);
Vector3.TransformCoordinatesToRef(localTarget, spaceMatInv, localTarget);
const yaw = Math.atan2(localTarget.x, localTarget.z);
const yawCheck = this.useAbsoluteValueForYaw ? Math.abs(yaw) : yaw;
let newYaw = yaw;
if (yawCheck > this._maxYaw || yawCheck < this._minYaw) {
if (xzlen == null) {
xzlen = Math.sqrt(localTarget.x * localTarget.x + localTarget.z * localTarget.z);
}
if (this._yawRange > Math.PI) {
if (this._isAngleBetween(yaw, this._maxYaw, this._midYawConstraint)) {
localTarget.z = this._maxYawCos * xzlen;
localTarget.x = this._maxYawSin * xzlen;
newYaw = this._maxYaw;
}
else if (this._isAngleBetween(yaw, this._midYawConstraint, this._minYaw)) {
localTarget.z = this._minYawCos * xzlen;
localTarget.x = this._minYawSin * xzlen;
newYaw = this._minYaw;
}
}
else {
if (yawCheck > this._maxYaw) {
localTarget.z = this._maxYawCos * xzlen;
localTarget.x = this._maxYawSin * xzlen;
if (yaw < 0 && this.useAbsoluteValueForYaw) {
localTarget.x *= -1;
}
newYaw = this._maxYaw;
}
else if (yawCheck < this._minYaw) {
localTarget.z = this._minYawCos * xzlen;
localTarget.x = this._minYawSin * xzlen;
if (yaw < 0 && this.useAbsoluteValueForYaw) {
localTarget.x *= -1;
}
newYaw = this._minYaw;
}
}
}
if (this._slerping && this._yawRange > Math.PI) {
//are we going to be crossing into the min/max region?
const boneFwd = BoneLookController._TmpVecs[8];
boneFwd.copyFrom(Axis.Z);
if (this._transformYawPitch) {
Vector3.TransformCoordinatesToRef(boneFwd, this._transformYawPitchInv, boneFwd);
}
const boneRotMat = BoneLookController._TmpMats[4];
this._boneQuat.toRotationMatrix(boneRotMat);
this.mesh.getWorldMatrix().multiplyToRef(boneRotMat, boneRotMat);
Vector3.TransformCoordinatesToRef(boneFwd, boneRotMat, boneFwd);
Vector3.TransformCoordinatesToRef(boneFwd, spaceMatInv, boneFwd);
const boneYaw = Math.atan2(boneFwd.x, boneFwd.z);
const angBtwTar = this._getAngleBetween(boneYaw, yaw);
const angBtwMidYaw = this._getAngleBetween(boneYaw, this._midYawConstraint);
if (angBtwTar > angBtwMidYaw) {
if (xzlen == null) {
xzlen = Math.sqrt(localTarget.x * localTarget.x + localTarget.z * localTarget.z);
}
const angBtwMax = this._getAngleBetween(boneYaw, this._maxYaw);
const angBtwMin = this._getAngleBetween(boneYaw, this._minYaw);
if (angBtwMin < angBtwMax) {
newYaw = boneYaw + Math.PI * 0.75;
localTarget.z = Math.cos(newYaw) * xzlen;
localTarget.x = Math.sin(newYaw) * xzlen;
}
else {
newYaw = boneYaw - Math.PI * 0.75;
localTarget.z = Math.cos(newYaw) * xzlen;
localTarget.x = Math.sin(newYaw) * xzlen;
}
}
}
if (yaw != newYaw) {
Vector3.TransformCoordinatesToRef(localTarget, spaceMat, localTarget);
localTarget.addInPlace(bonePos);
target = localTarget;
}
}
}
const zaxis = BoneLookController._TmpVecs[5];
const xaxis = BoneLookController._TmpVecs[6];
const yaxis = BoneLookController._TmpVecs[7];
const tmpQuat = BoneLookController._TmpQuat;
const boneScaling = BoneLookController._TmpVecs[9];
target.subtractToRef(bonePos, zaxis);
zaxis.normalize();
Vector3.CrossToRef(upAxis, zaxis, xaxis);
xaxis.normalize();
Vector3.CrossToRef(zaxis, xaxis, yaxis);
yaxis.normalize();
Matrix.FromXYZAxesToRef(xaxis, yaxis, zaxis, _tmpMat1);
if (xaxis.x === 0 && xaxis.y === 0 && xaxis.z === 0) {
return;
}
if (yaxis.x === 0 && yaxis.y === 0 && yaxis.z === 0) {
return;
}
if (zaxis.x === 0 && zaxis.y === 0 && zaxis.z === 0) {
return;
}
if (this.adjustYaw || this.adjustPitch || this.adjustRoll) {
Matrix.RotationYawPitchRollToRef(this.adjustYaw, this.adjustPitch, this.adjustRoll, _tmpMat2);
_tmpMat2.multiplyToRef(_tmpMat1, _tmpMat1);
}
boneScaling.copyFrom(this.bone.getScale());
if (this.slerpAmount < 1) {
if (!this._slerping) {
this.bone.getRotationQuaternionToRef(1 /* Space.WORLD */, this.mesh, this._boneQuat);
}
if (this._transformYawPitch) {
this._transformYawPitch.multiplyToRef(_tmpMat1, _tmpMat1);
}
Quaternion.FromRotationMatrixToRef(_tmpMat1, tmpQuat);
Quaternion.SlerpToRef(this._boneQuat, tmpQuat, this.slerpAmount, this._boneQuat);
this.bone.setRotationQuaternion(this._boneQuat, 1 /* Space.WORLD */, this.mesh);
this._slerping = true;
}
else {
if (this._transformYawPitch) {
this._transformYawPitch.multiplyToRef(_tmpMat1, _tmpMat1);
}
this.bone.setRotationMatrix(_tmpMat1, 1 /* Space.WORLD */, this.mesh);
this._slerping = false;
}
this.bone.setScale(boneScaling);
this._updateLinkedTransformRotation();
}
_getAngleDiff(ang1, ang2) {
let angDiff = ang2 - ang1;
angDiff %= Math.PI * 2;
if (angDiff > Math.PI) {
angDiff -= Math.PI * 2;
}
else if (angDiff < -Math.PI) {
angDiff += Math.PI * 2;
}
return angDiff;
}
_getAngleBetween(ang1, ang2) {
ang1 %= 2 * Math.PI;
ang1 = ang1 < 0 ? ang1 + 2 * Math.PI : ang1;
ang2 %= 2 * Math.PI;
ang2 = ang2 < 0 ? ang2 + 2 * Math.PI : ang2;
let ab = 0;
if (ang1 < ang2) {
ab = ang2 - ang1;
}
else {
ab = ang1 - ang2;
}
if (ab > Math.PI) {
ab = Math.PI * 2 - ab;
}
return ab;
}
_isAngleBetween(ang, ang1, ang2) {
ang %= 2 * Math.PI;
ang = ang < 0 ? ang + 2 * Math.PI : ang;
ang1 %= 2 * Math.PI;
ang1 = ang1 < 0 ? ang1 + 2 * Math.PI : ang1;
ang2 %= 2 * Math.PI;
ang2 = ang2 < 0 ? ang2 + 2 * Math.PI : ang2;
if (ang1 < ang2) {
if (ang > ang1 && ang < ang2) {
return true;
}
}
else {
if (ang > ang2 && ang < ang1) {
return true;
}
}
return false;
}
_updateLinkedTransformRotation() {
const bone = this.bone;
if (bone._linkedTransformNode) {
if (!bone._linkedTransformNode.rotationQuaternion) {
bone._linkedTransformNode.rotationQuaternion = new Quaternion();
}
bone.getRotationQuaternionToRef(0 /* Space.LOCAL */, null, bone._linkedTransformNode.rotationQuaternion);
}
}
}
BoneLookController._TmpVecs = BuildArray(10, Vector3.Zero);
BoneLookController._TmpQuat = Quaternion.Identity();
BoneLookController._TmpMats = BuildArray(5, Matrix.Identity);
//# sourceMappingURL=boneLookController.js.map