Skip to content

feat(ltk_anim): Add UncompressedBuilder API for creating animations #83

@Crauzer

Description

@Crauzer

Summary

Add a builder API for creating Uncompressed animation assets from raw animation data. This is simpler than compressed animations since it does not require curve fitting.

Background

Currently, Uncompressed::new() exists but requires manually constructing palettes and frame indices:

pub fn new(
    fps: f32,
    vector_palette: Vec<Vec3>,
    quat_palette: Vec<Quat>,
    joint_frames: HashMap<u32, Vec<UncompressedFrame>>,
) -> Self

This is low-level and requires users to:

  • Build deduplicated vector/quaternion palettes manually
  • Track palette indices for each frame
  • Construct UncompressedFrame entries with correct indices

Proposed API

A higher-level builder that handles palette construction automatically:

pub struct UncompressedBuilder {
    fps: f32,
    joints: Vec<u32>,
    samples: HashMap<u32, Vec<JointTransform>>,
}

pub struct JointTransform {
    pub rotation: Quat,
    pub translation: Vec3,
    pub scale: Vec3,
}

impl UncompressedBuilder {
    /// Create a new builder with the given FPS
    pub fn new(fps: f32) -> Self;
    
    /// Add a joint to the animation
    pub fn add_joint(&mut self, joint_hash: u32) -> &mut Self;
    
    /// Add a frame for a joint (call once per frame, in order)
    pub fn add_frame(
        &mut self,
        joint_hash: u32,
        transform: JointTransform,
    ) -> &mut Self;
    
    /// Add all frames for a joint at once
    pub fn set_joint_frames(
        &mut self,
        joint_hash: u32,
        frames: Vec<JointTransform>,
    ) -> &mut Self;
    
    /// Build the animation
    /// 
    /// Automatically:
    /// - Deduplicates vectors and quaternions into palettes
    /// - Constructs frame index references
    /// - Calculates duration from frame count
    pub fn build(self) -> Result<Uncompressed, Error>;
}

Example Usage

use ltk_anim::{UncompressedBuilder, JointTransform};
use glam::{Quat, Vec3};

let mut builder = UncompressedBuilder::new(30.0); // 30 FPS

// Add frames for each joint
for frame in 0..60 {
    builder.add_frame(joint_hash, JointTransform {
        rotation: Quat::from_rotation_y(frame as f32 * 0.1),
        translation: Vec3::new(0.0, frame as f32 * 0.01, 0.0),
        scale: Vec3::ONE,
    });
}

let animation = builder.build()?;
animation.to_writer(&mut file)?;

Implementation Notes

The build() method should:

  1. Collect all unique vectors (translations + scales) into a palette
  2. Collect all unique quaternions into a palette
  3. Use approximate equality for deduplication (configurable epsilon)
  4. Build UncompressedFrame entries with palette indices
  5. Validate all joints have the same frame count

Tasks

  • Add JointTransform struct (or reuse existing transform type)
  • Implement UncompressedBuilder
  • Add palette deduplication with configurable tolerance
  • Add validation (frame count consistency, etc.)
  • Add tests
  • Add documentation with examples

Complexity

This is simpler than compressed animation writing since:

  • No curve fitting required
  • No special frame ordering
  • No jump cache generation
  • Just palette building and index tracking

Marked as good first issue for contributors familiar with Rust builders.

Metadata

Metadata

Assignees

Labels

Projects

Status

Todo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions