Rig math primitives — quaternion algebra, vector helpers, and forward
kinematics over a bone hierarchy. Shared by retargeting and IK so both
operate on one definition of the math. Quaternions are `{x, y, z, w}`
arrays and vectors are `{x, y, z}` arrays, matching the glam conventions
the engine's rig data uses.
vdot(a: { number }, b: { number }) → number
Dot product of two 3-vectors.
| arg | type | description |
|---|
| a | { number } | First vector. |
| b | { number } | Second vector. |
examples
local d = rigmath.vdot({ 1, 0, 0 }, { 0, 1, 0 })vcross(a: { number }, b: { number }) →
Cross product of two 3-vectors.
| arg | type | description |
|---|
| a | { number } | First vector. |
| b | { number } | Second vector. |
examples
local n = rigmath.vcross({ 1, 0, 0 }, { 0, 1, 0 })vlen(v: { number }) → number
Length of a 3-vector.
| arg | type | description |
|---|
| v | { number } | The vector. |
examples
local l = rigmath.vlen({ 3, 4, 0 })vsub(a: { number }, b: { number }) →
Component-wise difference `a - b`.
| arg | type | description |
|---|
| a | { number } | Minuend. |
| b | { number } | Subtrahend. |
examples
local d = rigmath.vsub(tipPos, rootPos)
vadd(a: { number }, b: { number }) →
Component-wise sum `a + b`.
| arg | type | description |
|---|
| a | { number } | First addend. |
| b | { number } | Second addend. |
examples
local p = rigmath.vadd(rootPos, offset)
vscale(v: { number }, s: number) →
Scale a 3-vector by a scalar.
| arg | type | description |
|---|
| v | { number } | The vector. |
| s | number | The scalar. |
examples
local half = rigmath.vscale(dir, 0.5)
vnormalize(v: { number }) →
Normalize a 3-vector. A zero-length vector returns zero rather than NaN.
| arg | type | description |
|---|
| v | { number } | The vector to normalize. |
examples
local dir = rigmath.vnormalize(rigmath.vsub(target, root))
vperpendicular(v: { number }) →
A unit vector perpendicular to `v`, chosen deterministically. Used as a
bend axis when a chain is perfectly straight and carries no pole target.
| arg | type | description |
|---|
| v | { number } | The reference vector. |
examples
local axis = rigmath.vperpendicular(chainDirection)
qmul(a: { number }, b: { number }) →
Hamilton product `a * b` — apply `b`, then `a`. Matches glam's Quat
multiplication so results agree with the engine's own rig math.
| arg | type | description |
|---|
| a | { number } | Outer rotation. |
| b | { number } | Inner rotation. |
examples
local q = rigmath.qmul(parentGlobal, boneLocal)
qnormalize(q: { number }) →
Normalize a quaternion to unit length. A degenerate quaternion returns
identity rather than NaN.
| arg | type | description |
|---|
| q | { number } | The quaternion. |
examples
local q = rigmath.qnormalize(accumulated)
qinverse(q: { number }) →
Inverse of a unit quaternion, which is its conjugate. Normalizes first
so a bind rotation that drifted slightly off unit still inverts cleanly.
| arg | type | description |
|---|
| q | { number } | The quaternion to invert. |
examples
local inv = rigmath.qinverse(parentGlobalRotation)
qrotvec(q: { number }, v: { number }) →
Rotate a 3-vector by a quaternion.
| arg | type | description |
|---|
| q | { number } | The rotation. |
| v | { number } | The vector to rotate. |
examples
local forward = rigmath.qrotvec(boneRotation, { 0, 0, 1 })shortestArc(a: { number }, b: { number }) →
Shortest-arc quaternion rotating unit vector `a` onto unit vector `b`.
The antiparallel case resolves to a half turn about an arbitrary
perpendicular axis instead of producing NaN.
Arbitrarily small rotations are represented rather than rounded away. An
iterative solver refines a pose in ever-smaller steps, so a near-parallel
cutoff would stall it at whatever residual the cutoff angle spans — the
bones needing the finest corrections would be exactly the ones ignored.
| arg | type | description |
|---|
| a | { number } | Source unit vector. |
| b | { number } | Destination unit vector. |
examples
local q = rigmath.shortestArc(currentDir, wantedDir)
axisAngle(axis: { number }, angle: number) →
Quaternion from an axis and an angle in radians. The axis is normalized
internally; a degenerate axis yields identity.
| arg | type | description |
|---|
| axis | { number } | Rotation axis. |
| angle | number | Rotation angle in radians. |
examples
local q = rigmath.axisAngle({ 0, 1, 0 }, math.pi / 2)qslerp(a: { number }, b: { number }, t: number) →
Spherical linear interpolation along the shortest arc. `t = 0` returns
`a`, `t = 1` returns `b`. Falls back to normalized lerp for nearly parallel
inputs, where the arc formulation loses precision.
| arg | type | description |
|---|
| a | { number } | Start rotation. |
| b | { number } | End rotation. |
| t | number | Interpolation factor. |
examples
local blended = rigmath.qslerp(animatedRotation, solvedRotation, weight)
signedAngle(a: { number }, b: { number }, axis: { number }) → number
Signed angle in radians from `a` to `b` measured about `axis`. Both
vectors are projected onto the plane perpendicular to `axis` first, so the
result is the roll about that axis.
| arg | type | description |
|---|
| a | { number } | Source vector. |
| b | { number } | Destination vector. |
| axis | { number } | The axis to measure about; normalized internally. |
examples
local roll = rigmath.signedAngle(midOffset, poleOffset, chainDirection)
swingTwist(q: { number }, axis: { number }) →
Split a rotation into its twist about `axis` and the remaining swing.
Rotation limits clamp the twist and rebuild, which is what keeps a hinge
joint on its axis.
| arg | type | description |
|---|
| q | { number } | The rotation to decompose. |
| axis | { number } | The twist axis; normalized internally. |
examples
local twist, swing = rigmath.swingTwist(localRotation, hingeAxis)
qangle(q: { number }) → number
The rotation angle of a quaternion in radians, in `[0, pi]`.
| arg | type | description |
|---|
| q | { number } | The quaternion. |
examples
local a = rigmath.qangle(delta)
isFinite(n: number) → boolean
Whether a number is finite — neither NaN nor an infinity.
| arg | type | description |
|---|
| n | number | The number to test. |
examples
if not rigmath.isFinite(x) then return end
clamp(v: number, lo: number, hi: number) → number
Clamp `v` into `[lo, hi]`.
| arg | type | description |
|---|
| v | number | The value. |
| lo | number | Lower bound. |
| hi | number | Upper bound. |
examples
local w = rigmath.clamp(weight, 0, 1)
computeGlobals(bones: { any }) →
Resolve every bone's global rest rotation and position by forward
kinematics over the local rest transforms. Handles any bone ordering — a
parent may be listed after its child — by iterating until all resolve. A
malformed cyclic parent falls back to the bone's local transform.
| arg | type | description |
|---|
| bones | { any } | Array of `{ parent, rest = { t, r } }`; `parent` is 0-based, -1 for a root. |
examples
local gRot, gPos = rigmath.computeGlobals(rig.bones)
computeBoneLengths(bones: { any }, gPos: { { number } }) →
Each bone's length, measured as the distance to its farthest child.
Retargeting uses this to scale root translation by rig proportion.
| arg | type | description |
|---|
| bones | { any } | Array of `{ parent }`; `parent` is 0-based, -1 for a root. |
| gPos | { { number } } | Global positions parallel to `bones`, as returned by `computeGlobals`. |
examples
local lengths = rigmath.computeBoneLengths(rig.bones, gPos)