import { Matrix3 } from "./Matrix3"; import { Vector3 } from "./Vector3"; class Quaternion { public readonly x: number; public readonly y: number; public readonly z: number; public readonly w: number; constructor(x: number = 0, y: number = 0, z: number = 0, w: number = 1) { this.x = x; this.y = y; this.z = z; this.w = w; } equals(q: Quaternion): boolean { if (this.x !== q.x) { return false; } if (this.y !== q.y) { return false; } if (this.z !== q.z) { return false; } if (this.w !== q.w) { return false; } return true; } normalize(): Quaternion { const l = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w); return new Quaternion(this.x / l, this.y / l, this.z / l, this.w / l); } multiply(q: Quaternion): Quaternion { const w1 = this.w, x1 = this.x, y1 = this.y, z1 = this.z; const w2 = q.w, x2 = q.x, y2 = q.y, z2 = q.z; return new Quaternion( w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2, w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2, w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2, w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2, ); } inverse(): Quaternion { const l = this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; return new Quaternion(-this.x / l, -this.y / l, -this.z / l, this.w / l); } apply(v: Vector3): Vector3 { const vecQuat = new Quaternion(v.x, v.y, v.z, 0); const conjugate = new Quaternion(-this.x, -this.y, -this.z, this.w); const rotatedQuat = this.multiply(vecQuat).multiply(conjugate); return new Vector3(rotatedQuat.x, rotatedQuat.y, rotatedQuat.z); } flat(): number[] { return [this.x, this.y, this.z, this.w]; } clone(): Quaternion { return new Quaternion(this.x, this.y, this.z, this.w); } static FromEuler(e: Vector3): Quaternion { const halfX = e.x / 2; const halfY = e.y / 2; const halfZ = e.z / 2; const cy = Math.cos(halfY); const sy = Math.sin(halfY); const cp = Math.cos(halfX); const sp = Math.sin(halfX); const cz = Math.cos(halfZ); const sz = Math.sin(halfZ); const q = new Quaternion( cy * sp * cz + sy * cp * sz, sy * cp * cz - cy * sp * sz, cy * cp * sz - sy * sp * cz, cy * cp * cz + sy * sp * sz, ); return q; } toEuler(): Vector3 { const sinr_cosp = 2 * (this.w * this.x + this.y * this.z); const cosr_cosp = 1 - 2 * (this.x * this.x + this.y * this.y); const x = Math.atan2(sinr_cosp, cosr_cosp); let y; const sinp = 2 * (this.w * this.y - this.z * this.x); if (Math.abs(sinp) >= 1) { y = (Math.sign(sinp) * Math.PI) / 2; } else { y = Math.asin(sinp); } const siny_cosp = 2 * (this.w * this.z + this.x * this.y); const cosy_cosp = 1 - 2 * (this.y * this.y + this.z * this.z); const z = Math.atan2(siny_cosp, cosy_cosp); return new Vector3(x, y, z); } static FromMatrix3(matrix: Matrix3): Quaternion { const m = matrix.buffer; const trace = m[0] + m[4] + m[8]; let x, y, z, w; if (trace > 0) { const s = 0.5 / Math.sqrt(trace + 1.0); w = 0.25 / s; x = (m[7] - m[5]) * s; y = (m[2] - m[6]) * s; z = (m[3] - m[1]) * s; } else if (m[0] > m[4] && m[0] > m[8]) { const s = 2.0 * Math.sqrt(1.0 + m[0] - m[4] - m[8]); w = (m[7] - m[5]) / s; x = 0.25 * s; y = (m[1] + m[3]) / s; z = (m[2] + m[6]) / s; } else if (m[4] > m[8]) { const s = 2.0 * Math.sqrt(1.0 + m[4] - m[0] - m[8]); w = (m[2] - m[6]) / s; x = (m[1] + m[3]) / s; y = 0.25 * s; z = (m[5] + m[7]) / s; } else { const s = 2.0 * Math.sqrt(1.0 + m[8] - m[0] - m[4]); w = (m[3] - m[1]) / s; x = (m[2] + m[6]) / s; y = (m[5] + m[7]) / s; z = 0.25 * s; } return new Quaternion(x, y, z, w); } static FromAxisAngle(axis: Vector3, angle: number): Quaternion { const halfAngle = angle / 2; const sin = Math.sin(halfAngle); const cos = Math.cos(halfAngle); return new Quaternion(axis.x * sin, axis.y * sin, axis.z * sin, cos); } static LookRotation(direction: Vector3): Quaternion { const forward = new Vector3(0, 0, 1); const dot = forward.dot(direction); if (Math.abs(dot - -1.0) < 0.000001) { return new Quaternion(0, 1, 0, Math.PI); } if (Math.abs(dot - 1.0) < 0.000001) { return new Quaternion(); } const rotAngle = Math.acos(dot); const rotAxis = forward.cross(direction).normalize(); return Quaternion.FromAxisAngle(rotAxis, rotAngle); } toString(): string { return `[${this.flat().join(", ")}]`; } } export { Quaternion };