1use 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 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 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}