Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upfeat: Animation of properties of LocalTransform #453
Conversation
Rhuagh
added
status: working
type: feature
labels
Oct 25, 2017
| +} | ||
| + | ||
| +/// Defines a single animation. | ||
| +/// Defines relationships between the node index in `AnimationHierarchy` and a `Sampler` in |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + Done, | ||
| +} | ||
| + | ||
| +/// The actual animation data for a single attribute |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| +/// Attaches to an entity, to control what animations are currently active | ||
| +#[derive(Clone)] | ||
| +pub struct AnimationControl { | ||
| + /// Animation index into `AnimationStorage` |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| @@ -42,6 +42,7 @@ thread_profiler = { version = "0.1", optional = true } | ||
| [dev-dependencies] | ||
| cgmath = "0.14" | ||
| genmesh = "0.4" | ||
| +amethyst_animation = { path = "amethyst_animation", version = "0.1.0" } |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Oct 25, 2017
Member
So you decided not to reexport this package? Should we maybe do the same for the gltf crate then, which also doesn't seem like a "mandatory" crate?
torkleyy
Oct 25, 2017
Member
So you decided not to reexport this package? Should we maybe do the same for the gltf crate then, which also doesn't seem like a "mandatory" crate?
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| +repository = "https://github.com/amethyst/amethyst" | ||
| + | ||
| +readme = "README.md" | ||
| +license = "MIT OR Apache-2.0" |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + } | ||
| +} | ||
| + | ||
| +/// Bundle for a complete animation setup including sampler interpolation and animation control |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| +} | ||
| + | ||
| +pub trait Interpolate: Debug + Send + Sync { | ||
| + fn interpolate_vec( |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + } | ||
| +} | ||
| + | ||
| +fn linear<D>(time_index: usize, time: f32, times: &Vec<f32>, values: &Vec<D>, normalize: bool) -> D |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + | ||
| + | ||
| +fn do_control_set_pause(control_set: &mut SamplerControlSet) { | ||
| + match control_set.translation { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| +} | ||
| + | ||
| +fn do_control_set_unpause(now: &Instant, control_set: &mut SamplerControlSet) { | ||
| + match control_set.translation { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + fn run(&mut self, (samplers, mut controls, mut transforms): Self::SystemData) { | ||
| + let now = Instant::now(); | ||
| + for (control_set, transform) in (&mut controls, &mut transforms).join() { | ||
| + match control_set.translation { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Oct 25, 2017
Member
could be if let Some(sampler) = control_set.translation.as_mut().and_then(|c| samplers.get(c.sampler))
torkleyy
Oct 25, 2017
Member
could be if let Some(sampler) = control_set.translation.as_mut().and_then(|c| samplers.get(c.sampler))
| + do_sampling(&sampler, &duration, transform); | ||
| + } | ||
| + Done => do_end_control(&control, transform), | ||
| + _ => (), |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + RestState::Scale(s) => transform.scale = s, | ||
| + }, | ||
| + // looping is handled during duration update | ||
| + _ => (), |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
added
the
project: animation
label
Oct 26, 2017
Rhuagh
changed the title from
[WIP] feat: Animation of properties of LocalTransform
to
feat: Animation of properties of LocalTransform
Oct 26, 2017
Rhuagh
added
status: ready
status: working
and removed
status: working
status: ready
labels
Oct 26, 2017
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Rhuagh
Oct 26, 2017
Member
After minterpolate has been released this will be ready. Need to get more testing done on that first though.
|
After |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
Ready for review. |
Rhuagh
added
status: ready
status: working
and removed
status: working
status: ready
labels
Oct 28, 2017
| +/// - `sampler_storage`: `AssetStorage` for all `Sampler`s | ||
| +/// - `samplers`: the active sampler sets | ||
| +/// - `transforms`: `LocalTransform`s, used to retrieve the rest pose before animation starts. | ||
| +/// - `remove`: all entities pushed here will have the control object removed at the end of the system execution |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Oct 28, 2017
Member
If that's the case, could you use .drain() on the control storage?
Ah with "here" you mean pushed to the vector.
torkleyy
Oct 28, 2017
•
Member
If that's the case, could you use .drain() on the control storage?
Ah with "here" you mean pushed to the vector.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + // and remove control object if all samplers are done and removed | ||
| + (_, &AnimationCommand::Abort) | (&ControlState::Abort, _) | (&ControlState::Done, _) => { | ||
| + if check_and_terminate_animation(hierarchy, samplers) { | ||
| + remove.push(entity.clone()); |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Oct 28, 2017
Member
torkleyy
Oct 28, 2017
Member
| + None => false, | ||
| + } { | ||
| + controls.insert( | ||
| + entity.clone(), |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + entity: &Entity, | ||
| +) { | ||
| + if let Some(ref mut control) = controls.get_mut(*entity) { | ||
| + if let ControlState::Running(..) = control.state { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Oct 28, 2017
Member
Maybe it would be convenient to have is_running and is_paused as methods?
torkleyy
Oct 28, 2017
Member
Maybe it would be convenient to have is_running and is_paused as methods?
| +pub fn toggle_animation( | ||
| + controls: &mut WriteStorage<AnimationControl>, | ||
| + animation: &Handle<Animation>, | ||
| + entity: &Entity, |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + }) => true, | ||
| + _ => false, | ||
| + }; | ||
| + if running { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Oct 28, 2017
Member
If you add those methods above, this could be
if controls.get(entity).map(|c| c.state.is_running()).unwrap_or(false) {}
torkleyy
Oct 28, 2017
Member
If you add those methods above, this could be
if controls.get(entity).map(|c| c.state.is_running()).unwrap_or(false) {}
Rhuagh
added
status: ready
and removed
status: working
labels
Oct 28, 2017
torkleyy
approved these changes
Oct 28, 2017
Thanks for addressing, got a couple of nits but otherwise it's great
| @@ -54,6 +55,7 @@ impl Component for AnimationHierarchy { | ||
| /// Defines a single animation. | ||
| /// Defines relationships between the node index in `AnimationHierarchy` and a `Sampler` handle. | ||
| +/// If the animation only target a single node index, `AnimationHierarchy` is not required. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| @@ -11,7 +11,8 @@ use resources::{Animation, AnimationCommand, AnimationControl, AnimationHierarch | ||
| /// System for setting up animations, should run before `SamplerInterpolationSystem`. | ||
| /// | ||
| /// Will process all active `AnimationControl` + `AnimationHierarchy`, and do processing of the | ||
| -/// animations they describe. | ||
| +/// animations they describe. If an animation only target a single node/entity, there is no need for |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| +/// Check if the given animation list is for a single node. If so, we don't need an | ||
| +/// `AnimationHierarchy`. | ||
| +fn only_one_index(nodes: &Vec<(usize, Handle<Sampler>)>) -> bool { | ||
| + if nodes.len() == 0 { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| @@ -70,14 +71,36 @@ impl<'a> System<'a> for AnimationControlSystem { | ||
| } | ||
| } | ||
| +/// Check if the given animation list is for a single node. If so, we don't need an | ||
| +/// `AnimationHierarchy`. | ||
| +fn only_one_index(nodes: &Vec<(usize, Handle<Sampler>)>) -> bool { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
I'll squash after approvals are in place. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
requested a review
from amethyst/engine-devs
Oct 29, 2017
omni-viral
requested changes
Oct 29, 2017
I like it. We really need good animation system.
I have a few questions.
Does this system supports
- blending of animations
- control animation speed
- animations stacking
?
| +#[derive(Clone, Debug)] | ||
| +pub struct Animation { | ||
| + /// node index -> sampler handle | ||
| + pub nodes: Vec<(usize, Handle<Sampler>)>, |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
omni-viral
Oct 29, 2017
Member
Here should be Entity instead of usize.
Mappings such as in AnimationHierarchy should be covered by specs saveload feature.
But we need to develop PrefabMarker first.
omni-viral
Oct 29, 2017
Member
Here should be Entity instead of usize.
Mappings such as in AnimationHierarchy should be covered by specs saveload feature.
But we need to develop PrefabMarker first.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Rhuagh
Oct 29, 2017
Member
No, because you can play animation on many different entities when you use prefabs.
Rhuagh
Oct 29, 2017
Member
No, because you can play animation on many different entities when you use prefabs.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Rhuagh
Oct 29, 2017
Member
You want to be able to share animations between many families of entities.
Rhuagh
Oct 29, 2017
Member
You want to be able to share animations between many families of entities.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
omni-viral
Oct 29, 2017
Member
Can you describe use case? Which can't be done with attaching other entities to entities of prefab.
omni-viral
Oct 29, 2017
•
Member
Can you describe use case? Which can't be done with attaching other entities to entities of prefab.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Rhuagh
Oct 29, 2017
Member
A quite common scenario for indie game development is that you have a single bone structure for many different units (think human, vulkan, klingon etc), and all units that share bone structure can share animations. This saves on time and cost of developing the game, because you don't have to produce as many animations.
So, animations are loaded once and then potentially shared between many different types of units/entities whatever.
The second issue with the suggestion you have is one of forward lookup. Given an animation and entity to play that animation on, how do i find which child entities to add samplers to ? If the only link we have is that the child entities have a reference to the entity of the prefab they were created from, you would have to look through the entire Parent (or some other component) storage to find the entitites that should be animated, which would be far to expensive imho. The other solution would be to have the AnimationHierarchy contain Entity -> Entity mappings. I went with the "node index -> Entity" solution, because it makes animation asset loading much, much easier.
Rhuagh
Oct 29, 2017
•
Member
A quite common scenario for indie game development is that you have a single bone structure for many different units (think human, vulkan, klingon etc), and all units that share bone structure can share animations. This saves on time and cost of developing the game, because you don't have to produce as many animations.
So, animations are loaded once and then potentially shared between many different types of units/entities whatever.
The second issue with the suggestion you have is one of forward lookup. Given an animation and entity to play that animation on, how do i find which child entities to add samplers to ? If the only link we have is that the child entities have a reference to the entity of the prefab they were created from, you would have to look through the entire Parent (or some other component) storage to find the entitites that should be animated, which would be far to expensive imho. The other solution would be to have the AnimationHierarchy contain Entity -> Entity mappings. I went with the "node index -> Entity" solution, because it makes animation asset loading much, much easier.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Rhuagh
Oct 29, 2017
Member
Animations can also target only a part of a hierarchy, not necessarily the entire hierarchy. Example: Sword swing will probably only affect arms and upper torso.
And it would force anyone that wants to do animation on something to go via the prefab system, which might be unwanted. Note that this animation system has wider support than just skeletal animation, it can do animation of any LocalTransform, as long as the Animation can target it via AnimationHierarchy.
Rhuagh
Oct 29, 2017
•
Member
Animations can also target only a part of a hierarchy, not necessarily the entire hierarchy. Example: Sword swing will probably only affect arms and upper torso.
And it would force anyone that wants to do animation on something to go via the prefab system, which might be unwanted. Note that this animation system has wider support than just skeletal animation, it can do animation of any LocalTransform, as long as the Animation can target it via AnimationHierarchy.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Rhuagh
Oct 29, 2017
Member
Regardless, this is not something that can be done within the scope of this PR, because it needs prefab loading first.
Rhuagh
Oct 29, 2017
Member
Regardless, this is not something that can be done within the scope of this PR, because it needs prefab loading first.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
omni-viral
Oct 30, 2017
Member
Example: Sword swing will probably only affect arms and upper torso.
But this system animate LocalTransform of entities. Will it share some parts with skeletal animation system?
omni-viral
Oct 30, 2017
Member
Example: Sword swing will probably only affect arms and upper torso.
But this system animate LocalTransform of entities. Will it share some parts with skeletal animation system?
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| +/// Sampler control set, containing optional samplers for each of the possible channels. | ||
| +/// | ||
| +/// Note that the `AnimationOutput` in the `Sampler` referenced, and the `RestState` referenced in | ||
| +/// each `SamplerControl` should match the attribute channel from the set here. There is no check |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Rhuagh
Oct 29, 2017
Member
This is in part due to how it looked at the start. I did consider changing this note, but came to the conclusion I wanted to get something out first. I will deal with this in a later PR.
Rhuagh
Oct 29, 2017
Member
This is in part due to how it looked at the start. I did consider changing this note, but came to the conclusion I wanted to get something out first. I will deal with this in a later PR.
| + let mut remove = Vec::default(); | ||
| + for (entity, control) in (&*entities, &mut controls).join() { | ||
| + if let Some(state) = animation_storage.get(&control.animation).and_then( | ||
| + |ref animation| { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + match times { | ||
| + // finite amount of looping, if 0 go do done, | ||
| + // else reduce iteration count and keep sampling | ||
| + Some(i) => if i <= 1 { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
This is a first version, so no blending, speed cobtrol or stacking no. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
omni-viral
Oct 29, 2017
Member
@Rhuagh ok. But you need to keep those features in mind so that adding them won't require total rewrite.
|
@Rhuagh ok. But you need to keep those features in mind so that adding them won't require total rewrite. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Rhuagh
Oct 29, 2017
Member
For the most part it will go something like this:
Speed control: additional parameter on AnimationCommand, and on SamplerControl.
Animation blending: SamplerControlSet gets a weight parameter, and it becomes Many<SamplerControlSet> as Component, Many<AnimationControl> as Component. Needs support in both systems.
Animation stacking: Basically the same as blending with the addition AnimationControl gets a control type parameter to differentiate between blend and stacking, and animation control system needs support for determining what to do at animation end.
|
For the most part it will go something like this: Speed control: additional parameter on |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
@omni-viral review addressed and issues fixed, please review again. |
| + ); | ||
| + | ||
| + fn run(&mut self, (samplers, mut controls, mut transforms): Self::SystemData) { | ||
| + let now = Instant::now(); |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
omni-viral
Oct 30, 2017
Member
I believe that all systems should use central timer from amethyst_core
omni-viral
Oct 30, 2017
Member
I believe that all systems should use central timer from amethyst_core
| + type HandleStorage = DenseVecStorage<Handle<Self>>; | ||
| +} | ||
| + | ||
| +impl Into<Result<Animation>> for Animation { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Rhuagh
Oct 30, 2017
Member
I'm using the Processor from the assets crate to do asset processing for Sampler and Animation, and it has Asset::Data = Animation (it converts into itself since it doesn't need a factory to convert or such), and that requires an impl Into<assets::Result<Asset>> for Asset::Data
Rhuagh
Oct 30, 2017
•
Member
I'm using the Processor from the assets crate to do asset processing for Sampler and Animation, and it has Asset::Data = Animation (it converts into itself since it doesn't need a factory to convert or such), and that requires an impl Into<assets::Result<Asset>> for Asset::Data
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Rhuagh
Oct 30, 2017
Member
Yes, I could do manual processing of them in my own system, just felt silly to build a new system for something that already exists.
Rhuagh
Oct 30, 2017
Member
Yes, I could do manual processing of them in my own system, just felt silly to build a new system for something that already exists.
| + } | ||
| +} | ||
| + | ||
| +fn next_duration(last_frame: f32, mut dur_s: f32) -> Duration { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
omni-viral
Oct 30, 2017
Member
It should return number of loops eaten by loop. This number should be subtracted from number of loops here
omni-viral
Oct 30, 2017
Member
It should return number of loops eaten by loop. This number should be subtracted from number of loops here
| + ), | ||
| + EndControl::Loop(Some(i)) => ( | ||
| + Running(now.clone(), next_duration(last_frame, duration_secs)), | ||
| + Some(EndControl::Loop(Some(i - 1))), |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
omni-viral
Oct 30, 2017
Member
You should subtract number equal to time_elapsed / animation_duration and put time_elapsed % animation_duration into Running(_, >here<)
omni-viral
Oct 30, 2017
Member
You should subtract number equal to time_elapsed / animation_duration and put time_elapsed % animation_duration into Running(_, >here<)
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + | ||
| +fn next_duration(last_frame: f32, mut dur_s: f32) -> Duration { | ||
| + while last_frame != 0. && dur_s > last_frame { | ||
| + dur_s -= last_frame; |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
omni-viral
Oct 30, 2017
Member
Division should work faster then loop.
And you probably want to work with milliseconds in u32 (or nanoseconds in u64) instead of seconds in f32.
omni-viral
Oct 30, 2017
Member
Division should work faster then loop.
And you probably want to work with milliseconds in u32 (or nanoseconds in u64) instead of seconds in f32.
| + }, | ||
| + )) | ||
| + } | ||
| + Weights => Err(GltfError::NotImplemented), |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| +) { | ||
| + if !match controls.get_mut(entity) { | ||
| + Some(ref mut control) => { | ||
| + if control.state.is_paused() { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
omni-viral
Oct 30, 2017
Member
If pause was requested then it will be paused on next system run.
Command should be set to Start unconditionally.
omni-viral
Oct 30, 2017
Member
If pause was requested then it will be paused on next system run.
Command should be set to Start unconditionally.
| + end: EndControl, | ||
| +) { | ||
| + if !match controls.get_mut(entity) { | ||
| + Some(ref mut control) => { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + entity: Entity, | ||
| + end: EndControl, | ||
| +) { | ||
| + if !match controls.get_mut(entity) { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
omni-viral
Oct 30, 2017
Member
if !match is poorly readable.
Use single match block with guards or if let Some if appropriate.
omni-viral
Oct 30, 2017
Member
if !match is poorly readable.
Use single match block with guards or if let Some if appropriate.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Rhuagh
Oct 30, 2017
Member
The problem is borrowing rules. I'd have to break it up into two if let ..
Rhuagh
Oct 30, 2017
Member
The problem is borrowing rules. I'd have to break it up into two if let ..
| + .map(|c| c.state.is_running()) | ||
| + .unwrap_or(false) | ||
| + { | ||
| + pause_animation(controls, entity); |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Rhuagh
Oct 30, 2017
Member
Since we only support a single animation atm, it felt like an unnecessary check for now.
Rhuagh
Oct 30, 2017
Member
Since we only support a single animation atm, it felt like an unnecessary check for now.
Xaeroxe
approved these changes
Oct 30, 2017
Due to my inexperience with 3D animation it's really hard for me to say whether this is adequate or not, however I don't see anything here that particularly upsets me so I'll give this an approval with the disclaimer that I don't know a ton about what I'm reading here.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
@omni-viral review comments addressed, please review again. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Nov 1, 2017
Member
Some more reviews on this would be really appreciated. I'll try to do another in-depth review on this, but one or two more wouldn't hurt.
|
Some more reviews on this would be really appreciated. I'll try to do another in-depth review on this, but one or two more wouldn't hurt. |
Rhuagh
added some commits
Oct 30, 2017
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
bors r+ |
Rhuagh commentedOct 25, 2017
•
edited
Edited 4 times
-
Rhuagh
edited Oct 26, 2017 (most recent)
-
Rhuagh
edited Oct 25, 2017
-
Rhuagh
edited Oct 25, 2017
-
Rhuagh
edited Oct 25, 2017
Opening early to get feedback. Note that this PR only contains animation of properties on LocalTransform, and have no skinning support. That will come in a later PR.
Work left:
Testing!!!DocumentationInterpolation needs a rework