tiramisu/model
3D Model loading and manipulation.
This module provides functions for loading 3D models in various formats (GLTF, OBJ, FBX) and extracting their components like scenes and animations.
Supported Formats
- GLTF/GLB (recommended) - Modern, efficient, well-supported format
- OBJ - Simple mesh format, no animations
- FBX - Common in game development, supports animations
Example
import tiramisu/model
import gleam/javascript/promise
// Load a GLTF model
use result <- promise.await(model.load_gltf("/models/character.glb"))
case result {
Ok(gltf) -> {
// Get the scene (root Object3D)
let scene = model.get_scene(gltf)
// Get animations if any
let clips = model.get_animations(gltf)
}
Error(Nil) -> io.println("Failed to load model")
}
Types
Configuration for a single animation clip.
Use new_animation() to create an animation with defaults, then use the builder
functions (set_loop(), set_speed(), etc.) to configure it.
pub type Animation {
Animation(
clip: savoiardi.AnimationClip,
loop: LoopMode,
speed: Float,
weight: Float,
)
}
Constructors
-
Animation( clip: savoiardi.AnimationClip, loop: LoopMode, speed: Float, weight: Float, )Arguments
- speed
-
Playback speed multiplier (1.0 = normal, 2.0 = double speed, 0.5 = half speed)
- weight
-
Animation weight for blending (0.0 to 1.0, where 1.0 = full influence)
Animation playback configuration for a Model scene node.
You can play a single animation or blend between two animations for smooth transitions.
pub type AnimationPlayback {
SingleAnimation(Animation)
BlendedAnimations(
from: Animation,
to: Animation,
blend_factor: Float,
)
}
Constructors
-
SingleAnimation(Animation)Play a single animation
-
Blend between two animations with a blend factor (0.0 = fully ‘from’, 1.0 = fully ‘to’)
pub type Clip =
savoiardi.AnimationClip
Loaded FBX model data.
Contains the scene graph and animations. FBX is commonly used in game development tools like Unity and Unreal Engine.
pub type FBXData =
savoiardi.FBXData
Loaded GLTF/GLB model data.
Contains the scene graph, animations, cameras, and other assets. GLTF is the recommended format for 3D models on the web.
pub type GLTFData =
savoiardi.GLTFData
Controls how an animation loops.
pub type LoopMode {
LoopOnce
LoopRepeat
}
Constructors
-
LoopOncePlay the animation once and stop at the end
-
LoopRepeatPlay the animation repeatedly in a loop
pub type Object3D =
savoiardi.Object3D
Values
pub fn apply_texture(
object: savoiardi.Object3D,
tex: savoiardi.Texture,
filter_mode: texture.FilterMode,
) -> Nil
Apply a texture to all meshes in an Object3D hierarchy.
This is useful for loaded models that reference external textures, or when you want to override the model’s textures.
Example
let floor = model.get_fbx_scene(loaded_fbx)
model.apply_texture(floor, dungeon_texture, texture.NearestFilter)
pub fn center_object(
object: savoiardi.Object3D,
) -> savoiardi.Object3D
Center an Object3D so its geometric center is at the origin.
Computes the bounding box of the entire object hierarchy and adjusts all children’s positions so the center of the bounding box is at (0, 0, 0).
This is useful for loaded models (FBX, GLTF, OBJ) where the origin may not be at the geometric center of the mesh.
Example
let fbx_data = model.get_fbx_scene(loaded_fbx)
let centered = model.center_object(fbx_data)
// Now the model's center is at (0, 0, 0)
Notes
- This mutates the object in place and returns it for convenience
- The children’s positions are adjusted, not the root object’s position
pub fn clip_duration(
clip: savoiardi.AnimationClip,
) -> duration.Duration
Get the duration of an animation clip.
The duration is in seconds.
Example
let anim = model.new_animation(walk_clip)
let duration = model.clip_duration(walk_clip)
// Use this to sync game events with animation timing
pub fn clip_name(clip: savoiardi.AnimationClip) -> String
Get the name of an animation clip.
Useful for finding specific animations by name when loading from GLTF files.
Example
import gleam/list
import tiramisu/model
// After loading a GLTF model
let clips = model.get_animations(gltf_data)
let walk_clip = list.find(clips, fn(clip) {
model.clip_name(clip) == "Walk"
})
pub fn get_animations(
gltf: savoiardi.GLTFData,
) -> List(savoiardi.AnimationClip)
Get all animation clips from loaded GLTF data.
Returns an empty list if the model has no animations.
Example
let clips = model.get_animations(gltf_data)
// Use clips with model.new_animation() and scene.object_3d()
pub fn get_cameras(
gltf: savoiardi.GLTFData,
) -> List(savoiardi.Object3D)
Get all cameras from loaded GLTF data.
GLTF files can include camera definitions. Returns an empty list if the model has no cameras.
pub fn get_fbx_animations(
fbx: savoiardi.FBXData,
) -> List(savoiardi.AnimationClip)
Get all animation clips from loaded FBX data.
Returns an empty list if the model has no animations.
Example
let clips = model.get_fbx_animations(fbx_data)
// Use clips with model.new_animation() and scene.object_3d()
pub fn get_fbx_scene(
fbx: savoiardi.FBXData,
) -> savoiardi.Object3D
Get the root scene (Object3D) from loaded FBX data.
FBX data is itself an Object3D (THREE.Group), so this just returns it.
Example
let scene = model.get_fbx_scene(fbx_data)
// Use with scene.model() node
pub fn get_scene(gltf: savoiardi.GLTFData) -> savoiardi.Object3D
Get the root scene (Object3D) from loaded GLTF data.
This is the main 3D object hierarchy that you can add to your scene.
Example
let scene = model.get_scene(gltf_data)
// Use with scene.model() node
pub fn load_fbx(
from url: String,
on_success on_success: fn(savoiardi.FBXData) -> msg,
on_error on_error: msg,
) -> effect.Effect(msg)
pub fn load_gltf(
from url: String,
on_success on_success: fn(savoiardi.GLTFData) -> msg,
on_error on_error: msg,
) -> effect.Effect(msg)
pub fn load_obj(
from url: String,
on_success on_success: fn(savoiardi.Object3D) -> msg,
on_error on_error: msg,
) -> effect.Effect(msg)
pub fn new_animation(clip: savoiardi.AnimationClip) -> Animation
Create an animation from a clip with default settings.
Defaults: loop repeat, normal speed (1.0x), full weight (1.0).
Example
import tiramisu/model
// After loading a GLTF model
let clips = model.get_animations(gltf_data)
let walk_clip = list.find(clips, fn(clip) { model.clip_name(clip) == "Walk" })
let walk_animation = model.new_animation(walk_clip)
pub fn set_loop(anim: Animation, mode: LoopMode) -> Animation
Set the loop mode for an animation.
Example
let jump_animation = model.new_animation(jump_clip)
|> model.set_loop(model.LoopOnce) // Play once, don't loop
pub fn set_speed(anim: Animation, speed: Float) -> Animation
Set the playback speed multiplier.
The speed multiplier affects how fast the animation plays:
1.0= normal speed2.0= double speed (twice as fast)0.5= half speed (slow motion)- Negative values play the animation in reverse
Example
let run_animation = model.new_animation(run_clip)
|> model.set_speed(1.5) // 50% faster running
pub fn set_weight(anim: Animation, weight: Float) -> Animation
Set the animation weight for blending.
The weight value ranges from 0.0 to 1.0:
1.0= full influence (default)0.5= half influence (useful for blending)0.0= no influence (animation has no effect)
Example
// Blend two animations manually
let walk = model.new_animation(walk_clip) |> model.set_weight(0.7)
let idle = model.new_animation(idle_clip) |> model.set_weight(0.3)
// Or use BlendedAnimations with a blend factor
model.BlendedAnimations(from: walk, to: idle, blend_factor: 0.5)