1use std::collections::HashMap;
37use std::fs::{self, File};
38use std::io::{self, BufReader, Cursor, Read, Seek};
39use std::path::Path;
40
41use draco_core::mesh::Mesh;
42
43use crate::fbx_scene::push_warning;
44use crate::fbx_templates::{ObjectProperties, PropertyTemplates};
45use crate::fbx_transform::{
46 collect_transform_warnings, identity_transform, parse_transform, transform_array,
47};
48
49pub use crate::fbx_container::{FbxMemoryReader, FbxNode, FbxProperty, FbxReader};
52
53#[derive(Debug)]
54struct FbxGeometrySource {
55 mesh: Mesh,
56 material_indices: Vec<i32>,
57 control_points: Vec<[f32; 3]>,
58 polygon_vertex_indices: Vec<i32>,
59 layers: FbxMeshLayers,
60 edges: Vec<i32>,
61}
62
63#[doc(hidden)]
64pub use crate::fbx_scene::{
65 FbxAnimChannel, FbxAnimChannelPath, FbxAnimInterpolation, FbxAnimSampler, FbxAnimation,
66 FbxBinormalSet, FbxColorSet, FbxCreaseKind, FbxCreaseLayer, FbxLayerSet, FbxMeshInstance,
67 FbxMeshLayers, FbxNodeAttribute, FbxNodeId, FbxNormalSet, FbxScene, FbxSceneNode,
68 FbxSmoothingLayer, FbxTangentSet, FbxTexture, FbxTextureBinding, FbxTextureSlot, FbxTransform,
69 FbxUvSet, FbxWarning, FbxWarningCode,
70};
71impl crate::traits::Reader for FbxReader<BufReader<File>> {
73 fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
74 FbxReader::open(path)
75 }
76
77 fn read_meshes(&mut self) -> io::Result<Vec<draco_core::mesh::Mesh>> {
78 FbxReader::read_meshes(self)
81 }
82}
83
84impl crate::traits::Reader for FbxReader<Cursor<Vec<u8>>> {
85 fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
86 Self::from_bytes(fs::read(path)?)
87 }
88
89 fn read_meshes(&mut self) -> io::Result<Vec<draco_core::mesh::Mesh>> {
90 FbxReader::read_meshes(self)
91 }
92}
93impl<R: Read + Seek> FbxReader<R> {
94 pub fn read_scene(&mut self) -> io::Result<FbxScene> {
113 let nodes = self.read_nodes()?;
114 let global_settings = parse_global_settings(&nodes);
115
116 let index = FbxObjectIndex::build(&nodes);
117 let FbxObjectIndex {
120 model_map,
121 model_order,
122 geometry_map,
123 attribute_map,
124 material_map,
125 texture_map,
126 video_map,
127 deformer_map,
128 pose_map,
129 connections,
130 templates,
131 ..
132 } = &index;
133
134 let mut warnings = self.warnings().to_vec();
137 if index.name_keyed_objects > 0 {
143 let count = index.name_keyed_objects;
144 push_warning(
145 &mut warnings,
146 FbxWarningCode::NameKeyedObjectModel,
147 format!(
148 "FBX document identifies its {count} objects by name rather than by id, \
149 which is the pre-7000 layout; no geometry, materials or animation were \
150 imported from it"
151 ),
152 None,
153 );
154 }
155 collect_transform_warnings(model_map, model_order, templates, &mut warnings);
156 let node_attributes = parse_node_attributes(
157 attribute_map,
158 model_map,
159 connections,
160 templates,
161 &mut warnings,
162 );
163
164 let (materials, material_index_by_id, textures) = parse_materials_and_textures(
166 material_map,
167 texture_map,
168 video_map,
169 connections,
170 templates,
171 );
172
173 let mut model_material_ids: HashMap<i64, Vec<i32>> = HashMap::new();
176 for conn in connections {
177 if conn.kind == ConnectionKind::Oo
178 && material_map.contains_key(&conn.child)
179 && model_map.contains_key(&conn.parent)
180 {
181 let mat_index = material_index_by_id[&conn.child] as i32;
182 model_material_ids
183 .entry(conn.parent)
184 .or_default()
185 .push(mat_index);
186 }
187 }
188
189 let mut model_children: HashMap<i64, Vec<i64>> = HashMap::new();
191 for conn in connections.iter() {
192 if model_map.contains_key(&conn.child) || model_map.contains_key(&conn.parent) {
193 model_children
194 .entry(conn.parent)
195 .or_default()
196 .push(conn.child);
197 }
198 }
199
200 let ordered_model_ids = model_order;
201 let model_node_ids: HashMap<i64, FbxNodeId> = ordered_model_ids
202 .iter()
203 .copied()
204 .enumerate()
205 .map(|(index, id)| (id, FbxNodeId((index + 1) as u32)))
206 .collect();
207
208 let mut model_mesh_instances: std::collections::HashMap<i64, Vec<FbxMeshInstance>> =
210 std::collections::HashMap::new();
211 let mut geometry_ids: Vec<i64> = geometry_map.keys().copied().collect();
212 geometry_ids.sort_unstable();
213 for geom_id in geometry_ids {
214 let geom_node = geometry_map[&geom_id];
215 if let Some(source) = geometry_to_mesh(geom_node, &mut warnings)? {
216 let mesh = &source.mesh;
217 let material_indices = source.material_indices.clone();
218 for conn in connections.iter() {
220 if conn.child == geom_id && model_map.contains_key(&conn.parent) {
221 let mut indices = material_indices.clone();
224 if let Some(model_mats) = model_material_ids.get(&conn.parent) {
225 if indices.is_empty() {
226 if !model_mats.is_empty() {
227 let first = model_mats[0];
228 indices = vec![first; mesh.num_faces()];
230 }
231 } else {
232 indices = indices
237 .into_iter()
238 .map(|slot| {
239 usize::try_from(slot)
240 .ok()
241 .and_then(|slot| model_mats.get(slot).copied())
242 .unwrap_or(model_mats[0])
243 })
244 .collect();
245 }
246 }
247 let mesh_instance = FbxMeshInstance {
248 name: object_name(geom_node),
249 mesh: source.mesh.clone(),
250 control_points: source.control_points.clone(),
251 polygon_vertex_indices: source.polygon_vertex_indices.clone(),
252 layers: source.layers.clone(),
253 edges: source.edges.clone(),
254 material_indices: indices,
255 skin: parse_skin_for_geometry(
256 geom_id,
257 deformer_map,
258 pose_map,
259 connections,
260 &model_node_ids,
261 ),
262 morph_targets: parse_morph_targets_for_geometry(
263 geom_id,
264 geometry_map,
265 deformer_map,
266 connections,
267 ),
268 };
269 model_mesh_instances
270 .entry(conn.parent)
271 .or_default()
272 .push(mesh_instance);
273 }
274 }
275 }
276 }
277
278 let model_name_map: HashMap<i64, String> = model_map
280 .iter()
281 .filter_map(|(id, node)| object_name(node).map(|name| (*id, name)))
282 .collect();
283
284 let animations = self.parse_animations(
285 &nodes,
286 &index,
287 &model_name_map,
288 &model_node_ids,
289 &morph_animation_targets(geometry_map, deformer_map, connections, model_map),
290 );
291
292 let mut root_nodes = Vec::new();
294 let top_level: Vec<i64> = ordered_model_ids
296 .iter()
297 .copied()
298 .filter(|id| {
299 !connections
300 .iter()
301 .any(|conn| conn.child == *id && model_map.contains_key(&conn.parent))
302 })
303 .collect();
304
305 let graph = ModelGraph {
306 models: model_map,
307 children: &model_children,
308 mesh_instances: &model_mesh_instances,
309 node_ids: &model_node_ids,
310 attributes: &node_attributes,
311 templates,
312 };
313 for id in top_level {
314 root_nodes.push(build_model_node(id, &graph, &mut Vec::new()));
315 }
316
317 Ok(FbxScene {
318 global_settings,
319 root_nodes,
320 materials,
321 textures,
322 animations,
323 warnings,
324 })
325 }
326}
327
328fn object_name(node: &FbxNode) -> Option<String> {
330 match node.properties.get(1) {
331 Some(FbxProperty::String(name)) => name
332 .split('\0')
333 .next()
334 .filter(|name| !name.is_empty())
335 .map(str::to_string),
336 _ => None,
337 }
338}
339
340struct ModelGraph<'a, 'n> {
345 models: &'a std::collections::HashMap<i64, &'n FbxNode>,
346 children: &'a std::collections::HashMap<i64, Vec<i64>>,
347 mesh_instances: &'a std::collections::HashMap<i64, Vec<FbxMeshInstance>>,
348 node_ids: &'a std::collections::HashMap<i64, FbxNodeId>,
349 attributes: &'a std::collections::HashMap<i64, FbxNodeAttribute>,
350 templates: &'a PropertyTemplates<'n>,
351}
352
353fn build_model_node(id: i64, graph: &ModelGraph<'_, '_>, ancestors: &mut Vec<i64>) -> FbxSceneNode {
354 let node_src = graph.models.get(&id).unwrap();
355 let mut node = FbxSceneNode::new(object_name(node_src));
356 node.id = graph.node_ids[&id];
357 if let Some((transform, transform_stack, has_complex_transform_stack)) =
358 parse_transform(ObjectProperties::new(node_src, graph.templates))
359 {
360 node.transform = Some(transform);
361 node.transform_stack = Some(transform_stack);
362 node.has_complex_transform_stack = has_complex_transform_stack;
363 }
364 node.attribute = graph.attributes.get(&id).cloned();
365 if let Some(mesh_instances) = graph.mesh_instances.get(&id) {
366 node.mesh_instances.extend(mesh_instances.clone());
367 }
368
369 const MAX_MODEL_DEPTH: usize = 256;
373 if ancestors.len() >= MAX_MODEL_DEPTH {
374 return node;
375 }
376 if let Some(children) = graph.children.get(&id) {
377 ancestors.push(id);
378 for &cid in children {
379 if graph.models.contains_key(&cid) && !ancestors.contains(&cid) {
384 node.children.push(build_model_node(cid, graph, ancestors));
385 }
386 }
387 ancestors.pop();
388 }
389 node
390}
391
392fn parse_global_settings(nodes: &[FbxNode]) -> Option<crate::fbx_scene::FbxGlobalSettings> {
393 let properties = nodes
394 .iter()
395 .find(|node| node.name == "GlobalSettings")?
396 .children
397 .iter()
398 .find(|node| node.name == "Properties70")?;
399 let integer = |property: &FbxNode| {
400 property.properties.iter().find_map(|value| match value {
401 FbxProperty::I16(value) => Some(*value as i32),
402 FbxProperty::I32(value) => Some(*value),
403 FbxProperty::I64(value) => i32::try_from(*value).ok(),
404 _ => None,
405 })
406 };
407 let number = |property: &FbxNode| {
408 property.properties.iter().find_map(|value| match value {
409 FbxProperty::F32(value) => Some(f64::from(*value)),
410 FbxProperty::F64(value) => Some(*value),
411 _ => None,
412 })
413 };
414 let mut result = crate::fbx_scene::FbxGlobalSettings::default();
415 for property in &properties.children {
416 let Some(FbxProperty::String(name)) = property.properties.first() else {
417 continue;
418 };
419 match name.as_str() {
420 "UpAxis" => result.up_axis = integer(property),
421 "UpAxisSign" => result.up_axis_sign = integer(property),
422 "FrontAxis" => result.front_axis = integer(property),
423 "FrontAxisSign" => result.front_axis_sign = integer(property),
424 "CoordAxis" => result.coord_axis = integer(property),
425 "CoordAxisSign" => result.coord_axis_sign = integer(property),
426 "UnitScaleFactor" => result.unit_scale_factor = number(property),
427 "OriginalUnitScaleFactor" => result.original_unit_scale_factor = number(property),
428 "TimeMode" => result.time_mode = integer(property),
429 _ => {}
430 }
431 }
432 (result != crate::fbx_scene::FbxGlobalSettings::default()).then_some(result)
433}
434
435fn child_i32_array(node: &FbxNode, child_name: &str) -> Vec<i32> {
436 node.children
437 .iter()
438 .find(|child| child.name == child_name)
439 .and_then(|child| child.properties.first())
440 .and_then(|value| match value {
441 FbxProperty::I32Array(values) => Some(values.clone()),
442 _ => None,
443 })
444 .unwrap_or_default()
445}
446
447fn child_f64_array(node: &FbxNode, child_name: &str) -> Vec<f64> {
448 node.children
449 .iter()
450 .find(|child| child.name == child_name)
451 .and_then(|child| child.properties.first())
452 .and_then(|value| match value {
453 FbxProperty::F64Array(values) => Some(values.clone()),
454 FbxProperty::F32Array(values) => Some(values.iter().copied().map(f64::from).collect()),
455 _ => None,
456 })
457 .unwrap_or_default()
458}
459
460fn parse_skin_for_geometry(
461 geometry_id: i64,
462 deformers: &std::collections::HashMap<i64, &FbxNode>,
463 poses: &std::collections::HashMap<i64, &FbxNode>,
464 connections: &[FbxConnection],
465 model_node_ids: &std::collections::HashMap<i64, FbxNodeId>,
466) -> Option<crate::fbx_scene::FbxSkin> {
467 let skin_ids: Vec<i64> = connections
468 .iter()
469 .filter(|connection| {
470 connection.kind == ConnectionKind::Oo && connection.parent == geometry_id
471 })
472 .map(|connection| connection.child)
473 .filter(|id| {
474 deformers
475 .get(id)
476 .and_then(|node| deformer_type(node).map(str::to_string))
477 .as_deref()
478 == Some("Skin")
479 })
480 .collect();
481 if skin_ids.is_empty() {
482 return None;
483 }
484
485 let mut clusters = Vec::new();
486 for skin_id in skin_ids {
487 for cluster_id in connections
488 .iter()
489 .filter(|connection| {
490 connection.kind == ConnectionKind::Oo && connection.parent == skin_id
491 })
492 .map(|connection| connection.child)
493 {
494 let Some(cluster) = deformers.get(&cluster_id) else {
495 continue;
496 };
497 if deformer_type(cluster) != Some("Cluster") {
498 continue;
499 }
500 let Some(joint_model_id) = connections
501 .iter()
502 .find(|connection| {
503 connection.kind == ConnectionKind::Oo && connection.parent == cluster_id
504 })
505 .map(|connection| connection.child)
506 else {
507 continue;
508 };
509 let Some(&joint_node_id) = model_node_ids.get(&joint_model_id) else {
510 continue;
511 };
512 let indices = child_i32_array(cluster, "Indexes")
513 .into_iter()
514 .filter_map(|index| u32::try_from(index).ok())
515 .collect::<Vec<_>>();
516 let mut weights = child_f64_array(cluster, "Weights")
517 .into_iter()
518 .map(|weight| weight as f32)
519 .collect::<Vec<_>>();
520 weights.truncate(indices.len());
521 if weights.len() != indices.len() {
522 continue;
523 }
524 clusters.push(crate::fbx_scene::FbxSkinCluster {
525 joint_node_id,
526 control_point_indices: indices,
527 weights,
528 mesh_bind_transform: transform_array(cluster, "Transform")
529 .unwrap_or_else(identity_transform),
530 joint_bind_transform: transform_array(cluster, "TransformLink")
531 .unwrap_or_else(identity_transform),
532 armature_bind_transform: transform_array(cluster, "TransformAssociateModel"),
533 });
534 }
535 }
536
537 let mut bind_pose = Vec::new();
538 let mut pose_ids: Vec<i64> = poses.keys().copied().collect();
542 pose_ids.sort_unstable();
543 for pose in pose_ids.iter().map(|id| poses[id]) {
544 let is_bind_pose = pose
545 .children
546 .iter()
547 .find(|child| child.name == "Type")
548 .and_then(|child| child.properties.first())
549 .and_then(|value| match value {
550 FbxProperty::String(value) => Some(value == "BindPose"),
551 _ => None,
552 })
553 .unwrap_or(false);
554 if !is_bind_pose {
555 continue;
556 }
557 for pose_node in &pose.children {
558 if pose_node.name != "PoseNode" {
559 continue;
560 }
561 let model_id = pose_node
562 .children
563 .iter()
564 .find(|child| child.name == "Node")
565 .and_then(|child| child.properties.first())
566 .and_then(object_id);
572 let matrix = transform_array(pose_node, "Matrix");
573 if let (Some(_model_id), Some(matrix), Some(&node_id)) = (
574 model_id,
575 matrix,
576 model_id.and_then(|id| model_node_ids.get(&id)),
577 ) {
578 if !bind_pose.iter().any(|(existing, _)| *existing == node_id) {
579 bind_pose.push((node_id, matrix));
580 }
581 }
582 }
583 }
584 Some(crate::fbx_scene::FbxSkin {
585 clusters,
586 bind_pose,
587 })
588}
589
590fn child_f64(node: &FbxNode, name: &str) -> Option<f64> {
591 node.children
592 .iter()
593 .find(|child| child.name == name)
594 .and_then(|child| child.properties.first())
595 .and_then(|value| match value {
596 FbxProperty::F64(value) => Some(*value),
597 FbxProperty::F32(value) => Some(*value as f64),
598 FbxProperty::I32(value) => Some(f64::from(*value)),
602 FbxProperty::I64(value) => Some(*value as f64),
603 _ => None,
604 })
605}
606
607fn parse_morph_targets_for_geometry(
608 geometry_id: i64,
609 geometries: &std::collections::HashMap<i64, &FbxNode>,
610 deformers: &std::collections::HashMap<i64, &FbxNode>,
611 connections: &[FbxConnection],
612) -> Vec<crate::fbx_scene::FbxMorphTarget> {
613 let mut targets = Vec::new();
614 for blend_shape_id in connections
615 .iter()
616 .filter(|connection| {
617 connection.kind == ConnectionKind::Oo && connection.parent == geometry_id
618 })
619 .map(|connection| connection.child)
620 {
621 let Some(blend_shape) = deformers.get(&blend_shape_id) else {
622 continue;
623 };
624 if deformer_type(blend_shape) != Some("BlendShape") {
625 continue;
626 }
627 for channel_id in connections
628 .iter()
629 .filter(|connection| {
630 connection.kind == ConnectionKind::Oo && connection.parent == blend_shape_id
631 })
632 .map(|connection| connection.child)
633 {
634 let Some(channel) = deformers.get(&channel_id) else {
635 continue;
636 };
637 if deformer_type(channel) != Some("BlendShapeChannel") {
638 continue;
639 }
640 for shape_id in connections
641 .iter()
642 .filter(|connection| {
643 connection.kind == ConnectionKind::Oo && connection.parent == channel_id
644 })
645 .map(|connection| connection.child)
646 {
647 let Some(shape) = geometries.get(&shape_id) else {
648 continue;
649 };
650 let indices = child_i32_array(shape, "Indexes")
651 .into_iter()
652 .filter_map(|index| u32::try_from(index).ok())
653 .collect::<Vec<_>>();
654 let vertices = child_f64_array(shape, "Vertices");
655 if vertices.len() != indices.len() * 3 {
656 continue;
657 }
658 let position_deltas = vertices
659 .chunks_exact(3)
660 .map(|values| [values[0] as f32, values[1] as f32, values[2] as f32])
661 .collect();
662 let full_weight = child_f64_array(channel, "FullWeights")
663 .first()
664 .copied()
665 .unwrap_or(100.0) as f32;
666 targets.push(crate::fbx_scene::FbxMorphTarget {
667 name: match shape.properties.get(1) {
668 Some(FbxProperty::String(name)) => {
669 name.split('\0').next().map(str::to_string)
670 }
671 _ => None,
672 },
673 control_point_indices: indices,
674 position_deltas,
675 normal_deltas: None,
676 default_weight: child_f64(channel, "DeformPercent").unwrap_or(0.0) as f32,
677 full_weight,
678 });
679 }
680 }
681 }
682 targets
683}
684
685fn morph_animation_targets(
689 geometries: &std::collections::HashMap<i64, &FbxNode>,
690 deformers: &std::collections::HashMap<i64, &FbxNode>,
691 connections: &[FbxConnection],
692 models: &std::collections::HashMap<i64, &FbxNode>,
693) -> std::collections::HashMap<i64, (i64, u32)> {
694 let mut result = std::collections::HashMap::new();
695 for geometry_id in geometries.keys().copied() {
696 let Some(model_id) = connections.iter().find_map(|connection| {
697 (connection.kind == ConnectionKind::Oo
698 && connection.child == geometry_id
699 && models.contains_key(&connection.parent))
700 .then_some(connection.parent)
701 }) else {
702 continue;
703 };
704 for blend_shape_id in connections
705 .iter()
706 .filter(|connection| {
707 connection.kind == ConnectionKind::Oo
708 && connection.parent == geometry_id
709 && deformers
710 .get(&connection.child)
711 .and_then(|node| deformer_type(node))
712 == Some("BlendShape")
713 })
714 .map(|connection| connection.child)
715 {
716 for (index, channel_id) in connections
717 .iter()
718 .filter(|connection| {
719 connection.kind == ConnectionKind::Oo
720 && connection.parent == blend_shape_id
721 && deformers
722 .get(&connection.child)
723 .and_then(|node| deformer_type(node))
724 == Some("BlendShapeChannel")
725 })
726 .map(|connection| connection.child)
727 .enumerate()
728 {
729 result.insert(channel_id, (model_id, index as u32));
730 }
731 }
732 }
733 result
734}
735
736#[derive(Debug, Clone, Copy, PartialEq, Eq)]
738enum ConnectionKind {
739 Oo,
741 Op,
743}
744
745struct FbxObjectIndex<'a> {
752 model_map: HashMap<i64, &'a FbxNode>,
753 model_order: Vec<i64>,
755 geometry_map: HashMap<i64, &'a FbxNode>,
756 material_map: HashMap<i64, &'a FbxNode>,
757 texture_map: HashMap<i64, &'a FbxNode>,
758 video_map: HashMap<i64, &'a FbxNode>,
759 astack_map: HashMap<i64, &'a FbxNode>,
760 alayer_map: HashMap<i64, &'a FbxNode>,
761 acnode_map: HashMap<i64, &'a FbxNode>,
762 acurve_map: HashMap<i64, &'a FbxNode>,
763 deformer_map: HashMap<i64, &'a FbxNode>,
764 pose_map: HashMap<i64, &'a FbxNode>,
765 attribute_map: HashMap<i64, &'a FbxNode>,
766 connections: Vec<FbxConnection>,
767 name_keyed_objects: usize,
774 templates: PropertyTemplates<'a>,
776}
777
778impl<'a> FbxObjectIndex<'a> {
779 fn build(nodes: &'a [FbxNode]) -> Self {
780 let mut index = Self {
781 model_map: HashMap::new(),
782 model_order: Vec::new(),
783 geometry_map: HashMap::new(),
784 material_map: HashMap::new(),
785 texture_map: HashMap::new(),
786 video_map: HashMap::new(),
787 astack_map: HashMap::new(),
788 alayer_map: HashMap::new(),
789 acnode_map: HashMap::new(),
790 acurve_map: HashMap::new(),
791 deformer_map: HashMap::new(),
792 pose_map: HashMap::new(),
793 attribute_map: HashMap::new(),
794 connections: Vec::new(),
795 name_keyed_objects: 0,
796 templates: PropertyTemplates::build(nodes),
797 };
798
799 for node in nodes {
800 if node.name == "Objects" {
801 for child in &node.children {
802 let Some(id) = child.properties.first().and_then(object_id) else {
803 if matches!(child.properties.first(), Some(FbxProperty::String(_))) {
804 index.name_keyed_objects += 1;
805 }
806 continue;
807 };
808 let id = &id;
809 match child.name.as_str() {
810 "Model" => {
811 let first_occurrence = index.model_map.insert(*id, child).is_none();
813 if first_occurrence {
814 index.model_order.push(*id);
815 }
816 }
817 "Geometry" => drop(index.geometry_map.insert(*id, child)),
818 "Material" => drop(index.material_map.insert(*id, child)),
819 "Texture" => drop(index.texture_map.insert(*id, child)),
820 "Video" => drop(index.video_map.insert(*id, child)),
821 "AnimationStack" => drop(index.astack_map.insert(*id, child)),
822 "AnimationLayer" => drop(index.alayer_map.insert(*id, child)),
823 "AnimationCurveNode" => drop(index.acnode_map.insert(*id, child)),
824 "AnimationCurve" => drop(index.acurve_map.insert(*id, child)),
825 "Pose" => drop(index.pose_map.insert(*id, child)),
826 "NodeAttribute" => drop(index.attribute_map.insert(*id, child)),
827 "Deformer" => drop(index.deformer_map.insert(*id, child)),
828 _ => {}
829 }
830 }
831 } else if node.name == "Connections" {
832 index
833 .connections
834 .extend(node.children.iter().filter_map(FbxConnection::from_node));
835 }
836 }
837 index
838 }
839}
840
841fn float_array(property: &FbxProperty) -> Option<Vec<f32>> {
848 match property {
849 FbxProperty::F32Array(values) => Some(values.clone()),
850 FbxProperty::F64Array(values) => Some(values.iter().map(|v| *v as f32).collect()),
851 _ => None,
852 }
853}
854
855fn object_id(property: &FbxProperty) -> Option<i64> {
862 match property {
863 FbxProperty::I64(value) => Some(*value),
864 FbxProperty::I32(value) => Some(i64::from(*value)),
865 _ => None,
866 }
867}
868
869#[derive(Debug, Clone)]
871struct FbxConnection {
872 kind: ConnectionKind,
873 child: i64,
874 parent: i64,
875 property: Option<String>,
876}
877
878impl FbxConnection {
879 fn from_node(node: &FbxNode) -> Option<Self> {
881 let kind = match node.properties.first() {
882 Some(FbxProperty::String(code)) if code == "OO" => ConnectionKind::Oo,
883 Some(FbxProperty::String(code)) if code == "OP" => ConnectionKind::Op,
884 _ => return None,
885 };
886 let child = node.properties.get(1).and_then(object_id)?;
887 let parent = node.properties.get(2).and_then(object_id)?;
888 let property = match node.properties.get(3) {
889 Some(FbxProperty::String(name)) => Some(name.clone()),
890 _ => None,
891 };
892 Some(Self {
893 kind,
894 child,
895 parent,
896 property,
897 })
898 }
899}
900
901fn parse_materials_and_textures<'a>(
907 material_map: &HashMap<i64, &'a FbxNode>,
908 texture_map: &HashMap<i64, &FbxNode>,
909 video_map: &HashMap<i64, &FbxNode>,
910 connections: &[FbxConnection],
911 templates: &PropertyTemplates<'a>,
912) -> (
913 Vec<crate::fbx_scene::FbxMaterial>,
914 HashMap<i64, usize>,
915 Vec<crate::fbx_scene::FbxTexture>,
916) {
917 let mut materials: Vec<crate::fbx_scene::FbxMaterial> = Vec::new();
918 let mut material_index_by_id: HashMap<i64, usize> = HashMap::new();
919 let mut material_ids: Vec<i64> = material_map.keys().copied().collect();
920 material_ids.sort_unstable();
921 for id in material_ids {
922 let mut material = parse_material(ObjectProperties::new(material_map[&id], templates));
923 material.textures = collect_material_texture_bindings(id, texture_map, connections);
924 material_index_by_id.insert(id, materials.len());
925 materials.push(material);
926 }
927
928 let mut texture_video: HashMap<i64, i64> = HashMap::new();
931 for conn in connections {
932 if conn.kind != ConnectionKind::Oo {
933 continue;
934 }
935 if texture_map.contains_key(&conn.child) && video_map.contains_key(&conn.parent) {
936 texture_video.entry(conn.child).or_insert(conn.parent);
937 }
938 if video_map.contains_key(&conn.child) && texture_map.contains_key(&conn.parent) {
939 texture_video.entry(conn.parent).or_insert(conn.child);
940 }
941 }
942
943 let mut textures: Vec<crate::fbx_scene::FbxTexture> = Vec::new();
944 let mut texture_index_by_id: HashMap<i64, usize> = HashMap::new();
945 let mut texture_ids: Vec<i64> = texture_map.keys().copied().collect();
946 texture_ids.sort_unstable();
947 for id in texture_ids {
948 let mut texture = parse_texture(texture_map[&id]);
949 if let Some(video) = texture_video.get(&id).and_then(|id| video_map.get(id)) {
950 let from_video = parse_texture(video);
951 texture.content = texture.content.or(from_video.content);
952 texture.filename = texture.filename.or(from_video.filename);
953 texture.name = texture.name.or(from_video.name);
954 }
955 texture_index_by_id.insert(id, textures.len());
956 textures.push(texture);
957 }
958
959 for material in &mut materials {
961 for binding in &mut material.textures {
962 let fbx_id = binding.texture_index as i64;
963 if let Some(&resolved) = texture_index_by_id.get(&fbx_id) {
964 binding.texture_index = resolved;
965 }
966 }
967 }
968
969 (materials, material_index_by_id, textures)
970}
971
972fn deformer_type(node: &FbxNode) -> Option<&str> {
975 match node.properties.get(2) {
976 Some(FbxProperty::String(value)) if !value.is_empty() => Some(value.as_str()),
977 _ => None,
978 }
979}
980
981fn collect_material_texture_bindings(
985 material_id: i64,
986 texture_map: &std::collections::HashMap<i64, &FbxNode>,
987 connections: &[FbxConnection],
988) -> Vec<crate::fbx_scene::FbxTextureBinding> {
989 let mut bindings = Vec::new();
990 for conn in connections {
991 if conn.kind != ConnectionKind::Op || conn.parent != material_id {
992 continue;
993 }
994 let Some(slot_name) = conn.property.as_deref() else {
995 continue;
996 };
997 let Some(slot) = crate::fbx_scene::FbxTextureSlot::from_property_name(slot_name) else {
998 continue;
999 };
1000 if !texture_map.contains_key(&conn.child) {
1001 continue;
1002 }
1003 bindings.push(crate::fbx_scene::FbxTextureBinding {
1004 slot,
1005 texture_index: conn.child as usize,
1006 });
1007 }
1008 bindings
1009}
1010
1011fn parse_node_attributes<'a>(
1019 attribute_map: &HashMap<i64, &'a FbxNode>,
1020 model_map: &HashMap<i64, &FbxNode>,
1021 connections: &[FbxConnection],
1022 templates: &PropertyTemplates<'a>,
1023 warnings: &mut Vec<FbxWarning>,
1024) -> HashMap<i64, FbxNodeAttribute> {
1025 let mut by_model: Vec<(i64, i64)> = connections
1026 .iter()
1027 .filter(|conn| {
1028 attribute_map.contains_key(&conn.child) && model_map.contains_key(&conn.parent)
1029 })
1030 .map(|conn| (conn.parent, conn.child))
1031 .collect();
1032 by_model.sort_unstable();
1033
1034 let mut resolved = HashMap::new();
1035 for (model_id, attribute_id) in by_model {
1036 let node = attribute_map[&attribute_id];
1037 let class = match node.properties.get(2) {
1038 Some(FbxProperty::String(class)) => class.as_str(),
1039 _ => continue,
1040 };
1041 match class {
1042 "Camera" => {
1043 let properties = ObjectProperties::new(node, templates);
1044 resolved.insert(model_id, FbxNodeAttribute::Camera(parse_camera(properties)));
1045 }
1046 "Light" => {
1047 let properties = ObjectProperties::new(node, templates);
1048 resolved.insert(model_id, FbxNodeAttribute::Light(parse_light(properties)));
1049 }
1050 "LimbNode" | "Limb" | "Null" | "Root" => {}
1054 other => push_warning(
1055 warnings,
1056 FbxWarningCode::DroppedNodeAttribute,
1057 format!(
1058 "FBX NodeAttribute of class {other} is not represented, so its properties \
1059 are absent from the scene"
1060 ),
1061 Some(other),
1062 ),
1063 }
1064 }
1065 resolved
1066}
1067
1068fn parse_camera(properties: ObjectProperties<'_>) -> crate::fbx_scene::FbxCamera {
1069 let scalar = |name: &str| properties.get(name).and_then(property_scalar);
1070 let vector = |name: &str| properties.get(name).and_then(property_vec3);
1071 crate::fbx_scene::FbxCamera {
1072 position: vector("Position"),
1073 interest_position: vector("InterestPosition"),
1074 up_vector: vector("UpVector"),
1075 projection_type: scalar("CameraProjectionType").map(|v| v as i32),
1076 field_of_view: scalar("FieldOfView"),
1077 field_of_view_x: scalar("FieldOfViewX"),
1078 field_of_view_y: scalar("FieldOfViewY"),
1079 focal_length: scalar("FocalLength"),
1080 near_plane: scalar("NearPlane"),
1081 far_plane: scalar("FarPlane"),
1082 aspect_width: scalar("AspectWidth"),
1083 aspect_height: scalar("AspectHeight"),
1084 film_width: scalar("FilmWidth"),
1085 film_height: scalar("FilmHeight"),
1086 film_aspect_ratio: scalar("FilmAspectRatio"),
1087 aperture_mode: scalar("ApertureMode").map(|v| v as i32),
1088 ortho_zoom: scalar("OrthoZoom"),
1089 }
1090}
1091
1092fn parse_light(properties: ObjectProperties<'_>) -> crate::fbx_scene::FbxLight {
1093 let scalar = |name: &str| properties.get(name).and_then(property_scalar);
1094 crate::fbx_scene::FbxLight {
1095 light_type: scalar("LightType").map(|v| v as i32),
1096 color: properties.get("Color").and_then(property_vec3),
1097 intensity: scalar("Intensity"),
1098 cast_light: scalar("CastLight").map(|v| v != 0.0),
1099 cast_shadows: scalar("CastShadows").map(|v| v != 0.0),
1100 decay_type: scalar("DecayType").map(|v| v as i32),
1101 decay_start: scalar("DecayStart"),
1102 }
1103}
1104
1105fn property_scalar(prop: &FbxNode) -> Option<f32> {
1106 for value in prop.properties.iter().skip(4) {
1109 match value {
1110 FbxProperty::F64(v) => return Some(*v as f32),
1111 FbxProperty::F32(v) => return Some(*v),
1112 FbxProperty::I32(v) => return Some(*v as f32),
1113 FbxProperty::I64(v) => return Some(*v as f32),
1114 _ => {}
1115 }
1116 }
1117 None
1118}
1119
1120fn property_vec3(prop: &FbxNode) -> Option<[f32; 3]> {
1121 let values: Vec<f32> = prop
1122 .properties
1123 .iter()
1124 .skip(4)
1125 .filter_map(|value| match value {
1126 FbxProperty::F64(v) => Some(*v as f32),
1127 FbxProperty::F32(v) => Some(*v),
1128 _ => None,
1129 })
1130 .take(3)
1131 .collect();
1132 (values.len() == 3).then(|| [values[0], values[1], values[2]])
1133}
1134
1135fn parse_material(properties: ObjectProperties<'_>) -> crate::fbx_scene::FbxMaterial {
1136 let name = match properties.node().properties.get(1) {
1137 Some(FbxProperty::String(raw)) => raw
1138 .split('\0')
1139 .next()
1140 .filter(|s| !s.is_empty())
1141 .map(str::to_string),
1142 _ => None,
1143 };
1144 let shading_model = read_shading_model(properties);
1145
1146 let get_color = |name: &str| properties.get(name).and_then(property_vec3);
1147 let get_scalar = |name: &str| properties.get(name).and_then(property_scalar);
1148
1149 crate::fbx_scene::FbxMaterial {
1150 name,
1151 shading_model,
1152 diffuse: get_color("DiffuseColor"),
1153 specular: get_color("SpecularColor"),
1154 emissive: get_color("EmissiveColor"),
1155 ambient: get_color("AmbientColor"),
1156 diffuse_factor: get_scalar("DiffuseFactor"),
1157 specular_factor: get_scalar("SpecularFactor"),
1158 shininess: get_scalar("Shininess"),
1159 emissive_factor: get_scalar("EmissiveFactor"),
1160 reflection_factor: get_scalar("ReflectionFactor"),
1161 transparency_factor: get_scalar("TransparencyFactor"),
1162 opacity: get_scalar("Opacity"),
1163 bump_factor: get_scalar("BumpFactor"),
1164 textures: Vec::new(),
1165 }
1166}
1167
1168fn read_shading_model(properties: ObjectProperties<'_>) -> Option<String> {
1182 let object = properties.node();
1183 let from_own_properties = properties
1184 .node()
1185 .children
1186 .iter()
1187 .filter(|child| child.name == "Properties70")
1188 .find_map(|block| crate::fbx_templates::find_property(block, "ShadingModel"))
1189 .and_then(string_value);
1190 let from_sibling_node = object
1191 .children
1192 .iter()
1193 .find(|child| child.name == "ShadingModel")
1194 .and_then(|child| match child.properties.first() {
1195 Some(FbxProperty::String(model)) if !model.is_empty() => Some(model.clone()),
1196 _ => None,
1197 });
1198 let from_template = properties
1199 .template()
1200 .and_then(|block| crate::fbx_templates::find_property(block, "ShadingModel"))
1201 .and_then(string_value);
1202 let from_class = match object.properties.get(2) {
1203 Some(FbxProperty::String(raw)) if !raw.is_empty() => Some(raw.clone()),
1204 _ => None,
1205 };
1206
1207 from_own_properties
1208 .or(from_sibling_node)
1209 .or(from_template)
1210 .or(from_class)
1211}
1212
1213fn string_value(property: &FbxNode) -> Option<String> {
1215 property
1216 .properties
1217 .iter()
1218 .skip(4)
1219 .find_map(|value| match value {
1220 FbxProperty::String(text) => Some(text.clone()),
1221 _ => None,
1222 })
1223}
1224
1225fn parse_texture(node: &FbxNode) -> crate::fbx_scene::FbxTexture {
1226 let name = match node.properties.get(1) {
1227 Some(FbxProperty::String(raw)) => raw
1228 .split('\0')
1229 .next()
1230 .filter(|s| !s.is_empty())
1231 .map(str::to_string),
1232 _ => None,
1233 };
1234 let mut filename = None;
1235 let mut content = None;
1236 for child in &node.children {
1237 match child.name.as_str() {
1238 "RelativeFilename" | "FileName" | "Filename" if filename.is_none() => {
1239 if let Some(FbxProperty::String(s)) = child.properties.first() {
1240 if !s.is_empty() {
1241 filename = Some(s.clone());
1242 }
1243 }
1244 }
1245 "Content" => {
1246 if let Some(FbxProperty::Raw(bytes)) = child.properties.first() {
1247 if !bytes.is_empty() {
1248 content = Some(bytes.clone());
1249 }
1250 }
1251 }
1252 _ => {}
1253 }
1254 }
1255 crate::fbx_scene::FbxTexture {
1256 name,
1257 content,
1258 filename,
1259 }
1260}
1261impl<R: Read + Seek> FbxReader<R> {
1262 pub fn read_meshes(&mut self) -> io::Result<Vec<Mesh>> {
1264 let nodes = self.read_nodes()?;
1265 let mut meshes = Vec::new();
1266 let mut warnings = Vec::new();
1270
1271 for node in &nodes {
1273 if node.name == "Objects" {
1274 for child in &node.children {
1275 if child.name == "Geometry" {
1276 if let Some(source) = geometry_to_mesh(child, &mut warnings)? {
1277 meshes.push(source.mesh);
1278 }
1279 }
1280 }
1281 }
1282 }
1283
1284 self.extend_warnings(warnings);
1285 Ok(meshes)
1286 }
1287}
1288
1289#[derive(Default)]
1295struct RawLayerNodes<'a> {
1296 normals: Vec<&'a FbxNode>,
1297 uvs: Vec<&'a FbxNode>,
1298 colors: Vec<&'a FbxNode>,
1299 tangents: Vec<&'a FbxNode>,
1300 binormals: Vec<&'a FbxNode>,
1301 smoothing: Vec<&'a FbxNode>,
1302 creases: Vec<(FbxCreaseKind, &'a FbxNode)>,
1303 material: Option<&'a FbxNode>,
1304}
1305
1306#[derive(Clone, Copy)]
1308struct LayerDomains {
1309 edges: Option<usize>,
1310 polygons: usize,
1311 control_points: usize,
1312}
1313
1314impl LayerDomains {
1315 fn check(self, mapping: Option<&str>) -> DomainCheck {
1324 match mapping {
1325 Some("ByEdge") => match self.edges {
1326 Some(count) => DomainCheck::Expect(count),
1327 None => DomainCheck::Unverifiable,
1328 },
1329 Some("ByPolygon") => DomainCheck::Expect(self.polygons),
1330 Some("ByVertice") | Some("ByVertex") | Some("ByControlPoint") => {
1331 DomainCheck::Expect(self.control_points)
1332 }
1333 _ => DomainCheck::Unknown,
1334 }
1335 }
1336}
1337
1338fn geometry_to_mesh(
1344 geometry: &FbxNode,
1345 warnings: &mut Vec<FbxWarning>,
1346) -> io::Result<Option<FbxGeometrySource>> {
1347 let mut vertices: Option<Vec<f64>> = None;
1348 let mut polygon_indices: Option<Vec<i32>> = None;
1349 let mut edges: Vec<i32> = Vec::new();
1350 let mut raw = RawLayerNodes::default();
1351
1352 for child in &geometry.children {
1353 match child.name.as_str() {
1354 "Vertices" => {
1355 if let Some(FbxProperty::F64Array(arr)) = child.properties.first() {
1356 vertices = Some(arr.clone());
1357 }
1358 }
1359 "Edges" => {
1360 if let Some(FbxProperty::I32Array(arr)) = child.properties.first() {
1361 edges = arr.clone();
1362 }
1363 }
1364 "PolygonVertexIndex" => {
1365 if let Some(FbxProperty::I32Array(arr)) = child.properties.first() {
1366 polygon_indices = Some(arr.clone());
1367 }
1368 }
1369 "LayerElementNormal" => raw.normals.push(child),
1370 "LayerElementColor" => raw.colors.push(child),
1371 "LayerElementUV" => raw.uvs.push(child),
1372 "LayerElementTangent" => raw.tangents.push(child),
1373 "LayerElementBinormal" => raw.binormals.push(child),
1374 "LayerElementSmoothing" => raw.smoothing.push(child),
1375 "LayerElementEdgeCrease" => raw.creases.push((FbxCreaseKind::Edge, child)),
1376 "LayerElementVertexCrease" => raw.creases.push((FbxCreaseKind::Vertex, child)),
1377 "LayerElementMaterial" if raw.material.is_none() => {
1378 raw.material = Some(child);
1379 }
1380 other if other.starts_with("LayerElement") => push_warning(
1384 warnings,
1385 FbxWarningCode::DroppedLayerElement,
1386 format!("FBX {other} is not imported, so its data is absent from the scene"),
1387 Some(other),
1388 ),
1389 _ => {}
1390 }
1391 }
1392
1393 let vertices = match vertices {
1394 Some(v) => v,
1395 None => return Ok(None),
1396 };
1397 let polygon_indices = match polygon_indices {
1398 Some(p) => p,
1399 None => return Ok(None),
1400 };
1401
1402 let control_points = vertices
1403 .chunks_exact(3)
1404 .map(|value| [value[0] as f32, value[1] as f32, value[2] as f32])
1405 .collect::<Vec<_>>();
1406
1407 let mut tri_polygon_index: Vec<usize> = Vec::new();
1410 let mut polygon_count = 0usize;
1411 let mut corners_in_polygon = 0usize;
1412 for &idx in &polygon_indices {
1413 corners_in_polygon += 1;
1414 if idx < 0 {
1415 for _ in 0..corners_in_polygon.saturating_sub(2) {
1416 tri_polygon_index.push(polygon_count);
1417 }
1418 corners_in_polygon = 0;
1419 polygon_count += 1;
1420 }
1421 }
1422
1423 let material_indices = raw
1425 .material
1426 .and_then(|layer| {
1427 let mapping = layer_string(layer, "MappingInformationType");
1428 let reference = layer_string(layer, "ReferenceInformationType");
1429 let data = layer_int_array(layer, "Materials");
1430 expand_material_indices(
1431 mapping.as_deref(),
1432 reference.as_deref(),
1433 data.as_deref(),
1434 polygon_count,
1435 &tri_polygon_index,
1436 )
1437 })
1438 .unwrap_or_default();
1439
1440 let domains = LayerDomains {
1441 edges: (!edges.is_empty()).then_some(edges.len()),
1442 polygons: polygon_count,
1443 control_points: control_points.len(),
1444 };
1445 let layers = parse_geometry_layers(raw, domains, warnings);
1446
1447 let render = crate::fbx_render_mesh::expand_to_render_mesh(
1451 crate::fbx_render_mesh::FbxGeometryLayers::new(&control_points, &polygon_indices, &layers),
1452 );
1453 let mesh = crate::fbx_render_mesh::build_draco_mesh(&render);
1454
1455 Ok(Some(FbxGeometrySource {
1456 mesh,
1457 material_indices,
1458 control_points,
1459 polygon_vertex_indices: polygon_indices,
1460 layers,
1461 edges,
1462 }))
1463}
1464
1465fn parse_geometry_layers(
1467 raw: RawLayerNodes<'_>,
1468 domains: LayerDomains,
1469 warnings: &mut Vec<FbxWarning>,
1470) -> FbxMeshLayers {
1471 let uv_sets: Vec<FbxUvSet> = raw
1472 .uvs
1473 .into_iter()
1474 .filter_map(|layer| {
1475 let values = chunk_layer_values(&read_layer_floats(layer, "UV")?);
1476 Some(layer_set(layer, values, &["UVIndex"]))
1477 })
1478 .collect();
1479 let normal_sets: Vec<FbxNormalSet> = raw
1480 .normals
1481 .into_iter()
1482 .filter_map(|layer| {
1483 let values = chunk_layer_values(&read_layer_floats(layer, "Normals")?);
1484 Some(layer_set(layer, values, &["NormalsIndex", "NormalIndex"]))
1486 })
1487 .collect();
1488 for set in &uv_sets {
1489 warn_unsupported_layer_mapping("LayerElementUV", set, warnings);
1490 }
1491 for set in &normal_sets {
1492 warn_unsupported_layer_mapping("LayerElementNormal", set, warnings);
1493 }
1494 let color_sets: Vec<FbxColorSet> = raw
1495 .colors
1496 .into_iter()
1497 .filter_map(|layer| {
1498 let floats = read_layer_floats(layer, "Colors")?;
1499 let values = if floats.len() % 4 == 0 {
1502 chunk_layer_values(&floats)
1503 } else {
1504 floats
1505 .chunks_exact(3)
1506 .map(|value| [value[0], value[1], value[2], 1.0])
1507 .collect()
1508 };
1509 Some(layer_set(layer, values, &["ColorIndex"]))
1510 })
1511 .collect();
1512 for set in &color_sets {
1513 warn_unsupported_layer_mapping("LayerElementColor", set, warnings);
1514 }
1515 let tangent_sets: Vec<FbxTangentSet> = raw
1516 .tangents
1517 .into_iter()
1518 .filter_map(|layer| parse_tangent_like(layer, "Tangents", "TangentsW", "TangentIndex"))
1519 .collect();
1520 let binormal_sets: Vec<FbxBinormalSet> = raw
1521 .binormals
1522 .into_iter()
1523 .filter_map(|layer| parse_tangent_like(layer, "Binormals", "BinormalsW", "BinormalIndex"))
1524 .collect();
1525 for set in &tangent_sets {
1526 warn_unsupported_layer_mapping("LayerElementTangent", &set.layer, warnings);
1527 }
1528 for set in &binormal_sets {
1529 warn_unsupported_layer_mapping("LayerElementBinormal", &set.layer, warnings);
1530 }
1531
1532 let mut smoothing_layers = Vec::new();
1538 for layer in raw.smoothing {
1539 let mapping = layer_string(layer, "MappingInformationType");
1540 let Some(values) = layer_int_array(layer, "Smoothing") else {
1541 continue;
1542 };
1543 if domains.check(mapping.as_deref()).accepts(values.len()) {
1544 smoothing_layers.push(FbxSmoothingLayer { mapping, values });
1545 } else {
1546 warn_misaligned_layer(
1547 "LayerElementSmoothing",
1548 mapping.as_deref(),
1549 values.len(),
1550 warnings,
1551 );
1552 }
1553 }
1554 let mut crease_layers = Vec::new();
1555 for (kind, layer) in raw.creases {
1556 let element = match kind {
1557 FbxCreaseKind::Edge => "LayerElementEdgeCrease",
1558 FbxCreaseKind::Vertex => "LayerElementVertexCrease",
1559 };
1560 let mapping = layer_string(layer, "MappingInformationType");
1561 let Some(values) = layer_f64_array(layer, element.trim_start_matches("LayerElement"))
1562 else {
1563 continue;
1564 };
1565 match domains.check(mapping.as_deref()) {
1566 domain if domain.accepts(values.len()) => {
1567 crease_layers.push(FbxCreaseLayer {
1568 kind,
1569 mapping,
1570 values,
1571 });
1572 }
1573 _ => warn_misaligned_layer(element, mapping.as_deref(), values.len(), warnings),
1574 }
1575 }
1576
1577 FbxMeshLayers {
1578 uv_sets,
1579 normal_sets,
1580 color_sets,
1581 tangent_sets,
1582 binormal_sets,
1583 smoothing_layers,
1584 crease_layers,
1585 }
1586}
1587
1588impl<R: Read + Seek> FbxReader<R> {
1589 fn parse_animations(
1592 &self,
1593 nodes: &[FbxNode],
1594 index: &FbxObjectIndex<'_>,
1595 model_name_map: &HashMap<i64, String>,
1596 model_node_ids: &HashMap<i64, FbxNodeId>,
1597 morph_targets: &HashMap<i64, (i64, u32)>,
1598 ) -> Vec<FbxAnimation> {
1599 let FbxObjectIndex {
1600 connections,
1601 astack_map,
1602 alayer_map,
1603 acnode_map,
1604 acurve_map,
1605 model_map,
1606 ..
1607 } = index;
1608 let fbx_ktime = fbx_ktime_for(nodes, self.version());
1609 let ktime_f = match fbx_ktime {
1616 0 => 1.0,
1617 v => v as f64,
1618 };
1619
1620 let mut acnode_targets: std::collections::HashMap<
1625 i64,
1626 (i64, i64, FbxAnimChannelPath, Option<u32>),
1627 > = std::collections::HashMap::new();
1628 for conn in connections {
1629 if conn.kind != ConnectionKind::Op {
1630 continue;
1631 }
1632 if !acnode_map.contains_key(&conn.child) {
1633 continue;
1634 }
1635 let Some(property) = conn.property.as_deref() else {
1636 continue;
1637 };
1638 let Some(path) = FbxAnimChannelPath::from_property_name(property) else {
1639 continue;
1640 };
1641 let (model_id, morph_target_index) = if model_map.contains_key(&conn.parent) {
1642 (conn.parent, None)
1643 } else if path == FbxAnimChannelPath::MorphWeight {
1644 let Some(&(model_id, target_index)) = morph_targets.get(&conn.parent) else {
1645 continue;
1646 };
1647 (model_id, Some(target_index))
1648 } else {
1649 continue;
1650 };
1651 let mut layer_id = None;
1653 for c2 in connections {
1654 if c2.kind == ConnectionKind::Oo
1655 && c2.child == conn.child
1656 && alayer_map.contains_key(&c2.parent)
1657 {
1658 layer_id = Some(c2.parent);
1659 break;
1660 }
1661 }
1662 if let Some(layer_id) = layer_id {
1663 acnode_targets.insert(conn.child, (layer_id, model_id, path, morph_target_index));
1664 }
1665 }
1666
1667 let mut acnode_curves: std::collections::HashMap<
1669 i64,
1670 std::collections::BTreeMap<u32, FbxAnimCurveData>,
1671 > = std::collections::HashMap::new();
1672 for conn in connections {
1673 if conn.kind != ConnectionKind::Op {
1674 continue;
1675 }
1676 if !acurve_map.contains_key(&conn.child) {
1677 continue;
1678 }
1679 if !acnode_targets.contains_key(&conn.parent) {
1680 continue;
1681 }
1682 let component = match conn.property.as_deref() {
1683 Some("d|X") => 0,
1684 Some("d|Y") => 1,
1685 Some("d|Z") => 2,
1686 _ => continue,
1687 };
1688 if let Some(curve) = parse_curve(acurve_map[&conn.child]) {
1689 acnode_curves
1690 .entry(conn.parent)
1691 .or_default()
1692 .insert(component, curve);
1693 }
1694 }
1695
1696 let mut stacks_layers: StacksLayers = std::collections::HashMap::new();
1704 let mut acnode_ids_sorted: Vec<i64> = acnode_targets.keys().copied().collect();
1705 acnode_ids_sorted.sort_unstable();
1706 for acnode_id in &acnode_ids_sorted {
1707 let (layer_id, model_id, path, morph_target_index) = &acnode_targets[acnode_id];
1708 let mut stack_ids = Vec::new();
1710 for c2 in connections {
1711 if c2.kind == ConnectionKind::Oo
1712 && c2.child == *layer_id
1713 && astack_map.contains_key(&c2.parent)
1714 {
1715 stack_ids.push(c2.parent);
1716 }
1717 }
1718 for stack_id in stack_ids {
1719 stacks_layers
1720 .entry(stack_id)
1721 .or_default()
1722 .entry(*layer_id)
1723 .or_default()
1724 .push((*acnode_id, *model_id, *path, *morph_target_index));
1725 }
1726 }
1727
1728 let mut animations = Vec::new();
1729 let mut stack_ids_sorted: Vec<i64> = stacks_layers.keys().copied().collect();
1730 stack_ids_sorted.sort_unstable();
1731 for stack_id in stack_ids_sorted {
1732 let layers = &stacks_layers[&stack_id];
1733 let stack_node = astack_map.get(&stack_id);
1734 let name = stack_node.and_then(|n| match n.properties.get(1) {
1735 Some(FbxProperty::String(raw)) => raw
1736 .split('\0')
1737 .next()
1738 .filter(|s| !s.is_empty())
1739 .map(str::to_string),
1740 _ => None,
1741 });
1742 let mut layer_ids_sorted: Vec<i64> = layers.keys().copied().collect();
1748 layer_ids_sorted.sort_unstable();
1749 let multiple_layers = layer_ids_sorted.len() > 1;
1750 for (layer_index, layer_id) in layer_ids_sorted.iter().copied().enumerate() {
1751 let mut channels = Vec::new();
1752 let mut max_time = 0.0f32;
1753 let entries = &layers[&layer_id];
1754 let mut groups: std::collections::HashMap<
1756 (i64, FbxAnimChannelPath, Option<u32>),
1757 Vec<i64>,
1758 > = std::collections::HashMap::new();
1759 for &(acnode_id, model_id, path, morph_target_index) in entries {
1760 groups
1761 .entry((model_id, path, morph_target_index))
1762 .or_default()
1763 .push(acnode_id);
1764 }
1765 let mut group_keys: Vec<(i64, FbxAnimChannelPath, Option<u32>)> =
1766 groups.keys().copied().collect();
1767 group_keys.sort_unstable_by_key(|(model_id, path, morph_target_index)| {
1768 (*model_id, *path as u8, *morph_target_index)
1769 });
1770 for (model_id, path, morph_target_index) in group_keys {
1771 let acnode_ids = &groups[&(model_id, path, morph_target_index)];
1772 let mut by_component: std::collections::BTreeMap<u32, FbxAnimCurveData> =
1776 std::collections::BTreeMap::new();
1777 for acnode_id in acnode_ids {
1778 if let Some(curves) = acnode_curves.get(acnode_id) {
1779 for (component, curve) in curves {
1780 by_component
1781 .entry(*component)
1782 .or_insert_with(|| curve.clone());
1783 }
1784 }
1785 }
1786 let Some(channel) = flatten_curve(&by_component, path, ktime_f) else {
1787 continue;
1788 };
1789 if let (Some(node_name), Some(&node_id)) =
1790 (model_name_map.get(&model_id), model_node_ids.get(&model_id))
1791 {
1792 max_time =
1793 max_time.max(channel.sampler.input.last().copied().unwrap_or(0.0));
1794 channels.push(FbxAnimChannel {
1795 node_id,
1796 node_name: node_name.clone(),
1797 path,
1798 morph_target_index,
1799 sampler: channel.sampler,
1800 });
1801 }
1802 }
1803 if channels.is_empty() {
1804 continue;
1805 }
1806 let clip_name = if multiple_layers {
1809 let layer_name = alayer_map
1810 .get(&layer_id)
1811 .and_then(|node| match node.properties.get(1) {
1812 Some(FbxProperty::String(raw)) => raw
1813 .split('\0')
1814 .next()
1815 .filter(|part| !part.is_empty())
1816 .map(str::to_string),
1817 _ => None,
1818 })
1819 .unwrap_or_else(|| format!("Layer{layer_index}"));
1820 Some(match &name {
1821 Some(stack) => format!("{stack}|{layer_name}"),
1822 None => layer_name,
1823 })
1824 } else {
1825 name.clone()
1826 };
1827 animations.push(FbxAnimation {
1828 name: clip_name,
1829 duration: max_time,
1830 channels,
1831 });
1832 }
1833 }
1834 animations
1835 }
1836}
1837
1838type StacksLayers = std::collections::HashMap<
1842 i64,
1843 std::collections::HashMap<i64, Vec<(i64, i64, FbxAnimChannelPath, Option<u32>)>>,
1844>;
1845
1846#[derive(Debug, Clone)]
1847struct FbxAnimCurveData {
1848 key_times: Vec<i64>,
1849 key_values: Vec<f32>,
1850 key_attr_flags: Vec<i32>,
1851 in_tangents: Vec<f32>,
1852 out_tangents: Vec<f32>,
1853}
1854
1855fn parse_curve(node: &FbxNode) -> Option<FbxAnimCurveData> {
1856 let mut key_times = None;
1857 let mut key_values = None;
1858 let mut key_attr_flags = None;
1859 let mut key_attr_data = None;
1860 let mut key_attr_ref_count = None;
1861 for child in &node.children {
1862 match child.name.as_str() {
1863 "KeyTime" => {
1864 if let Some(FbxProperty::I64Array(arr)) = child.properties.first() {
1865 key_times = Some(arr.clone());
1866 }
1867 }
1868 "KeyValueFloat" => {
1869 key_values = child.properties.first().and_then(float_array);
1870 }
1871 "KeyAttrFlags" => {
1872 if let Some(FbxProperty::I32Array(arr)) = child.properties.first() {
1873 key_attr_flags = Some(arr.clone());
1874 }
1875 }
1876 "KeyAttrDataFloat" => {
1877 key_attr_data = child.properties.first().and_then(float_array);
1878 }
1879 "KeyAttrRefCount" => {
1880 if let Some(FbxProperty::I32Array(arr)) = child.properties.first() {
1881 key_attr_ref_count = Some(arr.clone());
1882 }
1883 }
1884 _ => {}
1885 }
1886 }
1887 let key_times = key_times?;
1888 let key_values = key_values?;
1889 if key_times.is_empty() || key_values.len() != key_times.len() {
1890 return None;
1891 }
1892 let mut expanded_flags = Vec::with_capacity(key_times.len());
1893 let mut expanded_attrs = Vec::with_capacity(key_times.len());
1894 if let (Some(flags), Some(data), Some(refs)) =
1895 (key_attr_flags, key_attr_data, key_attr_ref_count)
1896 {
1897 if flags.len() == refs.len() && data.len() == refs.len() * 4 {
1898 for ((flag, count), attrs) in flags.into_iter().zip(refs).zip(data.chunks_exact(4)) {
1899 for _ in 0..count.max(0) {
1900 expanded_flags.push(flag);
1901 expanded_attrs.push([attrs[0], attrs[1]]);
1902 }
1903 }
1904 }
1905 }
1906 if expanded_flags.len() != key_times.len() {
1907 expanded_flags = vec![0x4; key_times.len()];
1908 expanded_attrs = vec![[0.0, 0.0]; key_times.len()];
1909 }
1910 let mut in_tangents = vec![0.0; key_times.len()];
1911 let mut out_tangents = vec![0.0; key_times.len()];
1912 for (index, attrs) in expanded_attrs.iter().enumerate() {
1913 out_tangents[index] = attrs[0];
1914 if index + 1 < in_tangents.len() {
1915 in_tangents[index + 1] = attrs[1];
1916 }
1917 }
1918 Some(FbxAnimCurveData {
1919 key_times,
1920 key_values,
1921 key_attr_flags: expanded_flags,
1922 in_tangents,
1923 out_tangents,
1924 })
1925}
1926
1927fn flatten_curve(
1933 by_component: &std::collections::BTreeMap<u32, FbxAnimCurveData>,
1934 path: FbxAnimChannelPath,
1935 ktime_f: f64,
1936) -> Option<FbxAnimChannel> {
1937 let time_axis = by_component
1938 .get(&0)
1939 .or_else(|| by_component.get(&1))
1940 .or_else(|| by_component.get(&2))?;
1941 let n = time_axis.key_times.len();
1942 let mut input = Vec::with_capacity(n);
1943 let component_count = path.component_count();
1944 let mut output = Vec::with_capacity(n * component_count);
1945 let mut in_tangents = Vec::with_capacity(n * component_count);
1946 let mut out_tangents = Vec::with_capacity(n * component_count);
1947 let flags = time_axis.key_attr_flags.first().copied().unwrap_or(0);
1948 let interpolation = FbxAnimInterpolation::from_key_attr_flags(flags);
1949 for i in 0..n {
1950 input.push((time_axis.key_times[i] as f64 / ktime_f) as f32);
1951 for component in 0..component_count as u32 {
1952 let value = by_component.get(&component).and_then(|curve| {
1953 if i < curve.key_values.len() {
1954 Some(curve.key_values[i])
1955 } else {
1956 None
1957 }
1958 });
1959 output.push(value.unwrap_or(0.0));
1960 in_tangents.push(
1961 by_component
1962 .get(&component)
1963 .and_then(|curve| curve.in_tangents.get(i))
1964 .copied()
1965 .unwrap_or(0.0),
1966 );
1967 out_tangents.push(
1968 by_component
1969 .get(&component)
1970 .and_then(|curve| curve.out_tangents.get(i))
1971 .copied()
1972 .unwrap_or(0.0),
1973 );
1974 }
1975 }
1976 let radians = |value: f32| f64::from(value).to_radians() as f32;
1984 if path == FbxAnimChannelPath::Rotation {
1985 for chunk in output.chunks_mut(3) {
1986 for value in chunk.iter_mut() {
1987 *value = radians(*value);
1988 }
1989 }
1990 for chunk in in_tangents.chunks_mut(3) {
1991 for value in chunk.iter_mut() {
1992 *value = radians(*value);
1993 }
1994 }
1995 for chunk in out_tangents.chunks_mut(3) {
1996 for value in chunk.iter_mut() {
1997 *value = radians(*value);
1998 }
1999 }
2000 }
2001 Some(FbxAnimChannel {
2002 node_id: FbxNodeId(0),
2003 node_name: String::new(),
2004 path,
2005 morph_target_index: None,
2006 sampler: FbxAnimSampler {
2007 input,
2008 output,
2009 interpolation,
2010 in_tangents: (interpolation == FbxAnimInterpolation::Cubic).then_some(in_tangents),
2011 out_tangents: (interpolation == FbxAnimInterpolation::Cubic).then_some(out_tangents),
2012 },
2013 })
2014}
2015
2016fn fbx_ktime_for(nodes: &[FbxNode], version: u32) -> u64 {
2023 const KTIME_V7: u64 = 46_186_158_000;
2024 const KTIME_V8: u64 = 141_120_000;
2025 if version >= 8000 {
2026 return KTIME_V8;
2027 }
2028 if version >= 7700 {
2029 for n in nodes {
2032 if n.name != "FBXHeaderExtension" {
2033 continue;
2034 }
2035 let mut header_version = 0;
2036 let mut other_flags: Option<&FbxNode> = None;
2037 for child in &n.children {
2038 if child.name == "FBXHeaderVersion" {
2039 if let Some(FbxProperty::I32(v)) = child.properties.first() {
2040 header_version = *v;
2041 }
2042 } else if child.name == "OtherFlags" && other_flags.is_none() {
2043 other_flags = Some(child);
2044 }
2045 }
2046 if header_version >= 1004 {
2047 if let Some(flags) = other_flags {
2048 for flag in &flags.children {
2049 if flag.name == "TCDefinition" {
2050 if let Some(FbxProperty::I32(v)) = flag.properties.first() {
2051 return if *v == 127 { KTIME_V7 } else { KTIME_V8 };
2052 }
2053 }
2054 }
2055 }
2056 }
2057 }
2058 return KTIME_V7;
2060 }
2061 KTIME_V7
2062}
2063
2064fn layer_string(layer: &FbxNode, name: &str) -> Option<String> {
2065 for child in &layer.children {
2066 if child.name == name {
2067 if let Some(FbxProperty::String(s)) = child.properties.first() {
2068 return Some(s.clone());
2069 }
2070 }
2071 }
2072 None
2073}
2074
2075fn layer_int_array(layer: &FbxNode, name: &str) -> Option<Vec<i32>> {
2076 for child in &layer.children {
2077 if child.name == name {
2078 if let Some(FbxProperty::I32Array(arr)) = child.properties.first() {
2079 return Some(arr.clone());
2080 }
2081 }
2082 }
2083 None
2084}
2085
2086fn warn_unsupported_layer_mapping<const N: usize>(
2094 element: &str,
2095 set: &FbxLayerSet<N>,
2096 warnings: &mut Vec<FbxWarning>,
2097) {
2098 const KNOWN_MAPPINGS: [&str; 7] = [
2099 "ByPolygonVertex",
2100 "ByPolygon",
2101 "ByVertice",
2102 "ByVertex",
2103 "ByControlPoint",
2104 "AllSame",
2105 "AllSameOrPolygon",
2106 ];
2107 if let Some(mapping) = set.mapping.as_deref() {
2108 if !KNOWN_MAPPINGS.contains(&mapping) {
2109 let subject = format!("{element}/{mapping}");
2110 push_warning(
2111 warnings,
2112 FbxWarningCode::UnsupportedLayerMapping,
2113 format!(
2114 "FBX {element} uses mapping {mapping}, which was resolved on the \
2115 control-point domain"
2116 ),
2117 Some(&subject),
2118 );
2119 }
2120 }
2121 if let Some(reference) = set.reference.as_deref() {
2122 if reference != "Direct" && reference != "IndexToDirect" {
2123 let subject = format!("{element}/{reference}");
2124 push_warning(
2125 warnings,
2126 FbxWarningCode::UnsupportedLayerMapping,
2127 format!("FBX {element} uses reference mode {reference}, which was read as Direct"),
2128 Some(&subject),
2129 );
2130 }
2131 }
2132}
2133
2134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2136enum DomainCheck {
2137 Expect(usize),
2139 Unverifiable,
2142 Unknown,
2144}
2145
2146impl DomainCheck {
2147 fn accepts(self, len: usize) -> bool {
2148 match self {
2149 DomainCheck::Expect(expected) => expected == len,
2150 DomainCheck::Unverifiable => true,
2151 DomainCheck::Unknown => false,
2152 }
2153 }
2154}
2155
2156fn warn_misaligned_layer(
2159 element: &str,
2160 mapping: Option<&str>,
2161 len: usize,
2162 warnings: &mut Vec<FbxWarning>,
2163) {
2164 let mapping = mapping.unwrap_or("no mapping");
2165 let subject = format!("{element}/{mapping}");
2166 push_warning(
2167 warnings,
2168 FbxWarningCode::UnsupportedLayerMapping,
2169 format!(
2170 "FBX {element} has {len} values, which does not match the domain \
2171 {mapping} addresses, so the layer was dropped"
2172 ),
2173 Some(&subject),
2174 );
2175}
2176
2177fn layer_f64_array(layer: &FbxNode, name: &str) -> Option<Vec<f64>> {
2180 for child in &layer.children {
2181 if child.name == name {
2182 return match child.properties.first() {
2183 Some(FbxProperty::F64Array(arr)) => Some(arr.clone()),
2184 Some(FbxProperty::F32Array(arr)) => {
2185 Some(arr.iter().map(|v| f64::from(*v)).collect())
2186 }
2187 _ => None,
2188 };
2189 }
2190 }
2191 None
2192}
2193
2194fn parse_tangent_like(
2200 layer: &FbxNode,
2201 values_node: &str,
2202 handedness_node: &str,
2203 index_node: &str,
2204) -> Option<FbxTangentSet> {
2205 let vectors: Vec<[f32; 3]> = chunk_layer_values(&read_layer_floats(layer, values_node)?);
2206 let handedness = read_layer_floats(layer, handedness_node)
2207 .filter(|signs| signs.len() == vectors.len())
2208 .unwrap_or_default();
2209 let has_handedness = !handedness.is_empty();
2210 let values = vectors
2211 .iter()
2212 .enumerate()
2213 .map(|(index, v)| {
2214 let sign = handedness.get(index).copied().unwrap_or(1.0);
2215 [v[0], v[1], v[2], sign]
2216 })
2217 .collect();
2218 Some(FbxTangentSet {
2219 layer: layer_set(layer, values, &[index_node]),
2220 has_handedness,
2221 })
2222}
2223
2224fn layer_set<const N: usize>(
2229 layer: &FbxNode,
2230 values: Vec<[f32; N]>,
2231 index_nodes: &[&str],
2232) -> FbxLayerSet<N> {
2233 FbxLayerSet {
2234 name: layer_string(layer, "Name"),
2235 mapping: layer_string(layer, "MappingInformationType"),
2236 reference: layer_string(layer, "ReferenceInformationType"),
2237 values,
2238 indices: index_nodes
2239 .iter()
2240 .find_map(|name| layer_int_array(layer, name))
2241 .unwrap_or_default(),
2242 }
2243}
2244
2245fn chunk_layer_values<const N: usize>(raw: &[f32]) -> Vec<[f32; N]> {
2248 raw.chunks_exact(N)
2249 .map(|value| std::array::from_fn(|i| value[i]))
2250 .collect()
2251}
2252
2253fn read_layer_floats(layer: &FbxNode, name: &str) -> Option<Vec<f32>> {
2257 for child in &layer.children {
2258 if child.name == name {
2259 if let Some(FbxProperty::F64Array(arr)) = child.properties.first() {
2260 return Some(arr.iter().map(|v| *v as f32).collect());
2261 }
2262 if let Some(FbxProperty::F32Array(arr)) = child.properties.first() {
2263 return Some(arr.clone());
2264 }
2265 }
2266 }
2267 None
2268}
2269
2270fn expand_material_indices(
2272 mapping: Option<&str>,
2273 reference: Option<&str>,
2274 data: Option<&[i32]>,
2275 polygon_count: usize,
2276 tri_polygon_index: &[usize],
2277) -> Option<Vec<i32>> {
2278 let mapping = mapping.unwrap_or("AllSame");
2279 let data = data?;
2280 let _ = reference;
2285 let per_polygon: Vec<i32> = match mapping {
2286 "AllSame" => {
2287 let value = data.first().copied().unwrap_or(0);
2288 vec![value; polygon_count.max(1)]
2289 }
2290 "ByPolygon" | "ByPolygonSide" => data.to_vec(),
2291 "ByPolygonVertex" => {
2292 let value = data.first().copied().unwrap_or(0);
2297 vec![value; polygon_count.max(1)]
2298 }
2299 _ => return None,
2300 };
2301 if per_polygon.is_empty() {
2302 return Some(Vec::new());
2303 }
2304 let mut out = Vec::with_capacity(tri_polygon_index.len());
2305 for &polygon_index in tri_polygon_index {
2306 let value = per_polygon
2307 .get(polygon_index)
2308 .copied()
2309 .unwrap_or(per_polygon[0]);
2310 out.push(value);
2311 }
2312 Some(out)
2313}