scena 1.5.1

A Rust-native scene-graph renderer with typed scene state, glTF assets, and explicit prepare/render lifecycles.
Documentation
use crate::animation::{
    AnimationChannel, AnimationLoopMode, AnimationMixer, AnimationMixerKey, AnimationPlaybackState,
    AnimationTarget,
};
use crate::diagnostics::AnimationError;

use super::{Scene, SceneImport, Transform};

impl Scene {
    /// Creates a paused mixer for a named imported animation clip.
    ///
    /// Returns the mixer key without starting playback. For the one-call "play
    /// this now" path, use [`Self::play_animation_by_name`].
    pub fn create_animation_mixer(
        &mut self,
        import: &SceneImport,
        clip_name: &str,
    ) -> Result<AnimationMixerKey, AnimationError> {
        let clip = import
            .clip(clip_name)
            .map_err(|_| AnimationError::ClipNotFound {
                name: clip_name.to_string(),
            })?
            .clip();
        Ok(self
            .animation_mixers
            .insert(AnimationMixer::new(clip, import.live_flag())))
    }

    /// Creates and starts a mixer for a named imported animation clip.
    ///
    /// This is the one-call path for "play this clip now". The returned
    /// mixer key can still be passed to [`Self::update_animation`],
    /// [`Self::set_animation_loop_mode`], [`Self::set_animation_speed`],
    /// pause, seek, and stop helpers.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use scena::{Assets, Scene};
    /// # async fn example() -> scena::Result<()> {
    /// let assets = Assets::new();
    /// let model = assets.load_scene("machine.glb").await?;
    /// let mut scene = Scene::new();
    /// let import = scene.instantiate(&model)?;
    ///
    /// let mixer = scene.play_animation_by_name(&import, "idle")?;
    /// scene.update_animation(mixer, 1.0 / 60.0)?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn play_animation_by_name(
        &mut self,
        import: &SceneImport,
        clip_name: &str,
    ) -> Result<AnimationMixerKey, AnimationError> {
        let mixer = self.create_animation_mixer(import, clip_name)?;
        self.play_animation(mixer)?;
        Ok(mixer)
    }

    /// Borrows the mixer state for a given key.
    pub fn animation_mixer(
        &self,
        mixer: AnimationMixerKey,
    ) -> Result<&AnimationMixer, AnimationError> {
        self.animation_mixers
            .get(mixer)
            .ok_or(AnimationError::MixerNotFound(mixer))
    }

    /// Starts the mixer; resumes from the current time if it was paused.
    pub fn play_animation(&mut self, mixer: AnimationMixerKey) -> Result<(), AnimationError> {
        self.animation_mixer_mut(mixer)?.play();
        Ok(())
    }

    /// Pauses the mixer at its current time. The next
    /// [`Self::play_animation`] resumes from the same time.
    pub fn pause_animation(&mut self, mixer: AnimationMixerKey) -> Result<(), AnimationError> {
        self.animation_mixer_mut(mixer)?.pause();
        Ok(())
    }

    /// Stops the mixer and snaps the animated nodes back to time zero.
    pub fn stop_animation(&mut self, mixer: AnimationMixerKey) -> Result<(), AnimationError> {
        let clip = {
            let mixer = self.animation_mixer_mut(mixer)?;
            mixer.stop();
            mixer.clip().clone()
        };
        self.apply_animation_clip(&clip, 0.0);
        Ok(())
    }

    /// Seeks the mixer to a specific time in seconds and applies the resulting
    /// pose to the animated nodes.
    pub fn seek_animation(
        &mut self,
        mixer: AnimationMixerKey,
        time_seconds: f32,
    ) -> Result<(), AnimationError> {
        let (clip, time_seconds) = {
            let mixer = self.animation_mixer_mut(mixer)?;
            mixer.seek(time_seconds);
            (mixer.clip().clone(), mixer.time_seconds())
        };
        self.apply_animation_clip(&clip, time_seconds);
        Ok(())
    }

    /// Sets the playback speed multiplier. `1.0` is real-time; negative values
    /// play the clip in reverse.
    pub fn set_animation_speed(
        &mut self,
        mixer: AnimationMixerKey,
        speed: f32,
    ) -> Result<(), AnimationError> {
        self.animation_mixer_mut(mixer)?.set_speed(speed);
        Ok(())
    }

    /// Sets the loop mode (loop, once, ping-pong).
    pub fn set_animation_loop_mode(
        &mut self,
        mixer: AnimationMixerKey,
        loop_mode: AnimationLoopMode,
    ) -> Result<(), AnimationError> {
        self.animation_mixer_mut(mixer)?.set_loop_mode(loop_mode);
        Ok(())
    }

    /// Advances the mixer by `delta_seconds` and applies the resulting pose to
    /// animated nodes. Hosts are expected to call this once per frame for
    /// every active mixer.
    pub fn update_animation(
        &mut self,
        mixer: AnimationMixerKey,
        delta_seconds: f32,
    ) -> Result<(), AnimationError> {
        let (clip, time_seconds, was_playing) = {
            let mixer = self.animation_mixer_mut(mixer)?;
            let was_playing = mixer.state() == AnimationPlaybackState::Playing;
            mixer.advance(delta_seconds);
            (mixer.clip().clone(), mixer.time_seconds(), was_playing)
        };
        if was_playing {
            self.apply_animation_clip(&clip, time_seconds);
        }
        Ok(())
    }

    fn animation_mixer_mut(
        &mut self,
        mixer: AnimationMixerKey,
    ) -> Result<&mut AnimationMixer, AnimationError> {
        let mixer_state = self
            .animation_mixers
            .get_mut(mixer)
            .ok_or(AnimationError::MixerNotFound(mixer))?;
        if mixer_state.is_stale() {
            return Err(AnimationError::StaleMixer(mixer));
        }
        Ok(mixer_state)
    }

    fn apply_animation_clip(&mut self, clip: &crate::animation::AnimationClip, time_seconds: f32) {
        let mut changed = false;
        for channel in clip.channels() {
            changed |= self.apply_animation_channel(channel, time_seconds);
        }
        if changed {
            self.structure_revision = self.structure_revision.saturating_add(1);
        }
    }

    fn apply_animation_channel(&mut self, channel: &AnimationChannel, time_seconds: f32) -> bool {
        let Some(node) = self.nodes.get_mut(channel.target_node()) else {
            return false;
        };
        let before = node.transform;
        let mut transform = before;
        match channel.target() {
            AnimationTarget::Translation => {
                let Some(value) = channel.sample_vec3(time_seconds) else {
                    return false;
                };
                transform.translation = value;
            }
            AnimationTarget::Scale => {
                let Some(value) = channel.sample_vec3(time_seconds) else {
                    return false;
                };
                transform.scale = value;
            }
            AnimationTarget::Rotation => {
                let Some(value) = channel.sample_quat(time_seconds) else {
                    return false;
                };
                transform.rotation = value;
            }
            AnimationTarget::Weights => {
                let Some(weights) = channel.sample_weights(time_seconds) else {
                    return false;
                };
                return self.set_morph_weights_unchecked(channel.target_node(), weights);
            }
        }
        if before == transform {
            return false;
        }
        node.transform = Transform { ..transform };
        true
    }
}