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
Groupnodes to create parent-child relationships. - Validation: Geometry and material constructors return
Resultto catch invalid parameters. - Performance: Use
InstancedMeshfor 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_type: scene.DirectionalLight(color: 0xffffff, intensity: 1.0),
transform: transform.identity,
),
]
}
Types
Opaque type for Three.js BufferGeometry.
Created by loading 3D models with asset.load_stl() or asset.load_model().
pub type BufferGeometry
3D geometry types supported by the engine.
Each variant represents a different primitive shape or custom geometry.
Use the validated constructor functions like box(), sphere(), etc.
pub type GeometryType {
BoxGeometry(width: Float, height: Float, depth: Float)
SphereGeometry(
radius: Float,
width_segments: Int,
height_segments: Int,
)
ConeGeometry(radius: Float, height: Float, segments: Int)
PlaneGeometry(width: Float, height: Float)
CircleGeometry(radius: Float, segments: Int)
CylinderGeometry(
radius_top: Float,
radius_bottom: Float,
height: Float,
radial_segments: Int,
)
TorusGeometry(
radius: Float,
tube: Float,
radial_segments: Int,
tubular_segments: Int,
)
TetrahedronGeometry(radius: Float, detail: Int)
IcosahedronGeometry(radius: Float, detail: Int)
CustomGeometry(BufferGeometry)
}
Constructors
-
BoxGeometry(width: Float, height: Float, depth: Float) -
SphereGeometry( radius: Float, width_segments: Int, height_segments: Int, ) -
ConeGeometry(radius: Float, height: Float, segments: Int) -
PlaneGeometry(width: Float, height: Float) -
CircleGeometry(radius: Float, segments: Int) -
CylinderGeometry( radius_top: Float, radius_bottom: Float, height: Float, radial_segments: Int, ) -
TorusGeometry( radius: Float, tube: Float, radial_segments: Int, tubular_segments: Int, ) -
TetrahedronGeometry(radius: Float, detail: Int) -
IcosahedronGeometry(radius: Float, detail: Int) -
CustomGeometry(BufferGeometry)
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 {
LODLevel(distance: Float, node: SceneNode)
}
Constructors
-
LODLevel(distance: Float, node: SceneNode)
Light types for illuminating the scene.
Different lights have different performance impacts and visual characteristics. Most games use a combination of ambient + directional for outdoor scenes, or ambient + point/spot for indoor scenes.
pub type LightType {
AmbientLight(color: Int, intensity: Float)
DirectionalLight(color: Int, intensity: Float)
PointLight(color: Int, intensity: Float, distance: Float)
SpotLight(
color: Int,
intensity: Float,
distance: Float,
angle: Float,
penumbra: Float,
)
HemisphereLight(
sky_color: Int,
ground_color: Int,
intensity: Float,
)
}
Constructors
-
AmbientLight(color: Int, intensity: Float)Global ambient light (affects all objects equally, no direction).
-
DirectionalLight(color: Int, intensity: Float)Directional light like the sun (parallel rays, infinite distance).
-
PointLight(color: Int, intensity: Float, distance: Float)Point light that radiates in all directions (like a light bulb).
-
SpotLight( color: Int, intensity: Float, distance: Float, angle: Float, penumbra: Float, )Cone-shaped spotlight (like a flashlight or stage light).
-
HemisphereLight( sky_color: Int, ground_color: Int, intensity: Float, )Hemisphere light with different colors for sky and ground (outdoor ambient).
Material types for rendering objects.
Materials define how surfaces appear when rendered. Different materials have different performance characteristics and visual properties.
Performance
BasicMaterial: Fastest, no lighting calculationsLambertMaterial,ToonMaterial: Fast, simple lightingPhongMaterial: Medium, specular highlightsStandardMaterial: Physically-based, most realistic but slower
pub type MaterialType {
BasicMaterial(
color: Int,
transparent: Bool,
opacity: Float,
map: option.Option(Texture),
)
StandardMaterial(
color: Int,
metalness: Float,
roughness: Float,
map: option.Option(Texture),
normal_map: option.Option(Texture),
)
PhongMaterial(
color: Int,
shininess: Float,
map: option.Option(Texture),
)
LambertMaterial(color: Int, map: option.Option(Texture))
ToonMaterial(color: Int, map: option.Option(Texture))
LineMaterial(color: Int, linewidth: Float)
SpriteMaterial(
color: Int,
transparent: Bool,
opacity: Float,
map: option.Option(Texture),
)
}
Constructors
-
BasicMaterial( color: Int, transparent: Bool, opacity: Float, map: option.Option(Texture), )Unlit material (no lighting calculations). Fast and useful for flat-shaded objects.
-
StandardMaterial( color: Int, metalness: Float, roughness: Float, map: option.Option(Texture), normal_map: option.Option(Texture), )Physically-based material with metalness/roughness workflow. Most realistic.
-
PhongMaterial( color: Int, shininess: Float, map: option.Option(Texture), )Shiny material with specular highlights (like plastic or ceramic).
-
LambertMaterial(color: Int, map: option.Option(Texture))Matte material (like cloth or wood). Non-shiny diffuse lighting.
-
ToonMaterial(color: Int, map: option.Option(Texture))Cartoon-style material with banded shading.
-
LineMaterial(color: Int, linewidth: Float)Material for rendering lines.
-
SpriteMaterial( color: Int, transparent: Bool, opacity: Float, map: option.Option(Texture), )Material for 2D sprites that always face the camera.
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 SceneNode {
Mesh(
id: String,
geometry: GeometryType,
material: MaterialType,
transform: transform.Transform,
physics: option.Option(physics.RigidBody),
)
InstancedMesh(
id: String,
geometry: GeometryType,
material: MaterialType,
instances: List(transform.Transform),
)
Group(
id: String,
transform: transform.Transform,
children: List(SceneNode),
)
Light(
id: String,
light_type: LightType,
transform: transform.Transform,
)
Camera(
id: String,
camera: camera.Camera,
transform: transform.Transform,
active: Bool,
viewport: option.Option(#(Int, Int, Int, Int)),
)
LOD(
id: String,
levels: List(LODLevel),
transform: transform.Transform,
)
Model3D(
id: String,
object: object3d.Object3D,
transform: transform.Transform,
animation: option.Option(object3d.AnimationPlayback),
physics: option.Option(physics.RigidBody),
)
Audio(
id: String,
buffer: audio.AudioBuffer,
config: audio.AudioConfig,
audio_type: audio.AudioType,
)
DebugBox(
id: String,
min: vec3.Vec3(Float),
max: vec3.Vec3(Float),
color: Int,
)
DebugSphere(
id: String,
center: vec3.Vec3(Float),
radius: Float,
color: Int,
)
DebugLine(
id: String,
from: vec3.Vec3(Float),
to: vec3.Vec3(Float),
color: Int,
)
DebugAxes(id: String, origin: vec3.Vec3(Float), size: Float)
DebugGrid(id: String, size: Float, divisions: Int, color: Int)
DebugPoint(
id: String,
position: vec3.Vec3(Float),
size: Float,
color: Int,
)
}
Constructors
-
Mesh( id: String, geometry: GeometryType, material: MaterialType, transform: transform.Transform, physics: option.Option(physics.RigidBody), ) -
InstancedMesh( id: String, geometry: GeometryType, material: MaterialType, 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: String, transform: transform.Transform, children: List(SceneNode), ) -
Light( id: String, light_type: LightType, transform: transform.Transform, ) -
Camera( id: String, camera: camera.Camera, transform: transform.Transform, 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) Use effect.set_active_camera(id) to switch between cameras Set viewport to render in a specific area (for picture-in-picture effects)
Arguments
- 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: String, levels: List(LODLevel), 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: String, object: object3d.Object3D, transform: transform.Transform, animation: option.Option(object3d.AnimationPlayback), physics: option.Option(physics.RigidBody), ) -
Audio( id: String, buffer: audio.AudioBuffer, config: audio.AudioConfig, audio_type: audio.AudioType, ) -
-
DebugSphere( id: String, center: vec3.Vec3(Float), radius: Float, color: Int, ) -
-
DebugAxes(id: String, origin: vec3.Vec3(Float), size: Float) -
DebugGrid(id: String, size: Float, divisions: Int, color: Int) -
DebugPoint( id: String, position: vec3.Vec3(Float), size: Float, color: Int, )
Opaque type for Three.js textures.
Created via asset.load_texture() and used in materials.
pub type Texture
Validation errors returned by geometry and material constructors.
These errors help catch invalid parameters at creation time instead of runtime.
pub type ValidationError {
InvalidDimension(String, Float)
InvalidSegmentCount(String, Int)
InvalidOpacity(Float)
InvalidMetalness(Float)
InvalidRoughness(Float)
InvalidIntensity(Float)
InvalidLinewidth(Float)
}
Constructors
-
InvalidDimension(String, Float)Dimension parameter (width, height, radius, etc.) is invalid (must be > 0)
-
InvalidSegmentCount(String, Int)Segment count parameter is invalid (typically must be >= 3)
-
InvalidOpacity(Float)Opacity must be between 0.0 and 1.0
-
InvalidMetalness(Float)Metalness must be between 0.0 and 1.0
-
InvalidRoughness(Float)Roughness must be between 0.0 and 1.0
-
InvalidIntensity(Float)Light intensity must be positive
-
InvalidLinewidth(Float)Line width must be positive
Values
pub fn basic_material(
color color: Int,
transparent transparent: Bool,
opacity opacity: Float,
) -> Result(MaterialType, ValidationError)
Create a validated basic (unlit) material.
Basic materials don’t react to lights, making them very fast to render. Opacity must be between 0.0 (fully transparent) and 1.0 (fully opaque).
Example
let assert Ok(red) = scene.basic_material(color: 0xff0000, transparent: False, opacity: 1.0)
let assert Ok(glass) = scene.basic_material(color: 0x88ccff, transparent: True, opacity: 0.5)
pub fn box(
width width: Float,
height height: Float,
depth depth: Float,
) -> Result(GeometryType, ValidationError)
Create a validated box geometry.
All dimensions must be positive (> 0).
Example
let assert Ok(cube) = scene.box(width: 1.0, height: 1.0, depth: 1.0)
let assert Ok(wall) = scene.box(width: 10.0, height: 3.0, depth: 0.1)
pub fn cylinder(
radius_top radius_top: Float,
radius_bottom radius_bottom: Float,
height height: Float,
radial_segments radial_segments: Int,
) -> Result(GeometryType, ValidationError)
Create a validated cylinder geometry.
Both radii must be non-negative, height positive, radial segments >= 3. Set one radius to 0 to create a cone shape.
Example
let assert Ok(cylinder) = scene.cylinder(radius_top: 1.0, radius_bottom: 1.0, height: 2.0, radial_segments: 32)
let assert Ok(cone) = scene.cylinder(radius_top: 0.0, radius_bottom: 1.0, height: 2.0, radial_segments: 32)
pub fn icosahedron(
radius radius: Float,
detail detail: Int,
) -> Result(GeometryType, ValidationError)
Create a validated icosahedron (20-sided polyhedron) geometry.
Detail level controls subdivision. Good for creating spheres with flat faces.
Example
let assert Ok(shape) = scene.icosahedron(radius: 1.0, detail: 2)
pub fn line_material(
color color: Int,
linewidth linewidth: Float,
) -> Result(MaterialType, ValidationError)
Create a validated line material for rendering lines.
Example
let assert Ok(line_mat) = scene.line_material(color: 0xff0000, linewidth: 2.0)
pub fn lod_level(
distance distance: Float,
node node: SceneNode,
) -> LODLevel
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)
pub fn sphere(
radius radius: Float,
width_segments width_segments: Int,
height_segments height_segments: Int,
) -> Result(GeometryType, ValidationError)
Create a validated sphere geometry.
Radius must be positive. Width segments >= 3, height segments >= 2. More segments = smoother sphere but more triangles.
Example
let assert Ok(ball) = scene.sphere(radius: 1.0, width_segments: 32, height_segments: 16)
let assert Ok(low_poly) = scene.sphere(radius: 1.0, width_segments: 8, height_segments: 6)
pub fn sprite_material(
color color: Int,
transparent transparent: Bool,
opacity opacity: Float,
) -> Result(MaterialType, ValidationError)
Create a validated sprite material for 2D billboards.
Sprites always face the camera and are useful for particles, UI elements, etc.
Example
let assert Ok(sprite_mat) = scene.sprite_material(color: 0xffffff, transparent: True, opacity: 0.8)
pub fn standard_material(
color color: Int,
metalness metalness: Float,
roughness roughness: Float,
) -> Result(MaterialType, ValidationError)
Create a validated physically-based (PBR) standard material.
Standard materials use metalness/roughness workflow for realistic rendering.
- Metalness: 0.0 = dielectric (plastic, wood), 1.0 = metal
- Roughness: 0.0 = mirror-smooth, 1.0 = completely rough
Example
let assert Ok(gold) = scene.standard_material(color: 0xffd700, metalness: 1.0, roughness: 0.3)
let assert Ok(plastic) = scene.standard_material(color: 0xff0000, metalness: 0.0, roughness: 0.5)
pub fn tetrahedron(
radius radius: Float,
detail detail: Int,
) -> Result(GeometryType, ValidationError)
Create a validated tetrahedron (4-sided polyhedron) geometry.
Detail level controls subdivision (0 = no subdivision, higher = more triangles).
Example
let assert Ok(shape) = scene.tetrahedron(radius: 1.0, detail: 0)
pub fn torus(
radius radius: Float,
tube tube: Float,
radial_segments radial_segments: Int,
tubular_segments tubular_segments: Int,
) -> Result(GeometryType, ValidationError)
Create a validated torus (donut) geometry.
Example
let assert Ok(donut) = scene.torus(radius: 2.0, tube: 0.5, radial_segments: 16, tubular_segments: 100)