Log inGet started
▣
module · drop-in viewer
asset⌬ modulemoduleprimary: init.luau·originates fromworld 07158574-5…

rigmath

Quaternion algebra, vector helpers, and forward kinematics over a bone hierarchy. One definition of the math, shared by retargeting and IK.

by◐lumi·posted 2mo ago
What it does

rigmath

Quaternion algebra, vector helpers, and forward kinematics over a bone hierarchy. One definition of the math, shared by retargeting and IK.

Quaternions are { x, y, z, w } arrays and vectors are { x, y, z } arrays, matching the conventions the engine's rig data already uses, so values read straight out of ecs.Skeleton.bones or a parsed .rig need no conversion.

Exports

Vectors:

  • vdot(a, b) -> number — dot product.
  • vcross(a, b) -> { number } — cross product.
  • vlen(v) -> number — Euclidean length.
  • vsub(a, b), vadd(a, b), vscale(v, s) -> { number } — component-wise arithmetic.
  • vnormalize(v) -> { number } — unit vector; a zero-length input returns zero.
  • vperpendicular(v) -> { number } — a deterministic unit vector at right angles to v.

Quaternions:

  • IDENTITY — { 0, 0, 0, 1 }.
  • qmul(a, b) -> { number } — Hamilton product; applies b, then a.
  • qnormalize(q), qinverse(q) -> { number }.
  • qrotvec(q, v) -> { number } — rotate a vector.
  • shortestArc(a, b) -> { number } — the rotation carrying unit vector a onto b.
  • axisAngle(axis, angle) -> { number } — from an axis and radians.
  • qslerp(a, b, t) -> { number } — shortest-arc interpolation.
  • qangle(q) -> number — rotation magnitude in radians, [0, pi].
  • signedAngle(a, b, axis) -> number — roll from a to b about axis, in radians.
  • swingTwist(q, axis) -> ({ number }, { number }) — twist about axis, then the remaining swing.

Scalars:

  • isFinite(n) -> boolean, clamp(v, lo, hi) -> number.

Forward kinematics:

  • computeGlobals(bones) -> (gRot, gPos) — global rest transforms from local ones.
  • computeBoneLengths(bones, gPos) -> { number } — each bone's distance to its farthest child.

Usage

local rigmath = require("modules.rigmath")

-- Point a bone's forward axis at a target.
local dir = rigmath.vnormalize(rigmath.vsub(targetPos, bonePos))
local swing = rigmath.shortestArc(rigmath.qrotvec(boneRot, { 0, 0, 1 }), dir)
local aimed = rigmath.qmul(swing, boneRot)

-- Blend the result in at a weight.
local final = rigmath.qslerp(boneRot, aimed, 0.5)

Notes

  • Degenerate input never produces NaN. A zero-length vector normalizes to zero, a degenerate quaternion normalizes to identity, and shortestArc on antiparallel vectors resolves to a half turn about a perpendicular axis.
  • computeGlobals tolerates any bone ordering, including a parent listed after its child, and falls back to the local transform for a bone left unresolved by a cyclic parent.
  • Bone parent indices are 0-based with -1 for a root, matching the rig format; the returned arrays are 1-based and parallel to the input.

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

conforms to

zero/source-extract/v2

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.

argtypedescription
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.

argtypedescription
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.

argtypedescription
v{ number }The vector.

examples

local l = rigmath.vlen({ 3, 4, 0 })

vsub(a: { number }, b: { number }) →

Component-wise difference `a - b`.

argtypedescription
a{ number }Minuend.
b{ number }Subtrahend.

examples

local d = rigmath.vsub(tipPos, rootPos)

vadd(a: { number }, b: { number }) →

Component-wise sum `a + b`.

argtypedescription
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.

argtypedescription
v{ number }The vector.
snumberThe 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.

argtypedescription
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.

argtypedescription
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.

argtypedescription
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.

argtypedescription
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.

argtypedescription
q{ number }The quaternion to invert.

examples

local inv = rigmath.qinverse(parentGlobalRotation)

qrotvec(q: { number }, v: { number }) →

Rotate a 3-vector by a quaternion.

argtypedescription
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.

argtypedescription
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.

argtypedescription
axis{ number }Rotation axis.
anglenumberRotation 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.

argtypedescription
a{ number }Start rotation.
b{ number }End rotation.
tnumberInterpolation 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.

argtypedescription
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.

argtypedescription
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]`.

argtypedescription
q{ number }The quaternion.

examples

local a = rigmath.qangle(delta)

isFinite(n: number) → boolean

Whether a number is finite — neither NaN nor an infinity.

argtypedescription
nnumberThe 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]`.

argtypedescription
vnumberThe value.
lonumberLower bound.
hinumberUpper 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.

argtypedescription
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.

argtypedescription
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)

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

2items
This part has no composite children. See the Files segment for its leaf payloads.
backing path · modules/rigmath.module

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported. This asset, its contents, and its direct deps are clean as of the latest commit.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.