@osbjs/osbjs
Version:
a minimalist osu! storyboarding framework
305 lines (304 loc) • 11.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Quaternion = void 0;
const _1 = require(".");
class Quaternion {
constructor(x = 0, y = 0, z = 0, w = 1) {
this.x = x;
this.y = y;
this.z = z;
this.w = w;
this.isIdentity = x === 0 && y === 0 && z === 0 && w === 1;
}
/**
* Compares the x, y, z and w properties of q to the equivalent properties of this quaternion to determine if they represent the same rotation.
*/
equals(q) {
return this.x === q.x && this.y === q.y && this.z === q.z && this.w === q.w;
}
/**
* Returns a new Quaternion with identical x, y, z and w properties to this one.
*/
clone() {
return new Quaternion(this.x, this.y, this.z, this.w);
}
/**
* Returns the angle between this quaternion and quaternion q in radians.
*/
angleTo(q) {
return 2 * Math.acos(Math.abs((0, _1.clamp)(Quaternion.dot(this, q), -1, 1)));
}
/**
* Computes the Euclidean length (straight-line length) of this quaternion, considered as a 4 dimensional vector.
*/
length() {
return Math.sqrt(this.lengthSqr());
}
/**
* Computes the squared Euclidean length (straight-line length) of this quaternion, considered as a 4 dimensional vector.
*/
lengthSqr() {
return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;
}
/**
* Returns dot product of 2 quaternions.
*/
static dot(q1, q2) {
return q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w;
}
/**
* Adds each element in one quaternion with its corresponding element in a second quaternion.
*/
static add(q1, q2) {
return new Quaternion(q1.x + q2.x, q1.y + q2.y, q1.z + q2.z, q1.w + q2.w);
}
/**
* Subtracts each element in a second quaternion from its corresponding element in a first quaternion.
*/
static sub(v1, v2) {
return new Quaternion(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z, v1.w - v2.w);
}
/**
* Returns the conjugate of a specified quaternion.
*/
static conjugate(q) {
return new Quaternion(q.x * -1, q.y * -1, q.z * -1, q.w);
}
/**
* Returns the inverse of a quaternion.
*/
static invert(q) {
// quaternion is assumed to have unit length
return Quaternion.conjugate(q);
}
/**
* Divides each component of a specified Quaternion by its length.
*/
static normalize(q) {
let l = q.length();
let result = new Quaternion();
if (l === 0) {
result.x = 0;
result.y = 0;
result.z = 0;
result.w = 1;
}
else {
l = 1 / l;
result.x = q.x * l;
result.y = q.y * l;
result.z = q.z * l;
result.w = q.w * l;
}
return result;
}
/**
* Returns the quaternion that results from multiplying two quaternions together.
*/
static multiply(q1, q2) {
const qax = q1.x, qay = q1.y, qaz = q1.z, qaw = q1.w;
const qbx = q2.x, qby = q2.y, qbz = q2.z, qbw = q2.w;
let result = new Quaternion();
result.x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby;
result.y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz;
result.z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx;
result.w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz;
return result;
}
/**
* Returns the quaternion that results from scaling all the components of a specified quaternion by a scalar factor.
*/
static multiplyScalar(q, s) {
return new Quaternion(q.x * s, q.y * s, q.z * s, q.w * s);
}
/**
* Divides one quaternion by a second quaternion.
*/
static divide(q1, q2) {
let result = new Quaternion();
const q1x = q1.x, q1y = q1.y, q1z = q1.z, q1w = q1.z;
const ls = q2.x * q2.x + q2.y * q2.y + q2.z * q2.z + q2.w * q2.w;
const invNorm = 1.0 / ls;
const q2x = -q2.x * invNorm, q2y = -q2.y * invNorm, q2z = -q2.z * invNorm, q2w = q2.w * invNorm;
result.x = q1x * q2w + q2x * q1w + q1y * q2z - q1z * q2y;
result.y = q1y * q2w + q2y * q1w + q1z * q2x - q1x * q2z;
result.z = q1z * q2w + q2z * q1w + q1x * q2y - q1y * q2x;
result.w = q1w * q2w - q1x * q2x + q1y * q2y + q1z * q2z;
return result;
}
/**
* Interpolates between two quaternions, using spherical linear interpolation.
*/
static slerp(q1, q2, t) {
if (t === 0)
return q1.clone();
if (t === 1)
return q2.clone();
const x = q1.x, y = q1.y, z = q1.z, w = q1.w;
let result = q1.clone();
let cosHalfTheta = w * q2.w + x * q2.x + y * q2.y + z * q2.z;
if (cosHalfTheta < 0) {
result.w = -q2.w;
result.x = -q2.x;
result.y = -q2.y;
result.z = -q2.z;
cosHalfTheta = -cosHalfTheta;
}
else {
result = q2.clone();
}
if (cosHalfTheta >= 1.0) {
result.w = w;
result.x = x;
result.y = y;
result.z = z;
return result;
}
const sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta;
if (sqrSinHalfTheta <= Number.EPSILON) {
const s = 1 - t;
result.w = s * w + t * result.w;
result.x = s * x + t * result.x;
result.y = s * y + t * result.y;
result.z = s * z + t * result.z;
return Quaternion.normalize(result);
}
const sinHalfTheta = Math.sqrt(sqrSinHalfTheta), halfTheta = Math.atan2(sinHalfTheta, cosHalfTheta), ratioA = Math.sin((1 - t) * halfTheta) / sinHalfTheta, ratioB = Math.sin(t * halfTheta) / sinHalfTheta;
result.w = w * ratioA + result.w * ratioB;
result.x = x * ratioA + result.x * ratioB;
result.y = y * ratioA + result.y * ratioB;
result.z = z * ratioA + result.z * ratioB;
return result;
}
/**
* Linearly interpolate between 2 quaternions,
* where alpha is the percent distance along the line - alpha = 0 will be this vector,
* and alpha = 1 will be v.
*/
static lerp(q1, q2, t) {
const t1 = 1 - t;
let q = new Quaternion();
const dot = q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w;
if (dot >= 0) {
q.x = t1 * q1.x + t * q2.x;
q.y = t1 * q1.y + t * q2.y;
q.z = t1 * q1.z + t * q2.z;
q.w = t1 * q1.w + t * q2.w;
}
else {
q.x = t1 * q1.x - t * q2.x;
q.y = t1 * q1.y - t * q2.y;
q.z = t1 * q1.z - t * q2.z;
q.w = t1 * q1.w - t * q2.w;
}
return Quaternion.normalize(q);
}
/**
* Concatenates two quaternions.
*/
static concat(q1, q2) {
let result = new Quaternion();
// Concatenate rotation is actually q2 * q1 instead of q1 * q2.
// So that's why q2 goes q1 and q1 goes q2.
const q1x = q2.x;
const q1y = q2.y;
const q1z = q2.z;
const q1w = q2.w;
const q2x = q1.x;
const q2y = q1.y;
const q2z = q1.z;
const q2w = q1.w;
const dot = q1x * q2x + q1y * q2y + q1z * q2z;
result.x = q1x * q2w + q2x * q1w + q1y * q2z - q1z * q2y;
result.y = q1y * q2w + q2y * q1w + q1z * q2x - q1x * q2z;
result.z = q1z * q2w + q2z * q1w + q1x * q2y - q1y * q2x;
result.w = q1w * q2w - dot;
return result;
}
/**
* Reverses the sign of each component of the quaternion.
*/
static negate(q) {
return new Quaternion(-q.x, -q.y, -q.z, -q.w);
}
/**
* Creates a quaternion from a unit vector and an angle to rotate around the vector.
*/
static createFromAxisAngle(axis, angle) {
const halfAngle = angle / 2, s = Math.sin(halfAngle), c = Math.cos(halfAngle);
let result = new Quaternion();
result.x = axis.x * s;
result.y = axis.y * s;
result.z = axis.z * s;
result.w = c;
return result;
}
/**
* Creates a quaternion from the specified rotation matrix.
*/
static createFromRotationMatrix(m) {
const result = new Quaternion();
const trace = m.m11 + m.m22 + m.m33;
if (trace > 0) {
let s = Math.sqrt(trace + 1);
result.w = s * 0.5;
s = 0.5 / s;
result.x = (m.m23 - m.m32) * s;
result.y = (m.m31 - m.m13) * s;
result.z = (m.m12 - m.m21) * s;
}
else {
if (m.m11 >= m.m22 && m.m11 >= m.m33) {
let s = Math.sqrt(1.0 + m.m11 - m.m22 - m.m33);
let invS = 0.5 / s;
result.x = 0.5 * s;
result.y = (m.m12 + m.m21) * invS;
result.z = (m.m13 + m.m31) * invS;
result.w = (m.m23 - m.m32) * invS;
}
else if (m.m22 > m.m33) {
let s = Math.sqrt(1.0 + m.m22 - m.m11 - m.m33);
let invS = 0.5 / s;
result.x = (m.m21 + m.m12) * invS;
result.y = 0.5 * s;
result.z = (m.m32 + m.m23) * invS;
result.w = (m.m31 - m.m13) * invS;
}
else {
let s = Math.sqrt(1.0 + m.m33 - m.m11 - m.m22);
let invS = 0.5 / s;
result.x = (m.m31 + m.m13) * invS;
result.y = (m.m32 + m.m23) * invS;
result.z = 0.5 * s;
result.w = (m.m12 - m.m21) * invS;
}
}
return result;
}
/**
* Creates a new quaternion from the given yaw, pitch, and roll.
* @param yaw Angle of rotation, in radians, around the Y-axis.
* @param pitch Angle of rotation, in radians, around the X-axis.
* @param roll Angle of rotation, in radians, around the Z-axis.
*/
static createFromYawPitchRoll(yaw, pitch, roll) {
let sr, cr, sp, cp, sy, cy;
const halfRoll = roll * 0.5;
sr = Math.sin(halfRoll);
cr = Math.cos(halfRoll);
const halfPitch = pitch * 0.5;
sp = Math.sin(halfPitch);
cp = Math.cos(halfPitch);
const halfyaw = yaw * 0.5;
sy = Math.sin(halfyaw);
cy = Math.cos(halfyaw);
const result = new Quaternion();
result.x = cy * sp * cr + sy * cp * sr;
result.y = sy * cp * cr - cy * sp * sr;
result.z = cy * cp * sr - sy * sp * cr;
result.w = cy * cp * cr + sy * sp * sr;
return result;
}
}
exports.Quaternion = Quaternion;
Quaternion.Identity = new Quaternion();