use serde::{Deserialize, Serialize};
use crate::{
data::runtime::skeleton::{BoneDataStatic, InheritMode, ShearMode, Skeleton},
error::Error,
utils::OptionMoveTo,
};
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct BoneDataStore {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub translate_x: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub translate_y: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rotation_deg: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scale_x: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scale_y: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub shear_x_deg: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub shear_y_deg: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub length: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub inherit_mode: Option<InheritMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_active: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub debug_color: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub shear_mode: Option<ShearMode>,
}
#[test]
fn t_serde() {
let b = BoneDataStore::default();
let s = serde_json::to_string(&b).unwrap();
println!("{}", s);
assert_eq!(s, r#"{"name":""}"#);
}
impl Skeleton {
pub fn from_bone_data_list(bones: &[BoneDataStore]) -> Result<Self, Error> {
Self::from_bone_data_list_config(bones, true)
}
pub fn from_bone_data_list_config(
bones: &[BoneDataStore],
ignore_color_err: bool,
) -> Result<Self, Error> {
let mut skeleton = Skeleton::new();
for bone in bones {
if skeleton.get_bone_index(&bone.name).is_some() {
return Err(Error::InvalidData(format!(
"Duplicate bone name: {}",
bone.name
)));
}
let parent_idx = match &bone.parent {
Some(pname) => Some(skeleton.get_bone_index(pname).ok_or_else(|| {
Error::InvalidData(format!(
"Parent bone not found: processing {}, parent {}",
bone.name, pname
))
})?),
None => None,
};
let mut bone_rt = BoneDataStatic::default();
bone.translate_x.write_to(&mut bone_rt.translate_x);
bone.translate_y.write_to(&mut bone_rt.translate_y);
bone.rotation_deg.write_to(&mut bone_rt.rotation_deg);
bone.scale_x.write_to(&mut bone_rt.scale_x);
bone.scale_y.write_to(&mut bone_rt.scale_y);
bone.shear_x_deg.write_to(&mut bone_rt.shear_x_deg);
bone.shear_y_deg.write_to(&mut bone_rt.shear_y_deg);
bone.length.write_to(&mut bone_rt.length);
bone.inherit_mode.write_to(&mut bone_rt.inherit_mode);
bone.shear_mode.write_to(&mut bone_rt.shear_mode);
bone.default_active.write_to(&mut bone_rt.default_active);
if let Some(color) = bone.debug_color.as_ref() {
if ignore_color_err {
if let Ok(c) = rrggbbaa_to_4f32(color) {
bone_rt.debug_color = c;
}
} else {
bone_rt.debug_color = rrggbbaa_to_4f32(color)?;
}
}
bone_rt.parent_index = parent_idx;
skeleton.insert_bone(&bone.name, bone_rt)?;
}
skeleton.build_children_map();
skeleton.build_roots();
Ok(skeleton)
}
}
fn rrggbbaa_to_4f32(rrggbbaa: &str) -> Result<[f32; 4], Error> {
if rrggbbaa.len() != 8 {
return Err(Error::InvalidData(format!(
"Invalid color format: {}",
rrggbbaa
)));
}
let r = u8::from_str_radix(&rrggbbaa[0..2], 16)
.map_err(|_| Error::InvalidData(format!("Invalid color format in r: {}", rrggbbaa)))?;
let g = u8::from_str_radix(&rrggbbaa[2..4], 16)
.map_err(|_| Error::InvalidData(format!("Invalid color format in g: {}", rrggbbaa)))?;
let b = u8::from_str_radix(&rrggbbaa[4..6], 16)
.map_err(|_| Error::InvalidData(format!("Invalid color format in b: {}", rrggbbaa)))?;
let a = u8::from_str_radix(&rrggbbaa[6..8], 16)
.map_err(|_| Error::InvalidData(format!("Invalid color format in a: {}", rrggbbaa)))?;
Ok([
r as f32 / 255.0,
g as f32 / 255.0,
b as f32 / 255.0,
a as f32 / 255.0,
])
}
#[test]
fn test_impl_skeleton() {
let bones = vec![
BoneDataStore {
name: "root".to_string(),
translate_x: Some(10.0),
translate_y: Some(20.0),
..Default::default()
},
BoneDataStore {
name: "child".to_string(),
parent: Some("root".to_string()),
rotation_deg: Some(45.0),
..Default::default()
},
];
let skeleton = Skeleton::from_bone_data_list(&bones).unwrap();
assert_eq!(skeleton.data_static.len(), 2);
let root_idx = skeleton.get_bone_index("root").unwrap();
let child_idx = skeleton.get_bone_index("child").unwrap();
assert_eq!(skeleton.data_static[child_idx].parent_index, Some(root_idx));
assert_eq!(skeleton.data_static[root_idx].translate_x, 10.0);
assert_eq!(skeleton.data_static[root_idx].translate_y, 20.0);
}
#[test]
fn test_skeleton_build_fail() {
{
let bones = vec![
BoneDataStore {
name: "root".to_string(),
translate_x: Some(10.0),
translate_y: Some(20.0),
..Default::default()
},
BoneDataStore {
name: "child".to_string(),
parent: Some("root1".to_string()),
rotation_deg: Some(45.0),
..Default::default()
},
];
let skeleton = Skeleton::from_bone_data_list(&bones);
assert_eq!(
skeleton.unwrap_err(),
Error::InvalidData("Parent bone not found: processing child, parent root1".to_string())
);
}
{
let bones = vec![
BoneDataStore {
name: "root".to_string(),
..Default::default()
},
BoneDataStore {
name: "root".to_string(),
..Default::default()
},
];
let skeleton = Skeleton::from_bone_data_list(&bones);
assert_eq!(
skeleton.unwrap_err(),
Error::InvalidData("Duplicate bone name: root".to_string())
);
}
{
let bones = vec![
BoneDataStore {
name: "child".to_string(),
parent: Some("root".to_string()),
..Default::default()
},
BoneDataStore {
name: "root".to_string(),
..Default::default()
},
];
let skeleton = Skeleton::from_bone_data_list(&bones);
assert_eq!(
skeleton.unwrap_err(),
Error::InvalidData("Parent bone not found: processing child, parent root".to_string())
);
}
}
#[test]
fn test_manual_construct() {
let skeleton_manual = {
let mut skeleton = Skeleton::new();
let root = {
let bone = BoneDataStatic {
length: 50.0,
debug_color: [1.0, 0.0, 0.0, 1.0],
..Default::default()
};
skeleton.insert_bone("root", bone).unwrap()
};
let bone1 = {
let bone = BoneDataStatic {
length: 60.0,
parent_index: Some(root),
translate_x: 50.0,
debug_color: [0.0, 1.0, 0.0, 1.0],
..Default::default()
};
skeleton.insert_bone("bone1", bone).unwrap()
};
let _bone2 = {
let bone = BoneDataStatic {
length: 40.0,
parent_index: Some(bone1),
translate_x: 60.0,
debug_color: [0.0, 0.0, 1.0, 1.0],
default_active: false,
..Default::default()
};
skeleton.insert_bone("bone2", bone).unwrap()
};
skeleton.build_children_map();
skeleton.build_roots();
skeleton
};
let skeleton_from_data = {
let bones = vec![
BoneDataStore {
name: "root".to_string(),
length: Some(50.0),
debug_color: Some("ff0000ff".to_string()),
..Default::default()
},
BoneDataStore {
name: "bone1".to_string(),
parent: Some("root".to_string()),
translate_x: Some(50.0),
length: Some(60.0),
debug_color: Some("00ff00ff".to_string()),
..Default::default()
},
BoneDataStore {
name: "bone2".to_string(),
parent: Some("bone1".to_string()),
translate_x: Some(60.0),
length: Some(40.0),
debug_color: Some("0000ffff".to_string()),
default_active: Some(false),
..Default::default()
},
];
Skeleton::from_bone_data_list(&bones).unwrap()
};
assert_eq!(
skeleton_manual.data_static.len(),
skeleton_from_data.data_static.len()
);
skeleton_manual
.data_static
.iter()
.zip(skeleton_from_data.data_static.iter())
.for_each(|((idx_a, name_a, data_a), (idx_b, name_b, data_b))| {
assert_eq!(idx_a, idx_b);
assert_eq!(name_a, name_b);
assert_eq!(data_a.translate_x, data_b.translate_x);
assert_eq!(data_a.translate_y, data_b.translate_y);
assert_eq!(data_a.rotation_deg, data_b.rotation_deg);
assert_eq!(data_a.scale_x, data_b.scale_x);
assert_eq!(data_a.scale_y, data_b.scale_y);
assert_eq!(data_a.shear_x_deg, data_b.shear_x_deg);
assert_eq!(data_a.shear_y_deg, data_b.shear_y_deg);
assert_eq!(data_a.length, data_b.length);
assert_eq!(data_a.inherit_mode, data_b.inherit_mode);
assert_eq!(data_a.parent_index, data_b.parent_index);
assert_eq!(data_a.debug_color, data_b.debug_color);
assert_eq!(data_a.shear_mode, data_b.shear_mode);
assert_eq!(data_a.default_active, data_b.default_active);
});
assert_eq!(skeleton_manual.children, skeleton_from_data.children);
assert_eq!(skeleton_manual.roots, skeleton_from_data.roots);
}
#[test]
fn test_build_color_err() {
let default_color = crate::data::runtime::skeleton::BoneDataStatic::default().debug_color;
{
let bones = vec![BoneDataStore {
name: "root".to_string(),
debug_color: Some("zzzzzzzz".to_string()),
..Default::default()
}];
let skeleton_fail = Skeleton::from_bone_data_list_config(&bones, false);
assert_eq!(
skeleton_fail.unwrap_err(),
Error::InvalidData("Invalid color format in r: zzzzzzzz".to_string())
);
let skeleton_ignore = Skeleton::from_bone_data_list(&bones).unwrap();
assert_eq!(
skeleton_ignore.data_static[skeleton_ignore.get_bone_index("root").unwrap()]
.debug_color,
default_color
);
}
{
let bones = vec![BoneDataStore {
name: "root".to_string(),
debug_color: Some("ffffzzzz".to_string()),
..Default::default()
}];
let skeleton_fail = Skeleton::from_bone_data_list_config(&bones, false);
assert_eq!(
skeleton_fail.unwrap_err(),
Error::InvalidData("Invalid color format in b: ffffzzzz".to_string())
);
let skeleton_ignore = Skeleton::from_bone_data_list(&bones).unwrap();
assert_eq!(
skeleton_ignore.data_static[skeleton_ignore.get_bone_index("root").unwrap()]
.debug_color,
default_color
);
}
{
let bones_success = vec![BoneDataStore {
name: "root".to_string(),
debug_color: Some("ff00ff00".to_string()),
..Default::default()
}];
let skeleton_success = Skeleton::from_bone_data_list_config(&bones_success, false).unwrap();
let skeleton_success_ignore = Skeleton::from_bone_data_list(&bones_success).unwrap();
assert_eq!(
skeleton_success.data_static[skeleton_success.get_bone_index("root").unwrap()]
.debug_color,
skeleton_success_ignore.data_static
[skeleton_success_ignore.get_bone_index("root").unwrap()]
.debug_color
);
assert_eq!(
skeleton_success.data_static[skeleton_success.get_bone_index("root").unwrap()]
.debug_color,
[1.0, 0.0, 1.0, 0.0]
);
}
}