LeCodesdocs

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

TypeScript
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 spin

Creating

TypeScript
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`
Note

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

TypeScript
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

TypeScript
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)
Note

q and −q encode the same rotation. Use sameRotation for "is it facing the same way", not equals.

Euler extraction

TypeScript
q.toEuler(order? /* "YXZ" */): Vec3   // angles in DEGREES; inverse of Quat.fromEuler

Interop

TypeScript
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       // iterable

Pitfalls

TypeScript
// ✗ 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 space

See also

  • Vec2 & Vec3Vec3.rotate(q), direction helpers (Vec3.forward = −Z)
  • Mat4Mat4.fromQuat, mat.rotation extracts a Quat
  • Conventions — degrees vs radians, −Z forward