use crate::data::{
Attachment, AttachmentId, BoneId, ConstraintId, EventId, Inherit, SkinKey, SlotId,
};
use crate::math::Color;
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Animation {
pub name: String,
pub duration: f32,
pub timelines: Vec<Timeline>,
pub bones: Vec<BoneId>,
pub color: Color,
property_ids: Vec<PropertyId>,
property_ends: Vec<u32>,
sorted_property_ids: Vec<PropertyId>,
}
impl Animation {
#[must_use]
pub fn new(name: impl Into<String>, duration: f32) -> Self {
Self {
name: name.into(),
duration,
timelines: Vec::new(),
bones: Vec::new(),
color: Color::WHITE,
property_ids: Vec::new(),
property_ends: Vec::new(),
sorted_property_ids: Vec::new(),
}
}
pub fn set_timelines(&mut self, timelines: Vec<Timeline>, attachments: &[Attachment]) {
self.property_ids.clear();
self.property_ends.clear();
for t in &timelines {
t.push_property_ids(attachments, &mut self.property_ids);
self.property_ends.push(self.property_ids.len() as u32);
}
self.sorted_property_ids.clone_from(&self.property_ids);
self.sorted_property_ids.sort_unstable();
self.sorted_property_ids.dedup();
self.timelines = timelines;
}
#[must_use]
pub fn timeline_property_ids(&self, i: usize) -> &[PropertyId] {
let start = if i == 0 {
0
} else {
self.property_ends[i - 1] as usize
};
&self.property_ids[start..self.property_ends[i] as usize]
}
#[must_use]
pub fn has_property(&self, id: PropertyId) -> bool {
self.sorted_property_ids.binary_search(&id).is_ok()
}
#[must_use]
pub fn has_timeline(&self, ids: &[PropertyId]) -> bool {
ids.iter().any(|&id| self.has_property(id))
}
}
pub type PropertyId = i64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i64)]
pub enum Property {
Rotate = 0,
X,
Y,
ScaleX,
ScaleY,
ShearX,
ShearY,
Inherit,
Rgb,
Alpha,
Rgb2,
Attachment,
Deform,
Event,
DrawOrder,
IkConstraint,
TransformConstraint,
PathConstraintPosition,
PathConstraintSpacing,
PathConstraintMix,
PhysicsConstraintInertia,
PhysicsConstraintStrength,
PhysicsConstraintDamping,
PhysicsConstraintMass,
PhysicsConstraintWind,
PhysicsConstraintGravity,
PhysicsConstraintMix,
PhysicsConstraintReset,
Sequence,
SliderTime,
SliderMix,
DrawOrderFolder,
}
#[inline]
#[must_use]
pub fn property_id(property: Property, payload: i64) -> PropertyId {
((property as i64) << 32) | payload
}
#[derive(Debug, Default, Clone, PartialEq)]
pub struct CurveFrames {
pub frames: Vec<f32>,
pub curves: Vec<f32>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct AnimationEvent {
pub time: f32,
pub event: EventId,
pub int_value: i32,
pub float_value: f32,
pub string_value: Option<std::sync::Arc<str>>,
pub volume: f32,
pub balance: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PhysicsProperty {
Inertia,
Strength,
Damping,
Mass,
Wind,
Gravity,
Mix,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Timeline {
Rotate {
bone: BoneId,
curves: CurveFrames,
},
Translate {
bone: BoneId,
curves: CurveFrames,
},
TranslateX {
bone: BoneId,
curves: CurveFrames,
},
TranslateY {
bone: BoneId,
curves: CurveFrames,
},
Scale {
bone: BoneId,
curves: CurveFrames,
},
ScaleX {
bone: BoneId,
curves: CurveFrames,
},
ScaleY {
bone: BoneId,
curves: CurveFrames,
},
Shear {
bone: BoneId,
curves: CurveFrames,
},
ShearX {
bone: BoneId,
curves: CurveFrames,
},
ShearY {
bone: BoneId,
curves: CurveFrames,
},
Inherit {
bone: BoneId,
frames: Vec<f32>,
inherits: Vec<Inherit>,
},
Attachment {
slot: SlotId,
frames: Vec<f32>,
names: Vec<Option<String>>,
keys: Vec<Option<SkinKey>>,
},
Rgba {
slot: SlotId,
curves: CurveFrames,
},
Rgb {
slot: SlotId,
curves: CurveFrames,
},
Alpha {
slot: SlotId,
curves: CurveFrames,
},
Rgba2 {
slot: SlotId,
curves: CurveFrames,
},
Rgb2 {
slot: SlotId,
curves: CurveFrames,
},
Deform {
slot: SlotId,
attachment: AttachmentId,
curves: CurveFrames,
vertices: Vec<Vec<f32>>,
},
Sequence {
slot: SlotId,
attachment: AttachmentId,
frames: Vec<f32>,
},
DrawOrder {
frames: Vec<f32>,
draw_orders: Vec<Option<Vec<SlotId>>>,
},
DrawOrderFolder {
slots: Vec<SlotId>,
frames: Vec<f32>,
draw_orders: Vec<Option<Vec<u16>>>,
},
Event {
frames: Vec<f32>,
events: Vec<AnimationEvent>,
},
IkConstraint {
constraint: ConstraintId,
curves: CurveFrames,
},
TransformConstraint {
constraint: ConstraintId,
curves: CurveFrames,
},
PathConstraintPosition {
constraint: ConstraintId,
curves: CurveFrames,
},
PathConstraintSpacing {
constraint: ConstraintId,
curves: CurveFrames,
},
PathConstraintMix {
constraint: ConstraintId,
curves: CurveFrames,
},
Physics {
constraint: Option<ConstraintId>,
property: PhysicsProperty,
curves: CurveFrames,
},
PhysicsReset {
constraint: Option<ConstraintId>,
frames: Vec<f32>,
},
Slider {
constraint: ConstraintId,
curves: CurveFrames,
},
SliderMix {
constraint: ConstraintId,
curves: CurveFrames,
},
}
impl Timeline {
fn push_property_ids(&self, attachments: &[Attachment], out: &mut Vec<PropertyId>) {
use Property as P;
let mut push = |p: P, payload: i64| out.push(property_id(p, payload));
match self {
Timeline::Rotate { bone, .. } => push(P::Rotate, bone.0.into()),
Timeline::Translate { bone, .. } => {
push(P::X, bone.0.into());
push(P::Y, bone.0.into());
}
Timeline::TranslateX { bone, .. } => push(P::X, bone.0.into()),
Timeline::TranslateY { bone, .. } => push(P::Y, bone.0.into()),
Timeline::Scale { bone, .. } => {
push(P::ScaleX, bone.0.into());
push(P::ScaleY, bone.0.into());
}
Timeline::ScaleX { bone, .. } => push(P::ScaleX, bone.0.into()),
Timeline::ScaleY { bone, .. } => push(P::ScaleY, bone.0.into()),
Timeline::Shear { bone, .. } => {
push(P::ShearX, bone.0.into());
push(P::ShearY, bone.0.into());
}
Timeline::ShearX { bone, .. } => push(P::ShearX, bone.0.into()),
Timeline::ShearY { bone, .. } => push(P::ShearY, bone.0.into()),
Timeline::Inherit { bone, .. } => push(P::Inherit, bone.0.into()),
Timeline::Attachment { slot, .. } => push(P::Attachment, slot.0.into()),
Timeline::Rgba { slot, .. } => {
push(P::Rgb, slot.0.into());
push(P::Alpha, slot.0.into());
}
Timeline::Rgb { slot, .. } => push(P::Rgb, slot.0.into()),
Timeline::Alpha { slot, .. } => push(P::Alpha, slot.0.into()),
Timeline::Rgba2 { slot, .. } => {
push(P::Rgb, slot.0.into());
push(P::Alpha, slot.0.into());
push(P::Rgb2, slot.0.into());
}
Timeline::Rgb2 { slot, .. } => {
push(P::Rgb, slot.0.into());
push(P::Rgb2, slot.0.into());
}
Timeline::Deform {
slot, attachment, ..
} => push(
P::Deform,
((i64::from(slot.0) << 16) | i64::from(attachment.0)) & 0xffff_ffff,
),
Timeline::Sequence {
slot, attachment, ..
} => {
let sequence_id = attachments[attachment.index()]
.sequence()
.map_or(0, |s| i64::from(s.id));
push(
P::Sequence,
((i64::from(slot.0) << 16) | sequence_id) & 0xffff_ffff,
);
}
Timeline::DrawOrder { .. } => push(P::DrawOrder, 0),
Timeline::DrawOrderFolder { slots, .. } => {
for slot in slots {
push(P::DrawOrderFolder, slot.0.into());
}
}
Timeline::Event { .. } => push(P::Event, 0),
Timeline::IkConstraint { constraint, .. } => push(P::IkConstraint, constraint.0.into()),
Timeline::TransformConstraint { constraint, .. } => {
push(P::TransformConstraint, constraint.0.into());
}
Timeline::PathConstraintPosition { constraint, .. } => {
push(P::PathConstraintPosition, constraint.0.into());
}
Timeline::PathConstraintSpacing { constraint, .. } => {
push(P::PathConstraintSpacing, constraint.0.into());
}
Timeline::PathConstraintMix { constraint, .. } => {
push(P::PathConstraintMix, constraint.0.into());
}
Timeline::Physics {
constraint,
property,
..
} => {
let p = match property {
PhysicsProperty::Inertia => P::PhysicsConstraintInertia,
PhysicsProperty::Strength => P::PhysicsConstraintStrength,
PhysicsProperty::Damping => P::PhysicsConstraintDamping,
PhysicsProperty::Mass => P::PhysicsConstraintMass,
PhysicsProperty::Wind => P::PhysicsConstraintWind,
PhysicsProperty::Gravity => P::PhysicsConstraintGravity,
PhysicsProperty::Mix => P::PhysicsConstraintMix,
};
push(p, constraint.map_or(-1, |c| c.0.into()) & 0xffff_ffff);
}
Timeline::PhysicsReset { constraint, .. } => push(
P::PhysicsConstraintReset,
constraint.map_or(-1, |c| c.0.into()) & 0xffff_ffff,
),
Timeline::Slider { constraint, .. } => push(P::SliderTime, constraint.0.into()),
Timeline::SliderMix { constraint, .. } => push(P::SliderMix, constraint.0.into()),
}
}
#[must_use]
pub fn is_additive(&self) -> bool {
matches!(
self,
Timeline::Rotate { .. }
| Timeline::Translate { .. }
| Timeline::TranslateX { .. }
| Timeline::TranslateY { .. }
| Timeline::Scale { .. }
| Timeline::ScaleX { .. }
| Timeline::ScaleY { .. }
| Timeline::Shear { .. }
| Timeline::ShearX { .. }
| Timeline::ShearY { .. }
| Timeline::Deform { .. }
| Timeline::TransformConstraint { .. }
| Timeline::PathConstraintPosition { .. }
| Timeline::Physics {
property: PhysicsProperty::Wind | PhysicsProperty::Gravity,
..
}
| Timeline::SliderMix { .. }
)
}
#[must_use]
pub fn is_instant(&self) -> bool {
matches!(
self,
Timeline::Inherit { .. }
| Timeline::Attachment { .. }
| Timeline::Sequence { .. }
| Timeline::Event { .. }
| Timeline::DrawOrder { .. }
| Timeline::DrawOrderFolder { .. }
| Timeline::PhysicsReset { .. }
)
}
#[must_use]
pub fn bone(&self) -> Option<BoneId> {
match self {
Timeline::Rotate { bone, .. }
| Timeline::Translate { bone, .. }
| Timeline::TranslateX { bone, .. }
| Timeline::TranslateY { bone, .. }
| Timeline::Scale { bone, .. }
| Timeline::ScaleX { bone, .. }
| Timeline::ScaleY { bone, .. }
| Timeline::Shear { bone, .. }
| Timeline::ShearX { bone, .. }
| Timeline::ShearY { bone, .. }
| Timeline::Inherit { bone, .. } => Some(*bone),
_ => None,
}
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)] mod tests {
use super::*;
#[test]
fn animation_defaults_empty() {
let a = Animation::new("walk", 1.5);
assert_eq!(a.name, "walk");
assert_eq!(a.duration, 1.5);
assert_eq!(a.timelines, []);
}
#[test]
fn timeline_variants_compile_and_clone() {
let variants = vec![
Timeline::Rotate {
bone: BoneId(0),
curves: CurveFrames::default(),
},
Timeline::Translate {
bone: BoneId(0),
curves: CurveFrames::default(),
},
Timeline::Inherit {
bone: BoneId(0),
frames: vec![],
inherits: vec![],
},
Timeline::Attachment {
slot: SlotId(0),
frames: vec![],
names: vec![],
keys: vec![],
},
Timeline::Rgba {
slot: SlotId(0),
curves: CurveFrames::default(),
},
Timeline::Deform {
slot: SlotId(0),
attachment: AttachmentId(0),
curves: CurveFrames::default(),
vertices: vec![],
},
Timeline::DrawOrder {
frames: vec![],
draw_orders: vec![],
},
Timeline::Event {
frames: vec![],
events: vec![],
},
Timeline::IkConstraint {
constraint: ConstraintId(0),
curves: CurveFrames::default(),
},
Timeline::Physics {
constraint: Some(ConstraintId(0)),
property: PhysicsProperty::Wind,
curves: CurveFrames::default(),
},
Timeline::PhysicsReset {
constraint: None,
frames: vec![],
},
];
let cloned = variants.clone();
assert_eq!(variants.len(), cloned.len());
}
}