From 6fc5ca1c18ada52d97ce72cac015b6b51bd7820d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20R=C3=B6nnberg?= Date: Sun, 11 Feb 2018 22:51:18 +0100 Subject: [PATCH] feat: Add stepping and exact input value control to animation system --- amethyst_animation/src/lib.rs | 6 +- amethyst_animation/src/resources.rs | 68 ++++++++++++++++++++++- amethyst_animation/src/systems/control.rs | 48 +++++++++++++++- amethyst_animation/src/util.rs | 48 +++++++++++++++- examples/animation/main.rs | 51 ++++++++++++----- 5 files changed, 201 insertions(+), 20 deletions(-) diff --git a/amethyst_animation/src/lib.rs b/amethyst_animation/src/lib.rs index be2824f590..d76f14c1db 100644 --- a/amethyst_animation/src/lib.rs +++ b/amethyst_animation/src/lib.rs @@ -14,13 +14,13 @@ extern crate specs; pub use self::bundle::{AnimationBundle, SamplingBundle, VertexSkinningBundle}; pub use self::resources::{Animation, AnimationCommand, AnimationControl, AnimationHierarchy, AnimationSampling, AnimationSet, ControlState, EndControl, Sampler, - SamplerControl, SamplerControlSet}; + SamplerControl, SamplerControlSet, StepDirection}; pub use self::skinning::{Joint, Skin, VertexSkinningSystem}; pub use self::systems::{AnimationControlSystem, AnimationProcessor, SamplerInterpolationSystem, SamplerProcessor}; pub use self::transform::TransformChannel; -pub use self::util::{pause_animation, play_animation, set_animation_rate, toggle_animation, - SamplerPrimitive}; +pub use self::util::{pause_animation, play_animation, set_animation_input, set_animation_rate, + step_animation, toggle_animation, SamplerPrimitive}; pub use minterpolate::{InterpolationFunction, InterpolationPrimitive}; mod skinning; diff --git a/amethyst_animation/src/resources.rs b/amethyst_animation/src/resources.rs index 77403ff47c..2acfa19740 100644 --- a/amethyst_animation/src/resources.rs +++ b/amethyst_animation/src/resources.rs @@ -2,9 +2,10 @@ use std::hash::Hash; use std::marker; use std::time::Duration; -use amethyst_assets::{Asset, Handle, Result}; +use amethyst_assets::{Asset, AssetStorage, Handle, Result}; +use amethyst_core::timing::{duration_to_secs, secs_to_duration}; use fnv::FnvHashMap; -use minterpolate::{InterpolationFunction, InterpolationPrimitive}; +use minterpolate::{get_input_index, InterpolationFunction, InterpolationPrimitive}; use specs::{Component, DenseVecStorage, Entity, VecStorage}; /// Master trait used to define animation sampling on a component @@ -257,12 +258,62 @@ where .for_each(|sampler| sampler.rate_multiplier = rate_multiplier); } + /// Forcible set the input value (point of interpolation) + pub fn set_input(&mut self, input: f32) + where + T: AnimationSampling, + { + let dur = secs_to_duration(input); + self.samplers.values_mut().for_each(|sampler| { + if let ControlState::Running(_) = sampler.state { + sampler.state = ControlState::Running(dur); + } + }); + } + /// Check if a control set can be terminated pub fn check_termination(&self) -> bool { self.samplers .values() .all(|t| t.state == ControlState::Done || t.state == ControlState::Requested) } + + /// Step animation + pub fn step( + &mut self, + samplers: &AssetStorage>, + direction: &StepDirection, + ) { + self.samplers + .values_mut() + .filter(|t| t.state != ControlState::Done) + .map(|c| (samplers.get(&c.sampler).unwrap(), c)) + .for_each(|(s, c)| { + set_step_state(c, s, direction); + }); + } +} + +fn set_step_state( + control: &mut SamplerControl, + sampler: &Sampler, + direction: &StepDirection, +) where + T: AnimationSampling, +{ + if let ControlState::Running(dur) = control.state { + let dur_s = duration_to_secs(dur); + let new_index = match (get_input_index(dur_s, &sampler.input), direction) { + (Some(index), &StepDirection::Forward) if index >= sampler.input.len() - 1 => { + sampler.input.len() - 1 + } + (Some(index), &StepDirection::Forward) => index + 1, + (Some(0), &StepDirection::Backward) => 0, + (Some(index), &StepDirection::Backward) => index - 1, + (None, _) => 0, + }; + control.state = ControlState::Running(secs_to_duration(sampler.input[new_index])); + } } impl Component for SamplerControlSet @@ -272,11 +323,24 @@ where type Storage = DenseVecStorage; } +/// Used when doing animation stepping (i.e only move forward/backward to discrete input values) +#[derive(Clone, Debug)] +pub enum StepDirection { + /// Take a step forward + Forward, + /// Take a step backward + Backward, +} + /// Animation command #[derive(Clone, Debug)] pub enum AnimationCommand { /// Start the animation, or unpause if it's paused Start, + /// Step the animation forward/backward (move to the next/previous input value in sequence) + Step(StepDirection), + /// Forcible set current interpolation point for the animation, value in seconds + SetInputValue(f32), /// Pause the animation Pause, /// Abort the animation, will cause the control object to be removed from the world diff --git a/amethyst_animation/src/systems/control.rs b/amethyst_animation/src/systems/control.rs index ce7e1e793d..44c5e07829 100644 --- a/amethyst_animation/src/systems/control.rs +++ b/amethyst_animation/src/systems/control.rs @@ -7,7 +7,8 @@ use minterpolate::InterpolationPrimitive; use specs::{Component, Entities, Entity, Fetch, Join, ReadStorage, System, WriteStorage}; use resources::{Animation, AnimationCommand, AnimationControl, AnimationHierarchy, - AnimationSampling, ControlState, Sampler, SamplerControl, SamplerControlSet}; + AnimationSampling, ControlState, Sampler, SamplerControl, SamplerControlSet, + StepDirection}; /// System for setting up animations, should run before `SamplerInterpolationSystem`. /// @@ -69,6 +70,12 @@ where }) { control.state = state; } + if let AnimationCommand::Step(_) = control.command { + control.command = AnimationCommand::Start; + } + if let AnimationCommand::SetInputValue(_) = control.command { + control.command = AnimationCommand::Start; + } } for entity in remove { @@ -180,6 +187,16 @@ where Some(ControlState::Running(Duration::from_secs(0))) } + (&ControlState::Running(..), &AnimationCommand::Step(ref dir)) => { + step_animation(hierarchy, samplers, sampler_storage, dir); + None + } + + (&ControlState::Running(..), &AnimationCommand::SetInputValue(value)) => { + set_animation_input(hierarchy, samplers, value); + None + } + // check for finished/aborted animations, wait for samplers to signal done, // then remove control objects (&ControlState::Running(..), _) => { @@ -292,6 +309,35 @@ fn unpause_animation( } } +fn step_animation( + hierarchy: &AnimationHierarchy, + controls: &mut WriteStorage>, + sampler_storage: &AssetStorage>, + direction: &StepDirection, +) where + T: AnimationSampling, +{ + for (_, node_entity) in &hierarchy.nodes { + if let Some(ref mut s) = controls.get_mut(*node_entity) { + s.step(sampler_storage, direction); + } + } +} + +fn set_animation_input( + hierarchy: &AnimationHierarchy, + controls: &mut WriteStorage>, + input: f32, +) where + T: AnimationSampling, +{ + for (_, node_entity) in &hierarchy.nodes { + if let Some(ref mut s) = controls.get_mut(*node_entity) { + s.set_input(input); + } + } +} + fn update_animation_rate( hierarchy: &AnimationHierarchy, samplers: &mut WriteStorage>, diff --git a/amethyst_animation/src/util.rs b/amethyst_animation/src/util.rs index d31a778ed3..9705aa800c 100644 --- a/amethyst_animation/src/util.rs +++ b/amethyst_animation/src/util.rs @@ -5,7 +5,7 @@ use minterpolate::InterpolationPrimitive; use specs::{Entity, WriteStorage}; use resources::{Animation, AnimationCommand, AnimationControl, AnimationSampling, ControlState, - EndControl}; + EndControl, StepDirection}; /// Play a given animation on the given entity. /// @@ -125,6 +125,52 @@ pub fn set_animation_rate( } } +/// Step animation. +/// +/// ## Parameters: +/// +/// - `controls`: animation control storage in the world. +/// - `animation`: handle to the animation +/// - `entity`: entity the animation is running on. +/// - `direction`: direction to step the animation +pub fn step_animation( + controls: &mut WriteStorage>, + animation: &Handle>, + entity: Entity, + direction: StepDirection, +) where + T: AnimationSampling, +{ + if let Some(ref mut control) = controls.get_mut(entity) { + if control.animation == *animation && control.state.is_running() { + control.command = AnimationCommand::Step(direction); + } + } +} + +/// Forcibly set animation input value (i.e. the point of interpolation) +/// +/// ## Parameters: +/// +/// - `controls`: animation control storage in the world. +/// - `animation`: handle to the animation +/// - `entity`: entity the animation is running on. +/// - `input`: input value to set +pub fn set_animation_input( + controls: &mut WriteStorage>, + animation: &Handle>, + entity: Entity, + input: f32, +) where + T: AnimationSampling, +{ + if let Some(ref mut control) = controls.get_mut(entity) { + if control.animation == *animation && control.state.is_running() { + control.command = AnimationCommand::SetInputValue(input); + } + } +} + /// Sampler primitive #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum SamplerPrimitive diff --git a/examples/animation/main.rs b/examples/animation/main.rs index fea05fb78c..29367f0e03 100644 --- a/examples/animation/main.rs +++ b/examples/animation/main.rs @@ -9,11 +9,11 @@ use amethyst::core::{GlobalTransform, Parent, Transform, TransformBundle}; use amethyst::core::cgmath::{Deg, InnerSpace, Vector3}; use amethyst::ecs::{Entity, World}; use amethyst::prelude::*; -use amethyst::renderer::{AmbientColor, Camera, DisplayConfig, DrawShaded, Event, KeyboardInput, - Light, Mesh, Pipeline, PointLight, PosNormTex, Projection, RenderBundle, - Rgba, Stage, VirtualKeyCode, WindowEvent}; -use amethyst_animation::{play_animation, Animation, AnimationBundle, EndControl, - InterpolationFunction, Sampler, TransformChannel}; +use amethyst::renderer::{AmbientColor, Camera, DisplayConfig, DrawShaded, ElementState, Event, + KeyboardInput, Light, Mesh, Pipeline, PointLight, PosNormTex, Projection, + RenderBundle, Rgba, Stage, VirtualKeyCode, WindowEvent}; +use amethyst_animation::{play_animation, step_animation, Animation, AnimationBundle, EndControl, + InterpolationFunction, Sampler, StepDirection, TransformChannel}; use genmesh::{MapToVertices, Triangulate, Vertices}; use genmesh::generators::SphereUV; @@ -54,18 +54,43 @@ impl State for Example { WindowEvent::KeyboardInput { input: KeyboardInput { - virtual_keycode: Some(VirtualKeyCode::Space), + virtual_keycode, + state: ElementState::Released, .. }, .. } => { - play_animation( - &mut world.write(), - self.animation.as_ref().unwrap(), - self.sphere.unwrap().clone(), - EndControl::Loop(None), - 1.0, - ); + match virtual_keycode { + Some(VirtualKeyCode::Space) => { + play_animation( + &mut world.write(), + self.animation.as_ref().unwrap(), + self.sphere.unwrap().clone(), + EndControl::Loop(None), + 0.0, + ); + } + + Some(VirtualKeyCode::Left) => { + step_animation( + &mut world.write(), + self.animation.as_ref().unwrap(), + self.sphere.unwrap().clone(), + StepDirection::Backward, + ); + } + + Some(VirtualKeyCode::Right) => { + step_animation( + &mut world.write(), + self.animation.as_ref().unwrap(), + self.sphere.unwrap().clone(), + StepDirection::Forward, + ); + } + + _ => {} + } Trans::None }