2 Quaternions
dxtr edited this page 2026-10-14 13:46:25 +02:00

Quaternions

A quat is a quaternion of four single floats, X, Y and Z, its vector part, then W, its real part. Rotations are unit quaternions, of length 1. dquat is the same of doubles; see Doubles. Functions that return a quaternion or a vector take an optional last argument, result; see Results and Allocation.

Making rotations

(gm:make-quat)   ; => #(0.0 0.0 0.0 1.0)
(gm:quat-from-axis-angle (gm:make-vec3 :y 1) (gm:radians 90))
;; => #(0.0 0.70710677 0.0 0.70710677)
  • (gm:make-quat) without arguments is the identity: no rotation. quat-identity returns it too, and can write it into a result.
  • quat-from-axis-angle makes the rotation by an angle, in radians, about an axis, counterclockwise when the axis points at you. The axis need not be normalised; a zero axis signals gmath-error.
  • gm:make-quat makes any quaternion from its components, given by keyword, (gm:make-quat :x x :y y :z z :w w): X, Y and Z are 0 if not given, and W 1.
  • quat-x, quat-y, quat-z and quat-w read its components, and setf writes them with single floats, as for vectors:
(gm:quat-w (gm:quat-from-axis-angle (gm:make-vec3 :y 1) (gm:radians 90)))   ; => 0.70710677

Rotating

quat-rotate rotates a vec3. A quarter turn about +Y takes +Z to +X:

(gm:quat-rotate (gm:quat-from-axis-angle (gm:make-vec3 :y 1) (gm:radians 90)) (gm:make-vec3 :z 1))
;; => #(0.99999994 0.0 5.9604645e-8)

Combining

quat* is the Hamilton product: (gm:quat* a b) rotates by B, then by A, as mat4* applies its right-hand matrix first:

(let* ((a (gm:quat-from-axis-angle (gm:make-vec3 :z 1) (gm:radians 90)))
       (b (gm:quat-from-axis-angle (gm:make-vec3 :x 1) (gm:radians 90))))
  (gm:vec3-approx= (gm:quat-rotate (gm:quat* a b) (gm:make-vec3 :y 1))
                   (gm:quat-rotate a (gm:quat-rotate b (gm:make-vec3 :y 1)))))
;; => T

Interpolating

quat-slerp returns the rotation a fraction of the way from one rotation to another, at a constant angular speed and along the shorter arc: the first at 0, the second at 1.

(let ((start (gm:make-quat))
      (end (gm:quat-from-axis-angle (gm:make-vec3 :z 1) (gm:radians 90))))
  (gm:quat-rotate (gm:quat-slerp start end 1/2) (gm:make-vec3 :x 1)))
;; => #(0.7071068 0.70710677 0.0)

Half way through a quarter turn, +X points at 45 degrees. A quaternion and its negation are the same rotation, so slerp takes whichever of the two is nearer, and never goes the long way round.

Other operations

Function Returns
quat-conjugate The vector part negated: for a rotation, the opposite rotation.
quat-inverse The inverse. For a rotation, quat-conjugate gives the same, more cheaply. Signals gmath-error for the zero quaternion.
quat-normalize The quaternion scaled to length 1, the rotation it stands for. Signals gmath-error for the zero quaternion. Normalise now and then a rotation built up from many products, which drifts from length 1.
quat-length, quat-dot The length and the dot product, as 4-vectors.
quat-copy A copy.
quat-approx= True if no component differs by more than an epsilon, 1e-5 by default. A quaternion and its negation are the same rotation but not equal here.
(let ((q (gm:quat-from-axis-angle (gm:make-vec3 :y 1) 1.0)))
  (gm:quat-approx= (gm:quat* q (gm:quat-conjugate q)) (gm:make-quat)))
;; => T

As matrices

mat4-from-quat makes the mat4 that rotates as a unit quaternion does, and mat4-compose builds a node's whole transform from a translation, a quaternion and a scale. See Transforms and Cameras.

(gm:mat4-approx= (gm:mat4-from-quat (gm:quat-from-axis-angle (gm:make-vec3 :x 1 :y 2 :z 3) 0.7))
                 (gm:mat4-rotation (gm:make-vec3 :x 1 :y 2 :z 3) 0.7))
;; => T