tiramisu/transform
Transform module - position, rotation, and scale for 3D objects.
Transforms define where objects are in 3D space, how they’re rotated, and their size. All transforms are immutable - functions return new transforms instead of modifying existing ones.
Internally, rotations are stored as quaternions for better interpolation and to avoid gimbal lock. You can create transforms with either Euler angles or quaternions.
Quick Example
import tiramisu/transform
import vec/vec3
// Create with Euler angles (most common)
let player_transform = transform.identity
|> transform.with_position(vec3.Vec3(0.0, 1.0, 0.0))
|> transform.with_euler_rotation(vec3.Vec3(0.0, 1.57, 0.0)) // 90 degrees in radians
|> transform.with_scale(vec3.Vec3(1.0, 2.0, 1.0)) // Tall player
// Or create with quaternion rotation
let quat = transform.Quaternion(0.0, 0.707, 0.0, 0.707)
let rotated = transform.identity |> transform.with_quaternion_rotation(quat)
Types
Quaternion represents a rotation in 3D space.
Quaternions are a mathematical representation that avoids gimbal lock and provides smooth interpolation between rotations.
pub type Quaternion {
Quaternion(x: Float, y: Float, z: Float, w: Float)
}
Constructors
-
Quaternion(x: Float, y: Float, z: Float, w: Float)
Transform represents the position, rotation, and scale of an object in 3D space.
- position: World-space coordinates (x, y, z)
- rotation: Quaternion rotation (stored internally, accessible as Euler angles)
- scale: Size multiplier per axis (1.0 = original size)
This type is opaque to ensure rotation consistency and proper quaternion handling.
pub opaque type Transform
Values
pub fn at(position position: vec3.Vec3(Float)) -> Transform
Create a transform at a specific position with default rotation and scale.
Example
let t = transform.at(position: vec3.Vec3(5.0, 0.0, -3.0))
// Object positioned at (5, 0, -3)
pub fn compose(first: Transform, second: Transform) -> Transform
Compose two transforms (apply second transform after first).
Useful for relative transformations. Combines positions, multiplies quaternions
for rotation, and multiplies scales. For proper hierarchical transforms,
use scene Group nodes instead.
Example
let base = transform.at(vec3.Vec3(5.0, 0.0, 0.0))
let offset = transform.at(vec3.Vec3(0.0, 2.0, 0.0))
let combined = transform.compose(base, offset)
// position: (5.0, 2.0, 0.0)
pub fn euler_to_quaternion(euler: vec3.Vec3(Float)) -> Quaternion
Convert Euler angles to a quaternion using Three.js’s conversion.
Uses XYZ rotation order matching Three.js default.
- euler.x = rotation around X axis (radians)
- euler.y = rotation around Y axis (radians)
- euler.z = rotation around Z axis (radians)
pub const identity: Transform
Create an identity transform (position at origin, no rotation, scale 1).
Example
let t = transform.identity
// position: (0, 0, 0), rotation: quaternion identity, scale: (1, 1, 1)
pub fn lerp(
from: Transform,
to to: Transform,
with t: Float,
) -> Transform
Linearly interpolate between two transforms.
Uses linear interpolation for position and scale, and spherical linear interpolation (slerp) for rotation to ensure smooth rotation transitions.
Parameter t should be between 0.0 and 1.0:
t = 0.0returnsfromt = 1.0returnstot = 0.5returns halfway between
Example
let start = transform.at(vec3.Vec3(0.0, 0.0, 0.0))
let end = transform.at(vec3.Vec3(10.0, 0.0, 0.0))
let halfway = transform.lerp(start, to: end, with: 0.5)
// position: (5.0, 0.0, 0.0)
pub fn look_at(
from from: Transform,
to to: Transform,
up up: option.Option(vec3.Vec3(Float)),
) -> Transform
Create a transform that looks at a target position from a source position.
Calculates the rotation needed to point from from towards to.
Example
let camera_pos = transform.at(vec3.Vec3(0.0, 5.0, 10.0))
let target_pos = transform.at(vec3.Vec3(0.0, 0.0, 0.0))
let look_transform = transform.look_at(from: camera_pos, to: target_pos, up: option.None)
// Camera now faces the origin
pub fn multiply_quaternions(
q1: Quaternion,
q2: Quaternion,
) -> Quaternion
Multiply two quaternions (q1 * q2) using Three.js.
Represents the combined rotation of applying q1 then q2. This is useful for combining rotations, such as applying a billboard rotation followed by a camera roll adjustment.
Example
let rotation1 = transform.euler_to_quaternion(Vec3(0.0, 1.57, 0.0))
let rotation2 = transform.euler_to_quaternion(Vec3(0.5, 0.0, 0.0))
let combined = transform.multiply_quaternions(rotation1, rotation2)
pub fn position(transform: Transform) -> vec3.Vec3(Float)
Get the position of a transform.
Example
let pos = transform.position(my_transform)
// Returns vec3.Vec3(x, y, z)
pub fn quaternion_from_basis(
x_axis: vec3.Vec3(Float),
y_axis: vec3.Vec3(Float),
z_axis: vec3.Vec3(Float),
) -> Quaternion
Build a quaternion from three orthonormal basis vectors. Uses Three.js’s Matrix4.makeBasis for proper quaternion construction.
pub fn quaternion_to_euler(q: Quaternion) -> vec3.Vec3(Float)
Convert a quaternion to Euler angles.
Returns angles in radians using XYZ rotation order matching Three.js default.
pub fn rotate_by(
transform: Transform,
euler: vec3.Vec3(Float),
) -> Transform
Rotate a transform by applying an additional rotation (relative rotation).
Converts the Euler angle rotation to a quaternion and multiplies it with the current rotation.
Example
let t = transform.identity
|> transform.rotate_by(vec3.Vec3(0.0, 1.57, 0.0)) // Turn 90° right
|> transform.rotate_by(vec3.Vec3(0.0, 1.57, 0.0)) // Turn another 90° right
// Now facing backward
pub fn rotate_x(transform: Transform, angle: Float) -> Transform
Rotate around the X axis (pitch/look up-down).
Example
let t = transform.identity
|> transform.rotate_x(0.5) // Look up slightly
pub fn rotate_y(transform: Transform, angle: Float) -> Transform
Rotate around the Y axis (yaw/turn left-right).
Example
let t = transform.identity
|> transform.rotate_y(1.57) // Turn 90° right
pub fn rotate_z(transform: Transform, angle: Float) -> Transform
Rotate around the Z axis (roll/tilt left-right).
Example
let t = transform.identity
|> transform.rotate_z(0.3) // Tilt right
pub fn rotation(transform: Transform) -> vec3.Vec3(Float)
Get the rotation of a transform as Euler angles (in radians).
Returns rotation as Vec3(x_rotation, y_rotation, z_rotation) in radians.
Example
let euler = transform.rotation(my_transform)
// Returns vec3.Vec3(x_rotation, y_rotation, z_rotation)
pub fn rotation_quaternion(transform: Transform) -> Quaternion
Get the rotation of a transform as a quaternion.
Example
let quat = transform.rotation_quaternion(my_transform)
// Returns Quaternion(x, y, z, w)
pub fn scale(transform: Transform) -> vec3.Vec3(Float)
Get the scale of a transform.
Example
let scale = transform.scale(my_transform)
// Returns vec3.Vec3(x, y, z)
pub fn scale_by(
transform: Transform,
scale_factor: vec3.Vec3(Float),
) -> Transform
Scale a transform by multiplying its current scale (relative scaling).
Example
let t = transform.identity
|> transform.scale_by(vec3.Vec3(2.0, 1.0, 2.0))
|> transform.scale_by(vec3.Vec3(2.0, 1.0, 1.0))
// scale: (4.0, 1.0, 2.0)
pub fn scale_uniform(
transform: Transform,
scale: Float,
) -> Transform
Set uniform scale on all axes (width = height = depth).
Example
let t = transform.identity
|> transform.scale_uniform(2.0)
// scale: (2.0, 2.0, 2.0) - twice as big in all dimensions
pub fn slerp(
from from: Quaternion,
to to: Quaternion,
amount amount: Float,
) -> Quaternion
Spherically interpolate between two quaternions.
Performs smooth interpolation between two rotations using spherical linear interpolation (slerp). This produces the smoothest possible rotation between two orientations.
- from: Starting rotation quaternion
- to: Target rotation quaternion
- amount: Interpolation factor (0.0 = from, 1.0 = to)
Example
let start_rotation = transform.euler_to_quaternion(Vec3(0.0, 0.0, 0.0))
let end_rotation = transform.euler_to_quaternion(Vec3(0.0, 1.57, 0.0))
// Rotate 50% of the way
let mid_rotation = transform.slerp(from: start_rotation, to: end_rotation, amount: 0.5)
pub fn translate(
transform: Transform,
by offset: vec3.Vec3(Float),
) -> Transform
Move a transform by adding to its current position (relative movement).
Example
let t = transform.at(vec3.Vec3(5.0, 0.0, 0.0))
|> transform.translate_by(vec3.Vec3(2.0, 1.0, 0.0))
// position: (7.0, 1.0, 0.0)
pub fn with_euler_rotation(
transform: Transform,
euler: vec3.Vec3(Float),
) -> Transform
Update the rotation of a transform using Euler angles (in radians).
Converts Euler angles to quaternion internally.
Example
let rotated = transform.identity
|> transform.with_euler_rotation(vec3.Vec3(0.0, 1.57, 0.0)) // 90° turn around Y axis
pub fn with_position(
transform: Transform,
position: vec3.Vec3(Float),
) -> Transform
Update the position of a transform.
Example
let moved = transform.identity
|> transform.with_position(vec3.Vec3(1.0, 2.0, 3.0))
pub fn with_quaternion_rotation(
transform: Transform,
quaternion: Quaternion,
) -> Transform
Update the rotation of a transform using a quaternion directly.
Use this when you already have a quaternion or want to avoid Euler angle conversion.
Example
let quat = transform.Quaternion(0.0, 0.707, 0.0, 0.707)
let rotated = transform.identity
|> transform.with_quaternion_rotation(quat)