Spaces:
Running
Running
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 }; | |