Skip to main content

fbx_dom/objects/
skin.rs

1//! FBX `Deformer` / `Skin` — Assimp [`Skin`](https://github.com/assimp/assimp/blob/master/code/AssetLib/FBX/FBXDocument.h).
2
3use 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    /// Resolve incoming `Cluster -> Skin` object links from [`OwnedDocument`].
41    ///
42    /// Assimp resolves this via destination-side connection scans + `ProcessSimpleConnection`.
43    /// In the owned snapshot, each object's outgoing OO links are in `connected_object_ids`,
44    /// so this finds clusters whose outgoing links include `self.object_index`.
45    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}