# transform
Math helpers for positions, rotations, and directions on transforms.
Exposed as the global `Transform` table via `--!global Transform` — no
explicit require needed in user code. Functions that take an entity
accept either an entity ID string or an entity proxy table from
`entity("id")`.
## Exports
Look-at and entity-aware helpers:
- `Transform.lookAtQuat(fx, fy, fz, tx, ty, tz) -> (qx?, qy?, qz?, qw?)` — quaternion from origin toward target. Nil when degenerate.
- `Transform.lookAt(entity, txOrTarget, ty?, tz?) -> (boolean, string?)` — make an
entity face a world position or another entity. Both slots read world space:
the subject and an entity target are read as `entity(id).position` and the aim
is written as `entity(id).rotation`, so a parent under either one still leaves
the aim on the point named. Returns whether the rotation was written, and the
reason when it was not.
- `Transform.distance(x1, y1, z1, x2, y2, z2) -> number` — Euclidean distance between two points.
- `Transform.distanceBetween(entityA, entityB) -> number?` — distance between two entities' world positions. Nil when either is unresolvable.
- `Transform.direction(fromX, fromY, fromZ, toX, toY, toZ) -> (dx, dy, dz)` — unit direction vector.
- `Transform.directionBetween(entityA, entityB) -> (dx, dy, dz)` — unit world-space direction between two entities' world positions.
Rotation shapes:
A quaternion **constructor** here returns the four components as four separate
values, so a caller either names them or braces the call to make one table:
```lua
local qx, qy, qz, qw = Transform.quatFromAxisAngle(0, 1, 0, math.rad(90))
entity("cam").localRotation = { Transform.quatFromAxisAngle(0, 1, 0, math.rad(90)) }
```
A rotation-taking **surface** reads that table through
`Transform.toQuaternion`, which also takes euler DEGREES — so
`{ qx, qy, qz, qw }`, `{ x =, y =, z =, w = }`, `{ pitch, yaw, roll }` and
`{ pitch =, yaw =, roll = }` all mean the same thing wherever a rotation is
assigned: `entity(id).rotation` / `.localRotation`, `entityOps.spawn`,
`entityOps.transform`, and the capture viewpoints.
- `Transform.toQuaternion(rotation, label?) -> { qx, qy, qz, qw }` — the shared reading of a rotation a caller wrote. Raises when the value matches no form, naming what arrived; a value that is one of the shapes a quaternion helper returns is named as such along with the packing it goes in as.
- `Transform.tryQuaternion(rotation, label?) -> ({ qx, qy, qz, qw } | nil, message?)` — the same reading without raising, for a surface that wants to raise the message at its own caller's line.
- `Transform.readVec3(value, label?) -> { x, y, z }` — the same for a vector.
- `Transform.snapVec3(v, step) -> { x, y, z }` — quantize a vector to a step grid.
Quaternion construction / conversion:
- `Transform.quatFromYaw(yaw)`, `Transform.quatFromYawPitch(yaw, pitch)`, `Transform.quatFromAxisAngle(ax, ay, az, angle)` — quaternion constructors.
- `Transform.quatIdentity()` — identity quaternion.
- `Transform.euler(qx, qy, qz, qw) -> (yaw, pitch, roll)` and the named alias `Transform.quatToEuler`.
- `Transform.eulerToQuat(yaw, pitch?, roll?)` — euler-to-quaternion in YXZ order.
Lerps and interpolation:
- `Transform.lerp(ax, ay, az, bx, by, bz, t) -> (x, y, z)` — vec3 lerp.
- `Transform.lerp1(a, b, t) -> number` — scalar lerp.
- `Transform.normalizeAngle(a) -> number` — wrap angle into `[-pi, pi]`.
- `Transform.lerpAngle(a, b, t) -> number` — shortest-arc angle lerp.
- `Transform.slerp(ax, ay, az, aw, bx, by, bz, bw, t) -> (qx, qy, qz, qw)` — quaternion slerp with shortest-path and near-parallel fallback.
Quaternion operations:
- `Transform.quatMul(...) -> (qx, qy, qz, qw)` — `qa * qb` composition.
- `Transform.quatInverse(qx, qy, qz, qw) -> (qx, qy, qz, qw)` — inverse (= conjugate for unit quats).
- `Transform.quatRotateVec(qx, qy, qz, qw, vx, vy, vz) -> (x, y, z)` — rotate a vec3 by a quaternion.
Pose helpers:
- `Transform.orbit(centerX, centerY, centerZ, radius, height, angle) -> (x, y, z, qx, qy, qz, qw)` — orbital pose facing the center.
- `Transform.worldToLocal(...)` / `Transform.localToWorld(...)` — pose-space conversions.
Nested `Transform.vec.*` namespace (component-wise vec3):
- `Transform.vec.add`, `sub`, `scale`, `dot`, `cross`, `length`, `normalize`.
Types:
- `Vec3 = { x: number, y: number, z: number }`
- `EntityRef = string | { entityId: string }`
## Usage
```luau
-- Look-at by coordinates or by target entity:
Transform.lookAt("cam", 0, 1, 0)
-- an entity target resolves to that entity's world position
local aimed, why = Transform.lookAt("cam", "box")
-- Orbit pose around a point:
local x, y, z, qx, qy, qz, qw = Transform.orbit(0, 1, 0, 5, 2, t)
entity.find("cam").localPosition = { x, y, z }
entity.find("cam").localRotation = { qx, qy, qz, qw }
-- Quaternion math:
local qx, qy, qz, qw = Transform.quatFromYawPitch(math.pi / 4, 0)
local sx, sy, sz, sw = Transform.slerp(0, 0, 0, 1, qx, qy, qz, qw, 0.5)
-- Component-wise vec3 helpers:
local nx, ny, nz = Transform.vec.normalize(1, 1, 0)
```
## Notes
- The `--!global Transform` directive promotes the module's typed
functions onto the runtime universe's globals bucket, so `Transform.*`
is available without any per-source `require`.
- Entity-aware functions (`lookAt`, `distanceBetween`,
`directionBetween`) report a missing entity or a missing transform in
their return value rather than raising: `lookAt` answers
`false, "unresolved"` / `"no-transform"` / `"incomplete-target"` /
`"degenerate"`, `distanceBetween` answers `nil`, and
`directionBetween` answers zeros.
- Quaternion APIs operate on raw `(qx, qy, qz, qw)` tuples for parity
with the entity proxy's `localRotation.get`/`set`. Use
`Transform.quatIdentity()` rather than hand-rolling `(0, 0, 0, 1)`.
- `Transform.slerp` flips the second quaternion if `dot < 0` to take
the shortest path, and falls back to lerp+normalize when the inputs
are within `dot > 0.9995` to avoid `1/0` near-parallel issues.