# AnyMotion

> A skeletal animation library that _might_ work for your game engine

[![Crates.io](https://img.shields.io/crates/v/anymotion.svg)](https://crates.io/crates/anymotion)
[![Documentation](https://docs.rs/anymotion/badge.svg)](https://docs.rs/anymotion)

## What This Is (Probably)

AnyMotion is a prototype skeletal animation library for Rust game engines. It follows Unix philosophy (does one thing, hopefully well) and provides the animation pipeline components you'd need to integrate into a larger engine.

**Fair Warning**: This is a library component, not a complete solution. You'll need to bring your own rendering, materials, lighting, and scene management. Think of it as a transmission for your car - it works, but you can't drive it alone.

## Installation

```toml
[dependencies]
anymotion = "0.1.0"
archetype_ecs = "1.1.7"  # Required for ECS integration
glam = "0.30"            # Math types
```

## Core Concepts

### What It Does

- ✅ **Animation Sampling**: Interpolates keyframe data at any time point
- ✅ **Transform Hierarchy**: Propagates local transforms to global space
- ✅ **Skinning Matrices**: Computes GPU-ready joint matrices
- ✅ **ECS Integration**: Works with `archetype_ecs` (should work with others too)
- ✅ **GLTF Loading**: Reads skeletons and animations from `.glb` files

### What It Doesn't Do

- ❌ Rendering (use `ash_renderer`, `wgpu`, or your own)
- ❌ Materials/Lighting (that's your engine's job)
- ❌ Asset management (just provides loaders)
- ❌ Physics/IK (maybe someday?)

## Quick Start

### 1. Basic Animation Sampling (No ECS)

```rust
use anymotion::prelude::*;

fn main() -> Result<()> {
    // Create test data (or load from GLTF)
    let skeleton = create_test_skeleton();
    let clip = create_test_walk_clip()?;
    
    // Sample animation at specific time
    let time = 0.5; // seconds
    if let Some((pos, rot, scale)) = clip.sample_bone("Hips", time) {
        println!("Hips at t={time}s: pos={pos:?}");
    }
    
    Ok(())
}
```

### 2. ECS Integration (The Real Deal)

```rust
use anymotion::prelude::*;
use archetype_ecs::{World, GlobalTransform, LocalTransform};

fn setup_animation(world: &mut World) -> Result<()> {
    // 1. Load skeleton and animation
    let skeleton = create_test_skeleton();
    let clip = create_test_walk_clip()?;
    
    // 2. Store in asset resources
    let skeleton_handle = world
        .resource_mut::<SkeletonAssets>()?
        .add(skeleton.clone());
    
    let clip_handle = world
        .resource_mut::<AnimationClipAssets>()?
        .add(clip);
    
    // 3. Spawn bone entities
    let mut bone_entities = Vec::new();
    for (i, _bone) in skeleton.bones.iter().enumerate() {
        let entity = world.spawn((
            BoneJoint {
                bone_index: i,
                skeleton: skeleton_handle,
            },
            LocalTransform::default(),
            GlobalTransform::identity(),
        ));
        bone_entities.push(entity);
    }
    
    // 4. Set up parent relationships (CRITICAL!)
    // Example: Root (0) -> Hips (1) -> Spine (2)
    let _ = world.add_component(
        bone_entities[1],
        Parent { entity: bone_entities[0] }
    );
    let _ = world.add_component(
        bone_entities[2],
        Parent { entity: bone_entities[1] }
    );
    
    // 5. Spawn animated character
    world.spawn((
        Animator::new(skeleton_handle, clip_handle),
        SkinnedMesh {
            skeleton: skeleton_handle,
            mesh_handle: 0, // Your mesh ID
            bone_entities: bone_entities.clone(),
            joint_offset: 0,
        },
        GlobalTransform::identity(),
        JointPalette::new(skeleton.bones.len()),
    ));
    
    Ok(())
}

fn update_loop(world: &mut World, dt: f32) {
    // This runs the entire animation pipeline:
    // 1. Sample animations -> LocalTransform
    // 2. Propagate hierarchy -> GlobalTransform
    // 3. Compute skinning matrices -> JointPalette
    // 4. Upload to GPU (if renderer is set up)
    AnimationPipeline::update(world, dt);
}
```

### 3. Loading from GLTF

```rust
use anymotion::loader::load_gltf;

fn load_character() -> Result<()> {
    let (skeleton, clips) = load_gltf("assets/character.glb")?;
    
    println!("Loaded skeleton with {} bones", skeleton.bones.len());
    println!("Loaded {} animation clips", clips.len());
    
    for clip in &clips {
        println!("  - {}: {:.2}s", clip.name, clip.duration);
    }
    
    Ok(())
}
```

## API Reference

### Core Types

#### `Skeleton`

Represents a bone hierarchy with inverse bind matrices.

```rust
pub struct Skeleton {
    pub bones: Vec<Bone>,
}

impl Skeleton {
    pub fn new() -> Self;
    pub fn add_bone(&mut self, name: String, parent: Option<usize>, inverse_bind: Mat4);
    pub fn validate(&self) -> Result<()>;
    pub fn find_bone(&self, name: &str) -> Option<usize>;
}
```

#### `AnimationClip`

Contains keyframe data for multiple bones.

```rust
pub struct AnimationClip {
    pub name: String,
    pub duration: f32,
    // ... (internal)
}

impl AnimationClip {
    pub fn sample_bone(&self, bone_name: &str, time: f32) -> Option<(Vec3, Quat, Vec3)>;
    pub fn sample_all(&self, time: f32) -> HashMap<String, (Vec3, Quat, Vec3)>;
}
```

#### `Animator` (Component)

Drives animation playback.

```rust
pub struct Animator {
    pub skeleton: SkeletonHandle,
    pub current_clip: AnimationClipHandle,
    pub player: AnimationPlayer,
}

// AnimationPlayer controls playback
pub struct AnimationPlayer {
    pub time: f32,
    pub speed: f32,        // Default: 1.0
    pub is_playing: bool,  // Default: true
    pub is_looping: bool,  // Default: true
}
```

#### `BoneJoint` (Component)

Marks an entity as a bone in a skeleton.

```rust
pub struct BoneJoint {
    pub bone_index: usize,
    pub skeleton: SkeletonHandle,
}
```

#### `Parent` (Component)

Defines parent-child relationships for transform hierarchy.

```rust
pub struct Parent {
    pub entity: EntityId,
}
```

#### `SkinnedMesh` (Component)

Links a mesh to a skeleton for GPU skinning.

```rust
pub struct SkinnedMesh {
    pub skeleton: SkeletonHandle,
    pub mesh_handle: u32,
    pub bone_entities: Vec<EntityId>,
    pub joint_offset: u32,
}
```

#### `JointPalette` (Component)

Stores computed skinning matrices for GPU upload.

```rust
pub struct JointPalette {
    pub matrices: Vec<Mat4>,
}

impl JointPalette {
    pub fn new(count: usize) -> Self;
}
```

### Systems

#### `AnimationPipeline::update(world, dt)`

**The main entry point.** Runs all animation systems in the correct order:

1. `animation_sampling_system` - Samples animations → `LocalTransform`
2. `animation_blending_system` - Blends animations (if needed)
3. `transform_hierarchy_system` - Propagates `LocalTransform` → `GlobalTransform`
4. `skinning_palette_system` - Computes skinning matrices → `JointPalette`
5. `JointUploadSystem` - Uploads to GPU (if renderer available)

```rust
// In your game loop
fn update(&mut self, dt: f32) {
    AnimationPipeline::update(&mut self.world, dt);
}
```

You can also run systems individually if needed:

```rust
use anymotion::{
    animation_sampling_system,
    animation_blending_system,
    skinning_palette_system,
};

animation_sampling_system(&mut world, dt);
transform_hierarchy_system(&mut world);
skinning_palette_system(&mut world);
```

### Loaders

#### `create_test_skeleton()` / `create_test_walk_clip()`

Helper functions for testing/prototyping.

```rust
let skeleton = create_test_skeleton();
// Creates: Root -> Hips -> Spine, LeftLeg, RightLeg

let clip = create_test_walk_clip()?;
// Animates the "Hips" bone
```

#### `load_gltf(path)`

Loads skeleton and animations from GLTF/GLB files.

```rust
let (skeleton, clips) = load_gltf("character.glb")?;
```

**Known Limitations**:
- Only reads first skin in file
- Assumes linear interpolation
- No support for morph targets yet

## Architecture

### Transform Hierarchy

The library uses a standard parent-child transform hierarchy:

```
LocalTransform (per-bone) → Parent links → GlobalTransform (computed)
```

**CRITICAL**: You must set up `Parent` components correctly, or transforms won't propagate!

```rust
// Example: 3-bone chain
let root = world.spawn((LocalTransform::default(), GlobalTransform::identity()));
let child1 = world.spawn((LocalTransform::default(), GlobalTransform::identity()));
let child2 = world.spawn((LocalTransform::default(), GlobalTransform::identity()));

world.add_component(child1, Parent { entity: root });
world.add_component(child2, Parent { entity: child1 });

// Now: root -> child1 -> child2
```

### Skinning Pipeline

```
Skeleton (bind pose) + GlobalTransform (animated) → Skinning Matrices → GPU
```

The skinning matrix for bone `i` is:

```
JointMatrix[i] = GlobalTransform[i] * InverseBindMatrix[i]
```

This is computed by `skinning_palette_system` and stored in `JointPalette`.

### Data Flow Diagram

```
┌─────────────────┐
│ AnimationClip   │
│ (keyframes)     │
└────────┬────────┘
         │ sample(time)
         ↓
┌─────────────────┐
│ LocalTransform  │ ← animation_sampling_system
│ (per bone)      │
└────────┬────────┘
         │ + Parent links
         ↓
┌─────────────────┐
│ GlobalTransform │ ← transform_hierarchy_system
│ (world space)   │
└────────┬────────┘
         │ + InverseBindMatrix
         ↓
┌─────────────────┐
│ JointPalette    │ ← skinning_palette_system
│ (GPU matrices)  │
└────────┬────────┘
         │
         ↓
       GPU Skinning
```

## Integration Patterns

### With `ash_renderer`

```rust
use anymotion::prelude::*;
use ash_renderer::prelude::*;
use std::sync::{Arc, Mutex};

// 1. Store renderer in ECS world
world.insert_resource(Arc::new(Mutex::new(renderer)));

// 2. Run animation pipeline
AnimationPipeline::update(&mut world, dt);

// 3. Renderer automatically uploads joint matrices
// (JointUploadSystem handles this)

// 4. Draw skinned mesh
if let Ok(mut renderer) = renderer_arc.lock() {
    renderer.draw_skinned_mesh(
        mesh_handle,
        material_handle,
        transform_matrix,
        joint_offset,
    );
}
```

### With Custom ECS

If you're not using `archetype_ecs`, you'll need to:

1. Implement equivalent components (`LocalTransform`, `GlobalTransform`, etc.)
2. Run the systems manually in the correct order
3. Handle resource storage yourself

The core math (`AnimationClip::sample`, etc.) is ECS-agnostic.

## Performance Notes

**What We've Tested**:
- ✅ Zero allocations in `AnimationPipeline::update` hot loop
- ✅ Sparse updates (only animated bones are modified)
- ✅ Single-pass hierarchy propagation

**What We Haven't Tested**:
- Large skeletons (100+ bones)
- Many animated characters (100+ entities)
- Complex blend trees

**Optimization Tips**:
- Use `AnimationPipeline::update` instead of individual systems (better cache locality)
- Keep bone hierarchies shallow when possible
- Batch character updates if you have many

## Known Issues & Limitations

### Current Bugs
- None known (as of v0.1.0)

### Missing Features
- ❌ Animation blending (component exists, system is stubbed)
- ❌ Inverse Kinematics (IK)
- ❌ Root motion extraction
- ❌ Animation events/callbacks
- ❌ Additive animations
- ❌ Animation compression

### Design Limitations
- Requires `archetype_ecs` (or manual system integration)
- Assumes you have a renderer that supports GPU skinning
- GLTF loader is basic (no morph targets, no sparse accessors)

## Testing

```bash
# Run all tests
cargo test

# Run with output
cargo test -- --nocapture

# Run specific test
cargo test test_animation_pipeline_propagation

# Run examples
cargo run --example 01_basic_animation
cargo run --example 02_skinned_mesh  # (white screen is normal - see below)
```

### About the White Screen Example

The `02_skinned_mesh` example shows a white screen. **This is expected!**

The example proves the animation pipeline works (verified by passing tests), but doesn't set up materials, lighting, or a proper scene. It's a minimal integration demo, not a visual showcase.

To actually see animated characters, you'd need to integrate this library into a game engine with:
- Material system
- Lighting system
- Camera controller
- Scene management

## Troubleshooting

### "My bones aren't animating!"

Check:
1. Did you set up `Parent` components for the bone hierarchy?
2. Are you calling `AnimationPipeline::update` every frame?
3. Is the `Animator` component's `player.is_playing` set to `true`?
4. Does your animation clip actually have tracks for those bones?

### "Transforms are wrong!"

Check:
1. Are you using `LocalTransform` from `archetype_ecs`?
2. Did you spawn entities with both `LocalTransform` AND `GlobalTransform`?
3. Is `transform_hierarchy_system` running after `animation_sampling_system`?

### "GPU skinning doesn't work!"

Check:
1. Did you set up `SkinnedMesh` component with correct `bone_entities`?
2. Is `JointPalette` component present?
3. Is your renderer actually using the joint matrices?

## Contributing

Contributions are welcome, though I make no promises about merge speed or quality standards.

Please ensure:
- `cargo test` passes
- `cargo clippy` is clean
- Code is reasonably documented

## License

Licensed under the Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE)).

## Acknowledgments

This library wouldn't exist without:
- Unity DOTS (architecture inspiration)
- Unreal Engine (skinning pipeline reference)
- `archetype_ecs` (ECS foundation)
- `ash_renderer` (rendering integration)
- The Rust gamedev community

## Version History

### v0.1.0 (2025-12-29)

**Initial Release** - The "It Compiles" Edition

- ✅ Core animation sampling
- ✅ Transform hierarchy propagation
- ✅ Skinning matrix calculation
- ✅ GLTF loading (basic)
- ✅ ECS integration with `archetype_ecs`
- ✅ Animation pipeline orchestration
- ✅ 37 passing tests
- ✅ Zero clippy warnings

**Known Issues**:
- Animation blending system is stubbed
- No visual examples (white screen is expected)
- GLTF loader is minimal

---

**Questions? Issues?** Open an issue on GitHub. I'll try to respond, but no guarantees.

**Want to help?** PRs welcome. The codebase is reasonably clean (I think).

**Using this in production?** You're braver than I am. Let me know how it goes!
