module Transform
Math helpers for positions, rotations, and directions on transforms. Exposed as the global `Transform` table; entity-aware helpers accept an id string or an entity proxy.
global Transform
eulerZyxDegToQuat(pitchDeg: number, yawDeg: number, rollDeg: number) → void
| arg | type | description |
|---|
| pitchDeg | number | |
| yawDeg | number | |
| rollDeg | number | |
resolveEntityId(e: ?) → string
Internal: the id behind an entity reference. Accepts an EntityRef proxy, a
bare id string, or an entity NAME, and answers the id of the entity that
reference names. Returns nil when the reference resolves to no entity in the
scene, so a caller can refuse it rather than aim at a position it never read.
A string is tested as a NAME first, with `entity.find`, and then as an id,
with `entity.exists`. Both stay silent for a string that matches nothing, so
an unresolvable reference reaches the caller as this function's nil rather
than as an error on the log — which is what `entity(id)` would leave behind.
getPos(entityId: string) → void
Internal: read a WORLD position via the entity proxy, given an id
`resolveEntityId` already answered for. Returns (x, y, z) or
(nil, nil, nil) when the entity lacks a transform.
World rather than local, because every surface built on this reads two
entities against each other — a distance, a direction, an aim — and the
answer only holds when both are expressed in the same frame. A parent's
offset moves an entity's world position and leaves its local one where it
was, so a local read puts the two entities in different frames and the
number that comes out belongs to neither.
| arg | type | description |
|---|
| entityId | string | |
readKeyedPoint(value: any) → void
Internal: the component reads a point table answers, taken through whatever
metatable it carries. A sealed proxy answers an index outside its own
members by raising, so both reads run guarded and the table reaches
`pointFrom` as the point it spells or as none.
| arg | type | description |
|---|
| value | any | |
readArrayPoint(value: any) → void
| arg | type | description |
|---|
| value | any | |
pointFrom(value: any) → void
Internal: the three components of a point a caller wrote as one table —
`{ x, y, z }`, `{ x =, y =, z = }`, or a live vec handle. Returns
(nil, nil, nil) for a table that spells no point, so a target slot reading
both entities and points can refuse it rather than aim at zeros.
| arg | type | description |
|---|
| value | any | |
lookAtQuat(fx: number, fy: number, fz: number, tx: number, ty: number, tz: number) →
Compute quaternion to look from origin position toward a target.
Returns four components `(qx, qy, qz, qw)`, or `nil` when the from
and to points are too close to derive a meaningful direction.
| arg | type | description |
|---|
| fx | number | Origin x. |
| fy | number | Origin y. |
| fz | number | Origin z. |
| tx | number | Target x. |
| ty | number | Target y. |
| tz | number | Target z. |
examples
local qx, qy, qz, qw = Transform.lookAtQuat(0, 0, 0, 1, 0, 1)
quatFromBasis(rx: number, ry: number, rz: number, ux: number, uy: number, uz: number, fx: number, fy: number, fz: number) →
Build the rotation whose right, up and forward ARE the given axes. Where
`lookAtQuat` derives a rotation from a direction alone — yaw and pitch, with
pitch clamped just short of straight up or down and no say in the roll — this
states all three axes, so a view straight down has a defined image-up instead
of whatever the yaw implied. The axes are expected orthonormal and are used as
given: `right` and `up` are the entity's local +X and +Y, `forward` its local
-Z (the direction it faces).
| arg | type | description |
|---|
| rx | number | Right axis x. |
| ry | number | Right axis y. |
| rz | number | Right axis z. |
| ux | number | Up axis x. |
| uy | number | Up axis y. |
| uz | number | Up axis z. |
| fx | number | Forward axis x. |
| fy | number | Forward axis y. |
| fz | number | Forward axis z. |
examples
local qx, qy, qz, qw = Transform.quatFromBasis(1,0,0, 0,1,0, 0,0,-1) -- identity
-- looking straight down with the subject's front toward the top of frame
local qx, qy, qz, qw = Transform.quatFromBasis(1,0,0, 0,0,-1, 0,-1,0)
lookRotation(fx: number, fy: number, fz: number, tx: number, ty: number, tz: number, ux: number?, uy: number?, uz: number?) →
The rotation that aims an entity standing at one world point at another,
with a world up hint deciding the roll. Where `lookAtQuat` derives the aim
from yaw and pitch alone — clamping the pitch just short of vertical, so a
point directly overhead comes back a twentieth of a degree off — this builds
all three axes, so the aim lands on the point at any elevation and straight
up and straight down are ordinary cases.
The aimed axis is the entity's local -Z, the same forward `quatFromBasis`,
`Transform.lookAt` and `entity(id):lookAt` state and the direction
`entity(id).transform.forward` reads back.
The up hint is a world direction the entity's own +Y is turned toward as
far as the aim allows; it never bends the forward axis. A hint parallel to
the aim leaves the roll undetermined, and a hint of no length names no
direction — both fall back to a stable roll rather than a NaN.
coincide, so no facing direction exists.
| arg | type | description |
|---|
| fx | number | Eye x — where the entity stands. |
| fy | number | Eye y. |
| fz | number | Eye z. |
| tx | number | Target x — the world point it faces. |
| ty | number | Target y. |
| tz | number | Target z. |
| ux | number? | Up hint x. World +Y when the hint is omitted. |
| uy | number? | Up hint y. |
| uz | number? | Up hint z. |
examples
local qx, qy, qz, qw = Transform.lookRotation(0, 2, 10, 0, 1, 0)
entity("cam").rotation = { Transform.lookRotation(0, 2, 10, 0, 1, 0) }-- a dutch tilt: the same aim, rolled by leaning the up hint
local q = { Transform.lookRotation(0, 2, 10, 0, 1, 0, 0.2, 1, 0) }lookAt(entityOrId: string | EntityRef, txOrTarget: any, ty: any?, tz: number?, up: any?) →
Make an entity face a world position. The target slot accepts three
explicit coordinates, one point as `{ x, y, z }` / `{ x =, y =, z = }` / a
vector, or an entity — an id string, an entity NAME, or a proxy — whose
WORLD position is resolved. A table carrying an entity id reads as that
entity; any other table reads as the point it spells. The subject slot
takes the three entity spellings.
Everything here is world space: the subject and the target are
read as `entity(id).position` and the aim is written as
`entity(id).rotation`, so a parent under either one moves the entity and
the aim still lands on the point named.
Returns whether the rotation was written, so a caller that named an entity
the scene does not carry learns the aim did not happen instead of reading
a stale orientation back as the answer.
proxy whose world position is resolved as the look-at target.
`{ x =, y =, z = }` or a vector. World +Y when omitted. It never bends the
aim; it only says which way is up around it. When the target slot is an
entity or a point this is the third argument, and when it is coordinates
the fifth.
second value.
The target and up slots take any value, because naming which of the shapes
arrived is this call's own job: a value that is none of them comes back as
a reason rather than as an error raised out of the argument check.
False plus a reason otherwise: `"unresolved"` when a reference names no
entity, `"no-transform"` when one carries no transform,
`"incomplete-target"` when the target spells no point — coordinates with
a y or z missing, or a table carrying neither three numbers nor x/y/z,
`"incomplete-up"` when the up hint spells none either, `"degenerate"`
when the two points coincide so no facing direction exists.
| arg | type | description |
|---|
| entityOrId | string | EntityRef | Entity id, name, or proxy for the entity to rotate. |
| txOrTarget | any | A number (world x), a point table, or an entity id / name / |
| ty | any? | World y of the target. Omitted when `txOrTarget` is a point or an entity. |
| tz | number? | World z of the target. Omitted when `txOrTarget` is a point or an entity. |
| up | any? | Optional world up hint deciding the roll — `{ x, y, z }`, |
examples
Transform.lookAt("cam", 0, 1, 0)Transform.lookAt("cam", "box") -- resolve target entity positionTransform.lookAt(cam, box) -- entity proxies for both
Transform.lookAt("cam", { 0, 1, 0 }) -- one point tableTransform.lookAt("cam", "box", { 0, 0, 1 }) -- rolled to a +Z updistance(x1: number, y1: number, z1: number, x2: number, y2: number, z2: number) → number
Euclidean distance between two world-space positions.
| arg | type | description |
|---|
| x1 | number | First point x. |
| y1 | number | First point y. |
| z1 | number | First point z. |
| x2 | number | Second point x. |
| y2 | number | Second point y. |
| z2 | number | Second point z. |
examples
local d = Transform.distance(0, 0, 0, 1, 1, 1)
distanceBetween(entityA: string | EntityRef, entityB: string | EntityRef) → number
Distance between two entities in world space. Each entity's world
position is what is measured, so a parent's offset counts toward the
distance the way the scene shows it.
| arg | type | description |
|---|
| entityA | string | EntityRef | First entity (id string or proxy). |
| entityB | string | EntityRef | Second entity (id string or proxy). |
examples
local d = Transform.distanceBetween("cam", "box")direction(fromX: number, fromY: number, fromZ: number, toX: number, toY: number, toZ: number) →
Normalized direction vector from point A to point B. Returns
zeros when the two points coincide (within ~0.001 units).
| arg | type | description |
|---|
| fromX | number | From x. |
| fromY | number | From y. |
| fromZ | number | From z. |
| toX | number | To x. |
| toY | number | To y. |
| toZ | number | To z. |
examples
local dx, dy, dz = Transform.direction(0, 0, 0, 1, 0, 0)
directionBetween(entityA: string | EntityRef, entityB: string | EntityRef) →
Normalized world-space direction from one entity to another, read
from their world positions. Returns zeros if either entity can't be
resolved.
| arg | type | description |
|---|
| entityA | string | EntityRef | Source entity (id string or proxy). |
| entityB | string | EntityRef | Target entity (id string or proxy). |
examples
local dx, dy, dz = Transform.directionBetween("cam", "target")quatFromYaw(yaw: number) →
Create quaternion from yaw (Y-axis rotation) in radians. Uses the
negative-yaw convention shared with `quatFromYawPitch`, `lookAtQuat`,
and `T.euler` extraction — so `T.euler(T.quatFromYaw(y))` round-trips
to `y`.
| arg | type | description |
|---|
| yaw | number | Rotation in radians around the Y axis. |
examples
local qx, qy, qz, qw = Transform.quatFromYaw(math.pi / 2)
quatFromYawPitch(yaw: number, pitch: number) →
Create quaternion from yaw and pitch in radians.
| arg | type | description |
|---|
| yaw | number | Y-axis rotation in radians. |
| pitch | number | X-axis rotation in radians. |
examples
local qx, qy, qz, qw = Transform.quatFromYawPitch(0, math.pi / 4)
quatFromAxisAngle(ax: number, ay: number, az: number, angle: number) →
Create quaternion from axis and angle (radians). Returns the
identity quaternion when the axis is degenerate (length < 0.001).
| arg | type | description |
|---|
| ax | number | Axis x. |
| ay | number | Axis y. |
| az | number | Axis z. |
| angle | number | Rotation angle in radians. |
examples
local qx, qy, qz, qw = Transform.quatFromAxisAngle(0, 1, 0, math.pi)
quatIdentity( ) →
Identity quaternion (`0, 0, 0, 1`).
examples
local qx, qy, qz, qw = Transform.quatIdentity()
euler(qx: number, qy: number, qz: number, qw: number) →
Convert quaternion to euler angles (yaw, pitch, roll) in radians.
| arg | type | description |
|---|
| qx | number | Quaternion x. |
| qy | number | Quaternion y. |
| qz | number | Quaternion z. |
| qw | number | Quaternion w. |
examples
local yaw, pitch, roll = Transform.euler(0, 0, 0, 1)
orbit(centerX: number, centerY: number, centerZ: number, radius: number, height: number, angle: number) →
Position + rotation for orbiting around a center point. Returns
the world position followed by the orientation that faces the center.
| arg | type | description |
|---|
| centerX | number | Center x. |
| centerY | number | Center y. |
| centerZ | number | Center z. |
| radius | number | Horizontal distance from the center. |
| height | number | Vertical offset from `centerY`. |
| angle | number | Orbital angle in radians. |
examples
local x, y, z, qx, qy, qz, qw = Transform.orbit(0, 1, 0, 5, 2, t)
lerp(ax: number, ay: number, az: number, bx: number, by: number, bz: number, t: number) →
Linearly interpolate between two positions.
| arg | type | description |
|---|
| ax | number | Start x. |
| ay | number | Start y. |
| az | number | Start z. |
| bx | number | End x. |
| by | number | End y. |
| bz | number | End z. |
| t | number | Interpolation factor `[0, 1]`. |
examples
local x, y, z = Transform.lerp(0, 0, 0, 1, 1, 1, 0.5)
lerp1(a: number, b: number, t: number) → number
Linearly interpolate two scalars.
| arg | type | description |
|---|
| a | number | Start value. |
| b | number | End value. |
| t | number | Interpolation factor `[0, 1]`. |
examples
local v = Transform.lerp1(0, 10, 0.5)
normalizeAngle(a: number) → number
Normalize an angle into `[-pi, pi]`.
| arg | type | description |
|---|
| a | number | The angle in radians. |
examples
local a = Transform.normalizeAngle(3 * math.pi)
lerpAngle(a: number, b: number, t: number) → number
Lerp between two angles via the shortest arc; returns a value in `[-pi, pi]`.
| arg | type | description |
|---|
| a | number | Start angle in radians. |
| b | number | End angle in radians. |
| t | number | Interpolation factor `[0, 1]`. |
examples
local a = Transform.lerpAngle(0, math.pi, 0.5)
slerp(ax: number, ay: number, az: number, aw: number, bx: number, by: number, bz: number, bw: number, t: number) →
Spherical linear interpolation between two quaternions. Picks
the shortest path (flips sign if dot < 0). Falls back to
lerp+normalize when the two quats are very close (avoids
div-by-zero on near-parallel inputs).
| arg | type | description |
|---|
| ax | number | Start quaternion x. |
| ay | number | Start quaternion y. |
| az | number | Start quaternion z. |
| aw | number | Start quaternion w. |
| bx | number | End quaternion x. |
| by | number | End quaternion y. |
| bz | number | End quaternion z. |
| bw | number | End quaternion w. |
| t | number | Interpolation factor `[0, 1]`. |
examples
local qx, qy, qz, qw = Transform.slerp(0, 0, 0, 1, 1, 0, 0, 0, 0.5)
quatMul(ax: number, ay: number, az: number, aw: number, bx: number, by: number, bz: number, bw: number) →
Quaternion multiplication: returns `qa * qb` (composition: rotate
by `qb` then `qa`).
| arg | type | description |
|---|
| ax | number | Left quat x. |
| ay | number | Left quat y. |
| az | number | Left quat z. |
| aw | number | Left quat w. |
| bx | number | Right quat x. |
| by | number | Right quat y. |
| bz | number | Right quat z. |
| bw | number | Right quat w. |
examples
local qx, qy, qz, qw = Transform.quatMul(ax, ay, az, aw, bx, by, bz, bw)
quatInverse(qx: number, qy: number, qz: number, qw: number) →
Quaternion inverse. Equal to the conjugate for unit quaternions.
| arg | type | description |
|---|
| qx | number | Quaternion x. |
| qy | number | Quaternion y. |
| qz | number | Quaternion z. |
| qw | number | Quaternion w. |
examples
local ix, iy, iz, iw = Transform.quatInverse(qx, qy, qz, qw)
quatRotateVec(qx: number, qy: number, qz: number, qw: number, vx: number, vy: number, vz: number) →
Rotate a 3-vector by a quaternion.
| arg | type | description |
|---|
| qx | number | Quaternion x. |
| qy | number | Quaternion y. |
| qz | number | Quaternion z. |
| qw | number | Quaternion w. |
| vx | number | Vector x. |
| vy | number | Vector y. |
| vz | number | Vector z. |
examples
local rx, ry, rz = Transform.quatRotateVec(qx, qy, qz, qw, 1, 0, 0)
eulerToQuat(yaw: number, pitch: number?, roll: number?) →
Identity-aware overload of euler-to-quaternion. Uses the negative-yaw
convention shared with `quatFromYaw`, `quatFromYawPitch`, `lookAtQuat`, and
`T.euler` extraction — so `T.euler(T.eulerToQuat(y, p, r))` returns
`(y, p, r)`. Order is yaw (Y) then pitch (X) then roll (Z).
| arg | type | description |
|---|
| yaw | number | Y-axis rotation in radians. |
| pitch | number? | X-axis rotation in radians. Defaults to 0. |
| roll | number? | Z-axis rotation in radians. Defaults to 0. |
examples
local qx, qy, qz, qw = Transform.eulerToQuat(math.pi / 2)
quatToEuler(qx: number, qy: number, qz: number, qw: number) →
Convert quaternion to `(yaw, pitch, roll)`. Alias of `euler` with
the explicit name so callers don't have to remember the order.
| arg | type | description |
|---|
| qx | number | Quaternion x. |
| qy | number | Quaternion y. |
| qz | number | Quaternion z. |
| qw | number | Quaternion w. |
examples
local yaw, pitch, roll = Transform.quatToEuler(qx, qy, qz, qw)
worldToLocal(px: number, py: number, pz: number, pqx: number, pqy: number, pqz: number, pqw: number, wx: number, wy: number, wz: number) →
Transform a world-space position into a parent's local space.
| arg | type | description |
|---|
| px | number | Parent position x. |
| py | number | Parent position y. |
| pz | number | Parent position z. |
| pqx | number | Parent rotation x. |
| pqy | number | Parent rotation y. |
| pqz | number | Parent rotation z. |
| pqw | number | Parent rotation w. |
| wx | number | World x. |
| wy | number | World y. |
| wz | number | World z. |
examples
local lx, ly, lz = Transform.worldToLocal(px, py, pz, pqx, pqy, pqz, pqw, wx, wy, wz)
localToWorld(px: number, py: number, pz: number, pqx: number, pqy: number, pqz: number, pqw: number, lx: number, ly: number, lz: number) →
Transform a local-space position into world space using a parent pose.
| arg | type | description |
|---|
| px | number | Parent position x. |
| py | number | Parent position y. |
| pz | number | Parent position z. |
| pqx | number | Parent rotation x. |
| pqy | number | Parent rotation y. |
| pqz | number | Parent rotation z. |
| pqw | number | Parent rotation w. |
| lx | number | Local x. |
| ly | number | Local y. |
| lz | number | Local z. |
examples
local wx, wy, wz = Transform.localToWorld(px, py, pz, pqx, pqy, pqz, pqw, lx, ly, lz)
readVec3(value: Vec3Input, label: string?) →
Normalize a vector a caller wrote to a plain `{ x, y, z }` array.
Accepts a positional array `{1, 2, 3}`, a keyed table
`{x =, y =, z =}`, or a live vec handle. Missing components read as 0.
Raises when the value is not a vector; `label` names the caller in
that error.
| arg | type | description |
|---|
| value | Vec3Input | The vector to normalize. |
| label | string? | Name reported in the error when the value is not a vector. Defaults to "Transform". |
examples
local v = Transform.readVec3({ x = 1, y = 2, z = 3 })quaternionFrom(rotation: any, label: string) → void
Internal: the one reading of a rotation a caller wrote. Returns the
canonical `{ qx, qy, qz, qw }`, or nil and the message describing what
arrived. Where the value is one of the two shapes a quaternion helper
produces — the four components of a multi-return, or the whole table of a
single-return — the message names the call that hands it back that way and
the packing the value goes in as, because the value itself is correct and
only its packing is wrong. The label names the surface that read it, which
is an assignment target at one call site and a function at the next, so the
packing is shown on its own rather than written into an assignment.
| arg | type | description |
|---|
| rotation | any | |
| label | string | |
tryQuaternion(rotation: any, label: string?) →
Read a rotation a caller wrote WITHOUT raising: returns the
canonical `{ qx, qy, qz, qw }`, or nil and the message describing what
arrived. The forms are the `RotationInput` union — a quaternion
(`{x,y,z,w}` or `{x=,y=,z=,w=}`) or euler DEGREES (`{pitch,yaw,roll}` or
`{pitch=,yaw=,roll=}`). Takes any value because reporting on a value that
is none of those forms is the whole job; a setter built on this raises the
returned message itself, so the error points at the line that wrote the
value rather than at the reading.
| arg | type | description |
|---|
| rotation | any | The value to read as a rotation. |
| label | string? | Name reported in the message. Defaults to "Transform". |
examples
local q, why = Transform.tryQuaternion(value, "myTool")
toQuaternion(rotation: any, label: string?) →
Normalize a rotation a caller wrote to a `{ qx, qy, qz, qw }`
quaternion. Accepts a quaternion (`{x,y,z,w}` or `{x=,y=,z=,w=}`) or
euler DEGREES (`{pitch,yaw,roll}` or `{pitch=,yaw=,roll=}`), so one
call site takes whichever form the caller finds natural. This is the
reading every rotation-taking surface in the engine shares, so a
quaternion and euler degrees mean the same thing at all of them.
Raises when the value matches no form; `label` names the caller in that
error, and a value that is one of the shapes a quaternion helper returns
is named as such along with the packing it goes in as.
| arg | type | description |
|---|
| rotation | any | The rotation to normalize, in any form of the `RotationInput` union. |
| label | string? | Name reported in the error when the value is not a rotation. Defaults to "Transform". |
examples
local q = Transform.toQuaternion({ pitch = 0, yaw = 90, roll = 0 })snapVec3(v: { number }, step: number | Vec3Input) →
Quantize each component of a vector to the nearest multiple of
`step` — a number for uniform steps, or a vector for per-axis steps.
A step of 0 on an axis leaves that axis at its exact value.
| arg | type | description |
|---|
| v | { number } | The vector to quantize, as `{ x, y, z }`. |
| step | number | Vec3Input | Uniform step size, or a per-axis vector of step sizes. |
examples
local v = Transform.snapVec3({ 1.4, 2.6, -0.4 }, 1)q(value: number, s: number) → number
| arg | type | description |
|---|
| value | number | |
| s | number | |
add(ax: number, ay: number, az: number, bx: number, by: number, bz: number) →
Component-wise vec3 addition.
| arg | type | description |
|---|
| ax | number | First vector x. |
| ay | number | First vector y. |
| az | number | First vector z. |
| bx | number | Second vector x. |
| by | number | Second vector y. |
| bz | number | Second vector z. |
examples
local x, y, z = Transform.vec.add(1, 2, 3, 4, 5, 6)
sub(ax: number, ay: number, az: number, bx: number, by: number, bz: number) →
Component-wise vec3 subtraction (`a - b`).
| arg | type | description |
|---|
| ax | number | First vector x. |
| ay | number | First vector y. |
| az | number | First vector z. |
| bx | number | Second vector x. |
| by | number | Second vector y. |
| bz | number | Second vector z. |
examples
local x, y, z = Transform.vec.sub(4, 5, 6, 1, 2, 3)
scale(x: number, y: number, z: number, s: number) →
Component-wise scalar multiplication of a vec3.
| arg | type | description |
|---|
| x | number | Vector x. |
| y | number | Vector y. |
| z | number | Vector z. |
| s | number | Scalar factor. |
examples
local x, y, z = Transform.vec.scale(1, 2, 3, 2)
dot(ax: number, ay: number, az: number, bx: number, by: number, bz: number) → number
Dot product of two vec3s.
| arg | type | description |
|---|
| ax | number | First vector x. |
| ay | number | First vector y. |
| az | number | First vector z. |
| bx | number | Second vector x. |
| by | number | Second vector y. |
| bz | number | Second vector z. |
examples
local d = Transform.vec.dot(1, 0, 0, 0, 1, 0)
cross(ax: number, ay: number, az: number, bx: number, by: number, bz: number) →
Cross product `a x b`.
| arg | type | description |
|---|
| ax | number | First vector x. |
| ay | number | First vector y. |
| az | number | First vector z. |
| bx | number | Second vector x. |
| by | number | Second vector y. |
| bz | number | Second vector z. |
examples
local cx, cy, cz = Transform.vec.cross(1, 0, 0, 0, 1, 0)
length(x: number, y: number, z: number) → number
Euclidean length of a vec3.
| arg | type | description |
|---|
| x | number | Vector x. |
| y | number | Vector y. |
| z | number | Vector z. |
examples
local len = Transform.vec.length(1, 2, 3)
normalize(x: number, y: number, z: number) →
Normalize a vec3. Returns zeros when the input is degenerate
(length < 1e-8).
| arg | type | description |
|---|
| x | number | Vector x. |
| y | number | Vector y. |
| z | number | Vector z. |
examples
local nx, ny, nz = Transform.vec.normalize(0, 5, 0)