tiramisu/animation

Animation and tweening system for smooth transitions and Three.js model animations.

This module provides two main systems:

Tweening Example

import tiramisu/animation
import vec/vec3

type Model {
  Model(position_tween: animation.Tween(vec3.Vec3))
}

// Create a tween from one position to another over 2 seconds
let tween = animation.tween_vec3(
  start: vec3.Vec3(0.0, 0.0, 0.0),
  end: vec3.Vec3(5.0, 10.0, 0.0),
  duration: 2000.0,  // milliseconds
  easing: animation.EaseInOutQuad,
)

// Update each frame
fn update(model: Model, ctx: Context) {
  let updated_tween = animation.update_tween(model.position_tween, ctx.delta_time)
  let current_position = animation.get_tween_value(updated_tween)
  Model(position_tween: updated_tween)
}

Model Animation Example

import tiramisu/animation
import tiramisu/scene

// Assuming you loaded a GLTF model with animations
let model = asset.get_model(assets, "character")
let clips = asset.model_animations(model)
let walk_clip = list.find(clips, fn(clip) { animation.clip_name(clip) == "Walk" })

// Create animation and configure it
let walk_anim = animation.new_animation(walk_clip)
  |> animation.set_speed(1.5)  // 1.5x speed
  |> animation.set_loop(animation.LoopRepeat)

// Add to scene node
scene.Model(
  id: "character",
  model: model,
  transform: transform.identity,
  animation: option.Some(animation.SingleAnimation(walk_anim)),
  physics: option.None,
)

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: AnimationClip,
    loop: LoopMode,
    speed: Float,
    weight: Float,
  )
}

Constructors

  • Animation(
      clip: 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)

An opaque reference to a Three.js AnimationClip.

Animation clips are typically loaded from GLTF model files using asset.model_animations(). Each clip contains keyframe data for animating a 3D model.

pub type AnimationClip

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

  • BlendedAnimations(
      from: Animation,
      to: Animation,
      blend_factor: Float,
    )

    Blend between two animations with a blend factor (0.0 = fully ‘from’, 1.0 = fully ‘to’)

Easing functions for smooth animations.

Easing functions control the rate of change over time, making animations feel more natural. Use ease() to apply an easing function to a value in [0, 1].

pub type Easing {
  Linear
  EaseInQuad
  EaseOutQuad
  EaseInOutQuad
  EaseInCubic
  EaseOutCubic
  EaseInOutCubic
  EaseInSine
  EaseOutSine
  EaseInOutSine
}

Constructors

  • Linear
  • EaseInQuad
  • EaseOutQuad
  • EaseInOutQuad
  • EaseInCubic
  • EaseOutCubic
  • EaseInOutCubic
  • EaseInSine
  • EaseOutSine
  • EaseInOutSine

Controls how an animation loops.

pub type LoopMode {
  LoopOnce
  LoopRepeat
}

Constructors

  • LoopOnce

    Play the animation once and stop at the end

  • LoopRepeat

    Play the animation repeatedly in a loop

A tween that interpolates between two values over time.

Tweens are generic over any type a that can be interpolated (Float, Vec3, Transform, etc.). Use the convenience constructors like tween_float(), tween_vec3(), or tween_transform() for common types.

The duration is in milliseconds, and elapsed tracks the current progress.

pub type Tween(a) {
  Tween(
    start_value: a,
    end_value: a,
    duration: Float,
    elapsed: Float,
    easing: Easing,
    lerp_fn: fn(a, a, Float) -> a,
  )
}

Constructors

  • Tween(
      start_value: a,
      end_value: a,
      duration: Float,
      elapsed: Float,
      easing: Easing,
      lerp_fn: fn(a, a, Float) -> a,
    )

    Arguments

    duration

    Duration of the tween in milliseconds

    elapsed

    Time elapsed since tween started in milliseconds

Values

pub fn clip_duration(clip: AnimationClip) -> Float

Get the duration of an animation clip.

The duration is in seconds.

Example

