Transform
The Transform namespace — 75 functions.
globals/Transform/direction
Transform.direction(fromX: number, fromY: number, fromZ: number, toX: number, toY: number, toZ: number) -> (number, number, number)
Normalized direction vector from point A to point B. Returns zeros when the two points coincide (within ~0.001 units).
Parameters
fromXnumber— From x.fromYnumber— From y.fromZnumber— From z.toXnumber— To x.toYnumber— To y.toZnumber— To z.
Returns (number, number, number) — Three numbers dx, dy, dz — the unit direction.
local dx, dy, dz = Transform.direction(0, 0, 0, 1, 0, 0)
globals/Transform/directionBetween
Transform.directionBetween(entityA: string | EntityRef, entityB: string | EntityRef) -> (number, number, number)
Normalized world-space direction from one entity to another, read from their world positions. Returns zeros if either entity can't be resolved.
Parameters
entityAstring | EntityRef— Source entity (id string or proxy).entityBstring | EntityRef— Target entity (id string or proxy).
Returns (number, number, number) — Three numbers dx, dy, dz — the unit direction.
local dx, dy, dz = Transform.directionBetween("cam", "target")
globals/Transform/distance
Transform.distance(x1: number, y1: number, z1: number, x2: number, y2: number, z2: number) -> number
Euclidean distance between two world-space positions.
Parameters
x1number— First point x.y1number— First point y.z1number— First point z.x2number— Second point x.y2number— Second point y.z2number— Second point z.
Returns number — The Euclidean distance.
local d = Transform.distance(0, 0, 0, 1, 1, 1)
globals/Transform/distanceBetween
Transform.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.
Parameters
entityAstring | EntityRef— First entity (id string or proxy).entityBstring | EntityRef— Second entity (id string or proxy).
Returns number? — The Euclidean distance, or nil when either entity can't be resolved.
local d = Transform.distanceBetween("cam", "box")
globals/Transform/euler
Transform.euler(qx: number, qy: number, qz: number, qw: number) -> (number, number, number)
Convert quaternion to euler angles (yaw, pitch, roll) in radians.
Parameters
qxnumber— Quaternion x.qynumber— Quaternion y.qznumber— Quaternion z.qwnumber— Quaternion w.
Returns (number, number, number) — Three numbers yaw, pitch, roll (Y, X, Z rotations).
local yaw, pitch, roll = Transform.euler(0, 0, 0, 1)
globals/Transform/eulerToQuat
Transform.eulerToQuat(yaw: number, pitch: number?, roll: number?) -> (number, number, number, 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).
Parameters
yawnumber— Y-axis rotation in radians.pitchnumber(optional) — X-axis rotation in radians. Defaults to 0.rollnumber(optional) — Z-axis rotation in radians. Defaults to 0.
Returns (number, number, number, number) — Four numbers qx, qy, qz, qw.
local qx, qy, qz, qw = Transform.eulerToQuat(math.pi / 2)
globals/Transform/lerp
Transform.lerp(ax: number, ay: number, az: number, bx: number, by: number, bz: number, t: number) -> (number, number, number)
Linearly interpolate between two positions.
Parameters
axnumber— Start x.aynumber— Start y.aznumber— Start z.bxnumber— End x.bynumber— End y.bznumber— End z.tnumber— Interpolation factor[0, 1].
Returns (number, number, number) — Three numbers — the interpolated position.
local x, y, z = Transform.lerp(0, 0, 0, 1, 1, 1, 0.5)
globals/Transform/lerp1
Transform.lerp1(a: number, b: number, t: number) -> number
Linearly interpolate two scalars.
Parameters
anumber— Start value.bnumber— End value.tnumber— Interpolation factor[0, 1].
Returns number — The interpolated scalar.
local v = Transform.lerp1(0, 10, 0.5)
globals/Transform/lerpAngle
Transform.lerpAngle(a: number, b: number, t: number) -> number
Lerp between two angles via the shortest arc; returns a value in [-pi, pi].
Parameters
anumber— Start angle in radians.bnumber— End angle in radians.tnumber— Interpolation factor[0, 1].
Returns number — The interpolated angle, normalized to [-pi, pi].
local a = Transform.lerpAngle(0, math.pi, 0.5)
globals/Transform/localToWorld
Transform.localToWorld(px: number, py: number, pz: number, pqx: number, pqy: number, pqz: number, pqw: number, lx: number, ly: number, lz: number) -> (number, number, number)
Transform a local-space position into world space using a parent pose.
Parameters
pxnumber— Parent position x.pynumber— Parent position y.pznumber— Parent position z.pqxnumber— Parent rotation x.pqynumber— Parent rotation y.pqznumber— Parent rotation z.pqwnumber— Parent rotation w.lxnumber— Local x.lynumber— Local y.lznumber— Local z.
Returns (number, number, number) — Three numbers wx, wy, wz — the world position.
local wx, wy, wz = Transform.localToWorld(px, py, pz, pqx, pqy, pqz, pqw, lx, ly, lz)
globals/Transform/lookAt
Transform.lookAt(entityOrId: string | EntityRef, txOrTarget: any?, ty: any?, tz: number?, up: any?) -> (boolean, string?)
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.
Parameters
entityOrIdstring | EntityRef— Entity id, name, or proxy for the entity to rotate.txOrTargetany(optional) — A number (world x), a point table, or an entity id / name / proxy whose world position is resolved as the look-at target.tyany(optional) — World y of the target. Omitted whentxOrTargetis a point or an entity.tznumber(optional) — World z of the target. Omitted whentxOrTargetis a point or an entity.upany(optional) — Optional world up hint deciding the roll —{ x, y, z },{ 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.
Returns (boolean, string?) — True when the entity's world rotation was written, and nil for the 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.
Transform.lookAt("cam", 0, 1, 0)
Transform.lookAt("cam", "box") -- resolve target entity position
Transform.lookAt(cam, box) -- entity proxies for both
Transform.lookAt("cam", { 0, 1, 0 }) -- one point table
Transform.lookAt("cam", "box", { 0, 0, 1 }) -- rolled to a +Z up
globals/Transform/lookAtQuat
Transform.lookAtQuat(fx: number, fy: number, fz: number, tx: number, ty: number, tz: number) -> (number?, number?, number?, 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.
Parameters
fxnumber— Origin x.fynumber— Origin y.fznumber— Origin z.txnumber— Target x.tynumber— Target y.tznumber— Target z.
Returns (number?, number?, number?, number?) — Four numbers qx, qy, qz, qw — the look-at quaternion. Nil when degenerate.
local qx, qy, qz, qw = Transform.lookAtQuat(0, 0, 0, 1, 0, 1)
globals/Transform/lookRotation
Transform.lookRotation(fx: number, fy: number, fz: number, tx: number, ty: number, tz: number, ux: number?, uy: number?, uz: number?) -> (number?, number?, number?, 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.
Parameters
fxnumber— Eye x — where the entity stands.fynumber— Eye y.fznumber— Eye z.txnumber— Target x — the world point it faces.tynumber— Target y.tznumber— Target z.uxnumber(optional) — Up hint x. World +Y when the hint is omitted.uynumber(optional) — Up hint y.uznumber(optional) — Up hint z.
Returns (number?, number?, number?, number?) — Four numbers qx, qy, qz, qw. Nil when the eye and the target coincide, so no facing direction exists.
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) }
globals/Transform/normalizeAngle
Transform.normalizeAngle(a: number) -> number
Normalize an angle into [-pi, pi].
Parameters
anumber— The angle in radians.
Returns number — The same angle wrapped into [-pi, pi].
local a = Transform.normalizeAngle(3 * math.pi)
globals/Transform/orbit
Transform.orbit(centerX: number, centerY: number, centerZ: number, radius: number, height: number, angle: number) -> (number, number, number, number, number, number, number)
Position + rotation for orbiting around a center point. Returns the world position followed by the orientation that faces the center.
Parameters
centerXnumber— Center x.centerYnumber— Center y.centerZnumber— Center z.radiusnumber— Horizontal distance from the center.heightnumber— Vertical offset fromcenterY.anglenumber— Orbital angle in radians.
Returns (number, number, number, number, number, number, number) — Seven numbers x, y, z, qx, qy, qz, qw.
local x, y, z, qx, qy, qz, qw = Transform.orbit(0, 1, 0, 5, 2, t)
globals/Transform/quatFromAxisAngle
Transform.quatFromAxisAngle(ax: number, ay: number, az: number, angle: number) -> (number, number, number, number)
Create quaternion from axis and angle (radians). Returns the identity quaternion when the axis is degenerate (length < 0.001).
Parameters
axnumber— Axis x.aynumber— Axis y.aznumber— Axis z.anglenumber— Rotation angle in radians.
Returns (number, number, number, number) — Four numbers qx, qy, qz, qw.
local qx, qy, qz, qw = Transform.quatFromAxisAngle(0, 1, 0, math.pi)
globals/Transform/quatFromBasis
Transform.quatFromBasis(rx: number, ry: number, rz: number, ux: number, uy: number, uz: number, fx: number, fy: number, fz: number) -> (number, number, number, 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).
Parameters
rxnumber— Right axis x.rynumber— Right axis y.rznumber— Right axis z.uxnumber— Up axis x.uynumber— Up axis y.uznumber— Up axis z.fxnumber— Forward axis x.fynumber— Forward axis y.fznumber— Forward axis z.
Returns (number, number, number, number) — x, y, z, w of the rotation quaternion.
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)
globals/Transform/quatFromYaw
Transform.quatFromYaw(yaw: number) -> (number, number, number, 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.
Parameters
yawnumber— Rotation in radians around the Y axis.
Returns (number, number, number, number) — Four numbers qx, qy, qz, qw.
local qx, qy, qz, qw = Transform.quatFromYaw(math.pi / 2)
globals/Transform/quatFromYawPitch
Transform.quatFromYawPitch(yaw: number, pitch: number) -> (number, number, number, number)
Create quaternion from yaw and pitch in radians.
Parameters
yawnumber— Y-axis rotation in radians.pitchnumber— X-axis rotation in radians.
Returns (number, number, number, number) — Four numbers qx, qy, qz, qw.
local qx, qy, qz, qw = Transform.quatFromYawPitch(0, math.pi / 4)
globals/Transform/quatIdentity
Transform.quatIdentity() -> (number, number, number, number)
Identity quaternion (0, 0, 0, 1).
Returns (number, number, number, number) — Four numbers qx, qy, qz, qw — the identity.
local qx, qy, qz, qw = Transform.quatIdentity()
globals/Transform/quatInverse
Transform.quatInverse(qx: number, qy: number, qz: number, qw: number) -> (number, number, number, number)
Quaternion inverse. Equal to the conjugate for unit quaternions.
Parameters
qxnumber— Quaternion x.qynumber— Quaternion y.qznumber— Quaternion z.qwnumber— Quaternion w.
Returns (number, number, number, number) — Four numbers qx, qy, qz, qw — the inverse.
local ix, iy, iz, iw = Transform.quatInverse(qx, qy, qz, qw)
globals/Transform/quatMul
Transform.quatMul(ax: number, ay: number, az: number, aw: number, bx: number, by: number, bz: number, bw: number) -> (number, number, number, number)
Quaternion multiplication: returns qa * qb (composition: rotate
by qb then qa).
Parameters
axnumber— Left quat x.aynumber— Left quat y.aznumber— Left quat z.awnumber— Left quat w.bxnumber— Right quat x.bynumber— Right quat y.bznumber— Right quat z.bwnumber— Right quat w.
Returns (number, number, number, number) — Four numbers qx, qy, qz, qw — the composed quaternion.
local qx, qy, qz, qw = Transform.quatMul(ax, ay, az, aw, bx, by, bz, bw)
globals/Transform/quatRotateVec
Transform.quatRotateVec(qx: number, qy: number, qz: number, qw: number, vx: number, vy: number, vz: number) -> (number, number, number)
Rotate a 3-vector by a quaternion.
Parameters
qxnumber— Quaternion x.qynumber— Quaternion y.qznumber— Quaternion z.qwnumber— Quaternion w.vxnumber— Vector x.vynumber— Vector y.vznumber— Vector z.
Returns (number, number, number) — Three numbers — the rotated vector.
local rx, ry, rz = Transform.quatRotateVec(qx, qy, qz, qw, 1, 0, 0)
globals/Transform/quatToEuler
Transform.quatToEuler(qx: number, qy: number, qz: number, qw: number) -> (number, number, number)
Convert quaternion to (yaw, pitch, roll). Alias of euler with
the explicit name so callers don't have to remember the order.
Parameters
qxnumber— Quaternion x.qynumber— Quaternion y.qznumber— Quaternion z.qwnumber— Quaternion w.
Returns (number, number, number) — Three numbers yaw, pitch, roll (Y, X, Z rotations).
local yaw, pitch, roll = Transform.quatToEuler(qx, qy, qz, qw)
globals/Transform/readVec3
Transform.readVec3(value: Vec3Input, label: string?) -> { number }
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.
Parameters
valueVec3Input— The vector to normalize.labelstring(optional) — Name reported in the error when the value is not a vector. Defaults to "Transform".
Returns { number } — A three-element array { x, y, z }.
local v = Transform.readVec3({ x = 1, y = 2, z = 3 })
globals/Transform/slerp
Transform.slerp(ax: number, ay: number, az: number, aw: number, bx: number, by: number, bz: number, bw: number, t: number) -> (number, number, number, 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).
Parameters
axnumber— Start quaternion x.aynumber— Start quaternion y.aznumber— Start quaternion z.awnumber— Start quaternion w.bxnumber— End quaternion x.bynumber— End quaternion y.bznumber— End quaternion z.bwnumber— End quaternion w.tnumber— Interpolation factor[0, 1].
Returns (number, number, number, number) — Four numbers qx, qy, qz, qw — the interpolated unit quaternion.
local qx, qy, qz, qw = Transform.slerp(0, 0, 0, 1, 1, 0, 0, 0, 0.5)
globals/Transform/snapVec3
Transform.snapVec3(v: { number }, step: number | Vec3Input) -> { number }
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.
Parameters
v{ number }— The vector to quantize, as{ x, y, z }.stepnumber | Vec3Input— Uniform step size, or a per-axis vector of step sizes.
Returns { number } — A three-element array { x, y, z } snapped to the step grid.
local v = Transform.snapVec3({ 1.4, 2.6, -0.4 }, 1)
globals/Transform/toQuaternion
Transform.toQuaternion(rotation: any?, label: string?) -> { number }
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.
Parameters
rotationany(optional) — The rotation to normalize, in any form of theRotationInputunion.labelstring(optional) — Name reported in the error when the value is not a rotation. Defaults to "Transform".
Returns { number } — A four-element array { qx, qy, qz, qw }.
local q = Transform.toQuaternion({ pitch = 0, yaw = 90, roll = 0 })
globals/Transform/tryQuaternion
Transform.tryQuaternion(rotation: any?, label: string?) -> ({ number }?, 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.
Parameters
rotationany(optional) — The value to read as a rotation.labelstring(optional) — Name reported in the message. Defaults to "Transform".
Returns ({ number }?, string?) — The quaternion { qx, qy, qz, qw }, or nil and the message.
local q, why = Transform.tryQuaternion(value, "myTool")
globals/Transform/vec/add
Transform.vec.add(ax: number, ay: number, az: number, bx: number, by: number, bz: number) -> (number, number, number)
Component-wise vec3 addition.
Parameters
axnumber— First vector x.aynumber— First vector y.aznumber— First vector z.bxnumber— Second vector x.bynumber— Second vector y.bznumber— Second vector z.
Returns (number, number, number) — Three numbers — the sum.
local x, y, z = Transform.vec.add(1, 2, 3, 4, 5, 6)
globals/Transform/vec/cross
Transform.vec.cross(ax: number, ay: number, az: number, bx: number, by: number, bz: number) -> (number, number, number)
Cross product a x b.
Parameters
axnumber— First vector x.aynumber— First vector y.aznumber— First vector z.bxnumber— Second vector x.bynumber— Second vector y.bznumber— Second vector z.
Returns (number, number, number) — Three numbers cx, cy, cz — the cross product.
local cx, cy, cz = Transform.vec.cross(1, 0, 0, 0, 1, 0)
globals/Transform/vec/dot
Transform.vec.dot(ax: number, ay: number, az: number, bx: number, by: number, bz: number) -> number
Dot product of two vec3s.
Parameters
axnumber— First vector x.aynumber— First vector y.aznumber— First vector z.bxnumber— Second vector x.bynumber— Second vector y.bznumber— Second vector z.
Returns number — The scalar dot product.
local d = Transform.vec.dot(1, 0, 0, 0, 1, 0)
globals/Transform/vec/length
Transform.vec.length(x: number, y: number, z: number) -> number
Euclidean length of a vec3.
Parameters
xnumber— Vector x.ynumber— Vector y.znumber— Vector z.
Returns number — The length.
local len = Transform.vec.length(1, 2, 3)
globals/Transform/vec/normalize
Transform.vec.normalize(x: number, y: number, z: number) -> (number, number, number)
Normalize a vec3. Returns zeros when the input is degenerate (length < 1e-8).
Parameters
xnumber— Vector x.ynumber— Vector y.znumber— Vector z.
Returns (number, number, number) — Three numbers — the unit-length vec3.
local nx, ny, nz = Transform.vec.normalize(0, 5, 0)
globals/Transform/vec/scale
Transform.vec.scale(x: number, y: number, z: number, s: number) -> (number, number, number)
Component-wise scalar multiplication of a vec3.
Parameters
xnumber— Vector x.ynumber— Vector y.znumber— Vector z.snumber— Scalar factor.
Returns (number, number, number) — Three numbers — the scaled vec3.
local x, y, z = Transform.vec.scale(1, 2, 3, 2)
globals/Transform/vec/sub
Transform.vec.sub(ax: number, ay: number, az: number, bx: number, by: number, bz: number) -> (number, number, number)
Component-wise vec3 subtraction (a - b).
Parameters
axnumber— First vector x.aynumber— First vector y.aznumber— First vector z.bxnumber— Second vector x.bynumber— Second vector y.bznumber— Second vector z.
Returns (number, number, number) — Three numbers — the difference.
local x, y, z = Transform.vec.sub(4, 5, 6, 1, 2, 3)
globals/Transform/worldToLocal
Transform.worldToLocal(px: number, py: number, pz: number, pqx: number, pqy: number, pqz: number, pqw: number, wx: number, wy: number, wz: number) -> (number, number, number)
Transform a world-space position into a parent's local space.
Parameters
pxnumber— Parent position x.pynumber— Parent position y.pznumber— Parent position z.pqxnumber— Parent rotation x.pqynumber— Parent rotation y.pqznumber— Parent rotation z.pqwnumber— Parent rotation w.wxnumber— World x.wynumber— World y.wznumber— World z.
Returns (number, number, number) — Three numbers lx, ly, lz — the local position.
local lx, ly, lz = Transform.worldToLocal(px, py, pz, pqx, pqy, pqz, pqw, wx, wy, wz)
modules/Transform/README
Transform (global)
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.
Also available as global: Transform
modules/Transform/direction
direction(fromX: number, fromY: number, fromZ: number, toX: number, toY: number, toZ: number): (number, number, number)
Normalized direction vector from point A to point B. Returns zeros when the two points coincide (within ~0.001 units).
Parameters
fromXnumber— From x.fromYnumber— From y.fromZnumber— From z.toXnumber— To x.toYnumber— To y.toZnumber— To z.
local dx, dy, dz = Transform.direction(0, 0, 0, 1, 0, 0)
modules/Transform/directionBetween
directionBetween(entityA: string | EntityRef, entityB: string | EntityRef): (number, number, number)
Normalized world-space direction from one entity to another, read from their world positions. Returns zeros if either entity can't be resolved.
Parameters
entityAstring | EntityRef— Source entity (id string or proxy).entityBstring | EntityRef— Target entity (id string or proxy).
local dx, dy, dz = Transform.directionBetween("cam", "target")
modules/Transform/distance
distance(x1: number, y1: number, z1: number, x2: number, y2: number, z2: number): number
Euclidean distance between two world-space positions.
Parameters
x1number— First point x.y1number— First point y.z1number— First point z.x2number— Second point x.y2number— Second point y.z2number— Second point z.
local d = Transform.distance(0, 0, 0, 1, 1, 1)
modules/Transform/distanceBetween
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.
Parameters
entityAstring | EntityRef— First entity (id string or proxy).entityBstring | EntityRef— Second entity (id string or proxy).
local d = Transform.distanceBetween("cam", "box")
modules/Transform/euler
euler(qx: number, qy: number, qz: number, qw: number): (number, number, number)
Convert quaternion to euler angles (yaw, pitch, roll) in radians.
Parameters
qxnumber— Quaternion x.qynumber— Quaternion y.qznumber— Quaternion z.qwnumber— Quaternion w.
local yaw, pitch, roll = Transform.euler(0, 0, 0, 1)
modules/Transform/eulerToQuat
eulerToQuat(yaw: number, pitch: number?, roll: number?): (number, number, number, 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).
Parameters
yawnumber— Y-axis rotation in radians.pitchnumber?(optional) — X-axis rotation in radians. Defaults to 0.rollnumber?(optional) — Z-axis rotation in radians. Defaults to 0.
local qx, qy, qz, qw = Transform.eulerToQuat(math.pi / 2)
modules/Transform/lerp
lerp(ax: number, ay: number, az: number, bx: number, by: number, bz: number, t: number): (number, number, number)
Linearly interpolate between two positions.
Parameters
axnumber— Start x.aynumber— Start y.aznumber— Start z.bxnumber— End x.bynumber— End y.bznumber— End z.tnumber— Interpolation factor[0, 1].
local x, y, z = Transform.lerp(0, 0, 0, 1, 1, 1, 0.5)
modules/Transform/lerp1
lerp1(a: number, b: number, t: number): number
Linearly interpolate two scalars.
Parameters
anumber— Start value.bnumber— End value.tnumber— Interpolation factor[0, 1].
local v = Transform.lerp1(0, 10, 0.5)
modules/Transform/lerpAngle
lerpAngle(a: number, b: number, t: number): number
Lerp between two angles via the shortest arc; returns a value in [-pi, pi].
Parameters
anumber— Start angle in radians.bnumber— End angle in radians.tnumber— Interpolation factor[0, 1].
local a = Transform.lerpAngle(0, math.pi, 0.5)
modules/Transform/localToWorld
localToWorld(px: number, py: number, pz: number, pqx: number, pqy: number, pqz: number, pqw: number, lx: number, ly: number, lz: number): (number, number, number)
Transform a local-space position into world space using a parent pose.
Parameters
pxnumber— Parent position x.pynumber— Parent position y.pznumber— Parent position z.pqxnumber— Parent rotation x.pqynumber— Parent rotation y.pqznumber— Parent rotation z.pqwnumber— Parent rotation w.lxnumber— Local x.lynumber— Local y.lznumber— Local z.
local wx, wy, wz = Transform.localToWorld(px, py, pz, pqx, pqy, pqz, pqw, lx, ly, lz)
modules/Transform/lookAt
lookAt(entityOrId: string | EntityRef, txOrTarget: any, ty: any?, tz: number?, up: any?): (boolean, string?)
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.
Parameters
entityOrIdstring | EntityRef— Entity id, name, or proxy for the entity to rotate.txOrTargetany(optional) — A number (world x), a point table, or an entity id / name / proxy whose world position is resolved as the look-at target.tyany?(optional) — World y of the target. Omitted whentxOrTargetis a point or an entity.tznumber?(optional) — World z of the target. Omitted whentxOrTargetis a point or an entity.upany?(optional) — Optional world up hint deciding the roll —{ x, y, z },{ 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.
Transform.lookAt("cam", 0, 1, 0)
Transform.lookAt("cam", "box") -- resolve target entity position
Transform.lookAt(cam, box) -- entity proxies for both
Transform.lookAt("cam", { 0, 1, 0 }) -- one point table
Transform.lookAt("cam", "box", { 0, 0, 1 }) -- rolled to a +Z up
modules/Transform/lookAtQuat
lookAtQuat(fx: number, fy: number, fz: number, tx: number, ty: number, tz: number): (number?, number?, number?, 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.
Parameters
fxnumber— Origin x.fynumber— Origin y.fznumber— Origin z.txnumber— Target x.tynumber— Target y.tznumber— Target z.
local qx, qy, qz, qw = Transform.lookAtQuat(0, 0, 0, 1, 0, 1)
modules/Transform/lookRotation
lookRotation(fx: number, fy: number, fz: number, tx: number, ty: number, tz: number, ux: number?, uy: number?, uz: number?): (number?, number?, number?, 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.
Parameters
fxnumber— Eye x — where the entity stands.fynumber— Eye y.fznumber— Eye z.txnumber— Target x — the world point it faces.tynumber— Target y.tznumber— Target z.uxnumber?(optional) — Up hint x. World +Y when the hint is omitted.uynumber?(optional) — Up hint y.uznumber?(optional) — Up hint z.
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) }
modules/Transform/normalizeAngle
normalizeAngle(a: number): number
Normalize an angle into [-pi, pi].
Parameters
anumber— The angle in radians.
local a = Transform.normalizeAngle(3 * math.pi)
modules/Transform/orbit
orbit(centerX: number, centerY: number, centerZ: number, radius: number, height: number, angle: number): (number, number, number, number, number, number, number)
Position + rotation for orbiting around a center point. Returns the world position followed by the orientation that faces the center.
Parameters
centerXnumber— Center x.centerYnumber— Center y.centerZnumber— Center z.radiusnumber— Horizontal distance from the center.heightnumber— Vertical offset fromcenterY.anglenumber— Orbital angle in radians.
local x, y, z, qx, qy, qz, qw = Transform.orbit(0, 1, 0, 5, 2, t)
modules/Transform/quatFromAxisAngle
quatFromAxisAngle(ax: number, ay: number, az: number, angle: number): (number, number, number, number)
Create quaternion from axis and angle (radians). Returns the identity quaternion when the axis is degenerate (length < 0.001).
Parameters
axnumber— Axis x.aynumber— Axis y.aznumber— Axis z.anglenumber— Rotation angle in radians.
local qx, qy, qz, qw = Transform.quatFromAxisAngle(0, 1, 0, math.pi)
modules/Transform/quatFromBasis
quatFromBasis(rx: number, ry: number, rz: number, ux: number, uy: number, uz: number, fx: number, fy: number, fz: number): (number, number, number, 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).
Parameters
rxnumber— Right axis x.rynumber— Right axis y.rznumber— Right axis z.uxnumber— Up axis x.uynumber— Up axis y.uznumber— Up axis z.fxnumber— Forward axis x.fynumber— Forward axis y.fznumber— Forward axis z.
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)
modules/Transform/quatFromYaw
quatFromYaw(yaw: number): (number, number, number, 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.
Parameters
yawnumber— Rotation in radians around the Y axis.
local qx, qy, qz, qw = Transform.quatFromYaw(math.pi / 2)
modules/Transform/quatFromYawPitch
quatFromYawPitch(yaw: number, pitch: number): (number, number, number, number)
Create quaternion from yaw and pitch in radians.
Parameters
yawnumber— Y-axis rotation in radians.pitchnumber— X-axis rotation in radians.
local qx, qy, qz, qw = Transform.quatFromYawPitch(0, math.pi / 4)
modules/Transform/quatIdentity
quatIdentity(): (number, number, number, number)
Identity quaternion (0, 0, 0, 1).
local qx, qy, qz, qw = Transform.quatIdentity()
modules/Transform/quatInverse
quatInverse(qx: number, qy: number, qz: number, qw: number): (number, number, number, number)
Quaternion inverse. Equal to the conjugate for unit quaternions.
Parameters
qxnumber— Quaternion x.qynumber— Quaternion y.qznumber— Quaternion z.qwnumber— Quaternion w.
local ix, iy, iz, iw = Transform.quatInverse(qx, qy, qz, qw)
modules/Transform/quatMul
quatMul(ax: number, ay: number, az: number, aw: number, bx: number, by: number, bz: number, bw: number): (number, number, number, number)
Quaternion multiplication: returns qa * qb (composition: rotate
by qb then qa).
Parameters
axnumber— Left quat x.aynumber— Left quat y.aznumber— Left quat z.awnumber— Left quat w.bxnumber— Right quat x.bynumber— Right quat y.bznumber— Right quat z.bwnumber— Right quat w.
local qx, qy, qz, qw = Transform.quatMul(ax, ay, az, aw, bx, by, bz, bw)
modules/Transform/quatRotateVec
quatRotateVec(qx: number, qy: number, qz: number, qw: number, vx: number, vy: number, vz: number): (number, number, number)
Rotate a 3-vector by a quaternion.
Parameters
qxnumber— Quaternion x.qynumber— Quaternion y.qznumber— Quaternion z.qwnumber— Quaternion w.vxnumber— Vector x.vynumber— Vector y.vznumber— Vector z.
local rx, ry, rz = Transform.quatRotateVec(qx, qy, qz, qw, 1, 0, 0)
modules/Transform/quatToEuler
quatToEuler(qx: number, qy: number, qz: number, qw: number): (number, number, number)
Convert quaternion to (yaw, pitch, roll). Alias of euler with
the explicit name so callers don't have to remember the order.
Parameters
qxnumber— Quaternion x.qynumber— Quaternion y.qznumber— Quaternion z.qwnumber— Quaternion w.
local yaw, pitch, roll = Transform.quatToEuler(qx, qy, qz, qw)
modules/Transform/readVec3
readVec3(value: Vec3Input, label: string?): { number }
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.
Parameters
valueVec3Input— The vector to normalize.labelstring?(optional) — Name reported in the error when the value is not a vector. Defaults to "Transform".
local v = Transform.readVec3({ x = 1, y = 2, z = 3 })
modules/Transform/slerp
slerp(ax: number, ay: number, az: number, aw: number, bx: number, by: number, bz: number, bw: number, t: number): (number, number, number, 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).
Parameters
axnumber— Start quaternion x.aynumber— Start quaternion y.aznumber— Start quaternion z.awnumber— Start quaternion w.bxnumber— End quaternion x.bynumber— End quaternion y.bznumber— End quaternion z.bwnumber— End quaternion w.tnumber— Interpolation factor[0, 1].
local qx, qy, qz, qw = Transform.slerp(0, 0, 0, 1, 1, 0, 0, 0, 0.5)
modules/Transform/snapVec3
snapVec3(v: { number }, step: number | Vec3Input): { number }
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.
Parameters
v{ number }— The vector to quantize, as{ x, y, z }.stepnumber | Vec3Input— Uniform step size, or a per-axis vector of step sizes.
local v = Transform.snapVec3({ 1.4, 2.6, -0.4 }, 1)
modules/Transform/toQuaternion
toQuaternion(rotation: any, label: string?): { number }
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.
Parameters
rotationany(optional) — The rotation to normalize, in any form of theRotationInputunion.labelstring?(optional) — Name reported in the error when the value is not a rotation. Defaults to "Transform".
local q = Transform.toQuaternion({ pitch = 0, yaw = 90, roll = 0 })
modules/Transform/tryQuaternion
tryQuaternion(rotation: any, label: string?): ({ number }?, 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.
Parameters
rotationany(optional) — The value to read as a rotation.labelstring?(optional) — Name reported in the message. Defaults to "Transform".
local q, why = Transform.tryQuaternion(value, "myTool")
modules/Transform/vec.add
vec.add(ax: number, ay: number, az: number, bx: number, by: number, bz: number): (number, number, number)
Component-wise vec3 addition.
Parameters
axnumber— First vector x.aynumber— First vector y.aznumber— First vector z.bxnumber— Second vector x.bynumber— Second vector y.bznumber— Second vector z.
local x, y, z = Transform.vec.add(1, 2, 3, 4, 5, 6)
modules/Transform/vec.cross
vec.cross(ax: number, ay: number, az: number, bx: number, by: number, bz: number): (number, number, number)
Cross product a x b.
Parameters
axnumber— First vector x.aynumber— First vector y.aznumber— First vector z.bxnumber— Second vector x.bynumber— Second vector y.bznumber— Second vector z.
local cx, cy, cz = Transform.vec.cross(1, 0, 0, 0, 1, 0)
modules/Transform/vec.dot
vec.dot(ax: number, ay: number, az: number, bx: number, by: number, bz: number): number
Dot product of two vec3s.
Parameters
axnumber— First vector x.aynumber— First vector y.aznumber— First vector z.bxnumber— Second vector x.bynumber— Second vector y.bznumber— Second vector z.
local d = Transform.vec.dot(1, 0, 0, 0, 1, 0)
modules/Transform/vec.length
vec.length(x: number, y: number, z: number): number
Euclidean length of a vec3.
Parameters
xnumber— Vector x.ynumber— Vector y.znumber— Vector z.
local len = Transform.vec.length(1, 2, 3)
modules/Transform/vec.normalize
vec.normalize(x: number, y: number, z: number): (number, number, number)
Normalize a vec3. Returns zeros when the input is degenerate (length < 1e-8).
Parameters
xnumber— Vector x.ynumber— Vector y.znumber— Vector z.
local nx, ny, nz = Transform.vec.normalize(0, 5, 0)
modules/Transform/vec.scale
vec.scale(x: number, y: number, z: number, s: number): (number, number, number)
Component-wise scalar multiplication of a vec3.
Parameters
xnumber— Vector x.ynumber— Vector y.znumber— Vector z.snumber— Scalar factor.
local x, y, z = Transform.vec.scale(1, 2, 3, 2)
modules/Transform/vec.sub
vec.sub(ax: number, ay: number, az: number, bx: number, by: number, bz: number): (number, number, number)
Component-wise vec3 subtraction (a - b).
Parameters
axnumber— First vector x.aynumber— First vector y.aznumber— First vector z.bxnumber— Second vector x.bynumber— Second vector y.bznumber— Second vector z.
local x, y, z = Transform.vec.sub(4, 5, 6, 1, 2, 3)
modules/Transform/worldToLocal
worldToLocal(px: number, py: number, pz: number, pqx: number, pqy: number, pqz: number, pqw: number, wx: number, wy: number, wz: number): (number, number, number)
Transform a world-space position into a parent's local space.
Parameters
pxnumber— Parent position x.pynumber— Parent position y.pznumber— Parent position z.pqxnumber— Parent rotation x.pqynumber— Parent rotation y.pqznumber— Parent rotation z.pqwnumber— Parent rotation w.wxnumber— World x.wynumber— World y.wznumber— World z.
local lx, ly, lz = Transform.worldToLocal(px, py, pz, pqx, pqy, pqz, pqw, wx, wy, wz)