std/linear/quaternion
std/linear/src/quaternion.trb
Quaternion, a rotation of space that composes and interpolates without shearing and without the gimbal lock that
three angles in a row have.
It is four numbers: the axis of the rotation, scaled by the sine of half the angle, and the cosine of half the angle.
Nothing in the library asks a caller to know that; Quaternion.rotation builds one from an axis and an Angle, and
Quaternion.applied turns a point with it.
type Quaternion
type Quaternion<Scalar: Real = Float> with Multiply, Negate
A rotation of space, as four numbers.
The sense is right-handed: a positive angle around unitZ takes unitX towards unitY, which is the same sense
Angle and Matrix3.rotationAroundZ use.
Examples
const turn = Quaternion.rotation around: Vector3(0.0, 0.0, 1.0), by: Angle.degrees(90.0)
print turn.applied(to: Vector3(1.0, 0.0, 0.0)).isCloseTo(Vector3(0.0, 1.0, 0.0), tolerance: 0.0001)
Pitfalls
-
Only a quaternion of length one is a rotation. Every constructor here answers one, and composing two of them keeps the length - but arithmetic that drifts is put back with
Quaternion.normalized. -
A rotation and its negation turn space the same way, so two quaternions that are equal as rotations can compare unequal. Compare what they do (apply both to the axes) rather than the four numbers.
-
Quaternion.interpolatedtakes the straight path between the two and puts the length back, so a long rotation turns fastest in the middle.Quaternion.sphericallyInterpolatedturns at an even rate and costs an arc cosine and three sines; the two agree at both ends.
Related
field x
x: Scalar
The first component of the axis part.
field y
y: Scalar
The second component of the axis part.
field z
z: Scalar
The third component of the axis part.
field w
w: Scalar
The cosine of half the angle.
const identity
static identity: Self = Quaternion Scalar.zero, Scalar.zero, Scalar.zero, Scalar.one
No rotation, over whichever scalar is asked for: Quaternion<Fixed>.identity.
fn rotation
static fn rotation(around: Vector3<Scalar>, by: Angle<Scalar>): Self
The rotation by that angle around that axis. The axis is normalized first, so it may have any length; a zero axis answers no rotation at all.
print Quaternion.rotation(around: Vector3(0.0, 1.0, 0.0), by: Angle.degrees(180.0)).w
fn axisPart
fn axisPart(): Vector3<Scalar>
The axis part, without the fourth number.
fn conjugate
fn conjugate(): Self
The rotation that undoes this one, for a quaternion of length one.
fn negate
fn negate(): Self
Every number with its sign flipped, which turns space the same way and is therefore not the inverse.
fn lengthSquared
fn lengthSquared(): Scalar
The sum of the four squares, which is one for every rotation.
fn length
fn length(): Scalar
The length over all four numbers.
fn normalized
fn normalized(): Self
The same rotation with the length put back to one. A zero quaternion answers no rotation.
fn multiply
fn multiply(other: Self): Self
The composition: self after other, the same order Matrix3 composes in.
fn applied
fn applied(to: Vector3<Scalar>): Vector3<Scalar>
The point, turned by the rotation.
const turn = Quaternion.rotation around: Vector3(0.0, 0.0, 1.0), by: Angle.degrees(90.0)
print turn.applied(to: Vector3(2.0, 0.0, 0.0))
fn toMatrix3
fn toMatrix3(): Matrix3<Scalar>
The same rotation as a matrix, which is the form a shader and a scene graph want.
fn interpolated
fn interpolated(toward: Self, by: Scalar): Self
The rotation by of the way from here to there, along the straight line between the four numbers and then back
onto the sphere. It takes the shorter of the two ways around, which is what an animation means by it. It is cheap,
and it turns fastest in the middle of a long rotation; Quaternion.sphericallyInterpolated turns at an even rate.
fn sphericallyInterpolated
fn sphericallyInterpolated(toward: Self, by: Scalar): Self
The rotation by of the way from here to there at a constant speed: the angle from the start grows in step with
by, where Quaternion.interpolated speeds up in the middle of a long rotation. It takes the shorter of the two
ways around.
const start = Quaternion.rotation around: Vector3(0.0, 0.0, 1.0), by: Angle.degrees(10.0)
const turn = Quaternion.rotation around: Vector3(0.0, 0.0, 1.0), by: Angle.degrees(170.0)
print start.sphericallyInterpolated(toward: turn, by: 0.5)
Two quaternions that are the same rotation to within the resolution of the scalar (the dot product is one after
rounding) have no angle between them to divide by, and the answer is then Quaternion.interpolated's.
fn dot
fn dot(other: Self): Scalar
The dot product of the four numbers: the cosine of half the rotation between the two.
fn isCloseTo
fn isCloseTo(other: Self, tolerance: Scalar): Bool
Whether every number is within tolerance of the other quaternion's.