Skip to main content

fbx_dom/objects/
material.rs

1//! FBX `Material` — Assimp [`Material`](https://github.com/assimp/assimp/blob/master/code/AssetLib/FBX/FBXMaterial.cpp).
2
3use std::collections::HashMap;
4use std::convert::TryFrom;
5
6use crate::{OwnedDocument, OwnedObject, Property, objects::AttrExtractorExt};
7
8use super::{FbxObjectTag, FbxTypeMismatch, LayeredTexture, Texture, fbx_object_tag};
9
10const SHADING_MODEL: &str = "ShadingModel";
11const MULTILAYER: &str = "MultiLayer";
12
13#[derive(Debug, PartialEq)]
14pub struct Material {
15    pub object: OwnedObject,
16    pub shading_model: String,
17    pub multilayer: bool,
18}
19
20impl Material {
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    /// Resolve incoming `Texture -> Material` OP links keyed by destination property name.
38    pub fn get_textures<'a>(
39        &'a self,
40        document: &'a OwnedDocument,
41    ) -> HashMap<&'a str, &'a Texture> {
42        let material_id = self.inner().object_index;
43        let mut out = HashMap::new();
44        for texture in &document.textures {
45            for conn in &texture.inner().object_property_targets {
46                if conn.dest == material_id && !conn.property.is_empty() {
47                    out.insert(conn.property.as_str(), texture);
48                }
49            }
50        }
51        out
52    }
53
54    /// Resolve incoming `LayeredTexture -> Material` OP links keyed by destination property name.
55    pub fn get_layered_textures<'a>(
56        &'a self,
57        document: &'a OwnedDocument,
58    ) -> HashMap<&'a str, &'a LayeredTexture> {
59        let material_id = self.inner().object_index;
60        let mut out = HashMap::new();
61        for layered in &document.layered_textures {
62            for conn in &layered.inner().object_property_targets {
63                if conn.dest == material_id && !conn.property.is_empty() {
64                    out.insert(conn.property.as_str(), layered);
65                }
66            }
67        }
68        out
69    }
70}
71
72impl TryFrom<OwnedObject> for Material {
73    type Error = FbxTypeMismatch;
74
75    fn try_from(o: OwnedObject) -> Result<Self, Self::Error> {
76        if fbx_object_tag(&o) != FbxObjectTag::Material {
77            return Err(FbxTypeMismatch::wrong_object_kind(
78                o,
79                "Material".to_string(),
80            ));
81        }
82
83        let attrs = &o.attributes;
84        let shading_raw = match attrs.require_token(&SHADING_MODEL) {
85            Ok(s) => s,
86            Err(reason) => return Err(FbxTypeMismatch { object: o, reason }),
87        };
88        let shading_model = shading_raw.to_lowercase();
89        let multilayer = match attrs
90            .require_token(MULTILAYER)
91            .map(|t| t.parse::<i32>().unwrap_or(0))
92        {
93            Ok(b) => b != 0,
94            Err(reason) => return Err(FbxTypeMismatch { object: o, reason }),
95        };
96
97        Ok(Material {
98            object: o,
99            shading_model,
100            multilayer,
101        })
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use std::collections::HashMap;
108    use std::convert::TryFrom;
109
110    use crate::ObjectPropertyConnection;
111    use crate::objects::{
112        LAYERED_TEXTURE_CLASS_NAME, LAYERED_TEXTURE_TYPE_NAME, MATERIAL_CLASS_NAME,
113        MATERIAL_TYPE_NAME, TEXTURE_CLASS_NAME, TEXTURE_TYPE_NAME,
114    };
115    use crate::{OwnedDocument, OwnedObject};
116
117    use super::Material;
118
119    #[test]
120    fn resolves_texture_and_layered_texture_links() {
121        let material = Material::try_from(OwnedObject {
122            object_index: 800,
123            name: "Material::M".into(),
124            type_name: MATERIAL_TYPE_NAME.into(),
125            class_name: MATERIAL_CLASS_NAME.into(),
126            properties: HashMap::new(),
127            attributes: HashMap::from([
128                (
129                    "ShadingModel".to_string(),
130                    fbxscii::ElementAttribute::Leaf(Box::new(fbxscii::LeafAttribute {
131                        key: "ShadingModel".into(),
132                        tokens: vec!["Phong".into()],
133                    })),
134                ),
135                (
136                    "MultiLayer".to_string(),
137                    fbxscii::ElementAttribute::Leaf(Box::new(fbxscii::LeafAttribute {
138                        key: "MultiLayer".into(),
139                        tokens: vec!["0".into()],
140                    })),
141                ),
142            ]),
143            connected_object_ids: vec![],
144            object_property_targets: vec![],
145            pp_property_targets: HashMap::new(),
146        })
147        .unwrap();
148
149        let texture = crate::objects::Texture::try_from(OwnedObject {
150            object_index: 801,
151            name: "Texture::A".into(),
152            type_name: TEXTURE_TYPE_NAME.into(),
153            class_name: TEXTURE_CLASS_NAME.into(),
154            properties: HashMap::new(),
155            attributes: HashMap::new(),
156            connected_object_ids: vec![],
157            object_property_targets: vec![ObjectPropertyConnection {
158                dest: 800,
159                property: "DiffuseColor".into(),
160            }],
161            pp_property_targets: HashMap::new(),
162        })
163        .unwrap();
164
165        let layered = crate::objects::LayeredTexture::try_from(OwnedObject {
166            object_index: 802,
167            name: "LayeredTexture::A".into(),
168            type_name: LAYERED_TEXTURE_TYPE_NAME.into(),
169            class_name: LAYERED_TEXTURE_CLASS_NAME.into(),
170            properties: HashMap::new(),
171            attributes: HashMap::new(),
172            connected_object_ids: vec![],
173            object_property_targets: vec![ObjectPropertyConnection {
174                dest: 800,
175                property: "SpecularColor".into(),
176            }],
177            pp_property_targets: HashMap::new(),
178        })
179        .unwrap();
180
181        let mut owned = OwnedDocument::default();
182        owned.textures = vec![texture];
183        owned.layered_textures = vec![layered];
184
185        let textures = material.get_textures(&owned);
186        assert_eq!(textures.len(), 1);
187        assert_eq!(
188            textures.get("DiffuseColor").map(|t| t.inner().object_index),
189            Some(801)
190        );
191
192        let layered_textures = material.get_layered_textures(&owned);
193        assert_eq!(layered_textures.len(), 1);
194        assert_eq!(
195            layered_textures
196                .get("SpecularColor")
197                .map(|t| t.inner().object_index),
198            Some(802)
199        );
200    }
201}