nightshade 0.13.3

A cross-platform data-oriented game engine.
Documentation
use std::collections::HashMap;

use serde::{Deserialize, Serialize};

use crate::ecs::animation::components::{AnimationClip, AnimationPlayer};

use super::asset_uuid::AssetUuid;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneAnimationClipRef {
    #[serde(default)]
    pub uuid: Option<AssetUuid>,
    #[serde(default)]
    pub path: Option<String>,
    #[serde(default)]
    pub name: Option<String>,
}

impl SceneAnimationClipRef {
    pub fn from_uuid(uuid: AssetUuid) -> Self {
        Self {
            uuid: Some(uuid),
            path: None,
            name: None,
        }
    }

    pub fn from_path(path: impl Into<String>) -> Self {
        Self {
            uuid: None,
            path: Some(path.into()),
            name: None,
        }
    }

    pub fn from_name(name: impl Into<String>) -> Self {
        Self {
            uuid: None,
            path: None,
            name: Some(name.into()),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneAnimationPlayer {
    #[serde(default)]
    pub clips: Vec<AnimationClip>,
    #[serde(default)]
    pub clip_refs: Vec<SceneAnimationClipRef>,
    #[serde(default)]
    pub current_clip: Option<usize>,
    #[serde(default)]
    pub time: f32,
    #[serde(default = "default_animation_speed")]
    pub speed: f32,
    #[serde(default = "default_true")]
    pub looping: bool,
    #[serde(default)]
    pub playing: bool,
    #[serde(default)]
    pub play_all: bool,
    #[serde(default)]
    pub bone_name_to_node: HashMap<String, AssetUuid>,
}

fn default_animation_speed() -> f32 {
    1.0
}

fn default_true() -> bool {
    true
}

impl Default for SceneAnimationPlayer {
    fn default() -> Self {
        Self {
            clips: Vec::new(),
            clip_refs: Vec::new(),
            current_clip: None,
            time: 0.0,
            speed: 1.0,
            looping: true,
            playing: false,
            play_all: false,
            bone_name_to_node: HashMap::new(),
        }
    }
}

impl SceneAnimationPlayer {
    pub fn new(clips: Vec<AnimationClip>) -> Self {
        Self {
            clips,
            ..Default::default()
        }
    }

    pub fn with_current_clip(mut self, clip_index: usize) -> Self {
        self.current_clip = Some(clip_index);
        self
    }

    pub fn with_playing(mut self, playing: bool) -> Self {
        self.playing = playing;
        self
    }

    pub fn with_looping(mut self, looping: bool) -> Self {
        self.looping = looping;
        self
    }

    pub fn with_speed(mut self, speed: f32) -> Self {
        self.speed = speed;
        self
    }

    pub fn to_animation_player(&self) -> AnimationPlayer {
        AnimationPlayer {
            clips: self.clips.clone(),
            current_clip: self.current_clip,
            time: self.time,
            speed: self.speed,
            looping: self.looping,
            playing: self.playing,
            play_all: self.play_all,
            node_index_to_entity: HashMap::new(),
            bone_name_to_entity: HashMap::new(),
            blend_from_clip: None,
            blend_from_time: 0.0,
            blend_factor: 1.0,
            blend_duration: 0.0,
        }
    }

    pub fn to_animation_player_with_resolved_clips(
        &self,
        asset_registry: Option<&super::registry::AssetRegistry>,
        warnings: &mut Vec<String>,
    ) -> AnimationPlayer {
        let mut clips = self.clips.clone();

        for clip_ref in &self.clip_refs {
            let path = if let Some(uuid) = clip_ref.uuid {
                asset_registry.and_then(|registry| registry.resolve_uuid(uuid))
            } else if let Some(ref path_str) = clip_ref.path {
                Some(std::path::PathBuf::from(path_str))
            } else if let Some(ref name) = clip_ref.name {
                asset_registry
                    .and_then(|registry| registry.resolve_name(name))
                    .and_then(|uuid| {
                        asset_registry.and_then(|registry| registry.resolve_uuid(uuid))
                    })
            } else {
                None
            };

            if let Some(ref path) = path {
                match load_animation_clip_from_path(path) {
                    Ok(clip) => clips.push(clip),
                    Err(error) => warnings.push(format!(
                        "Failed to load animation clip from {}: {}",
                        path.display(),
                        error
                    )),
                }
            } else {
                warnings.push(format!(
                    "Could not resolve animation clip reference: {:?}",
                    clip_ref
                ));
            }
        }

        AnimationPlayer {
            clips,
            current_clip: self.current_clip,
            time: self.time,
            speed: self.speed,
            looping: self.looping,
            playing: self.playing,
            play_all: self.play_all,
            node_index_to_entity: HashMap::new(),
            bone_name_to_entity: HashMap::new(),
            blend_from_clip: None,
            blend_from_time: 0.0,
            blend_factor: 1.0,
            blend_duration: 0.0,
        }
    }
}

fn load_animation_clip_from_path(path: &std::path::Path) -> Result<AnimationClip, String> {
    let data = std::fs::read(path).map_err(|error| error.to_string())?;

    match path.extension().and_then(|extension| extension.to_str()) {
        Some("json") => serde_json::from_slice(&data).map_err(|error| error.to_string()),
        Some("bin") => {
            let decompressed =
                lz4_flex::decompress_size_prepended(&data).map_err(|error| error.to_string())?;
            bincode::deserialize(&decompressed).map_err(|error| error.to_string())
        }
        _ => bincode::deserialize(&data).map_err(|error| error.to_string()),
    }
}

impl From<&AnimationPlayer> for SceneAnimationPlayer {
    fn from(player: &AnimationPlayer) -> Self {
        Self {
            clips: player.clips.clone(),
            clip_refs: Vec::new(),
            current_clip: player.current_clip,
            time: player.time,
            speed: player.speed,
            looping: player.looping,
            playing: player.playing,
            play_all: player.play_all,
            bone_name_to_node: HashMap::new(),
        }
    }
}