1use std::collections::HashMap;
13use std::convert::TryFrom;
14use std::num::ParseFloatError;
15use std::num::ParseIntError;
16
17use crate::OwnedObject;
18use fbxscii::ElementAttribute;
19
20use super::AttrExtractor;
21use super::AttrExtractorExt;
22use super::{FbxObjectTag, FbxTryFromReason, FbxTypeMismatch, fbx_object_tag};
23
24const MAX_UV_CHANNELS: usize = 8;
25const MAX_COLOR_SETS: usize = 8;
26
27const ATTR_MAPPING_INFORMATION_TYPE: &str = "MappingInformationType";
28const ATTR_REFERENCE_INFORMATION_TYPE: &str = "ReferenceInformationType";
29
30const MAPPING_BY_VERTICE: &str = "ByVertice";
32const MAPPING_BY_POLYGON_VERTEX: &str = "ByPolygonVertex";
33const MAPPING_BY_POLYGON: &str = "ByPolygon";
34const MAPPING_ALL_SAME: &str = "AllSame";
35
36const REFERENCE_DIRECT: &str = "Direct";
37const REFERENCE_INDEX_TO_DIRECT: &str = "IndexToDirect";
38
39const ATTR_VERTICES: &str = "Vertices";
40const ATTR_POLYGON_VERTEX_INDEX: &str = "PolygonVertexIndex";
41const ATTR_LAYER_ELEMENT_NORMAL: &str = "LayerElementNormal";
42const ATTR_LAYER_ELEMENT_TANGENT: &str = "LayerElementTangent";
43const ATTR_LAYER_ELEMENT_BINORMAL: &str = "LayerElementBinormal";
44const ATTR_LAYER_ELEMENT_UV: &str = "LayerElementUV";
45const ATTR_MATERIALS: &str = "Materials";
46
47const ACCESSOR_KEY: &str = "a";
48
49#[derive(Debug, PartialEq)]
50pub struct MeshGeometry {
51 pub object: OwnedObject,
52 pub vertices: Vec<[f32; 3]>,
53 pub face_vertex_counts: Vec<u32>,
54 pub normals: Vec<[f32; 3]>,
55 pub tangents: Vec<[f32; 3]>,
56 pub binormals: Vec<[f32; 3]>,
57 pub texture_coords: [Vec<[f32; 2]>; MAX_UV_CHANNELS],
58 pub texture_coord_names: [String; MAX_UV_CHANNELS],
59 pub vertex_colors: [Vec<[f32; 4]>; MAX_COLOR_SETS],
60 pub material_indices: Vec<i32>,
61}
62
63impl MeshGeometry {
64 pub fn inner(&self) -> &OwnedObject {
65 &self.object
66 }
67
68 pub fn into_inner(self) -> OwnedObject {
69 self.object
70 }
71}
72
73impl TryFrom<OwnedObject> for MeshGeometry {
74 type Error = FbxTypeMismatch;
75
76 fn try_from(o: OwnedObject) -> Result<Self, Self::Error> {
77 match fbx_object_tag(&o) {
79 FbxObjectTag::MeshGeometry => {}
80 _ => {
81 return Err(FbxTypeMismatch::wrong_object_kind(
82 o,
83 "MeshGeometry".to_string(),
84 ));
85 }
86 }
87
88 let attrs = &o.attributes;
89
90 let verts_attr = match attrs.extract_case_insensitive(ATTR_VERTICES) {
92 Some(a) => a,
93 None => {
94 return Err(FbxTypeMismatch::new(
95 o,
96 FbxTryFromReason::MissingAttribute {
97 name: ATTR_VERTICES.to_string(),
98 },
99 ));
100 }
101 };
102 let vertices_result = parse_f32_array(verts_attr);
104 let Ok(vertices) = vertices_result else {
105 return Err(FbxTypeMismatch::new(
106 o,
107 FbxTryFromReason::InvalidAttributeFormat {
108 name: ATTR_VERTICES.to_string(),
109 detail: format!("invalid float token: {}", vertices_result.unwrap_err()),
110 },
111 ));
112 };
113 let vertices = vertices
114 .chunks_exact(3)
115 .map(|c| [c[0], c[1], c[2]])
116 .collect::<Vec<[f32; 3]>>();
117
118 let poly_attr = match attrs.extract_case_insensitive(ATTR_POLYGON_VERTEX_INDEX) {
120 Some(a) => a,
121 None => {
122 return Err(FbxTypeMismatch::new(
123 o,
124 FbxTryFromReason::MissingAttribute {
125 name: ATTR_POLYGON_VERTEX_INDEX.to_string(),
126 },
127 ));
128 }
129 };
130 let temp_faces_result = parse_i32_array(poly_attr);
131 let Ok(temp_faces) = temp_faces_result else {
132 return Err(FbxTypeMismatch::new(
133 o,
134 FbxTryFromReason::InvalidAttributeFormat {
135 name: ATTR_POLYGON_VERTEX_INDEX.to_string(),
136 detail: format!("invalid int token: {}", temp_faces_result.unwrap_err()),
137 },
138 ));
139 };
140
141 let (vertices, face_vertex_counts, mapping_counts, mapping_offsets, mappings) =
142 match expand_mesh_polygon_vertices(&vertices, &temp_faces) {
143 Ok(v) => v,
144 Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
145 };
146 let vertex_count = vertices.len();
147
148 let mut normals = Vec::new();
149 if let Some(el) = attrs.extract_case_insensitive(ATTR_LAYER_ELEMENT_NORMAL) {
150 let map = el.get_children_distinct();
151 let mapping_ty = match map
152 .require_token_case_insensitive(ATTR_MAPPING_INFORMATION_TYPE)
153 .map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
154 {
155 Ok(s) => s,
156 Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
157 };
158 let reference_ty = match map
159 .require_token_case_insensitive(ATTR_REFERENCE_INFORMATION_TYPE)
160 .map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
161 {
162 Ok(s) => s,
163 Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
164 };
165
166 let normals_flat = match resolve_flat_f32_channel(
167 &map,
168 ResolveFlatF32ChannelParams {
169 data_name: "Normals",
170 index_name: "NormalsIndex",
171 vertex_count,
172 components: 3,
173 mapping_counts: &mapping_counts,
174 mapping_offsets: &mapping_offsets,
175 mappings: &mappings,
176 mapping_ty: mapping_ty,
177 reference_ty: reference_ty,
178 },
179 ) {
180 Ok(v) => v,
181 Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
182 };
183 normals = normals_flat
184 .chunks_exact(3)
185 .map(|c| [c[0], c[1], c[2]])
186 .collect();
187 }
188
189 let mut tangents = Vec::new();
190 if let Some(el) = attrs.extract_case_insensitive(ATTR_LAYER_ELEMENT_TANGENT) {
191 let map = el.get_children_distinct();
192 let (data_name, index_name) = if map.extract_case_insensitive("Tangents").is_some() {
193 ("Tangents", "TangentsIndex")
194 } else {
195 ("Tangent", "TangentIndex")
196 };
197 let mapping_ty = match map
198 .require_token_case_insensitive(ATTR_MAPPING_INFORMATION_TYPE)
199 .map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
200 {
201 Ok(s) => s,
202 Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
203 };
204 let reference_ty = match map
205 .require_token_case_insensitive(ATTR_REFERENCE_INFORMATION_TYPE)
206 .map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
207 {
208 Ok(s) => s,
209 Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
210 };
211 let tangents_flat = match resolve_flat_f32_channel(
212 &map,
213 ResolveFlatF32ChannelParams {
214 data_name,
215 index_name,
216 vertex_count,
217 components: 3,
218 mapping_counts: &mapping_counts,
219 mapping_offsets: &mapping_offsets,
220 mappings: &mappings,
221 mapping_ty: mapping_ty,
222 reference_ty: reference_ty,
223 },
224 ) {
225 Ok(v) => v,
226 Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
227 };
228 tangents = tangents_flat
229 .chunks_exact(3)
230 .map(|c| [c[0], c[1], c[2]])
231 .collect();
232 }
233
234 let mut binormals = Vec::new();
235 if let Some(el) = attrs.extract_case_insensitive(ATTR_LAYER_ELEMENT_BINORMAL) {
236 let map = el.get_children_distinct();
237 let (data_name, index_name) = if map.extract_case_insensitive("Binormals").is_some() {
238 ("Binormals", "BinormalsIndex")
239 } else {
240 ("Binormal", "BinormalIndex")
241 };
242 let mapping_ty = match map
243 .require_token_case_insensitive(ATTR_MAPPING_INFORMATION_TYPE)
244 .map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
245 {
246 Ok(s) => s,
247 Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
248 };
249 let reference_ty = match map
250 .require_token_case_insensitive(ATTR_REFERENCE_INFORMATION_TYPE)
251 .map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
252 {
253 Ok(s) => s,
254 Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
255 };
256 let binormals_flat = match resolve_flat_f32_channel(
257 &map,
258 ResolveFlatF32ChannelParams {
259 data_name,
260 index_name,
261 vertex_count,
262 components: 3,
263 mapping_counts: &mapping_counts,
264 mapping_offsets: &mapping_offsets,
265 mappings: &mappings,
266 mapping_ty: mapping_ty,
267 reference_ty: reference_ty,
268 },
269 ) {
270 Ok(v) => v,
271 Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
272 };
273 binormals = binormals_flat
274 .chunks_exact(3)
275 .map(|c| [c[0], c[1], c[2]])
276 .collect();
277 }
278
279 let mut texture_coords: [Vec<[f32; 2]>; MAX_UV_CHANNELS] = Default::default();
280 let mut texture_coord_names: [String; MAX_UV_CHANNELS] = Default::default();
281
282 if let Some(el) = attrs.extract_case_insensitive(ATTR_LAYER_ELEMENT_UV) {
283 let map = el.get_children_distinct();
284 if let Ok(Some(name)) = map.optional_token_case_insensitive("Name") {
285 texture_coord_names[0] = name
286 .trim()
287 .trim_matches(|c| c == '"' || c == '\'')
288 .to_string();
289 }
290 let mapping_ty = match map
291 .require_token_case_insensitive(ATTR_MAPPING_INFORMATION_TYPE)
292 .map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
293 {
294 Ok(s) => s,
295 Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
296 };
297 let reference_ty = match map
298 .require_token_case_insensitive(ATTR_REFERENCE_INFORMATION_TYPE)
299 .map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
300 {
301 Ok(s) => s,
302 Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
303 };
304 let uv_flat = match resolve_flat_f32_channel(
305 &map,
306 ResolveFlatF32ChannelParams {
307 data_name: "UV",
308 index_name: "UVIndex",
309 vertex_count,
310 components: 2,
311 mapping_counts: &mapping_counts,
312 mapping_offsets: &mapping_offsets,
313 mappings: &mappings,
314 mapping_ty: mapping_ty,
315 reference_ty: reference_ty,
316 },
317 ) {
318 Ok(v) => v,
319 Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
320 };
321 texture_coords[0] = uv_flat.chunks_exact(2).map(|c| [c[0], c[1]]).collect();
322 }
323
324 let mut vertex_colors: [Vec<[f32; 4]>; MAX_COLOR_SETS] = Default::default();
325 if let Some(el) = attrs.extract_case_insensitive("LayerElementColor") {
326 let map = el.get_children_distinct();
327 let mapping_ty = match map
328 .require_token_case_insensitive(ATTR_MAPPING_INFORMATION_TYPE)
329 .map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
330 {
331 Ok(s) => s,
332 Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
333 };
334 let reference_ty = match map
335 .require_token_case_insensitive(ATTR_REFERENCE_INFORMATION_TYPE)
336 .map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
337 {
338 Ok(s) => s,
339 Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
340 };
341 let colors_flat = match resolve_flat_f32_channel(
342 &map,
343 ResolveFlatF32ChannelParams {
344 data_name: "Colors",
345 index_name: "ColorIndex",
346 vertex_count,
347 components: 4,
348 mapping_counts: &mapping_counts,
349 mapping_offsets: &mapping_offsets,
350 mappings: &mappings,
351 mapping_ty: mapping_ty,
352 reference_ty: reference_ty,
353 },
354 ) {
355 Ok(v) => v,
356 Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
357 };
358 vertex_colors[0] = colors_flat
359 .chunks_exact(4)
360 .map(|c| [c[0], c[1], c[2], c[3]])
361 .collect();
362 }
363
364 let material_indices =
365 if let Some(el) = attrs.extract_case_insensitive("LayerElementMaterial") {
366 let map = el.get_children_distinct();
367 let mapping_ty = match map
368 .require_token_case_insensitive(ATTR_MAPPING_INFORMATION_TYPE)
369 .map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
370 {
371 Ok(s) => s,
372 Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
373 };
374 let reference_ty = match map
375 .require_token_case_insensitive(ATTR_REFERENCE_INFORMATION_TYPE)
376 .map(|s| s.trim().trim_matches(|c| c == '"' || c == '\''))
377 {
378 Ok(s) => s,
379 Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
380 };
381 match read_vertex_data_materials(
382 &map,
383 &face_vertex_counts,
384 vertex_count,
385 &mapping_ty,
386 &reference_ty,
387 ) {
388 Ok(v) => v,
389 Err(reason) => return Err(FbxTypeMismatch::new(o, reason)),
390 }
391 } else {
392 Vec::new()
393 };
394
395 Ok(MeshGeometry {
396 object: o,
397 vertices,
398 face_vertex_counts,
399 normals,
400 tangents,
401 binormals,
402 texture_coords,
403 texture_coord_names,
404 vertex_colors,
405 material_indices,
406 })
407 }
408}
409
410fn parse_f32_array(attr: &ElementAttribute) -> Result<Vec<f32>, ParseFloatError> {
412 let children = attr.get_children_distinct();
413 let payload = children.get(ACCESSOR_KEY).unwrap_or(attr);
414 let tokens = payload.get_tokens();
415 tokens
416 .iter()
417 .flat_map(|t| t.split(','))
418 .map(|t| t.trim())
419 .filter(|t| !t.is_empty())
420 .map(|t| t.parse::<f32>())
421 .collect()
422}
423
424fn parse_i32_array(attr: &ElementAttribute) -> Result<Vec<i32>, ParseIntError> {
426 let children = attr.get_children_distinct();
427 let payload = children.get(ACCESSOR_KEY).unwrap_or(attr);
428 let tokens = payload.get_tokens();
429 tokens
430 .iter()
431 .flat_map(|t| t.split(','))
432 .map(|t| t.trim())
433 .filter(|t| !t.is_empty())
434 .map(|t| t.parse::<i32>())
435 .collect()
436}
437
438fn expand_mesh_polygon_vertices(
485 temp_verts: &[[f32; 3]],
486 temp_faces: &[i32],
487) -> Result<(Vec<[f32; 3]>, Vec<u32>, Vec<u32>, Vec<u32>, Vec<u32>), FbxTryFromReason> {
488 let vertex_count = temp_verts.len();
489 let mut mapping_counts = vec![0u32; vertex_count];
490 let mut expanded_vertices = Vec::new();
491 let mut face_vertex_counts = Vec::new();
492 let mut count = 0u32;
493
494 for &index in temp_faces {
496 let absi = if index < 0 {
498 (-index - 1) as usize
499 } else {
500 index as usize
501 };
502 if absi >= vertex_count {
503 return Err(FbxTryFromReason::InvalidAttributeFormat {
504 name: ATTR_POLYGON_VERTEX_INDEX.to_string(),
505 detail: format!("index {absi} out of range (vertex count {vertex_count})"),
506 });
507 }
508 expanded_vertices.push(temp_verts[absi]);
510 count += 1;
511 mapping_counts[absi] = mapping_counts[absi].saturating_add(1);
513 if index < 0 {
514 face_vertex_counts.push(count);
516 count = 0;
517 }
518 }
519
520 let polygon_vertex_count = expanded_vertices.len();
521 let mut mapping_offsets = vec![0u32; vertex_count];
523 let mut cursor = 0u32;
524 for i in 0..vertex_count {
525 mapping_offsets[i] = cursor;
526 cursor += mapping_counts[i];
527 mapping_counts[i] = 0;
528 }
529
530 let mut mappings = vec![0u32; polygon_vertex_count];
532 cursor = 0;
533 for &index in temp_faces {
534 let absi = if index < 0 {
535 (-index - 1) as usize
536 } else {
537 index as usize
538 };
539 let slot = mapping_offsets[absi] + mapping_counts[absi];
540 mapping_counts[absi] += 1;
541 mappings[slot as usize] = cursor;
542 cursor += 1;
543 }
544
545 Ok((
546 expanded_vertices,
547 face_vertex_counts,
548 mapping_counts,
549 mapping_offsets,
550 mappings,
551 ))
552}
553
554pub struct ResolveFlatF32ChannelParams<'a> {
555 data_name: &'a str,
556 index_name: &'a str,
557 vertex_count: usize,
558 components: usize,
559 mapping_counts: &'a [u32],
560 mapping_offsets: &'a [u32],
561 mappings: &'a [u32],
562 mapping_ty: &'a str,
563 reference_ty: &'a str,
564}
565
566fn resolve_flat_f32_channel(
579 source: &HashMap<String, ElementAttribute>,
580 params: ResolveFlatF32ChannelParams<'_>,
581) -> Result<Vec<f32>, FbxTryFromReason> {
582 let mut is_direct = params.reference_ty.eq_ignore_ascii_case(REFERENCE_DIRECT);
583 let mut is_index_to_direct = params
584 .reference_ty
585 .eq_ignore_ascii_case(REFERENCE_INDEX_TO_DIRECT);
586 let has_data = source.extract_case_insensitive(params.data_name).is_some();
587 let has_index = source.extract_case_insensitive(params.index_name).is_some();
588 if is_index_to_direct && !has_index {
590 is_direct = true;
591 is_index_to_direct = false;
592 }
593
594 if params.components == 0 {
595 return Ok(Vec::new());
596 }
597 let mut vertex_out = vec![0f32; params.vertex_count * params.components];
599
600 let by_vertice = params.mapping_ty.eq_ignore_ascii_case(MAPPING_BY_VERTICE);
601 let by_polygon_vertex = params
602 .mapping_ty
603 .eq_ignore_ascii_case(MAPPING_BY_POLYGON_VERTEX);
604
605 if by_vertice && is_direct {
606 if !has_data {
609 return Ok(Vec::new());
610 }
611 let channel_attribute = source.extract_case_insensitive(params.data_name).ok_or(
612 FbxTryFromReason::InvalidAttributeFormat {
613 name: params.data_name.to_string(),
614 detail: "data channel not found".to_string(),
615 },
616 )?;
617 let channel_data_result = parse_f32_array(&channel_attribute);
618 let Ok(channel_data) = channel_data_result else {
619 return Err(FbxTryFromReason::InvalidAttributeFormat {
620 name: params.data_name.to_string(),
621 detail: format!("invalid float token: {}", channel_data_result.unwrap_err()),
622 });
623 };
624 if channel_data.len() != params.mapping_offsets.len() * params.components {
625 return Err(FbxTryFromReason::InvalidAttributeFormat {
626 name: params.data_name.to_string(),
627 detail: format!(
628 "{} {}: expected {} floats, got {}",
629 MAPPING_BY_VERTICE,
630 REFERENCE_DIRECT,
631 params.mapping_offsets.len() * params.components,
632 channel_data.len()
633 ),
634 });
635 }
636 for i in 0..params.mapping_offsets.len() {
637 let src = i * params.components;
639 let istart = params.mapping_offsets[i] as usize;
640 let iend = istart + params.mapping_counts[i] as usize;
641 for j in istart..iend {
642 let dst = params.mappings[j] as usize * params.components;
644 if src + params.components > channel_data.len()
645 || dst + params.components > vertex_out.len()
646 {
647 return Err(FbxTryFromReason::InvalidAttributeFormat {
648 name: params.data_name.to_string(),
649 detail: format!("length mismatch for {MAPPING_BY_VERTICE}"),
650 });
651 }
652 vertex_out[dst..dst + params.components]
653 .copy_from_slice(&channel_data[src..src + params.components]);
654 }
655 }
656 } else if by_vertice && is_index_to_direct {
657 if !has_data || !has_index {
660 return Ok(Vec::new());
661 }
662
663 let channel_attribute = source.extract_case_insensitive(params.data_name).ok_or(
664 FbxTryFromReason::InvalidAttributeFormat {
665 name: params.data_name.to_string(),
666 detail: "data channel not found".to_string(),
667 },
668 )?;
669 let channel_data_result = parse_f32_array(&channel_attribute);
670 let Ok(channel_data) = channel_data_result else {
671 return Err(FbxTryFromReason::InvalidAttributeFormat {
672 name: params.data_name.to_string(),
673 detail: format!("invalid float token: {}", channel_data_result.unwrap_err()),
674 });
675 };
676 let channel_index_attribute = source.extract_case_insensitive(params.index_name).ok_or(
677 FbxTryFromReason::InvalidAttributeFormat {
678 name: params.index_name.to_string(),
679 detail: "index channel not found".to_string(),
680 },
681 )?;
682 let channel_index_data_result = parse_i32_array(&channel_index_attribute);
683 let Ok(channel_index_data) = channel_index_data_result else {
684 return Err(FbxTryFromReason::InvalidAttributeFormat {
685 name: params.index_name.to_string(),
686 detail: format!(
687 "invalid int token: {}",
688 channel_index_data_result.unwrap_err()
689 ),
690 });
691 };
692 if channel_index_data.len() != params.mapping_offsets.len() {
693 return Err(FbxTryFromReason::InvalidAttributeFormat {
694 name: params.index_name.to_string(),
695 detail: format!("length mismatch for {MAPPING_BY_VERTICE}"),
696 });
697 }
698 for i in 0..params.mapping_offsets.len() {
699 let idx = channel_index_data[i] as usize;
700 let src = idx * params.components; let istart = params.mapping_offsets[i] as usize;
702 let iend = istart + params.mapping_counts[i] as usize;
703 for j in istart..iend {
704 let dst = params.mappings[j] as usize * params.components;
705 if src + params.components > channel_data.len()
706 || dst + params.components > vertex_out.len()
707 {
708 return Err(FbxTryFromReason::InvalidAttributeFormat {
709 name: params.data_name.to_string(),
710 detail: format!("length mismatch for {MAPPING_BY_VERTICE}"),
711 });
712 }
713 vertex_out[dst..dst + params.components]
714 .copy_from_slice(&channel_data[src..src + params.components]);
715 }
716 }
717 } else if by_polygon_vertex && is_direct {
718 if !has_data {
720 return Ok(Vec::new());
721 }
722 let channel_attribute = source.extract_case_insensitive(params.data_name).ok_or(
723 FbxTryFromReason::InvalidAttributeFormat {
724 name: params.data_name.to_string(),
725 detail: "data channel not found".to_string(),
726 },
727 )?;
728 let channel_data_result = parse_f32_array(&channel_attribute);
729 let Ok(channel_data) = channel_data_result else {
730 return Err(FbxTryFromReason::InvalidAttributeFormat {
731 name: params.data_name.to_string(),
732 detail: format!("invalid float token: {}", channel_data_result.unwrap_err()),
733 });
734 };
735 if channel_data.len() != params.vertex_count * params.components {
736 return Err(FbxTryFromReason::InvalidAttributeFormat {
737 name: params.data_name.to_string(),
738 detail: format!(
739 "{} {}: expected {} floats, got {}",
740 MAPPING_BY_POLYGON_VERTEX,
741 REFERENCE_DIRECT,
742 params.vertex_count * params.components,
743 channel_data.len()
744 ),
745 });
746 }
747 vertex_out = channel_data;
748 } else if by_polygon_vertex && is_index_to_direct {
749 if !has_data || !has_index {
752 return Ok(Vec::new());
753 }
754 let channel_attribute = source.extract_case_insensitive(params.data_name).ok_or(
756 FbxTryFromReason::InvalidAttributeFormat {
757 name: params.data_name.to_string(),
758 detail: "data channel not found".to_string(),
759 },
760 )?;
761 let channel_data_result = parse_f32_array(&channel_attribute);
762 let Ok(channel_data) = channel_data_result else {
763 return Err(FbxTryFromReason::InvalidAttributeFormat {
764 name: params.data_name.to_string(),
765 detail: format!("invalid float token: {}", channel_data_result.unwrap_err()),
766 });
767 };
768 let channel_index_attribute = source.extract_case_insensitive(params.index_name).ok_or(
769 FbxTryFromReason::InvalidAttributeFormat {
770 name: params.index_name.to_string(),
771 detail: "index channel not found".to_string(),
772 },
773 )?;
774 let channel_index_data_result = parse_i32_array(&channel_index_attribute);
775 let Ok(mut channel_index_data) = channel_index_data_result else {
776 return Err(FbxTryFromReason::InvalidAttributeFormat {
777 name: params.index_name.to_string(),
778 detail: format!(
779 "invalid int token: {}",
780 channel_index_data_result.unwrap_err()
781 ),
782 });
783 };
784 if channel_index_data.len() > params.vertex_count {
785 channel_index_data.truncate(params.vertex_count);
786 }
787 if channel_index_data.len() != params.vertex_count {
789 return Err(FbxTryFromReason::InvalidAttributeFormat {
790 name: params.index_name.to_string(),
791 detail: format!(
792 "{} {}: expected {} indices, got {}",
793 MAPPING_BY_POLYGON_VERTEX,
794 REFERENCE_INDEX_TO_DIRECT,
795 params.vertex_count,
796 channel_index_data.len()
797 ),
798 });
799 }
800 for (slot, &i) in channel_index_data.iter().enumerate() {
801 let dst = slot * params.components; if dst + params.components > vertex_out.len() {
803 return Err(FbxTryFromReason::InvalidAttributeFormat {
804 name: params.data_name.to_string(),
805 detail: format!("length mismatch for {MAPPING_BY_POLYGON_VERTEX}"),
806 });
807 }
808 if i == -1 {
809 vertex_out[dst..dst + params.components].fill(0.0);
810 continue;
811 }
812 let src = i as usize * params.components; if src + params.components > channel_data.len() {
814 return Err(FbxTryFromReason::InvalidAttributeFormat {
815 name: params.data_name.to_string(),
816 detail: format!("length mismatch for {MAPPING_BY_POLYGON_VERTEX}"),
817 });
818 }
819 vertex_out[dst..dst + params.components]
820 .copy_from_slice(&channel_data[src..src + params.components]);
821 }
822 } else {
823 return Ok(Vec::new());
825 }
826 Ok(vertex_out)
827}
828
829fn read_vertex_data_materials(
831 source: &HashMap<String, ElementAttribute>,
832 face_vertex_counts: &[u32],
833 polygon_vertex_count: usize,
834 mapping_ty: &str,
835 reference_ty: &str,
836) -> Result<Vec<i32>, FbxTryFromReason> {
837 let face_count = face_vertex_counts.len();
839 if face_count == 0 {
840 return Ok(Vec::new());
841 }
842
843 let Some(mat_el) = source.extract_case_insensitive("Materials") else {
845 return Ok(Vec::new());
846 };
847 let materials_out_result = parse_i32_array(mat_el);
848 let Ok(materials_out) = materials_out_result else {
849 return Err(FbxTryFromReason::InvalidAttributeFormat {
850 name: ATTR_MATERIALS.to_string(),
851 detail: format!("invalid int token: {}", materials_out_result.unwrap_err()),
852 });
853 };
854
855 if mapping_ty.eq_ignore_ascii_case(MAPPING_ALL_SAME) {
856 if materials_out.is_empty() {
859 return Ok(Vec::new());
860 }
861 let count_neg = materials_out.iter().filter(|&&n| n < 0).count();
862 if count_neg == materials_out.len() {
863 return Ok(Vec::new());
864 }
865 let v = materials_out[0];
866 Ok(vec![v; polygon_vertex_count])
867 } else if mapping_ty.eq_ignore_ascii_case(MAPPING_BY_POLYGON)
868 && reference_ty.eq_ignore_ascii_case(REFERENCE_INDEX_TO_DIRECT)
869 {
870 if materials_out.len() != face_count {
873 return Err(FbxTryFromReason::InvalidAttributeFormat {
874 name: ATTR_MATERIALS.to_string(),
875 detail: format!(
876 "{}: expected {} material indices, got {}",
877 MAPPING_BY_POLYGON,
878 face_count,
879 materials_out.len()
880 ),
881 });
882 }
883 let count_neg = materials_out.iter().filter(|&&n| n < 0).count();
884 if count_neg == materials_out.len() {
885 return Ok(Vec::new());
886 }
887 let mut per_corner = Vec::with_capacity(polygon_vertex_count);
888 for (&m, &n) in materials_out.iter().zip(face_vertex_counts.iter()) {
889 for _ in 0..n {
890 per_corner.push(m);
891 }
892 }
893 if per_corner.len() != polygon_vertex_count {
894 return Err(FbxTryFromReason::InvalidAttributeFormat {
895 name: ATTR_MATERIALS.to_string(),
896 detail: format!(
897 "expanded material indices length {} != polygon vertex count {}",
898 per_corner.len(),
899 polygon_vertex_count
900 ),
901 });
902 }
903 Ok(per_corner)
904 } else {
905 Ok(Vec::new())
906 }
907}
908
909#[cfg(test)]
910mod tests {
911 use std::collections::HashMap;
912 use std::convert::TryFrom;
913
914 use fbxscii::{
915 Element, ElementAmphitheatre, ElementAttribute, LeafAttribute, SubTreeAttribute,
916 };
917
918 use crate::OwnedObject;
919
920 use super::super::{
921 FbxTryFromReason, GEOMETRY_LINE_CLASS_NAME, GEOMETRY_MESH_CLASS_NAME, GEOMETRY_TYPE_NAME,
922 };
923 use super::MeshGeometry;
924
925 fn leaf(tokens: &[&str]) -> ElementAttribute {
926 ElementAttribute::Leaf(Box::new(LeafAttribute {
927 key: String::new(),
928 tokens: tokens.iter().map(|s| (*s).to_string()).collect(),
929 }))
930 }
931
932 fn append_layer_string_child(
933 arena: &mut ElementAmphitheatre,
934 root_idx: usize,
935 key: &str,
936 token: &str,
937 ) {
938 let mut el = Element::new(key.to_string());
939 el.tokens = vec![token.to_string()];
940 el.parent_index = Some(root_idx);
941 let idx = arena.insert(el);
942 arena.get_mut(root_idx).unwrap().children.push(idx);
943 }
944
945 fn layer_element_normal(mapping: &str, reference: &str, normals_csv: &str) -> ElementAttribute {
947 let mut arena = ElementAmphitheatre::new();
948 let root_idx = arena.insert(Element::new("LayerElementNormal".into()));
949 append_layer_string_child(
950 &mut arena,
951 root_idx,
952 super::ATTR_MAPPING_INFORMATION_TYPE,
953 mapping,
954 );
955 append_layer_string_child(
956 &mut arena,
957 root_idx,
958 super::ATTR_REFERENCE_INFORMATION_TYPE,
959 reference,
960 );
961 let mut normals_el = Element::new("Normals".into());
962 normals_el.tokens = vec![normals_csv.to_string()];
963 normals_el.parent_index = Some(root_idx);
964 let normals_idx = arena.insert(normals_el);
965 arena.get_mut(root_idx).unwrap().children.push(normals_idx);
966
967 ElementAttribute::SubTree(Box::new(SubTreeAttribute {
968 amphitheatre: arena,
969 root_element_index: root_idx,
970 }))
971 }
972
973 fn layer_element_material(
974 mapping: &str,
975 reference: &str,
976 materials_csv: &str,
977 ) -> ElementAttribute {
978 let mut arena = ElementAmphitheatre::new();
979 let root_idx = arena.insert(Element::new("LayerElementMaterial".into()));
980 append_layer_string_child(
981 &mut arena,
982 root_idx,
983 super::ATTR_MAPPING_INFORMATION_TYPE,
984 mapping,
985 );
986 append_layer_string_child(
987 &mut arena,
988 root_idx,
989 super::ATTR_REFERENCE_INFORMATION_TYPE,
990 reference,
991 );
992 let mut mats_el = Element::new(super::ATTR_MATERIALS.into());
993 mats_el.tokens = vec![materials_csv.to_string()];
994 mats_el.parent_index = Some(root_idx);
995 let mats_idx = arena.insert(mats_el);
996 arena.get_mut(root_idx).unwrap().children.push(mats_idx);
997
998 ElementAttribute::SubTree(Box::new(SubTreeAttribute {
999 amphitheatre: arena,
1000 root_element_index: root_idx,
1001 }))
1002 }
1003
1004 fn owned_mesh(attrs: HashMap<String, ElementAttribute>) -> OwnedObject {
1005 OwnedObject {
1006 object_index: 11,
1007 name: "Geometry::TestMesh".into(),
1008 type_name: GEOMETRY_TYPE_NAME.into(),
1009 class_name: GEOMETRY_MESH_CLASS_NAME.into(),
1010 properties: HashMap::new(),
1011 attributes: attrs,
1012 connected_object_ids: vec![],
1013 object_property_targets: vec![],
1014 pp_property_targets: HashMap::new(),
1015 }
1016 }
1017
1018 fn minimal_base_attrs() -> HashMap<String, ElementAttribute> {
1019 let mut attrs = HashMap::new();
1020 attrs.insert("Vertices".into(), leaf(&["0,0,0,1,0,0,0,1,0"]));
1021 attrs.insert("PolygonVertexIndex".into(), leaf(&["0,1,-3"]));
1022 attrs
1023 }
1024
1025 #[test]
1026 fn minimal_triangle_expands_corners() {
1027 let attrs = minimal_base_attrs();
1028 let mesh = MeshGeometry::try_from(owned_mesh(attrs)).unwrap();
1029 assert_eq!(mesh.vertices.len(), 3);
1030 assert_eq!(mesh.face_vertex_counts, vec![3u32]);
1031 assert!(mesh.normals.is_empty());
1032 assert!(mesh.material_indices.is_empty());
1033 }
1034
1035 #[test]
1036 fn mapping_and_reference_trim_quotes_and_lowercase_keys() {
1037 let mut attrs = minimal_base_attrs();
1038 let mut arena = ElementAmphitheatre::new();
1039 let root_idx = arena.insert(Element::new("LayerElementNormal".into()));
1040 append_layer_string_child(
1041 &mut arena,
1042 root_idx,
1043 "mappinginformationtype",
1044 " \"ByPolygonVertex\" ",
1045 );
1046 append_layer_string_child(&mut arena, root_idx, "referenceinformationtype", "'Direct'");
1047 let mut normals_el = Element::new("Normals".into());
1048 normals_el.tokens = vec!["0,0,1,0,0,1,0,0,1".to_string()];
1049 normals_el.parent_index = Some(root_idx);
1050 let normals_idx = arena.insert(normals_el);
1051 arena.get_mut(root_idx).unwrap().children.push(normals_idx);
1052 attrs.insert(
1053 "LayerElementNormal".into(),
1054 ElementAttribute::SubTree(Box::new(SubTreeAttribute {
1055 amphitheatre: arena,
1056 root_element_index: root_idx,
1057 })),
1058 );
1059 let mesh = MeshGeometry::try_from(owned_mesh(attrs)).unwrap();
1060 assert_eq!(mesh.vertices.len(), 3);
1061 assert_eq!(mesh.normals.len(), 3);
1062 }
1063
1064 #[test]
1065 fn normals_by_polygon_vertex_direct() {
1066 let mut attrs = minimal_base_attrs();
1067 attrs.insert(
1068 "LayerElementNormal".into(),
1069 layer_element_normal("ByPolygonVertex", "Direct", "0,0,1,0,0,1,0,0,1"),
1070 );
1071 let mesh = MeshGeometry::try_from(owned_mesh(attrs)).unwrap();
1072 assert_eq!(
1073 mesh.normals,
1074 vec![[0.0, 0.0, 1.0], [0.0, 0.0, 1.0], [0.0, 0.0, 1.0],]
1075 );
1076 }
1077
1078 #[test]
1079 fn materials_all_same_replicates_first_index() {
1080 let mut attrs = minimal_base_attrs();
1081 attrs.insert(
1082 "LayerElementMaterial".into(),
1083 layer_element_material("AllSame", "IndexToDirect", "5"),
1084 );
1085 let mesh = MeshGeometry::try_from(owned_mesh(attrs)).unwrap();
1086 assert_eq!(mesh.material_indices, vec![5, 5, 5]);
1087 }
1088
1089 #[test]
1090 fn wrong_object_kind_line_geometry() {
1091 let o = OwnedObject {
1092 object_index: 1,
1093 name: "G".into(),
1094 type_name: GEOMETRY_TYPE_NAME.into(),
1095 class_name: GEOMETRY_LINE_CLASS_NAME.into(),
1096 properties: HashMap::new(),
1097 attributes: HashMap::new(),
1098 connected_object_ids: vec![],
1099 object_property_targets: vec![],
1100 pp_property_targets: HashMap::new(),
1101 };
1102 let err = MeshGeometry::try_from(o).unwrap_err();
1103 assert!(matches!(
1104 err.reason,
1105 FbxTryFromReason::WrongObjectKind { ref expected, .. } if expected == "MeshGeometry"
1106 ));
1107 }
1108
1109 #[test]
1110 fn missing_mapping_information_type() {
1111 let mut attrs = minimal_base_attrs();
1112 let mut arena = ElementAmphitheatre::new();
1113 let root_idx = arena.insert(Element::new("LayerElementNormal".into()));
1114 append_layer_string_child(
1115 &mut arena,
1116 root_idx,
1117 super::ATTR_REFERENCE_INFORMATION_TYPE,
1118 "Direct",
1119 );
1120 let mut normals_el = Element::new("Normals".into());
1121 normals_el.tokens = vec!["0,0,1,0,0,1,0,0,1".to_string()];
1122 normals_el.parent_index = Some(root_idx);
1123 let normals_idx = arena.insert(normals_el);
1124 arena.get_mut(root_idx).unwrap().children.push(normals_idx);
1125 attrs.insert(
1126 "LayerElementNormal".into(),
1127 ElementAttribute::SubTree(Box::new(SubTreeAttribute {
1128 amphitheatre: arena,
1129 root_element_index: root_idx,
1130 })),
1131 );
1132 let err = MeshGeometry::try_from(owned_mesh(attrs)).unwrap_err();
1133 assert!(matches!(
1134 err.reason,
1135 FbxTryFromReason::MissingAttribute { ref name } if name == "MappingInformationType"
1136 ));
1137 }
1138
1139 #[test]
1140 fn missing_reference_information_type() {
1141 let mut attrs = minimal_base_attrs();
1142 let mut arena = ElementAmphitheatre::new();
1143 let root_idx = arena.insert(Element::new("LayerElementNormal".into()));
1144 append_layer_string_child(
1145 &mut arena,
1146 root_idx,
1147 super::ATTR_MAPPING_INFORMATION_TYPE,
1148 "ByPolygonVertex",
1149 );
1150 let mut normals_el = Element::new("Normals".into());
1151 normals_el.tokens = vec!["0,0,1,0,0,1,0,0,1".to_string()];
1152 normals_el.parent_index = Some(root_idx);
1153 let normals_idx = arena.insert(normals_el);
1154 arena.get_mut(root_idx).unwrap().children.push(normals_idx);
1155 attrs.insert(
1156 "LayerElementNormal".into(),
1157 ElementAttribute::SubTree(Box::new(SubTreeAttribute {
1158 amphitheatre: arena,
1159 root_element_index: root_idx,
1160 })),
1161 );
1162 let err = MeshGeometry::try_from(owned_mesh(attrs)).unwrap_err();
1163 assert!(matches!(
1164 err.reason,
1165 FbxTryFromReason::MissingAttribute { ref name } if name == "ReferenceInformationType"
1166 ));
1167 }
1168}