use std::sync::Arc;
use crate::data::{
Animation, Attachment, AttachmentId, AttachmentRef, BoneData, ConstraintData, ConstraintId,
EventData, Skin, SkinId, SkinKeys, SlotData, SlotId, Timeline,
};
#[derive(Debug, Default, Clone, PartialEq)]
pub struct SkeletonData {
pub name: String,
pub version: String,
pub hash: String,
pub bones: Vec<BoneData>,
pub slots: Vec<SlotData>,
pub skins: Vec<Arc<Skin>>,
pub skin_keys: SkinKeys,
pub default_skin: Option<SkinId>,
pub events: Vec<EventData>,
pub animations: Vec<Animation>,
pub constraints: Vec<ConstraintData>,
pub attachments: Vec<Attachment>,
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
pub reference_scale: f32,
pub fps: f32,
pub images_path: String,
pub audio_path: String,
}
impl SkeletonData {
#[must_use]
pub fn find_bone(&self, name: &str) -> Option<&BoneData> {
self.bones.iter().find(|b| b.name == name)
}
#[must_use]
pub fn find_slot(&self, name: &str) -> Option<&SlotData> {
self.slots.iter().find(|s| s.name == name)
}
#[must_use]
pub fn find_skin(&self, name: &str) -> Option<&Arc<Skin>> {
self.skins.iter().find(|s| s.name == name)
}
#[must_use]
pub fn find_event(&self, name: &str) -> Option<&EventData> {
self.events.iter().find(|e| e.name == name)
}
#[must_use]
pub fn find_animation(&self, name: &str) -> Option<&Animation> {
self.animations.iter().find(|a| a.name == name)
}
#[must_use]
pub fn find_constraint(&self, name: &str) -> Option<ConstraintId> {
self.constraints
.iter()
.position(|c| c.name() == name)
.map(|i| ConstraintId(i as u16))
}
#[must_use]
pub fn skin_attachment(
&self,
skin: SkinId,
slot: SlotId,
placeholder: &str,
) -> Option<AttachmentId> {
match self.skins[skin.index()].get_named(&self.skin_keys, slot, placeholder)? {
AttachmentRef::Data(id) => Some(id),
AttachmentRef::Owned(_) => None,
}
}
pub fn intern_attachment_keys(&mut self) {
let keys = &mut self.skin_keys;
let mut intern = |slot: SlotId, name: Option<&str>| {
name.filter(|n| !n.is_empty()).map(|n| keys.intern(slot, n))
};
for slot in &mut self.slots {
slot.attachment_key = intern(slot.index, slot.attachment_name.as_deref());
}
for animation in &mut self.animations {
for timeline in &mut animation.timelines {
if let Timeline::Attachment {
slot, names, keys, ..
} = timeline
{
*keys = names.iter().map(|n| intern(*slot, n.as_deref())).collect();
}
}
}
}
#[must_use]
pub fn default_skin(&self) -> Option<&Arc<Skin>> {
self.default_skin.and_then(|id| self.skins.get(id.index()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::data::{BoneData, BoneId, EventData, EventId, SlotData, SlotId};
fn sample_skeleton() -> SkeletonData {
let mut sd = SkeletonData::default();
sd.bones.push(BoneData::new(BoneId(0), "root", None));
sd.bones
.push(BoneData::new(BoneId(1), "body", Some(BoneId(0))));
sd.slots.push(SlotData::new(SlotId(0), "body", BoneId(1)));
sd.events.push(EventData::new(EventId(0), "footstep"));
sd.animations.push(Animation::new("walk", 1.0));
sd.skins.push(Arc::new(Skin::new("default")));
sd.default_skin = Some(SkinId(0));
sd
}
#[test]
fn find_bone_hits_and_misses() {
let sd = sample_skeleton();
assert_eq!(sd.find_bone("root").unwrap().index, BoneId(0));
assert_eq!(sd.find_bone("body").unwrap().index, BoneId(1));
assert!(sd.find_bone("missing").is_none());
}
#[test]
fn find_by_name_across_collections() {
let sd = sample_skeleton();
assert!(sd.find_slot("body").is_some());
assert!(sd.find_event("footstep").is_some());
assert!(sd.find_animation("walk").is_some());
assert!(sd.find_skin("default").is_some());
assert!(sd.find_constraint("anything").is_none());
}
#[test]
fn default_skin_round_trips() {
let sd = sample_skeleton();
assert_eq!(sd.default_skin().unwrap().name, "default");
}
}