1use 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 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(); 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}