UNPKG

@osbjs/osbjs

Version:

a minimalist osu! storyboarding framework

305 lines (304 loc) 11.2 kB
"use strict"; 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();