Quat
Quaternion (x, y, z, w) — the rotation type behind the 3D node API (node.quaternion). Same
contract as Vec3: mutable fields, pure methods (each returns a new Quat;
only set / copy mutate), and a raw [x, y, z, w] array works anywhere a Quat is accepted
(QuatLike). Right-handed, matching gl-matrix / three.js conventions.
For most day-to-day rotation you set node.eulerAngles (degrees) and never touch Quat; reach
for it when composing rotations, slerping, or aiming (lookRotation, fromTo).
At a glance
const target = Quat.lookRotation(enemy.position.sub(turret.position))
setLoop(dt => {
turret.quaternion = new Quat(turret.quaternion).slerp(target, 1 - Math.exp(-8 * dt))
})
const tilt = Quat.fromEuler(0, 0, 15) // DEGREES
const spin = Quat.fromAxisAngle([0, 1, 0], Math.PI / 2) // RADIANS
node.quaternion = spin.mul(tilt) // tilt first, then spinCreating
new Quat() // identity (0, 0, 0, 1)
new Quat(x, y, z, w)
new Quat([x, y, z, w]) // copy any QuatLike
Quat.from(q: QuatLike): Quat
Quat.identity // getter — fresh identity each access
Quat.fromEuler(x, y, z, order? /* "YXZ" */): Quat // angles in DEGREES
Quat.fromAxisAngle(axis, rad): Quat // angle in RADIANS; axis normalized for you
Quat.fromTo(a, b): Quat // shortest rotation taking direction a onto b
Quat.lookRotation(forward, up? /* Vec3.up */): Quat // orient so local −Z points along `forward`the degrees/radians split follows the SDK-wide rule: euler factories take degrees,
raw-angle arguments take radians. EulerOrder is one of "XYZ" | "YXZ" | "ZXY" | "ZYX" | "YZX" | "XZY"; the default "YXZ" is yaw-pitch-roll and matches node.eulerAngles.
Combining & applying
a.mul(b): Quat // Hamilton product a ⊗ b — applies b FIRST, then a
a.invert(): Quat // the inverse rotation (= conjugate() for unit quaternions)
a.conjugate(): Quat
a.normalize(): Quat // re-unitize after accumulating error (zero → identity)
a.rotateVec3(v): Vec3 // rotate a vector; same as new Vec3(v).rotate(a)Multiplication order reads right-to-left, like matrices: yaw.mul(pitch) pitches in local space,
then yaws.
Interpolation & comparison
a.slerp(b, t): Quat // spherical interpolation, t ∈ [0, 1]; takes the short way around
a.angle(b): number // angular distance between two unit quaternions, RADIANS
a.dot(b): number
a.equals(b, eps? /* 1e-6 */): boolean // component-wise — treats q and −q as DIFFERENT
a.sameRotation(b, eps? /* 1e-6 */): boolean // true if they represent the same rotation (±q equal)q and −q encode the same rotation. Use sameRotation for "is it facing the same
way", not equals.
Euler extraction
q.toEuler(order? /* "YXZ" */): Vec3 // angles in DEGREES; inverse of Quat.fromEulerInterop
q.set(x, y, z, w): this // the only mutators
q.copy(other): this
q.clone(): Quat
q.toArray(): [number, number, number, number]
const [x, y, z, w] = q // iterablePitfalls
// ✗ degrees into fromAxisAngle (it takes radians)
Quat.fromAxisAngle([0, 1, 0], 90)
Quat.fromAxisAngle([0, 1, 0], 90 * DEG2RAD) // ✓ — or use Quat.fromEuler(0, 90, 0)
// ✗ expecting a.mul(b) to apply a first — it applies b first, then a
world = local.mul(delta) // rotates by delta in local space
world = delta.mul(local) // ✓ if you wanted delta applied in world spaceSee also
- Vec2 & Vec3 —
Vec3.rotate(q), direction helpers (Vec3.forward= −Z) - Mat4 —
Mat4.fromQuat,mat.rotationextracts aQuat - Conventions — degrees vs radians, −Z forward