fbx_dom/objects/
material.rs1use 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 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 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}