Skip to main content

fbx_dom/objects/
animation_layer.rs

1//! FBX `AnimationLayer` — Assimp [`AnimationLayer`](https://github.com/assimp/assimp/blob/master/code/AssetLib/FBX/FBXAnimation.cpp) / [`FBXDocument.h`](https://github.com/assimp/assimp/blob/master/code/AssetLib/FBX/FBXDocument.h).
2//!
3//! Attached curve nodes are resolved via connections in Assimp; this wrapper exposes the
4//! `AnimationLayer.FbxAnimLayer` property table on [`OwnedObject`].
5
6use std::collections::HashMap;
7use std::convert::TryFrom;
8
9use crate::Property;
10use crate::{OwnedDocument, OwnedObject};
11
12use super::{AnimationCurveNode, FbxObjectTag, FbxTypeMismatch, fbx_object_tag};
13
14#[derive(Debug, PartialEq)]
15pub struct AnimationLayer(pub OwnedObject);
16
17impl AnimationLayer {
18    pub fn inner(&self) -> &OwnedObject {
19        &self.0
20    }
21
22    pub fn into_inner(self) -> OwnedObject {
23        self.0
24    }
25
26    pub fn properties(&self) -> &HashMap<String, Property> {
27        &self.0.properties
28    }
29
30    pub fn property(&self, name: &str) -> Option<&Property> {
31        self.0.properties.get(name)
32    }
33
34    /// FBX SDK / template `Weight` (percentage); Assimp treats the layer property table as optional.
35    pub fn weight(&self) -> f32 {
36        match self.property("Weight") {
37            Some(Property::Float(v)) => *v,
38            _ => 100.0,
39        }
40    }
41
42    pub fn mute(&self) -> bool {
43        match self.property("Mute") {
44            Some(Property::Bool(v)) => *v,
45            _ => false,
46        }
47    }
48
49    pub fn solo(&self) -> bool {
50        match self.property("Solo") {
51            Some(Property::Bool(v)) => *v,
52            _ => false,
53        }
54    }
55
56    /// Resolve incoming `AnimationCurveNode -> AnimationLayer` OO links.
57    pub fn get_animation_curve_nodes<'a>(
58        &'a self,
59        document: &'a OwnedDocument,
60    ) -> Vec<&'a AnimationCurveNode> {
61        let layer_id = self.inner().object_index;
62        document
63            .animation_curve_nodes
64            .iter()
65            .filter(|node| node.inner().connected_object_ids.contains(&layer_id))
66            .collect()
67    }
68}
69
70impl TryFrom<OwnedObject> for AnimationLayer {
71    type Error = FbxTypeMismatch;
72
73    fn try_from(o: OwnedObject) -> Result<Self, Self::Error> {
74        match fbx_object_tag(&o) {
75            FbxObjectTag::AnimationLayer => Ok(AnimationLayer(o)),
76            _ => Err(FbxTypeMismatch::wrong_object_kind(
77                o,
78                "AnimationLayer".to_string(),
79            )),
80        }
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use std::collections::HashMap;
87    use std::convert::TryFrom;
88
89    use crate::objects::{
90        ANIMATION_CURVE_NODE_CLASS_NAME, ANIMATION_CURVE_NODE_TYPE_NAME,
91        ANIMATION_LAYER_CLASS_NAME, ANIMATION_LAYER_TYPE_NAME, AnimationCurveNode,
92    };
93    use crate::{OwnedDocument, OwnedObject, Property};
94
95    use super::AnimationLayer;
96
97    #[test]
98    fn layer_weight_mute_solo_defaults_and_properties() {
99        let o_empty = OwnedObject {
100            object_index: 3,
101            name: "AnimLayer::L0".into(),
102            type_name: ANIMATION_LAYER_TYPE_NAME.into(),
103            class_name: ANIMATION_LAYER_CLASS_NAME.into(),
104            properties: HashMap::new(),
105            attributes: HashMap::new(),
106            connected_object_ids: vec![],
107            object_property_targets: vec![],
108            pp_property_targets: HashMap::new(),
109        };
110        let layer = AnimationLayer::try_from(o_empty).unwrap();
111        assert_eq!(layer.weight(), 100.0);
112        assert_eq!(layer.mute(), false);
113        assert_eq!(layer.solo(), false);
114
115        let mut properties = HashMap::new();
116        properties.insert("Weight".to_string(), Property::Float(50.0));
117        properties.insert("Mute".to_string(), Property::Bool(true));
118        properties.insert("Solo".to_string(), Property::Bool(true));
119        let o = OwnedObject {
120            object_index: 4,
121            name: "AnimLayer::L1".into(),
122            type_name: ANIMATION_LAYER_TYPE_NAME.into(),
123            class_name: ANIMATION_LAYER_CLASS_NAME.into(),
124            properties,
125            attributes: HashMap::new(),
126            connected_object_ids: vec![],
127            object_property_targets: vec![],
128            pp_property_targets: HashMap::new(),
129        };
130        let layer = AnimationLayer::try_from(o).unwrap();
131        assert_eq!(layer.weight(), 50.0);
132        assert_eq!(layer.mute(), true);
133        assert_eq!(layer.solo(), true);
134    }
135
136    #[test]
137    fn resolves_animation_curve_node_links() {
138        let layer = AnimationLayer::try_from(OwnedObject {
139            object_index: 2000,
140            name: "AnimLayer::Layer0".into(),
141            type_name: ANIMATION_LAYER_TYPE_NAME.into(),
142            class_name: ANIMATION_LAYER_CLASS_NAME.into(),
143            properties: HashMap::new(),
144            attributes: HashMap::new(),
145            connected_object_ids: vec![],
146            object_property_targets: vec![],
147            pp_property_targets: HashMap::new(),
148        })
149        .unwrap();
150        let node = AnimationCurveNode::try_from(OwnedObject {
151            object_index: 2001,
152            name: "AnimCurveNode::T".into(),
153            type_name: ANIMATION_CURVE_NODE_TYPE_NAME.into(),
154            class_name: ANIMATION_CURVE_NODE_CLASS_NAME.into(),
155            properties: HashMap::new(),
156            attributes: HashMap::new(),
157            connected_object_ids: vec![2000],
158            object_property_targets: vec![],
159            pp_property_targets: HashMap::new(),
160        })
161        .unwrap();
162        let mut owned = OwnedDocument::default();
163        owned.animation_curve_nodes = vec![node];
164        let links = layer.get_animation_curve_nodes(&owned);
165        assert_eq!(links.len(), 1);
166        assert_eq!(links[0].inner().object_index, 2001);
167    }
168}