Skip to main content

fbx_dom/objects/
shape_geometry.rs

1//! FBX `Geometry` / `Shape` — Assimp [`ShapeGeometry`](https://github.com/assimp/assimp/blob/master/code/AssetLib/FBX/FBXMeshGeometry.cpp).
2
3use std::convert::TryFrom;
4use std::num::ParseFloatError;
5use std::num::ParseIntError;
6
7use crate::OwnedObject;
8
9use super::AttrExtractor;
10use super::{FbxObjectTag, FbxTryFromReason, FbxTypeMismatch, fbx_object_tag};
11
12#[derive(Debug, PartialEq)]
13pub struct ShapeGeometry {
14    pub object: OwnedObject,
15    pub indices: Vec<u32>,
16    pub vertices: Vec<[f32; 3]>,
17    pub normals: Vec<[f32; 3]>,
18}
19
20impl ShapeGeometry {
21    pub fn inner(&self) -> &OwnedObject {
22        &self.object
23    }
24
25    pub fn into_inner(self) -> OwnedObject {
26        self.object
27    }
28}
29
30impl TryFrom<OwnedObject> for ShapeGeometry {
31    type Error = FbxTypeMismatch;
32
33    fn try_from(o: OwnedObject) -> Result<Self, Self::Error> {
34        match fbx_object_tag(&o) {
35            FbxObjectTag::ShapeGeometry => {}
36            _ => {
37                return Err(FbxTypeMismatch::wrong_object_kind(
38                    o,
39                    "ShapeGeometry".to_string(),
40                ));
41            }
42        }
43
44        let attrs = &o.attributes;
45
46        let idx_tokens = match attrs.extract_case_insensitive("Indexes") {
47            Some(a) => a.get_tokens(),
48            None => {
49                return Err(FbxTypeMismatch::new(
50                    o,
51                    FbxTryFromReason::MissingAttribute {
52                        name: "Indexes".to_string(),
53                    },
54                ));
55            }
56        };
57        let indices_result = idx_tokens
58            .iter()
59            .flat_map(|t| t.split(','))
60            .map(|t| t.trim())
61            .filter(|t| !t.is_empty())
62            .map(|t| t.parse::<u32>())
63            .collect::<Result<Vec<u32>, ParseIntError>>();
64        let Ok(indices) = indices_result else {
65            return Err(FbxTypeMismatch::new(
66                o,
67                FbxTryFromReason::InvalidAttributeFormat {
68                    name: "Indexes".to_string(),
69                    detail: format!("invalid int token: {}", indices_result.unwrap_err()),
70                },
71            ));
72        };
73
74        let verts_tokens = match attrs.extract_case_insensitive("Vertices") {
75            Some(a) => a.get_tokens(),
76            None => {
77                return Err(FbxTypeMismatch::new(
78                    o,
79                    FbxTryFromReason::MissingAttribute {
80                        name: "Vertices".to_string(),
81                    },
82                ));
83            }
84        };
85        let vertices_result = verts_tokens
86            .iter()
87            .flat_map(|t| t.split(','))
88            .map(|t| t.trim())
89            .filter(|t| !t.is_empty())
90            .map(|t| t.parse::<f32>())
91            .collect::<Result<Vec<f32>, ParseFloatError>>();
92        let Ok(vertices_unchunked) = vertices_result else {
93            return Err(FbxTypeMismatch::new(
94                o,
95                FbxTryFromReason::InvalidAttributeFormat {
96                    name: "Vertices".to_string(),
97                    detail: format!("invalid float token: {}", vertices_result.unwrap_err()),
98                },
99            ));
100        };
101        let vertices = vertices_unchunked
102            .chunks_exact(3)
103            .map(|c| [c[0], c[1], c[2]])
104            .collect::<Vec<[f32; 3]>>();
105
106        // Validate that no index is out of bounds
107        for index in indices.iter() {
108            if *index >= vertices.len() as u32 {
109                return Err(FbxTypeMismatch::new(
110                    o,
111                    FbxTryFromReason::InvalidAttributeFormat {
112                        name: "Indexes".to_string(),
113                        detail: format!("index out of bounds: {}", *index),
114                    },
115                ));
116            }
117        }
118
119        let normals = if let Some(n_attr) = attrs.extract_case_insensitive("Normals") {
120            let n_tokens = n_attr.get_tokens();
121            let normals_result = n_tokens
122                .iter()
123                .flat_map(|t| t.split(','))
124                .map(|t| t.trim())
125                .filter(|t| !t.is_empty())
126                .map(|t| t.parse::<f32>())
127                .collect::<Result<Vec<f32>, ParseFloatError>>()
128                .unwrap_or_default(); // If the parse fails, return an empty vector. This is intentional.
129            let normals = normals_result
130                .chunks_exact(3)
131                .map(|c| [c[0], c[1], c[2]])
132                .collect::<Vec<[f32; 3]>>();
133            normals
134        } else {
135            Vec::new()
136        };
137
138        Ok(ShapeGeometry {
139            object: o,
140            indices,
141            vertices,
142            normals,
143        })
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use std::collections::HashMap;
150    use std::convert::TryFrom;
151
152    use fbxscii::{ElementAttribute, LeafAttribute};
153
154    use crate::OwnedObject;
155
156    use super::super::{GEOMETRY_SHAPE_CLASS_NAME, GEOMETRY_TYPE_NAME};
157    use super::ShapeGeometry;
158
159    fn leaf(tokens: &[&str]) -> ElementAttribute {
160        ElementAttribute::Leaf(Box::new(LeafAttribute {
161            key: String::new(),
162            tokens: tokens.iter().map(|s| (*s).to_string()).collect(),
163        }))
164    }
165
166    fn owned_shape(attrs: HashMap<String, ElementAttribute>) -> OwnedObject {
167        OwnedObject {
168            object_index: 10,
169            name: "Geometry::TestShape".into(),
170            type_name: GEOMETRY_TYPE_NAME.into(),
171            class_name: GEOMETRY_SHAPE_CLASS_NAME.into(),
172            properties: HashMap::new(),
173            attributes: attrs,
174            connected_object_ids: vec![],
175            object_property_targets: vec![],
176            pp_property_targets: HashMap::new(),
177        }
178    }
179
180    #[test]
181    fn basic_parse() {
182        let mut attrs = HashMap::new();
183        attrs.insert("Indexes".into(), leaf(&["0,1,2"]));
184        attrs.insert("Vertices".into(), leaf(&["0,0,0,1,0,0,0,1,0"]));
185        let o = owned_shape(attrs);
186        let sg = ShapeGeometry::try_from(o).unwrap();
187        let expected_indices = vec![0, 1, 2];
188        let expected_vertices = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
189        let expected_normals = Vec::<[f32; 3]>::new();
190        assert_eq!(sg.indices, expected_indices);
191        assert_eq!(sg.vertices, expected_vertices);
192        assert_eq!(sg.normals, expected_normals);
193    }
194}