use awsm_scene::animation::{
ClipDirection, ClipLoop, LayerModeDoc, MixerDoc, SamplerKind, StoredAnimation, StoredTrack,
TrackTarget, TrackValue, TransformProp,
};
use awsm_scene::{AssetId, NodeId};
use glam::{Quat, Vec2, Vec3, Vec4};
use super::{
AnimationChannel, AnimationClipGroup, AnimationClipKey, AnimationData, AnimationLayer,
AnimationLoopStyle, AnimationMixer, AnimationPlayDirection, AnimationSampler, AnimationStrip,
AnimationTarget, TargetMask, TransformAnimation, VertexAnimation,
};
pub fn lower_stored_clip(
clip: &StoredAnimation,
resolve: impl Fn(&TrackTarget) -> Option<AnimationTarget>,
) -> AnimationClipGroup {
let channels: Vec<AnimationChannel> = clip
.tracks
.iter()
.filter(|t| !t.mute)
.filter_map(|t| lower_stored_track(t, &resolve))
.collect();
let mut group = AnimationClipGroup::new(clip.name.clone(), clip.duration, channels);
group.loop_style = match clip.loop_style {
ClipLoop::Loop => Some(AnimationLoopStyle::Loop),
ClipLoop::PingPong => Some(AnimationLoopStyle::PingPong),
ClipLoop::Once => None,
};
group.speed = clip.speed;
group.play_direction = match clip.direction {
ClipDirection::Forward => AnimationPlayDirection::Forward,
ClipDirection::Reverse => AnimationPlayDirection::Backward,
};
group
}
fn lower_stored_track(
track: &StoredTrack,
resolve: &impl Fn(&TrackTarget) -> Option<AnimationTarget>,
) -> Option<AnimationChannel> {
let target = resolve(&track.target)?;
if track.times.is_empty() || track.keys.len() != track.times.len() {
return None;
}
let prop = match &track.target {
TrackTarget::Transform { prop, .. } => Some(*prop),
_ => None,
};
let morph_index = match &track.target {
TrackTarget::Morph { index, .. } => Some(*index),
_ => None,
};
let to_data = |v: &TrackValue| -> AnimationData {
match morph_index {
Some(i) => morph_scalar_to_vertex(v, i),
None => track_value_to_data(v, prop),
}
};
let values: Vec<AnimationData> = track.keys.iter().map(|k| to_data(&k.value)).collect();
let sampler = match track.sampler {
SamplerKind::Linear => AnimationSampler::new_linear(track.times.clone(), values),
SamplerKind::Step => AnimationSampler::new_step(track.times.clone(), values),
SamplerKind::Cubic => {
let in_tangents = track.keys.iter().map(|k| to_data(&k.in_tangent)).collect();
let out_tangents = track.keys.iter().map(|k| to_data(&k.out_tangent)).collect();
AnimationSampler::new_cubic_spline(
track.times.clone(),
values,
in_tangents,
out_tangents,
)
}
};
Some(AnimationChannel::new(target, sampler))
}
pub fn lower_stored_mixer(
doc: &MixerDoc,
clip_key: impl Fn(AssetId) -> Option<AnimationClipKey>,
mask: impl Fn(&[NodeId], bool) -> TargetMask,
) -> AnimationMixer {
let mut mixer = AnimationMixer::new();
for layer in &doc.layers {
let strips: Vec<AnimationStrip> = layer
.strips
.iter()
.filter_map(|s| {
clip_key(s.clip).map(|key| AnimationStrip {
clip: key,
start: s.start,
len: s.len,
scale: s.scale,
repeat: s.repeat,
})
})
.collect();
let mut runtime_layer = match layer.mode {
LayerModeDoc::Replace => AnimationLayer::new_replace(strips),
LayerModeDoc::Additive { base_clip } => {
AnimationLayer::new_additive(base_clip.and_then(&clip_key), strips)
}
};
runtime_layer.weight = layer.weight;
if !layer.mask_nodes.is_empty() {
runtime_layer.mask = Some(mask(&layer.mask_nodes, layer.include_descendants));
}
mixer.layers.push(runtime_layer);
}
mixer
}
fn track_value_to_data(value: &TrackValue, prop: Option<TransformProp>) -> AnimationData {
match (prop, value) {
(Some(TransformProp::Translation), TrackValue::Vec3(v)) => {
AnimationData::Transform(TransformAnimation::new_translation(Vec3::from_array(*v)))
}
(Some(TransformProp::Scale), TrackValue::Vec3(v)) => {
AnimationData::Transform(TransformAnimation::new_scale(Vec3::from_array(*v)))
}
(Some(TransformProp::Rotation), TrackValue::Quat(q)) => {
AnimationData::Transform(TransformAnimation::new_rotation(Quat::from_array(*q)))
}
(None, TrackValue::Scalar(s)) => AnimationData::F32(*s),
(None, TrackValue::Vec2(v)) => AnimationData::Vec2(Vec2::from_array(*v)),
(None, TrackValue::Vec3(v)) => AnimationData::Vec3(Vec3::from_array(*v)),
(None, TrackValue::Vec4(v)) => AnimationData::Vec4(Vec4::from_array(*v)),
(None, TrackValue::Quat(q)) => AnimationData::Quat(Quat::from_array(*q)),
(Some(_), TrackValue::Scalar(s)) => AnimationData::F32(*s),
(Some(_), TrackValue::Vec2(v)) => AnimationData::Vec2(Vec2::from_array(*v)),
(Some(_), TrackValue::Vec4(v)) => AnimationData::Vec4(Vec4::from_array(*v)),
(Some(TransformProp::Translation) | Some(TransformProp::Scale), TrackValue::Quat(q)) => {
AnimationData::Quat(Quat::from_array(*q))
}
(Some(TransformProp::Rotation), TrackValue::Vec3(v)) => {
AnimationData::Vec3(Vec3::from_array(*v))
}
}
}
fn morph_scalar_to_vertex(value: &TrackValue, index: usize) -> AnimationData {
let scalar = match value {
TrackValue::Scalar(s) => *s,
TrackValue::Vec2(v) => v.first().copied().unwrap_or(0.0),
TrackValue::Vec3(v) => v.first().copied().unwrap_or(0.0),
TrackValue::Vec4(v) => v.first().copied().unwrap_or(0.0),
TrackValue::Quat(q) => q.first().copied().unwrap_or(0.0),
};
let mut weights = vec![0.0_f32; index + 1];
weights[index] = scalar;
AnimationData::Vertex(VertexAnimation::new(weights))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transforms::TransformKey;
use awsm_scene::animation::{Interp, Keyframe};
fn vec3_key(v: [f32; 3]) -> Keyframe {
Keyframe {
value: TrackValue::Vec3(v),
interp: Interp::Linear,
in_tangent: TrackValue::Vec3([0.0; 3]),
out_tangent: TrackValue::Vec3([0.0; 3]),
}
}
#[test]
fn stored_translation_clip_lowers_and_samples() {
let node = NodeId::new();
let stored = StoredAnimation {
id: AssetId::new(),
name: "Walk".into(),
duration: 1.0,
loop_style: ClipLoop::Loop,
speed: 1.0,
direction: ClipDirection::Forward,
color: String::new(),
tracks: vec![StoredTrack {
target: TrackTarget::Transform {
node,
prop: TransformProp::Translation,
},
sampler: SamplerKind::Linear,
mute: false,
solo: false,
expanded: false,
times: vec![0.0, 1.0],
keys: vec![vec3_key([0.0, 0.0, 0.0]), vec3_key([0.0, 10.0, 0.0])],
}],
};
let key = TransformKey::default();
let group = lower_stored_clip(&stored, |_t| Some(AnimationTarget::Transform(key)));
assert_eq!(group.channels.len(), 1);
assert_eq!(group.loop_style, Some(AnimationLoopStyle::Loop));
assert!((group.speed - 1.0).abs() < 1e-12);
let mut got = None;
group.for_each_sample_at(0.5, |_target, data| got = Some(data));
match got {
Some(AnimationData::Transform(t)) => {
let tr = t.translation.expect("translation present");
assert!((tr.x - 0.0).abs() < 1e-5);
assert!((tr.y - 5.0).abs() < 1e-5);
assert!((tr.z - 0.0).abs() < 1e-5);
}
other => panic!("expected Transform translation, got {other:?}"),
}
}
#[test]
fn muted_and_unresolved_tracks_are_skipped() {
let stored = StoredAnimation {
id: AssetId::new(),
name: "C".into(),
duration: 1.0,
loop_style: ClipLoop::Once,
speed: 1.0,
direction: ClipDirection::Forward,
color: String::new(),
tracks: vec![StoredTrack {
target: TrackTarget::Transform {
node: NodeId::new(),
prop: TransformProp::Translation,
},
sampler: SamplerKind::Linear,
mute: true,
solo: false,
expanded: false,
times: vec![0.0, 1.0],
keys: vec![vec3_key([0.0; 3]), vec3_key([1.0; 3])],
}],
};
let group = lower_stored_clip(&stored, |_t| {
Some(AnimationTarget::Transform(TransformKey::default()))
});
assert_eq!(group.channels.len(), 0);
assert_eq!(group.loop_style, None); }
}