Skip to main content

fbx_dom/objects/
blend_shape_channel.rs

1//! FBX `Deformer` / `BlendShapeChannel` — Assimp [`BlendShapeChannel`](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::{AttrExtractor, FbxObjectTag, FbxTypeMismatch, ShapeGeometry, fbx_object_tag};
9
10const ATTR_DEFORM_PERCENT: &str = "DeformPercent";
11const ATTR_FULL_WEIGHTS: &str = "FullWeights";
12
13#[derive(Debug, PartialEq)]
14pub struct BlendShapeChannel {
15    object: OwnedObject,
16    pub percent: f32,
17    pub full_weights: Vec<f32>,
18}
19
20impl BlendShapeChannel {
21    pub fn inner(&self) -> &OwnedObject {
22        &self.object
23    }
24
25    pub fn into_inner(self) -> OwnedObject {
26        self.object
27    }
28
29    pub fn properties(&self) -> &HashMap<String, Property> {
30        &self.object.properties
31    }
32
33    pub fn property(&self, name: &str) -> Option<&Property> {
34        self.object.properties.get(name)
35    }
36
37    pub fn deform_percent(&self) -> f32 {
38        self.percent
39    }
40
41    pub fn full_weights(&self) -> &[f32] {
42        &self.full_weights
43    }
44
45    /// Resolve `ShapeGeometry -> BlendShapeChannel` links via owned OO connections.
46    pub fn get_shape_geometries<'a>(
47        &'a self,
48        document: &'a OwnedDocument,
49    ) -> Vec<&'a ShapeGeometry> {
50        let channel_id = self.inner().object_index;
51        document
52            .shape_geometries
53            .iter()
54            .filter(|shape| shape.inner().connected_object_ids.contains(&channel_id))
55            .collect()
56    }
57}
58
59impl TryFrom<OwnedObject> for BlendShapeChannel {
60    type Error = FbxTypeMismatch;
61
62    fn try_from(o: OwnedObject) -> Result<Self, Self::Error> {
63        match fbx_object_tag(&o) {
64            FbxObjectTag::BlendShapeChannel => {
65                let percent = o
66                    .attributes
67                    .extract_case_insensitive(ATTR_DEFORM_PERCENT)
68                    .and_then(|a| a.get_tokens().first())
69                    .and_then(|t| t.trim().parse::<f32>().ok())
70                    .unwrap_or(0.0);
71                let full_weights = o
72                    .attributes
73                    .extract_case_insensitive(ATTR_FULL_WEIGHTS)
74                    .map(|attr| {
75                        attr.get_tokens()
76                            .iter()
77                            .flat_map(|t| t.split(','))
78                            .map(|t| t.trim())
79                            .filter(|t| !t.is_empty())
80                            .filter_map(|t| t.parse::<f32>().ok())
81                            .collect::<Vec<f32>>()
82                    })
83                    .unwrap_or_default();
84                Ok(BlendShapeChannel {
85                    object: o,
86                    percent,
87                    full_weights,
88                })
89            }
90            _ => Err(FbxTypeMismatch::wrong_object_kind(
91                o,
92                "BlendShapeChannel".to_string(),
93            )),
94        }
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use std::collections::HashMap;
101    use std::convert::TryFrom;
102
103    use fbxscii::{ElementAttribute, LeafAttribute};
104
105    use crate::objects::{
106        DEFORMER_BLEND_SHAPE_CHANNEL_CLASS_NAME, DEFORMER_TYPE_NAME, GEOMETRY_SHAPE_CLASS_NAME,
107        GEOMETRY_TYPE_NAME, ShapeGeometry,
108    };
109    use crate::{OwnedDocument, OwnedObject, Property};
110
111    use super::{ATTR_DEFORM_PERCENT, ATTR_FULL_WEIGHTS, BlendShapeChannel};
112
113    fn leaf(tokens: &[&str]) -> ElementAttribute {
114        ElementAttribute::Leaf(Box::new(LeafAttribute {
115            key: String::new(),
116            tokens: tokens.iter().map(|s| (*s).to_string()).collect(),
117        }))
118    }
119
120    #[test]
121    fn parses_percent_and_full_weights() {
122        let mut attrs = HashMap::new();
123        attrs.insert(ATTR_DEFORM_PERCENT.into(), leaf(&["37.5"]));
124        attrs.insert(ATTR_FULL_WEIGHTS.into(), leaf(&["1,0.5,0.25"]));
125        let mut props = HashMap::new();
126        props.insert("Foo".into(), Property::String("bar".into()));
127        let o = OwnedObject {
128            object_index: 14,
129            name: "BlendShapeChannel::A".into(),
130            type_name: DEFORMER_TYPE_NAME.into(),
131            class_name: DEFORMER_BLEND_SHAPE_CHANNEL_CLASS_NAME.into(),
132            properties: props,
133            attributes: attrs,
134            connected_object_ids: vec![],
135            object_property_targets: vec![],
136            pp_property_targets: HashMap::new(),
137        };
138        let c = BlendShapeChannel::try_from(o).unwrap();
139        assert_eq!(c.deform_percent(), 37.5);
140        assert_eq!(c.full_weights(), &[1.0, 0.5, 0.25]);
141        assert_eq!(c.property("Foo"), Some(&Property::String("bar".into())));
142    }
143
144    #[test]
145    fn resolves_shape_geometry_connections() {
146        let channel = BlendShapeChannel::try_from(OwnedObject {
147            object_index: 40,
148            name: "BlendShapeChannel::Conn".into(),
149            type_name: DEFORMER_TYPE_NAME.into(),
150            class_name: DEFORMER_BLEND_SHAPE_CHANNEL_CLASS_NAME.into(),
151            properties: HashMap::new(),
152            attributes: HashMap::new(),
153            connected_object_ids: vec![],
154            object_property_targets: vec![],
155            pp_property_targets: HashMap::new(),
156        })
157        .unwrap();
158
159        let shape = ShapeGeometry::try_from(OwnedObject {
160            object_index: 41,
161            name: "Geometry::Shape".into(),
162            type_name: GEOMETRY_TYPE_NAME.into(),
163            class_name: GEOMETRY_SHAPE_CLASS_NAME.into(),
164            properties: HashMap::new(),
165            attributes: HashMap::from([
166                ("Indexes".to_string(), leaf(&["0"])),
167                ("Vertices".to_string(), leaf(&["0,0,0"])),
168            ]),
169            connected_object_ids: vec![40],
170            object_property_targets: vec![],
171            pp_property_targets: HashMap::new(),
172        })
173        .unwrap();
174
175        let mut owned = OwnedDocument::default();
176        owned.shape_geometries = vec![shape];
177        let linked = channel.get_shape_geometries(&owned);
178        assert_eq!(linked.len(), 1);
179        assert_eq!(linked[0].inner().object_index, 41);
180    }
181}