1use std::collections::HashMap;
4use std::convert::TryFrom;
5
6use crate::{OwnedDocument, OwnedObject, Property};
7
8use super::Cluster;
9use super::{AttrExtractor, FbxObjectTag, FbxTypeMismatch, fbx_object_tag};
10
11const ATTR_LINK_DEFORM_ACURACY: &str = "Link_DeformAcuracy";
12
13#[derive(Debug, PartialEq)]
14pub struct Skin {
15 object: OwnedObject,
16 pub accuracy: f32,
17}
18
19impl Skin {
20 pub fn inner(&self) -> &OwnedObject {
21 &self.object
22 }
23
24 pub fn into_inner(self) -> OwnedObject {
25 self.object
26 }
27
28 pub fn properties(&self) -> &HashMap<String, Property> {
29 &self.object.properties
30 }
31
32 pub fn property(&self, name: &str) -> Option<&Property> {
33 self.object.properties.get(name)
34 }
35
36 pub fn accuracy(&self) -> f32 {
37 self.accuracy
38 }
39
40 pub fn get_clusters<'a>(&'a self, document: &'a OwnedDocument) -> Vec<&'a Cluster> {
46 let skin_id = self.inner().object_index;
47 document
48 .clusters
49 .iter()
50 .filter(|cluster| cluster.inner().connected_object_ids.contains(&skin_id))
51 .collect()
52 }
53}
54
55impl TryFrom<OwnedObject> for Skin {
56 type Error = FbxTypeMismatch;
57
58 fn try_from(o: OwnedObject) -> Result<Self, Self::Error> {
59 match fbx_object_tag(&o) {
60 FbxObjectTag::Skin => {
61 let accuracy = o
62 .attributes
63 .extract_case_insensitive(ATTR_LINK_DEFORM_ACURACY)
64 .and_then(|a| a.get_tokens().first())
65 .and_then(|t| t.trim().parse::<f32>().ok())
66 .unwrap_or(0.0);
67 Ok(Skin {
68 object: o,
69 accuracy,
70 })
71 }
72 _ => Err(FbxTypeMismatch::wrong_object_kind(o, "Skin".to_string())),
73 }
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use std::collections::HashMap;
80 use std::convert::TryFrom;
81
82 use fbxscii::{ElementAttribute, LeafAttribute};
83
84 use crate::objects::{
85 Cluster, DEFORMER_CLUSTER_CLASS_NAME, DEFORMER_SKIN_CLASS_NAME, DEFORMER_TYPE_NAME,
86 };
87 use crate::{OwnedDocument, OwnedObject, Property};
88
89 use super::{ATTR_LINK_DEFORM_ACURACY, Skin};
90
91 fn leaf(tokens: &[&str]) -> ElementAttribute {
92 ElementAttribute::Leaf(Box::new(LeafAttribute {
93 key: String::new(),
94 tokens: tokens.iter().map(|s| (*s).to_string()).collect(),
95 }))
96 }
97
98 #[test]
99 fn parses_accuracy_and_properties() {
100 let mut attrs = HashMap::new();
101 attrs.insert(ATTR_LINK_DEFORM_ACURACY.into(), leaf(&["0.75"]));
102 let mut props = HashMap::new();
103 props.insert("Foo".into(), Property::Int(1));
104 let o = OwnedObject {
105 object_index: 10,
106 name: "Skin::A".into(),
107 type_name: DEFORMER_TYPE_NAME.into(),
108 class_name: DEFORMER_SKIN_CLASS_NAME.into(),
109 properties: props,
110 attributes: attrs,
111 connected_object_ids: vec![],
112 object_property_targets: vec![],
113 pp_property_targets: HashMap::new(),
114 };
115 let s = Skin::try_from(o).unwrap();
116 assert_eq!(s.accuracy(), 0.75);
117 assert_eq!(s.property("Foo"), Some(&Property::Int(1)));
118 }
119
120 #[test]
121 fn defaults_accuracy_to_zero_when_missing() {
122 let o = OwnedObject {
123 object_index: 11,
124 name: "Skin::B".into(),
125 type_name: DEFORMER_TYPE_NAME.into(),
126 class_name: DEFORMER_SKIN_CLASS_NAME.into(),
127 properties: HashMap::new(),
128 attributes: HashMap::new(),
129 connected_object_ids: vec![],
130 object_property_targets: vec![],
131 pp_property_targets: HashMap::new(),
132 };
133 let s = Skin::try_from(o).unwrap();
134 assert_eq!(s.accuracy(), 0.0);
135 }
136
137 #[test]
138 fn resolves_clusters_from_owned_document_connections() {
139 let skin = Skin::try_from(OwnedObject {
140 object_index: 500,
141 name: "Skin::Target".into(),
142 type_name: DEFORMER_TYPE_NAME.into(),
143 class_name: DEFORMER_SKIN_CLASS_NAME.into(),
144 properties: HashMap::new(),
145 attributes: HashMap::new(),
146 connected_object_ids: vec![],
147 object_property_targets: vec![],
148 pp_property_targets: HashMap::new(),
149 })
150 .unwrap();
151
152 let mk_cluster = |id: u64, connected_object_ids: Vec<u64>| {
153 Cluster::try_from(OwnedObject {
154 object_index: id,
155 name: format!("Cluster::{id}"),
156 type_name: DEFORMER_TYPE_NAME.into(),
157 class_name: DEFORMER_CLUSTER_CLASS_NAME.into(),
158 properties: HashMap::new(),
159 attributes: HashMap::from([
160 (
161 "Transform".to_string(),
162 leaf(&["1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1"]),
163 ),
164 (
165 "TransformLink".to_string(),
166 leaf(&["1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1"]),
167 ),
168 ]),
169 connected_object_ids,
170 object_property_targets: vec![],
171 pp_property_targets: HashMap::new(),
172 })
173 .unwrap()
174 };
175
176 let matching = mk_cluster(1, vec![500]);
177 let non_matching = mk_cluster(2, vec![999]);
178 let mut owned = OwnedDocument::default();
179 owned.clusters = vec![matching, non_matching];
180
181 let clusters = skin.get_clusters(&owned);
182 assert_eq!(clusters.len(), 1);
183 assert_eq!(clusters[0].inner().object_index, 1);
184 }
185}