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 const identity_quaternion: Quaternion

Identity quaternion

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.0 returns from
  • t = 1.0 returns to
  • t = 0.5 returns 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)
pub fn with_scale(
  transform: Transform,
  scale: vec3.Vec3(Float),
) -> Transform

Update the scale of a transform.

Example

let scaled = transform.identity
  |> transform.with_scale(vec3.Vec3(2.0, 1.0, 2.0))  // Wide and deep, normal height
Search Document