Skip to main content

fbx_dom/objects/
texture.rs

1//! FBX `Texture` — Assimp [`Texture`](https://github.com/assimp/assimp/blob/master/code/AssetLib/FBX/FBXMaterial.cpp).
2
3use std::collections::HashMap;
4use std::convert::TryFrom;
5
6use crate::Property;
7use crate::objects::AttrExtractorExt;
8use crate::objects::AttrExtractorParseExt;
9use crate::{OwnedDocument, OwnedObject};
10
11use super::{FbxObjectTag, FbxTypeMismatch, Video, fbx_object_tag};
12
13const TYPE_ATTR: &str = "Type";
14const FILE_NAME_ATTR: &str = "FileName";
15const RELATIVE_FILENAME_ATTR: &str = "RelativeFilename";
16const MODEL_UV_TRANSLATION: &str = "ModelUVTranslation";
17const MODEL_UV_SCALING: &str = "ModelUVScaling";
18const TEXTURE_ALPHA_SOURCE: &str = "Texture_Alpha_Source";
19const CROPPING: &str = "Cropping";
20
21const PROP_SCALING: &str = "Scaling";
22const PROP_TRANSLATION: &str = "Translation";
23const PROP_ROTATION: &str = "Rotation";
24
25#[derive(Debug, PartialEq)]
26pub struct Texture {
27    object: OwnedObject,
28    pub texture_type: String,
29    pub file_name: String,
30    pub relative_file_name: Option<String>,
31    pub uv_translation: [f32; 2],
32    pub uv_scaling: [f32; 2],
33    pub uv_rotation: f32,
34    pub cropping: [i32; 4],
35    pub alpha_source: String,
36}
37
38impl Texture {
39    pub fn inner(&self) -> &OwnedObject {
40        &self.object
41    }
42
43    pub fn into_inner(self) -> OwnedObject {
44        self.object
45    }
46
47    /// Resolve incoming `Video -> Texture` OO links (Assimp `Texture::Media`).
48    pub fn get_media_video<'a>(&'a self, document: &'a OwnedDocument) -> Option<&'a Video> {
49        let texture_id = self.inner().object_index;
50        document
51            .videos
52            .iter()
53            .find(|video| video.inner().connected_object_ids.contains(&texture_id))
54    }
55}
56
57fn apply_texture_property_uv_overrides(
58    properties: &HashMap<String, Property>,
59    uv_translation: &mut [f32; 2],
60    uv_scaling: &mut [f32; 2],
61    uv_rotation: &mut f32,
62) {
63    // 3ds Max / FBX SDK path (Assimp reads after element scope).
64    if let Some(Property::Vec3(v)) = properties.get(PROP_SCALING) {
65        uv_scaling[0] = v[0];
66        uv_scaling[1] = v[1];
67    }
68    if let Some(Property::Vec3(v)) = properties.get(PROP_TRANSLATION) {
69        uv_translation[0] = v[0];
70        uv_translation[1] = v[1];
71    }
72    if let Some(Property::Vec3(v)) = properties.get(PROP_ROTATION) {
73        *uv_rotation = v[2];
74    }
75}
76
77impl TryFrom<OwnedObject> for Texture {
78    type Error = FbxTypeMismatch;
79
80    fn try_from(o: OwnedObject) -> Result<Self, Self::Error> {
81        if fbx_object_tag(&o) != FbxObjectTag::Texture {
82            return Err(FbxTypeMismatch::wrong_object_kind(o, "Texture".to_string()));
83        }
84
85        let attrs = &o.attributes;
86        let props = &o.properties;
87
88        let texture_type = match attrs.optional_token_case_insensitive(&TYPE_ATTR) {
89            Ok(s) => s.map(|s| s.to_string()).unwrap_or_default(),
90            Err(reason) => return Err(FbxTypeMismatch { object: o, reason }),
91        };
92        let file_name = match attrs.optional_token_case_insensitive(&FILE_NAME_ATTR) {
93            Ok(s) => s.map(|s| s.to_string()).unwrap_or_default(),
94            Err(reason) => return Err(FbxTypeMismatch { object: o, reason }),
95        };
96        let relative_file_name =
97            match attrs.optional_token_case_insensitive(&RELATIVE_FILENAME_ATTR) {
98                Ok(r) => r.map(|s| s.to_string()),
99                Err(reason) => return Err(FbxTypeMismatch { object: o, reason }),
100            };
101
102        let mut uv_translation = [0.0f32, 0.0f32];
103        let mut uv_scaling = [1.0f32, 1.0f32];
104        let mut uv_rotation = 0.0f32;
105        let mut cropping = [0i32; 4];
106
107        match attrs.optional_two_f32(&MODEL_UV_TRANSLATION) {
108            Ok(Some(t)) => uv_translation = t,
109            Ok(None) => {}
110            Err(reason) => return Err(FbxTypeMismatch { object: o, reason }),
111        }
112        match attrs.optional_two_f32_case_insensitive(&MODEL_UV_SCALING) {
113            Ok(Some(t)) => uv_scaling = t,
114            Ok(None) => {}
115            Err(reason) => return Err(FbxTypeMismatch { object: o, reason }),
116        }
117        match attrs.optional_four_i32_case_insensitive(&CROPPING) {
118            Ok(Some(c)) => cropping = c,
119            Ok(None) => {}
120            Err(reason) => return Err(FbxTypeMismatch { object: o, reason }),
121        }
122
123        apply_texture_property_uv_overrides(
124            props,
125            &mut uv_translation,
126            &mut uv_scaling,
127            &mut uv_rotation,
128        );
129
130        let alpha_source = match attrs.optional_token_case_insensitive(&TEXTURE_ALPHA_SOURCE) {
131            Ok(s) => s.map(|s| s.to_string()).unwrap_or_default(),
132            Err(reason) => return Err(FbxTypeMismatch { object: o, reason }),
133        };
134
135        Ok(Texture {
136            object: o,
137            texture_type,
138            file_name,
139            relative_file_name,
140            uv_translation,
141            uv_scaling,
142            uv_rotation,
143            cropping,
144            alpha_source,
145        })
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use std::collections::HashMap;
152    use std::convert::TryFrom;
153
154    use super::Texture;
155    use crate::OwnedDocument;
156    use crate::objects::{
157        TEXTURE_CLASS_NAME, TEXTURE_TYPE_NAME, VIDEO_CLASS_NAME, VIDEO_TYPE_NAME, Video,
158    };
159
160    #[test]
161    fn resolves_media_video_connection() {
162        let texture = Texture::try_from(crate::OwnedObject {
163            object_index: 700,
164            name: "Texture::A".into(),
165            type_name: TEXTURE_TYPE_NAME.into(),
166            class_name: TEXTURE_CLASS_NAME.into(),
167            properties: HashMap::new(),
168            attributes: HashMap::new(),
169            connected_object_ids: vec![],
170            object_property_targets: vec![],
171            pp_property_targets: HashMap::new(),
172        })
173        .unwrap();
174        let video = Video::try_from(crate::OwnedObject {
175            object_index: 701,
176            name: "Video::A".into(),
177            type_name: VIDEO_TYPE_NAME.into(),
178            class_name: VIDEO_CLASS_NAME.into(),
179            properties: HashMap::new(),
180            attributes: HashMap::from([
181                (
182                    "Type".to_string(),
183                    fbxscii::ElementAttribute::Leaf(Box::new(fbxscii::LeafAttribute {
184                        key: "Type".into(),
185                        tokens: vec!["Clip".into()],
186                    })),
187                ),
188                (
189                    "FileName".to_string(),
190                    fbxscii::ElementAttribute::Leaf(Box::new(fbxscii::LeafAttribute {
191                        key: "FileName".into(),
192                        tokens: vec!["a.png".into()],
193                    })),
194                ),
195            ]),
196            connected_object_ids: vec![700],
197            object_property_targets: vec![],
198            pp_property_targets: HashMap::new(),
199        })
200        .unwrap();
201        let mut doc = OwnedDocument::default();
202        doc.videos = vec![video];
203        assert_eq!(
204            texture
205                .get_media_video(&doc)
206                .map(|v| v.inner().object_index),
207            Some(701)
208        );
209    }
210}