Quaternion
A rotation, as a quaternion: four floats, [x, y, z, w]. ## Why this exists alongside RotationYXZ Transform stores rotation as three Euler angles and builds a matrix with Matrix4->RotationYXZ. That is fine to author with and fine to store, and it is wrong for two jobs: interpolation Blending between two orientations by lerping their Euler angles does not travel the short way round, wobbles on the way, and can stop dead when two axes line up. Track can already sample a Float along an eased curve; it had no way to sample an ORIENTATION. Slerp is that. accumulation Applying one rotation after another by adding angles is only correct while the axes stay independent. Multiplying quaternions is correct always, which is what a turret that turns relative to a hull that is already turning needs. Follows Matrix4's convention rather than Vector3's: static functions over a Float[4] the caller owns, each with an Into twin that writes rather than allocates. A rotation per object per frame is the same allocation problem a matrix per object per frame is, and for the same reason. Conventions here: right-handed, the same as Matrix4; the scalar is LAST (w at index 3), which is what OpenGL literature and GLM use; and every function assumes unit length except Normalize, Length and FromAxisAngle.
Operations
- Conjugate
- ConjugateInto
- Dot
- FromAxisAngle
- FromAxisAngleInto
- FromEuler
- FromEulerInto
- Identity
- IdentityInto
- Length
- Multiply
- MultiplyInto
- NormalizeInto
- Normalized
- Rotate
- Slerp
- SlerpInto
- ToMatrix
- ToMatrixInto
Conjugate # function
function : Conjugate(q:Float[]) ~ Float[]Parameters
| Name | Type | Description |
|---|---|---|
| q | Float | the rotation to reverse |
Return
| Type | Description |
|---|---|
| Float | a new opposite rotation |
ConjugateInto # function
The opposite rotation, written into a buffer the caller owns. For a unit rotation this is also the inverse, which is why there is no separate Inverse: negating the vector part is correct and cheap, and an inverse that silently assumed unit length would be a trap.
function : ConjugateInto(out:Float[], q:Float[]) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| out | Float | destination, at least 4 elements |
| q | Float | the rotation to reverse |
Dot # function
function : Dot(a:Float[], b:Float[]) ~ FloatParameters
| Name | Type | Description |
|---|---|---|
| a | Float | first rotation |
| b | Float | second rotation |
Return
| Type | Description |
|---|---|
| Float | the dot product. Negative when they are more than half a turn apart, which is what Slerp uses to decide which way round to go. |
FromAxisAngle # function
A rotation of 'angle' radians about an axis. The axis is normalised here, so it does not have to arrive that way -- passing (0, 2, 0) and (0, 1, 0) give the same rotation. A zero-length axis has no direction to turn about and yields identity rather than a quaternion full of NaN.
function : FromAxisAngle(x:Float, y:Float, z:Float, angle:Float) ~ Float[]Parameters
| Name | Type | Description |
|---|---|---|
| x | Float | axis x |
| y | Float | axis y |
| z | Float | axis z |
| angle | Float | radians, counter-clockwise looking down the axis |
Return
| Type | Description |
|---|---|
| Float | a new rotation |
FromAxisAngleInto # function
Axis-angle, written into a buffer the caller owns.
function : FromAxisAngleInto(out:Float[], x:Float, y:Float, z:Float, angle:Float) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| out | Float | destination, at least 4 elements |
| x | Float | axis x |
| y | Float | axis y |
| z | Float | axis z |
| angle | Float | radians |
FromEuler # function
The rotation Transform already means by (pitch, yaw, roll). Matches Matrix4->RotationYXZ exactly -- yaw, then pitch, then roll -- so converting a Transform's angles here and building a matrix from the result gives the same matrix Transform would have built itself. That equivalence is what makes it safe to move between the two.
function : FromEuler(yaw:Float, pitch:Float, roll:Float) ~ Float[]Parameters
| Name | Type | Description |
|---|---|---|
| yaw | Float | radians about Y, applied first |
| pitch | Float | radians about X |
| roll | Float | radians about Z, applied last |
Return
| Type | Description |
|---|---|
| Float | a new rotation |
FromEulerInto # function
Euler angles, written into a buffer the caller owns.
function : FromEulerInto(out:Float[], yaw:Float, pitch:Float, roll:Float) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| out | Float | destination, at least 4 elements |
| yaw | Float | radians about Y, applied first |
| pitch | Float | radians about X |
| roll | Float | radians about Z, applied last |
Identity # function
function : Identity() ~ Float[]Return
| Type | Description |
|---|---|
| Float | a new identity rotation -- no rotation at all |
IdentityInto # function
Identity, written into a buffer the caller owns.
function : IdentityInto(out:Float[]) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| out | Float | destination, at least 4 elements |
Length # function
function : Length(q:Float[]) ~ FloatParameters
| Name | Type | Description |
|---|---|---|
| q | Float | a rotation |
Return
| Type | Description |
|---|---|
| Float | its length. One, for any rotation that has not drifted. |
Multiply # function
Rotation product: rhs first, then lhs. The same argument order as Matrix4->Multiply, deliberately, so the two read alike at a call site.
function : Multiply(lhs:Float[], rhs:Float[]) ~ Float[]Parameters
| Name | Type | Description |
|---|---|---|
| lhs | Float | applied second |
| rhs | Float | applied first |
Return
| Type | Description |
|---|---|
| Float | a new rotation |
MultiplyInto # function
Rotation product, written into a buffer the caller owns. Safe to alias: reads both inputs into locals before writing, so MultiplyInto(q, q, r) does what it looks like it does.
function : MultiplyInto(out:Float[], lhs:Float[], rhs:Float[]) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| out | Float | destination, at least 4 elements |
| lhs | Float | applied second |
| rhs | Float | applied first |
NormalizeInto # function
Unit length, written into a buffer the caller owns. Repeatedly multiplying rotations accumulates floating-point drift, so anything doing that should normalise every so often. A zero-length input yields identity rather than NaN.
function : NormalizeInto(out:Float[], q:Float[]) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| out | Float | destination, at least 4 elements |
| q | Float | the rotation to normalise |
Normalized # function
function : Normalized(q:Float[]) ~ Float[]Parameters
| Name | Type | Description |
|---|---|---|
| q | Float | the rotation to normalise |
Return
| Type | Description |
|---|---|
| Float | a new unit-length rotation |
Rotate # function
Turn a vector by a rotation. Uses the expanded form rather than q * v * conjugate(q), which would need two quaternion products and a temporary.
function : Rotate(q:Float[], v:Vector3) ~ Vector3Parameters
| Name | Type | Description |
|---|---|---|
| q | Float | a unit rotation |
| v | Vector3 | the vector to turn |
Return
| Type | Description |
|---|---|
| Vector3 | a new turned vector |
Slerp # function
function : Slerp(a:Float[], b:Float[], t:Float) ~ Float[]Parameters
| Name | Type | Description |
|---|---|---|
| a | Float | rotation at t = 0 |
| b | Float | rotation at t = 1 |
| t | Float | 0..1, clamped |
Return
| Type | Description |
|---|---|
| Float | a new rotation between the two |
SlerpInto # function
Spherical linear interpolation: the shortest turn from 'a' to 'b'. This is the reason the class exists. It travels the short way round -- if the two are more than half a turn apart, 'b' is negated first, which is the same rotation reached the other way -- and it moves at a constant angular rate, so an object turning under it does not speed up in the middle. Falls back to a straight lerp when the two are very close, because the sine of a tiny angle divides badly.
function : SlerpInto(out:Float[], a:Float[], b:Float[], t:Float) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| out | Float | destination, at least 4 elements |
| a | Float | rotation at t = 0 |
| b | Float | rotation at t = 1 |
| t | Float | 0..1, clamped |
ToMatrix # function
function : ToMatrix(q:Float[]) ~ Float[]Parameters
| Name | Type | Description |
|---|---|---|
| q | Float | a unit rotation |
Return
| Type | Description |
|---|---|
| Float | a new 4x4 rotation matrix |
ToMatrixInto # function
The rotation as a 4x4 matrix, written into a buffer the caller owns. Column-major and right-handed, the same as everything Matrix4 produces, so the result can be multiplied straight into a model matrix.
function : ToMatrixInto(out:Float[], q:Float[]) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| out | Float | destination, at least 16 elements |
| q | Float | a unit rotation |