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
- Immutability: Scene nodes are immutable values. Updates create new nodes.
- Hierarchy: Use
Group
nodes to create parent-child relationships. - Validation: Geometry and material constructors return
Result
to catch invalid parameters. - Performance: Use
InstancedMesh
for many identical objects (1 draw call instead of thousands).
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),
)
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), )
-
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
-
-
DebugSphere( id: id, center: vec3.Vec3(Float), radius: 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)