Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit3bb1da2647

README.md

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:

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

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