use crate::ecs::animation::components::{
AnimationClip, AnimationProperty, AnimationValue, sample_animation_channel,
};
use crate::ecs::world::{ANIMATION_PLAYER, MORPH_WEIGHTS, World};
use freecs::Entity;
use nalgebra_glm::{Quat, Vec3};
use std::collections::HashMap;
use crate::prelude::*;
pub fn update_animation_players(world: &mut World) {
let _span = tracing::info_span!("animation").entered();
let delta_time = world.resources.window.timing.delta_time;
let mut finished: Vec<Entity> = Vec::new();
let mut markers: Vec<(Entity, String)> = Vec::new();
world
.core
.query_mut()
.with(ANIMATION_PLAYER)
.iter(|entity, table, idx| {
let player = &mut table.animation_player[idx];
let was_playing = player.playing;
let old_time = player.time;
let looping = player.looping;
let current_clip = player.current_clip;
player.update(delta_time);
if was_playing && !player.playing {
finished.push(entity);
}
if !was_playing {
return;
}
let new_time = player.time;
if let Some(index) = current_clip
&& let Some(clip) = player.clips.get(index)
&& !clip.events.is_empty()
{
let wrapped = new_time < old_time && looping && clip.duration > 0.0;
for marker in &clip.events {
let crossed = if wrapped {
marker.time > old_time || marker.time <= new_time
} else {
marker.time > old_time && marker.time <= new_time
};
if crossed {
markers.push((entity, marker.name.clone()));
}
}
}
});
for entity in finished {
crate::ecs::event::emit_event(
world,
crate::ecs::event::Event::AnimationFinished { entity },
);
}
for (entity, name) in markers {
crate::ecs::event::emit_event(
world,
crate::ecs::event::Event::AnimationEvent { entity, name },
);
}
}
pub fn apply_animations(world: &mut World) {
let mut per_player_values: Vec<HashMap<Entity, AnimationAccumulator>> = Vec::new();
world
.core
.query()
.with(ANIMATION_PLAYER)
.iter(|_, table, idx| {
let player = &table.animation_player[idx];
if !player.playing {
return;
}
let node_mapping = &player.node_index_to_entity;
let bone_name_mapping = &player.bone_name_to_entity;
let mut final_values: HashMap<Entity, AnimationAccumulator> = if player.play_all {
let mut merged = HashMap::new();
for clip in &player.clips {
merge_accumulators(
&mut merged,
sample_clip(clip, player.time, node_mapping, bone_name_mapping),
);
}
merged
} else if let Some(clip) = player.get_current_clip() {
sample_clip(clip, player.time, node_mapping, bone_name_mapping)
} else {
HashMap::new()
};
if let Some(from_clip) = player
.blend_from_clip
.and_then(|index| player.clips.get(index))
{
let from_values = sample_clip(
from_clip,
player.blend_from_time,
node_mapping,
bone_name_mapping,
);
final_values =
blend_animation_values(&from_values, &final_values, player.blend_factor);
}
for layer in &player.layers {
if !layer.playing || layer.weight <= 0.0 {
continue;
}
let Some(clip) = player.clips.get(layer.clip_index) else {
continue;
};
let layer_values = sample_clip(clip, layer.time, node_mapping, bone_name_mapping);
let masked: Option<std::collections::HashSet<Entity>> = if layer.mask.is_empty() {
None
} else {
Some(
layer
.mask
.iter()
.filter_map(|bone| bone_name_mapping.get(bone).copied())
.collect(),
)
};
for (entity, layer_value) in layer_values {
if let Some(allowed) = &masked
&& !allowed.contains(&entity)
{
continue;
}
let blended =
blend_accumulators(final_values.get(&entity), &layer_value, layer.weight);
final_values.insert(entity, blended);
}
}
if !final_values.is_empty() {
per_player_values.push(final_values);
}
});
for final_values in per_player_values {
for (target_entity, values) in final_values {
if let Some(transform) = mutate_local_transform(world, target_entity) {
if let Some(translation) = values.translation {
transform.translation = translation;
}
if let Some(rotation) = values.rotation {
transform.rotation = rotation.normalize();
}
if let Some(scale) = values.scale {
transform.scale = scale;
}
}
if let Some(weights) = values.morph_weights {
apply_morph_weights_to_entity_and_children(world, target_entity, &weights);
}
}
}
}
fn merge_accumulators(
into: &mut HashMap<Entity, AnimationAccumulator>,
from: HashMap<Entity, AnimationAccumulator>,
) {
for (target_entity, values) in from {
let entry = into.entry(target_entity).or_default();
if values.translation.is_some() {
entry.translation = values.translation;
}
if values.rotation.is_some() {
entry.rotation = values.rotation;
}
if values.scale.is_some() {
entry.scale = values.scale;
}
if values.morph_weights.is_some() {
entry.morph_weights = values.morph_weights;
}
}
}
fn sample_clip(
clip: &AnimationClip,
time: f32,
node_mapping: &HashMap<usize, Entity>,
bone_name_mapping: &HashMap<String, Entity>,
) -> HashMap<Entity, AnimationAccumulator> {
let mut entity_values: HashMap<Entity, AnimationAccumulator> = HashMap::new();
for channel in clip.channels.iter() {
let target_entity = if let Some(ref bone_name) = channel.target_bone_name {
bone_name_mapping.get(bone_name).copied()
} else {
None
}
.or_else(|| node_mapping.get(&channel.target_node).copied());
if let Some(target_entity) = target_entity {
let sampled_value = sample_animation_channel(channel, time);
if let Some(value) = sampled_value {
let entry = entity_values.entry(target_entity).or_default();
match (channel.target_property, value) {
(AnimationProperty::Translation, AnimationValue::Vec3(translation)) => {
entry.translation = Some(translation);
}
(AnimationProperty::Rotation, AnimationValue::Quat(rotation)) => {
entry.rotation = Some(rotation);
}
(AnimationProperty::Scale, AnimationValue::Vec3(scale)) => {
entry.scale = Some(scale);
}
(AnimationProperty::MorphWeights, AnimationValue::Weights(weights)) => {
entry.morph_weights = Some(weights);
}
_ => {}
}
}
}
}
entity_values
}
fn blend_animation_values(
from: &HashMap<Entity, AnimationAccumulator>,
to: &HashMap<Entity, AnimationAccumulator>,
factor: f32,
) -> HashMap<Entity, AnimationAccumulator> {
let mut result: HashMap<Entity, AnimationAccumulator> = HashMap::new();
for (&entity, to_acc) in to {
let from_acc = from.get(&entity);
let blended = blend_accumulators(from_acc, to_acc, factor);
result.insert(entity, blended);
}
for (&entity, from_acc) in from {
if !to.contains_key(&entity) {
result.insert(entity, from_acc.clone());
}
}
result
}
fn blend_accumulators(
from: Option<&AnimationAccumulator>,
to: &AnimationAccumulator,
factor: f32,
) -> AnimationAccumulator {
let from = from.cloned().unwrap_or_default();
AnimationAccumulator {
translation: match (from.translation, to.translation) {
(Some(a), Some(b)) => Some(a + (b - a) * factor),
(None, Some(b)) => Some(b),
(Some(a), None) => Some(a),
(None, None) => None,
},
rotation: match (from.rotation, to.rotation) {
(Some(a), Some(b)) => {
let a_norm = a.normalize();
let b_norm = b.normalize();
let dot = a_norm.dot(&b_norm);
let b_adj = if dot < 0.0 { -b_norm } else { b_norm };
Some(nalgebra_glm::quat_slerp(&a_norm, &b_adj, factor).normalize())
}
(None, Some(b)) => Some(b),
(Some(a), None) => Some(a),
(None, None) => None,
},
scale: match (from.scale, to.scale) {
(Some(a), Some(b)) => Some(a + (b - a) * factor),
(None, Some(b)) => Some(b),
(Some(a), None) => Some(a),
(None, None) => None,
},
morph_weights: match (&from.morph_weights, &to.morph_weights) {
(Some(a), Some(b)) => {
let blended: Vec<f32> = a
.iter()
.zip(b.iter())
.map(|(a_val, b_val)| a_val + (b_val - a_val) * factor)
.collect();
Some(blended)
}
(None, Some(b)) => Some(b.clone()),
(Some(a), None) => Some(a.clone()),
(None, None) => None,
},
}
}
#[derive(Default, Clone)]
struct AnimationAccumulator {
translation: Option<Vec3>,
rotation: Option<Quat>,
scale: Option<Vec3>,
morph_weights: Option<Vec<f32>>,
}
fn apply_morph_weights_to_entity_and_children(world: &mut World, entity: Entity, weights: &[f32]) {
let mut pending = vec![entity];
while let Some(current) = pending.pop() {
if world.core.entity_has_components(current, MORPH_WEIGHTS)
&& let Some(morph_weights) = world.core.get_morph_weights_mut(current)
{
morph_weights.weights = weights.to_vec();
}
if let Some(children) = world.resources.transform_state.children_cache.get(¤t) {
pending.extend_from_slice(children);
}
}
}