let clip = animation.new_animation(walk_clip)
let duration_seconds = animation.clip_duration(walk_clip)
// Use this to sync game events with animation timing
pub fn clip_name(clip: 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/asset

let model = asset.get_model(assets, "character")
let clips = asset.model_animations(model)

let walk_clip = list.find(clips, fn(clip) {
  animation.clip_name(clip) == "Walk"
})
pub fn ease(easing: Easing, t: Float) -> Float

Apply an easing function to a normalized value.

The value t is automatically clamped to the range [0, 1].

Example

// Linear progression
animation.ease(animation.Linear, 0.5)  // => 0.5

// Ease in quad - slow start, fast end
animation.ease(animation.EaseInQuad, 0.5)  // => 0.25

// Ease out quad - fast start, slow end
animation.ease(animation.EaseOutQuad, 0.5)  // => 0.75
pub fn get_tween_value(tween: Tween(a)) -> a

Get the current interpolated value of a tween.

Applies the easing function and returns the value at the current elapsed time. Once the tween completes, this returns the end value.

Example

let tween = animation.tween_float(0.0, 100.0, 1000.0, animation.Linear)
  |> animation.update_tween(500.0)  // Halfway through

let value = animation.get_tween_value(tween)  // => 50.0
pub fn is_tween_complete(tween: Tween(a)) -> Bool

Check if a tween has finished playing.

Example

fn update(model: Model, msg: Msg, ctx: Context) {
  let updated_tween = animation.update_tween(model.tween, ctx.delta_time)

  case animation.is_tween_complete(updated_tween) {
    True -> {
      // Tween finished, trigger next animation or event
      #(Model(..model, tween: animation.reset_tween(updated_tween)), effect.none())
    }
    False -> #(Model(..model, tween: updated_tween), effect.none())
  }
}
pub fn new_animation(clip: 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/animation
import tiramisu/asset

let model = asset.get_model(assets, "character")
let clips = asset.model_animations(model)
let walk_clip = list.find(clips, fn(clip) { animation.clip_name(clip) == "Walk" })

let walk_animation = animation.new_animation(walk_clip)
pub fn reset_tween(tween: Tween(a)) -> Tween(a)

Reset a tween back to the beginning (elapsed time = 0).

Example

// Play the tween again from the start
let reset = animation.reset_tween(completed_tween)
pub fn reverse_tween(tween: Tween(a)) -> Tween(a)

Reverse a tween by swapping its start and end values.

The elapsed time is preserved, so the tween continues from where it was but in the opposite direction.

Example

// Create a bouncing animation
fn update(model: Model, msg: Msg, ctx: Context) {
  let updated_tween = animation.update_tween(model.tween, ctx.delta_time)

  case animation.is_tween_complete(updated_tween) {
    True -> {
      // Bounce back by reversing the tween
      let reversed = animation.reverse_tween(updated_tween)
        |> animation.reset_tween()
      Model(..model, tween: reversed)
    }
    False -> Model(..model, tween: updated_tween)
  }
}
pub fn set_loop(anim: Animation, mode: LoopMode) -> Animation

Set the loop mode for an animation.

Example

let jump_animation = animation.new_animation(jump_clip)
  |> animation.set_loop(animation.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 speed
  • 2.0 = double speed (twice as fast)
  • 0.5 = half speed (slow motion)
  • Negative values play the animation in reverse

Example

let run_animation = animation.new_animation(run_clip)
  |> animation.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 = animation.new_animation(walk_clip) |> animation.set_weight(0.7)
let idle = animation.new_animation(idle_clip) |> animation.set_weight(0.3)

// Or use BlendedAnimations with a blend factor
animation.BlendedAnimations(from: walk, to: idle, blend_factor: 0.5)
pub fn tween(
  start: a,
  end: a,
  duration duration: Float,
  easing easing: Easing,
  lerp_fn lerp_fn: fn(a, a, Float) -> a,
) -> Tween(a)

Create a new tween with a custom interpolation function.

For most use cases, prefer tween_float(), tween_vec3(), or tween_transform().

Example

// Custom tween for a color value
type Color {
  Color(r: Float, g: Float, b: Float)
}

let color_tween = animation.tween(
  start: Color(1.0, 0.0, 0.0),  // Red
  end: Color(0.0, 0.0, 1.0),    // Blue
  duration: 1000.0,  // milliseconds
  easing: animation.EaseInOutQuad,
  lerp_fn: fn(a, b, t) {
    Color(
      r: a.r +. { b.r -. a.r } *. t,
      g: a.g +. { b.g -. a.g } *. t,
      b: a.b +. { b.b -. a.b } *. t,
    )
  },
)
pub fn tween_float(
  start: Float,
  end: Float,
  duration duration: Float,
  easing easing: Easing,
) -> Tween(Float)

Create a tween that interpolates a Float value.

The duration is in milliseconds.

Example

// Fade from 0.0 to 1.0 over 2 seconds
let fade_tween = animation.tween_float(
  start: 0.0,
  end: 1.0,
  duration: 2000.0,  // milliseconds
  easing: animation.EaseInOutQuad,
)
pub fn tween_transform(
  start: transform.Transform,
  end: transform.Transform,
  duration duration: Float,
  easing easing: Easing,
) -> Tween(transform.Transform)

Create a tween that interpolates a Transform (position, rotation, and scale).

The duration is in milliseconds.

Example

import tiramisu/transform
import vec/vec3

let start_transform = transform.at(position: vec3.Vec3(0.0, 0.0, 0.0))
  |> transform.scale(1.0)

let end_transform = transform.at(position: vec3.Vec3(5.0, 0.0, 0.0))
  |> transform.scale(2.0)
  |> transform.rotate_y(3.14159)

let transform_tween = animation.tween_transform(
  start: start_transform,
  end: end_transform,
  duration: 1500.0,  // milliseconds
  easing: animation.EaseInOutCubic,
)
pub fn tween_vec3(
  start: vec3.Vec3(Float),
  end: vec3.Vec3(Float),
  duration duration: Float,
  easing easing: Easing,
) -> Tween(vec3.Vec3(Float))

Create a tween that interpolates a Vec3 value (useful for positions).

The duration is in milliseconds.

Example

import vec/vec3

// Move from origin to (10, 5, 0) over 3 seconds
let position_tween = animation.tween_vec3(
  start: vec3.Vec3(0.0, 0.0, 0.0),
  end: vec3.Vec3(10.0, 5.0, 0.0),
  duration: 3000.0,  // milliseconds
  easing: animation.EaseOutQuad,
)
pub fn update_tween(
  tween: Tween(a),
  delta delta: Float,
) -> Tween(a)

Update a tween by advancing its elapsed time.

The delta parameter is in milliseconds (typically from ctx.delta_time).

Example

fn update(model: Model, msg: Msg, ctx: Context) {
  // Advance the tween by the frame delta
  let updated_tween = animation.update_tween(model.tween, ctx.delta_time)
  Model(..model, tween: updated_tween)
}
Search Document