tiramisu/scene

Scene graph module - declarative 3D scene construction.

This module provides types and functions for building 3D scenes declaratively. Scenes are composed of SceneNode values that describe meshes, lights, cameras, and groups.

Core Concepts

Quick Example

import tiramisu/scene
import tiramisu/transform
import gleam/option
import vec/vec3

pub fn view(model: Model) {
  let assert Ok(geometry) = scene.box(width: 1.0, height: 1.0, depth: 1.0)
  let assert Ok(material) = scene.basic_material(color: 0xff0000, transparent: False, opacity: 1.0)

  [
    scene.Mesh(
      id: "player",
      geometry: geometry,
      material: material,
      transform: transform.at(vec3.Vec3(0.0, 1.0, 0.0)),
      physics: option.None,
    ),
    scene.Light(
      id: "sun",
      light: scene.DirectionalLight(color: 0xffffff, intensity: 1.0),
      transform: transform.identity,
    ),
  ]
}

Types

Level of Detail (LOD) configuration.

Defines which mesh to display based on camera distance. Use with LOD scene node for automatic detail switching to improve performance.

Example

scene.LOD(
  id: "tree",
  levels: [
    scene.lod_level(distance: 0.0, node: high_detail_mesh),   // 0-50 units
    scene.lod_level(distance: 50.0, node: medium_detail_mesh), // 50-100 units
    scene.lod_level(distance: 100.0, node: low_detail_mesh),   // 100+ units
  ],
  transform: transform.identity,
)
pub type LODLevel(id) {
  LODLevel(distance: Float, node: Node(id))
}

Constructors

  • LODLevel(distance: Float, node: Node(id))

Scene node - the core building block of your 3D scene.

Scene nodes are immutable, declarative descriptions of objects in your game. Each frame, your view() function returns a list of scene nodes, and the engine efficiently updates only what changed.

Node Types

  • Mesh: Standard 3D object (1 draw call per mesh)
  • InstancedMesh: Many identical objects (1 draw call total!)
  • Group: Container for organizing child nodes in a hierarchy
  • Light: Illuminates the scene
  • Camera: Defines viewpoint (must have at least one with active: True)
  • LOD: Switches detail levels based on distance
  • Model3D: Loaded 3D model with animations
  • Audio: Background or positional audio
  • Debug*: Visualization helpers for development

Example

