Home Rumble Youtube Twitter/X Kofi Contact / Crypto

 

Quaternions in c3dlas only come in single-precision floats. Elements are stored in the i, j, k, real order, for easy compatibility with 3D vectors. Note that mathematicians often store the real component first which they often call "a" or sometimes "w". The full struct name is Quaternion, which is a typedef of Vector4 while this documentation uses the short typedef quat found in short_macros.h.

This documentation is intentionally written for an audience with minimal mathematical knowledge. Intuitively, you can think about quaternions as storing pre-computed values of sine for the complex (i, j, k) components and cosine for the real component. This allows you to compose multiple rotations using basic arithmetic, but more importantly it doesn't suffer from gimbal lock and can be interpolated smoothly. This is not a mathematically precise definition, but it's a lot better than the incomprehensible hogwash you'll find on Wikipedia.

It is important to remember that a quaternion represents a rotation operation, not a specific spatial orientation itself. In order to use quaternions to represent an abstract orientation, you must choose a basis orientation that is the starting point which the quaternion rotates from. In the case of a mesh, the starting point is implicit in the vertex positions themselves; each vertex is rotated by the quaternion into its new position. If you want to store "a direction vector" as a quaternion, such as the facing direction of an in-game object, you have to choose a default direction vector, such as (1,0,0), to feed into the stored quaternion. The vector can be arbitrary so long as it is consistent.

quat qFromRTheta(vec3 r, float theta)
Creates a quaternion based on a rotation of theta radians around the vector r.
void qToRTheta(quat q, vec3* r, float* theta)
Finds a vector r and a rotation theta (in radians) that are equivalent to q. This function chooses the lesser angle option, ie, the shortest way. There is not a unique solution for any given input, and this function makes arbitrary decisions about which vector to return.
quat qAdd(quat l, quat r)
Simple elementwise addition. Equivalent to vAdd4(l, r)
quat qSub(quat l, quat r)
Simple elementwise addition: out = l - r. Equivalent to vSub4(l, r)
quat qMul(quat left, quat right)
Performs quaternion complex-multiplication on left an right. The effect is to combine the rotations. Which rotation is "applied" to the other is confusing to think about in practice, even when you think you understand it, so just test and swap them if wrong. quaternion multiplication is not commutative; left * right != right * left.
quat qDiv(quat left, quat right)
Applies the inverse operation as qMul().
quat qRot(quat l, quat r) vec3 qRot3(vec3 a, quat r) vec2 qRot2(vec2 a, quat r)
Rotates a spatial vector by a quaternion. This is sometimes (confusingly) called "conjugation" by mathematicians. The effective source code makes things more clear. Not to be confused with qConj(), which merely negates the complex components. I didn't make up the names, blame the mathematicians.

quat qRot(quat l, quat r) { return qMul(qConj(l), qMul(r, l)); } vec3 qRot3(vec3 a, quat r) { vec4 a4 = {a.x, a.y, a.z, 0}; a4 = qMul(qMul(r, a4), qConj(r)); return (vec3){a4.x, a4.y, a4.z, 0}; } vec2 qRot2(vec2 a, quat r) { vec4 a4 = {a.x, a.y, 0, 0}; a4 = qMul(qMul(r, a4), qConj(r)); return (vec2){a4.x, a4.y, 0, 0}; }

quat qConj(quat q)
Returns the "conjugate" quaternion, which is a rotation to arrive at the same destination but going all the way around in the opposite direction. Not to be confused with the "conjugation operation" done by qRot(). I didn`'_SQ`'t make up the names, blame the mathematicians.
quat qInv(quat q)
Returns the inverse quaternion, which is the rotation that reverses or undoes the original. Multiplying a quaternion by its inverse will result in a quaternion that represents no rotation at all.
quat qNorm(quat q)
Returns a normalized quaternion. Non-normalized quaternions represent a rotation and a scaling, which, by and large, is not useful.

Interpolated quaternions take the shortest path from one orientation to another, but this is not usually visually intuitive. If you use the lerp functions to create a debug visualization of the arc that represents the rotation, you often get something resembling part of a cone, with the outer point swept along a circle orthogonal (perpendicular) to the expected flat arc centered on the object. This is correct but probably not what you're looking for. The "simple" arc between two points would not represent twisting along the axis as well, something that the cone does represent albeit strangely.

The way to make the visualization you're looking for is to call vCross() on the start and end vectors, then interpolate along the angle between them. If you also care about twisting, you can visualize that by doing the same with a vector orthogonal to the axis vectors.

quat qSlerp(quat a, quat b, float t)
Calculates an interpolated quaternion from a to b based on the normalized (0 to 1) value of t using Spherical Linear Interpolation. This function creates smooth, consistent motion with no lurches but is more costly inside.
quat qNlerp(quat a, quat b, float t)
Calculates an interpolated quaternion from a to b based on the normalized (0 to 1) value of t using simple normalized linear interpolation. This function is cheap but creates an inconsistent motion with accelerations near the beginning and end. This effect is visually negligible if the rotation is small.
quat qAngleBetween(quat a, quat b)
Calculates the smaller angle of rotation between two quaternions.
float qMod(quat q) float qMag(quat q) float qLen(quat q)
Returns the length of the quaternion. The three aliases exist because mathematicians can't make up their damn minds. The length of a quaternion is seldom useful as most quaternions in the wild will be normalized to a length of one.
quat qRotBetween(vec3 a, vec3 b)
Returns a quaternion that will rotate a to b.
quat qFromBasis(vec3 bx, vec3 by, vec3 bz)
Creates a quaternion with equivalent rotation to the 3x3 rotation matrix formed by the basis vector arguments. Alternately, creates a quaternion that will rotate any vector to coordinate space that has the arguments as its basis (cardinal) vectors. Alternately, creates a quaternion that will rotate (1,0,0) to bx, (0,1,0) to by, and (0,0,1) to bz.

Important: A quaternion can only represent a rotation, it cannot represent a mirroring, inversion, or negative scaling, while a 3x3 matrix can. The result of this function when fed basis vectors that do not represent a pure rotation matrix is not meaningful (aka garbage). I lost over two weeks of time dealing with bugs ultimately related to this, as there is exactly one other website on the internet that appears to explain this concept in relation to this function. The arguments should be 1) normalized, and 2) have cross products for any given pair that points in the same direction as the third one (I think). The solution to your bug is likely to simply invert one of the arguments.

void qNonUnitToMatrix(quat q, Matrix* out)
Creates a 4x4 rotation matrix that corresponds to the non-normalized quaternion input.
void qUnitToMatrix3(quat q, Matrix3* out)
Creates a 3x3 rotation matrix that corresponds to the normalized quaternion input. Uses a faster algorithm that only works with normalized inputs.
void qUnitToMatrix(quat q, Matrix* out)
Creates a 4x4 rotation matrix that corresponds to the normalized quaternion input. Uses a faster algorithm that only works with normalized inputs.