Skip to content

Math and Transforms

Game scripts use Lemonate's math types for positions, rotations, and coordinate conversions. All types live under engine/math/ and must be require'd.

Core types

Module Purpose
vector2 2D positions, UVs, screen coords
vector3 3D positions, directions, velocities
vector4 RGBA colors, homogeneous coords
quaternion Rotations (preferred over Euler for combining)
transform Position + rotation + scale bundle
matrix Advanced matrix math
bounds Axis-aligned bounding boxes

Creating values

local Vector3 = require 'engine/math/vector3'
local Quaternion = require 'engine/math/quaternion'
local Transform = require 'engine/math/transform'

local origin = Vector3.new(0, 0, 0)
local up = Vector3.new(0, 1, 0)
local identityRot = Quaternion.new(0, 0, 0, 1)
local t = Transform.new(Vector3.new(1, 2, 3), identityRot, Vector3.new(1, 1, 1))

-- Default transform at origin
local t2 = Transform.new()

Vector math

local a = Vector3.new(1, 0, 0)
local b = Vector3.new(0, 1, 0)
local sum = a:add(b)
local scaled = a:mul(2)
local len = a:magnitude()
local norm = a:normalized()
local dot = Vector3.dot(a, b)

Check the Vector3 reference for the full method list.

Working with SceneObject transforms

Prefer withTransform when mutating a scene object's local transform:

self.node:withTransform(function(t)
    t.position:add(Vector3.new(0, 0.1, 0))
end)

Read world space when needed (AI, camera-relative movement):

local world = self.node:getWorldTransform()
local forward = self.node:getForward()

Frame-rate independent movement -----------------------------

Use deltaTimeMs from Lifecycle Methods:

function Mover:update(deltaTimeMs)
    local dt = deltaTimeMs / 1000
    self.node:translate(self.speed * dt, 0, 0)
end

deltaTimeSmoothedMs averages over ~20 frames — useful for camera smoothing, less ideal for precise physics.

Rotations

Combine rotations with quaternions; avoid accumulating Euler angles each frame unless you understand gimbal limitations:

local Quaternion = require 'engine/math/quaternion'
local rot = Quaternion.fromEuler(0, 90, 0)  -- degrees, if available on API

See Quaternion.

Bounds and picking

Bounds helps with coarse collision and culling checks. See Bounds.

Noise

Procedural height or motion can use Perlin noise:

local Perlin = require 'engine/math/perlin'

Further reading