pub fn view(model: Model) {
  [
    scene.Group(
      id: "player",
      transform: transform.at(model.position),
      children: [
        scene.Mesh(
          id: "player-body",
          geometry: scene.BoxGeometry(1.0, 2.0, 1.0),
          material: scene.BasicMaterial(0x00ff00, False, 1.0, option.None),
          transform: transform.identity,
          physics: option.Some(model.physics_body),
        ),
      ],
    ),
  ]
}
pub type Node(id) {
  Mesh(
    id: id,
    geometry: geometry.Geometry,
    material: material.Material,
    transform: transform.Transform,
    physics: option.Option(physics.RigidBody),
  )
  InstancedMesh(
    id: id,
    geometry: geometry.Geometry,
    material: material.Material,
    instances: List(transform.Transform),
  )
  Group(
    id: id,
    transform: transform.Transform,
    children: List(Node(id)),
  )
  Light(
    id: id,
    light: light.Light,
    transform: transform.Transform,
  )
  Camera(
    id: id,
    camera: camera.Camera,
    transform: transform.Transform,
    look_at: option.Option(vec3.Vec3(Float)),
    active: Bool,
    viewport: option.Option(#(Int, Int, Int, Int)),
  )
  LOD(
    id: id,
    levels: List(LODLevel(id)),
    transform: transform.Transform,
  )
  Model3D(
    id: id,
    object: object3d.Object3D,
    transform: transform.Transform,
    animation: option.Option(object3d.AnimationPlayback),
    physics: option.Option(physics.RigidBody),
  )
  InstancedModel(
    id: id,
    object: object3d.Object3D,
    instances: List(transform.Transform),
    physics: option.Option(physics.RigidBody),
  )
  Audio(id: id, audio: audio.Audio)
  Particles(
    id: id,
    emitter: particle_emitter.ParticleEmitter,
    transform: transform.Transform,
    active: Bool,
  )
  DebugBox(
    id: id,
    min: vec3.Vec3(Float),
    max: vec3.Vec3(Float),
    color: Int,
  )
  DebugSphere(
    id: id,
    center: vec3.Vec3(Float),
    radius: Float,
    color: Int,
  )
  DebugLine(
    id: id,
    from: vec3.Vec3(Float),
    to: vec3.Vec3(Float),
    color: Int,
  )
  DebugAxes(id: id, origin: vec3.Vec3(Float), size: Float)
  DebugGrid(id: id, size: Float, divisions: Int, color: Int)
  DebugPoint(
    id: id,
    position: vec3.Vec3(Float),
    size: Float,
    color: Int,
  )
}

Constructors

  • Mesh(
      id: id,
      geometry: geometry.Geometry,
      material: material.Material,
      transform: transform.Transform,
      physics: option.Option(physics.RigidBody),
    )
  • InstancedMesh(
      id: id,
      geometry: geometry.Geometry,
      material: material.Material,
      instances: List(transform.Transform),
    )

    Instanced mesh - renders many copies of the same geometry/material with 1 draw call Much more efficient than creating individual Mesh nodes for identical objects

  • Group(
      id: id,
      transform: transform.Transform,
      children: List(Node(id)),
    )
  • Light(id: id, light: light.Light, transform: transform.Transform)
  • Camera(
      id: id,
      camera: camera.Camera,
      transform: transform.Transform,
      look_at: option.Option(vec3.Vec3(Float)),
      active: Bool,
      viewport: option.Option(#(Int, Int, Int, Int)),
    )

    Camera - defines a viewpoint in the scene Only one camera can be active at a time for rendering (when viewport is None) Set viewport to render in a specific area (for picture-in-picture effects)

    Arguments

    look_at

    Optional look-at target in world space. If None, camera uses transform rotation. If Some, camera will orient itself to look at the target point.

    viewport

    Optional viewport: (x, y, width, height) in pixels If None, camera fills entire canvas (only when active=True) If Some, camera renders in specified rectangle (regardless of active state)

  • LOD(
      id: id,
      levels: List(LODLevel(id)),
      transform: transform.Transform,
    )

    Level of Detail - automatically switches between different meshes based on camera distance Levels should be ordered from closest (distance: 0.0) to farthest

  • Model3D(
      id: id,
      object: object3d.Object3D,
      transform: transform.Transform,
      animation: option.Option(object3d.AnimationPlayback),
      physics: option.Option(physics.RigidBody),
    )
  • InstancedModel(
      id: id,
      object: object3d.Object3D,
      instances: List(transform.Transform),
      physics: option.Option(physics.RigidBody),
    )

    Instanced model - renders many copies of a loaded 3D model (FBX, GLTF, etc.) Automatically creates instanced meshes for each unique mesh/material combination Much more efficient than creating individual Model3D nodes for identical models If physics is Some, creates one rigid body per instance at each instance’s transform

  • Audio(id: id, audio: audio.Audio)
  • Particles(
      id: id,
      emitter: particle_emitter.ParticleEmitter,
      transform: transform.Transform,
      active: Bool,
    )

    Particle system - spawn and animate many small particles for visual effects Particles are simulated in the FFI layer and rendered efficiently using Three.js Points

  • DebugBox(
      id: id,
      min: vec3.Vec3(Float),
      max: vec3.Vec3(Float),
      color: Int,
    )
  • DebugSphere(
      id: id,
      center: vec3.Vec3(Float),
      radius: Float,
      color: Int,
    )
  • DebugLine(
      id: id,
      from: vec3.Vec3(Float),
      to: vec3.Vec3(Float),
      color: Int,
    )
  • DebugAxes(id: id, origin: vec3.Vec3(Float), size: Float)
  • DebugGrid(id: id, size: Float, divisions: Int, color: Int)
  • DebugPoint(
      id: id,
      position: vec3.Vec3(Float),
      size: Float,
      color: Int,
    )

Values

pub fn lod_level(
  distance distance: Float,
  node node: Node(id),
) -> LODLevel(id)

Create an LOD level with a distance threshold and scene node.

Levels should be ordered from closest (distance: 0.0) to farthest.

Example

let high_detail = scene.lod_level(distance: 0.0, node: detailed_mesh)
let low_detail = scene.lod_level(distance: 100.0, node: simple_mesh)
Search Document