1use std::collections::HashSet;
37use std::fs::File;
38use std::io::{self, BufWriter, Cursor, Write};
39use std::path::Path;
40
41use draco_core::geometry_attribute::GeometryAttributeType;
42use draco_core::geometry_indices::FaceIndex;
43use draco_core::mesh::Mesh;
44
45use crate::fbx_ascii_syntax::{name_class, FBX_VERSION};
46use crate::fbx_ascii_writer::print_document;
47use crate::fbx_encoder::{encode_node, write_footer, write_null_record, WriterOptions, FBX_MAGIC};
48use crate::fbx_node::{FbxNode, FbxProperty};
49use crate::fbx_scene::FbxNodeAttribute;
50use crate::traits::{WriteToBytes, Writer};
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
69pub enum FbxFormat {
70 #[default]
73 Binary,
74 Ascii,
76}
77
78#[derive(Debug, Clone)]
99pub struct FbxWriter {
100 format: FbxFormat,
102 compress: bool,
104 compression_threshold: usize,
106 meshes: Vec<MeshData>,
108 models: Vec<ModelData>,
110 materials: Vec<MaterialData>,
112 textures: Vec<TextureData>,
114 anim: Vec<AnimStackData>,
116 skins: Vec<SkinData>,
118 morphs: Vec<MorphData>,
119 global_settings: Option<crate::fbx_scene::FbxGlobalSettings>,
121 joint_scene_ids: HashSet<crate::fbx_scene::FbxNodeId>,
123 connections: Vec<PendingConnection>,
125 next_id: i64,
127}
128
129#[derive(Debug, Clone)]
131struct MeshData {
132 vertices: Vec<f64>,
133 indices: Vec<i32>,
134 name: String,
135 geometry_id: i64,
136 model_id: i64,
137 normals: Option<Vec<f64>>,
140 uvs: Option<Vec<f64>>,
142 material_indices: Vec<i32>,
144 control_points: Option<Vec<f64>>,
145 polygon_vertex_indices: Option<Vec<i32>>,
146 uv_sets: Vec<crate::fbx_scene::FbxUvSet>,
147 normal_sets: Vec<crate::fbx_scene::FbxNormalSet>,
148 color_sets: Vec<crate::fbx_scene::FbxColorSet>,
149 tangent_sets: Vec<crate::fbx_scene::FbxTangentSet>,
150 binormal_sets: Vec<crate::fbx_scene::FbxBinormalSet>,
151 smoothing_layers: Vec<crate::fbx_scene::FbxSmoothingLayer>,
152 crease_layers: Vec<crate::fbx_scene::FbxCreaseLayer>,
153 edges: Vec<i32>,
154}
155
156#[derive(Debug, Clone)]
158struct ModelData {
159 name: String,
160 model_id: i64,
161 scene_node_id: Option<crate::fbx_scene::FbxNodeId>,
163 parent_id: Option<i64>,
164 transform: Option<crate::fbx_scene::FbxTransform>,
165 transform_stack: Option<crate::fbx_scene::FbxTransformStack>,
166 material_ids: Vec<i64>,
168 attribute: Option<FbxNodeAttribute>,
170 class: &'static str,
171}
172
173impl ModelData {
174 fn has_attribute(&self) -> bool {
180 self.attribute.is_some() || self.class == "LimbNode"
181 }
182}
183
184#[derive(Debug, Clone)]
185struct SkinData {
186 skin_id: i64,
187 pose_id: i64,
188 geometry_id: i64,
189 clusters: Vec<SkinClusterData>,
190 bind_pose: Vec<(crate::fbx_scene::FbxNodeId, crate::fbx_scene::FbxTransform)>,
191}
192
193#[derive(Debug, Clone)]
194struct SkinClusterData {
195 cluster_id: i64,
196 source: crate::fbx_scene::FbxSkinCluster,
197}
198
199#[derive(Debug, Clone)]
200struct MorphData {
201 blend_shape_id: i64,
202 geometry_id: i64,
203 model_id: i64,
204 targets: Vec<MorphTargetData>,
205}
206
207#[derive(Debug, Clone)]
208struct MorphTargetData {
209 channel_id: i64,
210 shape_geometry_id: i64,
211 source: crate::fbx_scene::FbxMorphTarget,
212}
213
214#[derive(Debug, Clone)]
216struct MaterialData {
217 material_id: i64,
218 source: crate::fbx_scene::FbxMaterial,
219}
220
221#[derive(Debug, Clone)]
223struct TextureData {
224 texture_id: i64,
225 video_id: i64,
226 source: crate::fbx_scene::FbxTexture,
227}
228
229#[derive(Debug, Clone)]
231struct AnimStackData {
232 stack_id: i64,
233 layer_id: i64,
234 name: Option<String>,
235 duration: f32,
236 channels: Vec<crate::fbx_scene::FbxAnimChannel>,
237}
238
239#[derive(Debug, Clone)]
241struct PendingConnection {
242 kind: &'static str,
243 child: i64,
244 parent: i64,
245 property: Option<String>,
246}
247
248impl Default for FbxWriter {
249 fn default() -> Self {
250 Self::new()
251 }
252}
253
254impl FbxWriter {
255 pub fn new() -> Self {
257 Self {
258 format: FbxFormat::Binary,
259 compress: false,
260 compression_threshold: 128,
261 meshes: Vec::new(),
262 models: Vec::new(),
263 materials: Vec::new(),
264 textures: Vec::new(),
265 anim: Vec::new(),
266 skins: Vec::new(),
267 morphs: Vec::new(),
268 global_settings: None,
269 joint_scene_ids: HashSet::new(),
270 connections: Vec::new(),
271 next_id: 1000, }
273 }
274
275 pub fn with_format(mut self, format: FbxFormat) -> Self {
281 self.format = format;
282 self
283 }
284
285 pub fn with_compression(mut self, compress: bool) -> Self {
290 self.compress = compress;
291 self
292 }
293
294 pub fn with_compression_threshold(mut self, threshold: usize) -> Self {
299 self.compression_threshold = threshold;
300 self
301 }
302
303 fn allocate_id(&mut self) -> i64 {
305 let id = self.next_id;
306 self.next_id += 1;
307 id
308 }
309
310 fn add_model(
311 &mut self,
312 name: String,
313 parent_id: Option<i64>,
314 transform: Option<crate::fbx_scene::FbxTransform>,
315 material_ids: Vec<i64>,
316 ) -> i64 {
317 self.add_model_for_node(name, parent_id, transform, None, material_ids, None)
318 }
319
320 fn add_model_for_node(
323 &mut self,
324 name: String,
325 parent_id: Option<i64>,
326 transform: Option<crate::fbx_scene::FbxTransform>,
327 transform_stack: Option<crate::fbx_scene::FbxTransformStack>,
328 material_ids: Vec<i64>,
329 source: Option<&crate::fbx_scene::FbxSceneNode>,
330 ) -> i64 {
331 let scene_node_id = source.map(|node| node.id);
332 let attribute = source.and_then(|node| node.attribute.clone());
333 let model_id = self.allocate_id();
334 let class = match &attribute {
338 Some(FbxNodeAttribute::Camera(_)) => "Camera",
339 Some(FbxNodeAttribute::Light(_)) => "Light",
340 None if scene_node_id
341 .map(|id| self.joint_scene_ids.contains(&id))
342 .unwrap_or(false) =>
343 {
344 "LimbNode"
345 }
346 _ => "Mesh",
347 };
348 self.models.push(ModelData {
349 name,
350 model_id,
351 scene_node_id,
352 parent_id,
353 transform,
354 transform_stack,
355 material_ids,
356 attribute,
357 class,
358 });
359 model_id
360 }
361
362 #[allow(clippy::too_many_arguments)]
363 fn add_mesh_to_model(
364 &mut self,
365 mesh: &Mesh,
366 name: &str,
367 model_id: i64,
368 material_indices: &[i32],
369 skin: Option<crate::fbx_scene::FbxSkin>,
370 morph_targets: &[crate::fbx_scene::FbxMorphTarget],
371 layers: crate::fbx_render_mesh::FbxGeometryLayers<'_>,
372 edges: &[i32],
373 ) -> io::Result<()> {
374 let crate::fbx_render_mesh::FbxGeometryLayers {
375 control_points,
376 polygon_vertex_indices,
377 uv_sets,
378 normal_sets,
379 color_sets,
380 tangent_sets,
381 binormal_sets,
382 smoothing_layers,
383 crease_layers,
384 } = layers;
385 validate_supported_fbx_attributes(mesh)?;
386 let geometry_id = self.allocate_id();
387 let material_indices = material_indices
390 .iter()
391 .copied()
392 .take(mesh.num_faces())
393 .collect();
394 self.meshes.push(MeshData {
395 vertices: extract_vertices(mesh),
396 indices: extract_polygon_indices(mesh),
397 name: name.to_string(),
398 geometry_id,
399 model_id,
400 normals: extract_normals(mesh),
401 uvs: extract_uvs(mesh),
402 material_indices,
403 control_points: (!control_points.is_empty()).then(|| {
404 control_points
405 .iter()
406 .flat_map(|point| point.iter().map(|value| f64::from(*value)))
407 .collect()
408 }),
409 polygon_vertex_indices: (!polygon_vertex_indices.is_empty())
410 .then(|| polygon_vertex_indices.to_vec()),
411 uv_sets: uv_sets.to_vec(),
412 normal_sets: normal_sets.to_vec(),
413 color_sets: color_sets.to_vec(),
414 tangent_sets: tangent_sets.to_vec(),
415 binormal_sets: binormal_sets.to_vec(),
416 edges: edges.to_vec(),
417 smoothing_layers: smoothing_layers.to_vec(),
418 crease_layers: crease_layers.to_vec(),
419 });
420 if let Some(skin) = skin {
421 let skin_id = self.allocate_id();
422 let clusters = skin
423 .clusters
424 .iter()
425 .cloned()
426 .map(|source| SkinClusterData {
427 cluster_id: self.allocate_id(),
428 source,
429 })
430 .collect();
431 let pose_id = self.allocate_id();
432 self.skins.push(SkinData {
433 skin_id,
434 pose_id,
435 geometry_id,
436 clusters,
437 bind_pose: skin.bind_pose,
438 });
439 }
440 if !morph_targets.is_empty() {
441 let blend_shape_id = self.allocate_id();
442 let targets = morph_targets
443 .iter()
444 .cloned()
445 .map(|source| MorphTargetData {
446 channel_id: self.allocate_id(),
447 shape_geometry_id: self.allocate_id(),
448 source,
449 })
450 .collect();
451 self.morphs.push(MorphData {
452 blend_shape_id,
453 geometry_id,
454 model_id,
455 targets,
456 });
457 }
458 Ok(())
459 }
460
461 pub fn add_scene(&mut self, scene: &crate::FbxScene) -> io::Result<()> {
470 self.global_settings = scene.global_settings.clone();
471 fn collect_joint_ids(
472 node: &crate::fbx_scene::FbxSceneNode,
473 ids: &mut HashSet<crate::fbx_scene::FbxNodeId>,
474 ) {
475 for mesh in &node.mesh_instances {
476 if let Some(skin) = &mesh.skin {
477 ids.extend(skin.clusters.iter().map(|cluster| cluster.joint_node_id));
478 }
479 }
480 for child in &node.children {
481 collect_joint_ids(child, ids);
482 }
483 }
484 for node in &scene.root_nodes {
485 collect_joint_ids(node, &mut self.joint_scene_ids);
486 }
487 let material_ids: Vec<i64> = scene
490 .materials
491 .iter()
492 .map(|material| {
493 let id = self.allocate_id();
494 self.materials.push(MaterialData {
495 material_id: id,
496 source: material.clone(),
497 });
498 id
499 })
500 .collect();
501
502 for texture in &scene.textures {
503 let texture_id = self.allocate_id();
504 let video_id = self.allocate_id();
505 self.textures.push(TextureData {
506 texture_id,
507 video_id,
508 source: texture.clone(),
509 });
510 }
511 let texture_ids: Vec<(i64, i64)> = self
513 .textures
514 .iter()
515 .map(|t| (t.texture_id, t.video_id))
516 .collect();
517
518 for node in &scene.root_nodes {
521 self.add_scene_node(node, None, &material_ids, &texture_ids)?;
522 }
523
524 for (mat_data, &mat_id) in self.materials.iter().zip(material_ids.iter()) {
527 for binding in &mat_data.source.textures {
528 if let Some(&(tex_id, _video_id)) = texture_ids.get(binding.texture_index) {
529 self.connections.push(PendingConnection {
530 kind: "OP",
531 child: tex_id,
532 parent: mat_id,
533 property: Some(binding.slot.property_name().to_string()),
534 });
535 }
536 }
537 }
538
539 for animation in &scene.animations {
542 let stack_id = self.allocate_id();
543 let layer_id = self.allocate_id();
544 self.anim.push(AnimStackData {
545 stack_id,
546 layer_id,
547 name: animation.name.clone(),
548 duration: animation.duration,
549 channels: animation.channels.clone(),
550 });
551 }
552
553 Ok(())
554 }
555
556 fn add_scene_node(
557 &mut self,
558 node: &crate::fbx_scene::FbxSceneNode,
559 parent_id: Option<i64>,
560 material_ids: &[i64],
561 texture_ids: &[(i64, i64)],
562 ) -> io::Result<()> {
563 let referenced_material_indices: std::collections::BTreeSet<usize> = node
570 .mesh_instances
571 .iter()
572 .flat_map(|mesh| mesh.material_indices.iter())
573 .filter_map(|&idx| usize::try_from(idx).ok())
574 .filter(|&idx| idx < material_ids.len())
575 .collect();
576 let referenced_material_indices: Vec<usize> =
577 referenced_material_indices.into_iter().collect();
578 let referenced_material_ids: Vec<i64> = referenced_material_indices
579 .iter()
580 .map(|&idx| material_ids[idx])
581 .collect();
582 let model_id = self.add_model_for_node(
583 node.name.clone().unwrap_or_else(|| "Node".to_string()),
584 parent_id,
585 node.transform,
586 node.transform_stack.clone(),
587 referenced_material_ids.clone(),
588 Some(node),
589 );
590 let mesh_count = node.mesh_instances.len();
593 for mesh_instance in &node.mesh_instances {
594 let name = mesh_instance.name.as_deref().unwrap_or("");
598 let local_material_indices = if referenced_material_indices.is_empty() {
606 mesh_instance.material_indices.clone()
607 } else {
608 mesh_instance
609 .material_indices
610 .iter()
611 .map(|&index| {
612 usize::try_from(index)
613 .ok()
614 .and_then(|global| {
615 referenced_material_indices
616 .iter()
617 .position(|&slot| slot == global)
618 })
619 .map(|local| local as i32)
620 .unwrap_or(0)
621 })
622 .collect::<Vec<_>>()
623 };
624 self.add_mesh_to_model(
625 &mesh_instance.mesh,
626 name,
627 model_id,
628 &local_material_indices,
629 mesh_instance.skin.clone(),
630 &mesh_instance.morph_targets,
631 crate::fbx_render_mesh::FbxGeometryLayers::from_instance(mesh_instance),
632 &mesh_instance.edges,
633 )?;
634 }
635 let _ = mesh_count;
636 let _ = texture_ids;
637 for child in &node.children {
638 self.add_scene_node(child, Some(model_id), material_ids, texture_ids)?;
639 }
640 Ok(())
641 }
642
643 pub fn write<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
646 let file = File::create(path)?;
647 let mut writer = BufWriter::new(file);
648 self.write_to(&mut writer)
649 }
650
651 fn build_document(&self) -> io::Result<Vec<FbxNode>> {
658 let mut document = vec![
659 header_extension_node(),
660 global_settings_node(self.global_settings.as_ref()),
661 documents_node(),
662 definitions_node(
663 &self.meshes,
664 &self.models,
665 &self.materials,
666 &self.textures,
667 &self.anim,
668 &self.skins,
669 &self.morphs,
670 ),
671 ];
672 document.push(objects_node(
673 &self.meshes,
674 &self.models,
675 &self.materials,
676 &self.textures,
677 &self.anim,
678 &self.skins,
679 &self.morphs,
680 )?);
681 document.push(connections_node(
682 &self.models,
683 &self.meshes,
684 &self.textures,
685 &self.anim,
686 &self.connections,
687 &self.skins,
688 &self.morphs,
689 ));
690 Ok(document)
691 }
692
693 pub fn write_to<W: Write>(&self, writer: &mut W) -> io::Result<()> {
698 writer.write_all(&self.write_to_vec()?)
699 }
700
701 pub fn write_to_vec(&self) -> io::Result<Vec<u8>> {
703 if self.format == FbxFormat::Ascii {
704 return print_document(&self.build_document()?);
705 }
706 let options = WriterOptions {
707 compress: self.compress,
708 compression_threshold: self.compression_threshold,
709 };
710 let is_64 = FBX_VERSION >= 7500;
711
712 let mut cursor = Cursor::new(Vec::new());
713 cursor.write_all(FBX_MAGIC)?;
714 cursor.write_all(&[0x1A, 0x00])?; cursor.write_all(&FBX_VERSION.to_le_bytes())?;
716 for node in self.build_document()? {
717 encode_node(&mut cursor, &node, is_64, &options)?;
718 }
719 write_null_record(&mut cursor, is_64)?;
721 write_footer(&mut cursor)?;
722 Ok(cursor.into_inner())
723 }
724
725 pub fn mesh_count(&self) -> usize {
727 self.meshes.len()
728 }
729
730 pub fn is_compression_enabled(&self) -> bool {
732 self.compress
733 }
734}
735
736impl Writer for FbxWriter {
741 fn new() -> Self {
742 Self::default()
743 }
744
745 fn add_mesh(&mut self, mesh: &Mesh, name: Option<&str>) -> io::Result<()> {
746 let name = name.unwrap_or("Mesh").to_string();
747 let model_id = self.add_model(name.clone(), None, None, Vec::new());
748 self.add_mesh_to_model(
749 mesh,
750 &name,
751 model_id,
752 &[],
753 None,
754 &[],
755 crate::fbx_render_mesh::FbxGeometryLayers::default(),
758 &[],
759 )
760 }
761
762 fn write<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
763 self.write(path)
764 }
765
766 fn vertex_count(&self) -> usize {
767 self.meshes.iter().map(|m| m.vertices.len() / 3).sum()
768 }
769
770 fn face_count(&self) -> usize {
771 self.meshes.iter().map(|m| m.indices.len() / 3).sum()
772 }
773}
774
775impl WriteToBytes for FbxWriter {
776 fn write_to_vec(&self) -> io::Result<Vec<u8>> {
777 FbxWriter::write_to_vec(self)
778 }
779}
780
781pub fn write_fbx_mesh<P: AsRef<Path>>(path: P, mesh: &Mesh) -> io::Result<()> {
789 let mut writer = FbxWriter::new();
790 Writer::add_mesh(&mut writer, mesh, None)?;
791 writer.write(path)
792}
793
794#[cfg(feature = "compression")]
798pub fn write_fbx_mesh_compressed<P: AsRef<Path>>(path: P, mesh: &Mesh) -> io::Result<()> {
799 let mut writer = FbxWriter::new().with_compression(true);
800 Writer::add_mesh(&mut writer, mesh, None)?;
801 writer.write(path)
802}
803
804fn header_extension_node() -> FbxNode {
809 FbxNode {
810 name: "FBXHeaderExtension".to_string(),
811 properties: Vec::new(),
812 children: vec![
813 value_node("FBXHeaderVersion", FbxProperty::I32(1003)),
814 value_node("FBXVersion", FbxProperty::I32(FBX_VERSION as i32)),
815 value_node("Creator", FbxProperty::String("draco-io-rs".to_string())),
816 ],
817 }
818}
819
820fn global_settings_node(source: Option<&crate::fbx_scene::FbxGlobalSettings>) -> FbxNode {
821 let source = source.cloned().unwrap_or_default();
822 let mut properties = vec![
823 int_property_node("UpAxis", "int", "Integer", "", source.up_axis.unwrap_or(2)),
824 int_property_node(
825 "UpAxisSign",
826 "int",
827 "Integer",
828 "",
829 source.up_axis_sign.unwrap_or(1),
830 ),
831 int_property_node(
832 "FrontAxis",
833 "int",
834 "Integer",
835 "",
836 source.front_axis.unwrap_or(1),
837 ),
838 int_property_node(
839 "FrontAxisSign",
840 "int",
841 "Integer",
842 "",
843 source.front_axis_sign.unwrap_or(-1),
844 ),
845 int_property_node(
846 "CoordAxis",
847 "int",
848 "Integer",
849 "",
850 source.coord_axis.unwrap_or(0),
851 ),
852 int_property_node(
853 "CoordAxisSign",
854 "int",
855 "Integer",
856 "",
857 source.coord_axis_sign.unwrap_or(1),
858 ),
859 f64_property_node(
868 "UnitScaleFactor",
869 "double",
870 "Number",
871 "",
872 source.unit_scale_factor.unwrap_or(100.0),
873 ),
874 f64_property_node(
875 "OriginalUnitScaleFactor",
876 "double",
877 "Number",
878 "",
879 source.original_unit_scale_factor.unwrap_or(100.0),
880 ),
881 ];
882 if let Some(time_mode) = source.time_mode {
883 properties.push(int_property_node("TimeMode", "enum", "", "", time_mode));
884 }
885
886 FbxNode {
887 name: "GlobalSettings".to_string(),
888 properties: Vec::new(),
889 children: vec![
890 value_node("Version", FbxProperty::I32(1000)),
891 properties70_node(properties),
892 ],
893 }
894}
895
896fn properties70_node(properties: Vec<FbxNode>) -> FbxNode {
898 FbxNode {
899 name: "Properties70".to_string(),
900 properties: Vec::new(),
901 children: properties,
902 }
903}
904
905fn property_node(
911 name: &str,
912 type1: &str,
913 type2: &str,
914 flags: &str,
915 values: Vec<FbxProperty>,
916) -> FbxNode {
917 let mut properties = vec![
918 FbxProperty::String(name.to_string()),
919 FbxProperty::String(type1.to_string()),
920 FbxProperty::String(type2.to_string()),
921 FbxProperty::String(flags.to_string()),
922 ];
923 properties.extend(values);
924 FbxNode {
925 name: "P".to_string(),
926 properties,
927 children: Vec::new(),
928 }
929}
930
931fn int_property_node(name: &str, type1: &str, type2: &str, flags: &str, value: i32) -> FbxNode {
932 property_node(name, type1, type2, flags, vec![FbxProperty::I32(value)])
933}
934
935fn bool_property_node(name: &str, value: bool) -> FbxNode {
936 property_node(
939 name,
940 "bool",
941 "",
942 "",
943 vec![FbxProperty::I32(i32::from(value))],
944 )
945}
946
947fn f64_property_node(name: &str, type1: &str, type2: &str, flags: &str, value: f64) -> FbxNode {
948 property_node(name, type1, type2, flags, vec![FbxProperty::F64(value)])
949}
950
951fn vector_property_node(name: &str, values: [f32; 3]) -> FbxNode {
954 property_node(
955 name,
956 "Vector",
957 "",
958 "A",
959 values
960 .into_iter()
961 .map(|value| FbxProperty::F64(f64::from(value)))
962 .collect(),
963 )
964}
965
966fn enum_property_node(name: &str, value: i32) -> FbxNode {
967 int_property_node(name, "enum", "", "", value)
968}
969
970fn vec3_property_node(name: &str, values: [f64; 3]) -> FbxNode {
976 property_node(
977 name,
978 name,
979 "",
980 "A",
981 values.into_iter().map(FbxProperty::F64).collect(),
982 )
983}
984
985fn decompose_transform(
986 transform: crate::fbx_scene::FbxTransform,
987) -> io::Result<([f64; 3], [f64; 3], [f64; 3])> {
988 let matrix = transform.matrix.map(|row| row.map(f64::from));
989 if !matrix.iter().flatten().all(|value| value.is_finite()) {
990 return Err(io::Error::new(
991 io::ErrorKind::InvalidInput,
992 "FBX transform contains a non-finite value",
993 ));
994 }
995 if matrix[0][3].abs() > 1e-5
996 || matrix[1][3].abs() > 1e-5
997 || matrix[2][3].abs() > 1e-5
998 || (matrix[3][3] - 1.0).abs() > 1e-5
999 {
1000 return Err(io::Error::new(
1001 io::ErrorKind::InvalidInput,
1002 "FBX scene export requires an affine transform matrix",
1003 ));
1004 }
1005
1006 let mut rotation = [
1009 [matrix[0][0], matrix[1][0], matrix[2][0]],
1010 [matrix[0][1], matrix[1][1], matrix[2][1]],
1011 [matrix[0][2], matrix[1][2], matrix[2][2]],
1012 ];
1013 let mut scaling = [
1014 (rotation[0][0].powi(2) + rotation[1][0].powi(2) + rotation[2][0].powi(2)).sqrt(),
1015 (rotation[0][1].powi(2) + rotation[1][1].powi(2) + rotation[2][1].powi(2)).sqrt(),
1016 (rotation[0][2].powi(2) + rotation[1][2].powi(2) + rotation[2][2].powi(2)).sqrt(),
1017 ];
1018 if scaling.iter().any(|value| *value < 1e-8) {
1019 return Err(io::Error::new(
1020 io::ErrorKind::InvalidInput,
1021 "FBX scene export cannot decompose a zero-scale transform",
1022 ));
1023 }
1024 for column in 0..3 {
1025 for row in &mut rotation {
1026 row[column] /= scaling[column];
1027 }
1028 }
1029
1030 let determinant = rotation[0][0]
1031 * (rotation[1][1] * rotation[2][2] - rotation[1][2] * rotation[2][1])
1032 - rotation[0][1] * (rotation[1][0] * rotation[2][2] - rotation[1][2] * rotation[2][0])
1033 + rotation[0][2] * (rotation[1][0] * rotation[2][1] - rotation[1][1] * rotation[2][0]);
1034 if determinant < 0.0 {
1035 scaling[0] = -scaling[0];
1036 for row in &mut rotation {
1037 row[0] = -row[0];
1038 }
1039 }
1040
1041 let dot01 = rotation[0][0] * rotation[0][1]
1042 + rotation[1][0] * rotation[1][1]
1043 + rotation[2][0] * rotation[2][1];
1044 let dot02 = rotation[0][0] * rotation[0][2]
1045 + rotation[1][0] * rotation[1][2]
1046 + rotation[2][0] * rotation[2][2];
1047 let dot12 = rotation[0][1] * rotation[0][2]
1048 + rotation[1][1] * rotation[1][2]
1049 + rotation[2][1] * rotation[2][2];
1050 if dot01.abs() > 1e-4 || dot02.abs() > 1e-4 || dot12.abs() > 1e-4 {
1051 return Err(io::Error::new(
1052 io::ErrorKind::InvalidInput,
1053 "FBX scene export cannot represent transform shear",
1054 ));
1055 }
1056
1057 let y = (-rotation[2][0]).asin();
1058 let (x, z) = if y.cos().abs() > 1e-6 {
1059 (
1060 rotation[2][1].atan2(rotation[2][2]),
1061 rotation[1][0].atan2(rotation[0][0]),
1062 )
1063 } else {
1064 ((-rotation[1][2]).atan2(rotation[1][1]), 0.0)
1065 };
1066 Ok((
1067 [matrix[3][0], matrix[3][1], matrix[3][2]],
1068 [x.to_degrees(), y.to_degrees(), z.to_degrees()],
1069 scaling,
1070 ))
1071}
1072
1073fn documents_node() -> FbxNode {
1074 FbxNode {
1075 name: "Documents".to_string(),
1076 properties: Vec::new(),
1077 children: vec![
1078 value_node("Count", FbxProperty::I32(1)),
1079 FbxNode {
1080 name: "Document".to_string(),
1081 properties: vec![
1082 FbxProperty::I64(0), FbxProperty::String(String::new()),
1084 FbxProperty::String("Scene".to_string()),
1085 ],
1086 children: Vec::new(),
1087 },
1088 ],
1089 }
1090}
1091
1092#[allow(clippy::too_many_arguments)]
1099fn definitions_node(
1100 meshes: &[MeshData],
1101 models: &[ModelData],
1102 materials: &[MaterialData],
1103 textures: &[TextureData],
1104 anim: &[AnimStackData],
1105 skins: &[SkinData],
1106 morphs: &[MorphData],
1107) -> FbxNode {
1108 let node_attributes = models.iter().filter(|model| model.has_attribute()).count();
1113 let shape_count = morphs
1114 .iter()
1115 .map(|morph| morph.targets.len())
1116 .sum::<usize>();
1117 let curve_nodes: usize = anim.iter().map(|stack| stack.channels.len()).sum();
1118 let curve_count: usize = anim
1120 .iter()
1121 .flat_map(|stack| &stack.channels)
1122 .map(|channel| channel.path.component_count())
1123 .sum();
1124
1125 let mut object_types = vec![
1126 object_type_node("Geometry", (meshes.len() + shape_count) as i32),
1127 object_type_node("Model", models.len() as i32),
1128 ];
1129 if node_attributes > 0 {
1130 object_types.push(object_type_node("NodeAttribute", node_attributes as i32));
1131 }
1132 object_types.push(object_type_node("Material", materials.len() as i32));
1133 object_types.push(object_type_node("Texture", textures.len() as i32));
1134 object_types.push(object_type_node("Video", textures.len() as i32));
1135 object_types.push(object_type_node("AnimationStack", anim.len() as i32));
1136 object_types.push(object_type_node("AnimationLayer", anim.len() as i32));
1138 object_types.push(object_type_node("AnimationCurveNode", curve_nodes as i32));
1139 object_types.push(object_type_node("AnimationCurve", curve_count as i32));
1140 if !skins.is_empty() || !morphs.is_empty() {
1141 let deformer_count = skins.len()
1142 + skins.iter().map(|skin| skin.clusters.len()).sum::<usize>()
1143 + morphs.len()
1144 + morphs
1145 .iter()
1146 .map(|morph| morph.targets.len())
1147 .sum::<usize>();
1148 object_types.push(object_type_node("Deformer", deformer_count as i32));
1149 object_types.push(object_type_node("Pose", skins.len() as i32));
1150 }
1151
1152 let mut children = vec![
1153 value_node("Version", FbxProperty::I32(100)),
1154 value_node("Count", FbxProperty::I32(object_types.len() as i32)),
1155 ];
1156 children.extend(object_types);
1157 FbxNode {
1158 name: "Definitions".to_string(),
1159 properties: Vec::new(),
1160 children,
1161 }
1162}
1163
1164fn object_type_node(type_name: &str, count: i32) -> FbxNode {
1165 FbxNode {
1166 name: "ObjectType".to_string(),
1167 properties: vec![FbxProperty::String(type_name.to_string())],
1168 children: vec![value_node("Count", FbxProperty::I32(count))],
1169 }
1170}
1171
1172#[allow(clippy::too_many_arguments)]
1174fn objects_node(
1175 meshes: &[MeshData],
1176 models: &[ModelData],
1177 materials: &[MaterialData],
1178 textures: &[TextureData],
1179 anim: &[AnimStackData],
1180 skins: &[SkinData],
1181 morphs: &[MorphData],
1182) -> io::Result<FbxNode> {
1183 let mut children = Vec::new();
1184 for mesh_data in meshes {
1185 children.push(geometry_node(mesh_data));
1186 }
1187 for model_data in models {
1188 children.push(model_node(model_data)?);
1189 }
1190 children.extend(models.iter().filter_map(node_attribute_node));
1191 for material_data in materials {
1192 children.push(material_node(material_data));
1193 }
1194 for texture_data in textures {
1195 children.push(texture_node(texture_data));
1196 children.push(video_node(texture_data));
1197 }
1198 for stack in anim {
1199 children.extend(animation_stack_nodes(stack));
1200 }
1201 for skin in skins {
1202 children.extend(skin_nodes(skin, models));
1203 }
1204 for morph in morphs {
1205 children.extend(morph_nodes(morph));
1206 }
1207
1208 Ok(FbxNode {
1209 name: "Objects".to_string(),
1210 properties: Vec::new(),
1211 children,
1212 })
1213}
1214
1215fn model_node(model_data: &ModelData) -> io::Result<FbxNode> {
1216 let mut properties = Vec::new();
1217 if let Some(transform) = model_data.transform {
1218 let as_f64 = |value: [f32; 3]| value.map(f64::from);
1219 if let Some(stack) = model_data.transform_stack.as_ref() {
1220 for (name, value) in [
1225 ("Lcl Translation", stack.translation),
1226 ("Lcl Rotation", stack.rotation),
1227 ("Lcl Scaling", stack.scaling),
1228 ] {
1229 if let Some(value) = value {
1230 properties.push(vec3_property_node(name, as_f64(value)));
1231 }
1232 }
1233 if let Some(value) = stack.rotation_order {
1234 properties.push(int_property_node("RotationOrder", "enum", "", "", value));
1235 }
1236 if let Some(value) = stack.rotation_active {
1237 properties.push(bool_property_node("RotationActive", value));
1238 }
1239 for (name, value) in [
1240 ("PreRotation", stack.pre_rotation),
1241 ("PostRotation", stack.post_rotation),
1242 ("RotationOffset", stack.rotation_offset),
1243 ("RotationPivot", stack.rotation_pivot),
1244 ("ScalingOffset", stack.scaling_offset),
1245 ("ScalingPivot", stack.scaling_pivot),
1246 ] {
1247 if let Some(value) = value {
1248 properties.push(vec3_property_node(name, as_f64(value)));
1249 }
1250 }
1251 if let Some(value) = stack.inherit_type {
1252 properties.push(int_property_node("InheritType", "enum", "", "", value));
1253 }
1254 } else {
1255 let (translation, rotation, scaling) = decompose_transform(transform)?;
1262 properties.push(vec3_property_node("Lcl Translation", translation));
1263 properties.push(vec3_property_node("Lcl Rotation", rotation));
1264 properties.push(vec3_property_node("Lcl Scaling", scaling));
1265 }
1266 }
1267
1268 Ok(FbxNode {
1269 name: "Model".to_string(),
1270 properties: vec![
1271 FbxProperty::I64(model_data.model_id),
1272 FbxProperty::String(name_class(&model_data.name, "Model")),
1273 FbxProperty::String(model_data.class.to_string()),
1274 ],
1275 children: vec![
1276 value_node("Version", FbxProperty::I32(232)),
1277 properties70_node(properties),
1278 value_node("Shading", FbxProperty::Bool(true)),
1283 value_node("Culling", FbxProperty::String("CullingOff".to_string())),
1284 ],
1285 })
1286}
1287
1288fn node_attribute_node(model_data: &ModelData) -> Option<FbxNode> {
1301 let id = node_attribute_id(model_data.model_id);
1302 let name = name_class(&model_data.name, "NodeAttribute");
1303
1304 let (class, children) = match &model_data.attribute {
1305 Some(FbxNodeAttribute::Camera(camera)) => {
1306 let mut properties = Vec::new();
1307 for (property_name, value) in [
1308 ("Position", camera.position),
1309 ("UpVector", camera.up_vector),
1310 ("InterestPosition", camera.interest_position),
1311 ] {
1312 if let Some(value) = value {
1313 properties.push(vector_property_node(property_name, value));
1314 }
1315 }
1316 if let Some(value) = camera.projection_type {
1317 properties.push(enum_property_node("CameraProjectionType", value));
1318 }
1319 for (property_name, value) in [
1320 ("FieldOfView", camera.field_of_view),
1321 ("FieldOfViewX", camera.field_of_view_x),
1322 ("FieldOfViewY", camera.field_of_view_y),
1323 ("FocalLength", camera.focal_length),
1324 ("OrthoZoom", camera.ortho_zoom),
1325 ("FilmWidth", camera.film_width),
1329 ("FilmHeight", camera.film_height),
1330 ] {
1331 if let Some(value) = value {
1332 properties.push(scalar_property_node(property_name, f64::from(value)));
1333 }
1334 }
1335 if let Some(value) = camera.aperture_mode {
1336 properties.push(enum_property_node("ApertureMode", value));
1337 }
1338 for (property_name, value) in [
1339 ("NearPlane", camera.near_plane),
1340 ("FarPlane", camera.far_plane),
1341 ("AspectWidth", camera.aspect_width),
1342 ("AspectHeight", camera.aspect_height),
1343 ("FilmAspectRatio", camera.film_aspect_ratio),
1344 ] {
1345 if let Some(value) = value {
1346 properties.push(f64_property_node(
1347 property_name,
1348 "double",
1349 "Number",
1350 "",
1351 f64::from(value),
1352 ));
1353 }
1354 }
1355 (
1356 "Camera",
1357 vec![
1358 properties70_node(properties),
1359 value_node("TypeFlags", FbxProperty::String("Camera".to_string())),
1360 value_node("GeometryVersion", FbxProperty::I32(124)),
1361 ],
1362 )
1363 }
1364 Some(FbxNodeAttribute::Light(light)) => {
1365 let mut properties = Vec::new();
1366 if let Some(value) = light.light_type {
1367 properties.push(enum_property_node("LightType", value));
1368 }
1369 if let Some(value) = light.cast_light {
1370 properties.push(bool_property_node("CastLight", value));
1371 }
1372 if let Some(value) = light.color {
1373 properties.push(color_property_node("Color", value));
1374 }
1375 if let Some(value) = light.intensity {
1376 properties.push(scalar_property_node("Intensity", f64::from(value)));
1377 }
1378 if let Some(value) = light.cast_shadows {
1379 properties.push(bool_property_node("CastShadows", value));
1380 }
1381 if let Some(value) = light.decay_type {
1382 properties.push(enum_property_node("DecayType", value));
1383 }
1384 if let Some(value) = light.decay_start {
1385 properties.push(f64_property_node(
1386 "DecayStart",
1387 "double",
1388 "Number",
1389 "",
1390 f64::from(value),
1391 ));
1392 }
1393 (
1394 "Light",
1395 vec![
1396 value_node("GeometryVersion", FbxProperty::I32(124)),
1397 properties70_node(properties),
1398 value_node("TypeFlags", FbxProperty::String("Light".to_string())),
1399 ],
1400 )
1401 }
1402 None if model_data.class == "LimbNode" => (
1407 "LimbNode",
1408 vec![value_node(
1409 "TypeFlags",
1410 FbxProperty::String("Skeleton".to_string()),
1411 )],
1412 ),
1413 None => return None,
1414 };
1415
1416 Some(FbxNode {
1417 name: "NodeAttribute".to_string(),
1418 properties: vec![
1419 FbxProperty::I64(id),
1420 FbxProperty::String(name),
1421 FbxProperty::String(class.to_string()),
1422 ],
1423 children,
1424 })
1425}
1426
1427fn node_attribute_id(model_id: i64) -> i64 {
1428 1_000_000_000i64.saturating_add(model_id)
1432}
1433
1434fn flatten_fbx_transform(transform: crate::fbx_scene::FbxTransform) -> Vec<f64> {
1438 transform
1439 .matrix
1440 .into_iter()
1441 .flatten()
1442 .map(f64::from)
1443 .collect()
1444}
1445
1446fn skin_nodes(skin: &SkinData, models: &[ModelData]) -> Vec<FbxNode> {
1453 let mut nodes = vec![FbxNode {
1454 name: "Deformer".to_string(),
1455 properties: vec![
1456 FbxProperty::I64(skin.skin_id),
1457 FbxProperty::String(name_class("Skin", "Deformer")),
1458 FbxProperty::String("Skin".to_string()),
1459 ],
1460 children: vec![
1461 value_node("Version", FbxProperty::I32(101)),
1462 value_node("Link_DeformAcuracy", FbxProperty::F64(50.0)),
1463 ],
1464 }];
1465
1466 for cluster in &skin.clusters {
1467 let source = &cluster.source;
1468 let mut children = vec![
1469 value_node("Version", FbxProperty::I32(100)),
1470 FbxNode {
1471 name: "UserData".to_string(),
1472 properties: vec![
1473 FbxProperty::String(String::new()),
1474 FbxProperty::String(String::new()),
1475 ],
1476 children: Vec::new(),
1477 },
1478 value_node(
1479 "Indexes",
1480 FbxProperty::I32Array(
1481 source
1482 .control_point_indices
1483 .iter()
1484 .map(|&index| index as i32)
1485 .collect(),
1486 ),
1487 ),
1488 value_node(
1489 "Weights",
1490 FbxProperty::F64Array(source.weights.iter().copied().map(f64::from).collect()),
1491 ),
1492 value_node(
1493 "Transform",
1494 FbxProperty::F64Array(flatten_fbx_transform(source.mesh_bind_transform)),
1495 ),
1496 value_node(
1497 "TransformLink",
1498 FbxProperty::F64Array(flatten_fbx_transform(source.joint_bind_transform)),
1499 ),
1500 ];
1501 if let Some(armature_bind_transform) = source.armature_bind_transform {
1502 children.push(value_node(
1503 "TransformAssociateModel",
1504 FbxProperty::F64Array(flatten_fbx_transform(armature_bind_transform)),
1505 ));
1506 }
1507 nodes.push(FbxNode {
1508 name: "Deformer".to_string(),
1509 properties: vec![
1510 FbxProperty::I64(cluster.cluster_id),
1511 FbxProperty::String(name_class("Cluster", "SubDeformer")),
1512 FbxProperty::String("Cluster".to_string()),
1513 ],
1514 children,
1515 });
1516 }
1517
1518 let mut pose_children = vec![
1519 value_node("Type", FbxProperty::String("BindPose".to_string())),
1520 value_node("Version", FbxProperty::I32(100)),
1521 ];
1522 for (node_id, transform) in &skin.bind_pose {
1523 let Some(model) = models
1524 .iter()
1525 .find(|model| model.scene_node_id == Some(*node_id))
1526 else {
1527 continue;
1528 };
1529 pose_children.push(FbxNode {
1530 name: "PoseNode".to_string(),
1531 properties: Vec::new(),
1532 children: vec![
1533 value_node("Node", FbxProperty::I64(model.model_id)),
1534 value_node(
1535 "Matrix",
1536 FbxProperty::F64Array(flatten_fbx_transform(*transform)),
1537 ),
1538 ],
1539 });
1540 }
1541 nodes.push(FbxNode {
1542 name: "Pose".to_string(),
1543 properties: vec![
1544 FbxProperty::I64(skin.pose_id),
1545 FbxProperty::String(name_class("BindPose", "Pose")),
1546 FbxProperty::String("BindPose".to_string()),
1547 ],
1548 children: pose_children,
1549 });
1550
1551 nodes
1552}
1553
1554fn morph_nodes(morph: &MorphData) -> Vec<FbxNode> {
1557 let mut nodes = vec![FbxNode {
1558 name: "Deformer".to_string(),
1559 properties: vec![
1560 FbxProperty::I64(morph.blend_shape_id),
1561 FbxProperty::String(name_class("BlendShape", "Deformer")),
1562 FbxProperty::String("BlendShape".to_string()),
1563 ],
1564 children: Vec::new(),
1565 }];
1566
1567 for target in &morph.targets {
1568 let source = &target.source;
1569 let name = source.name.as_deref().unwrap_or("MorphTarget");
1570 nodes.push(FbxNode {
1571 name: "Deformer".to_string(),
1572 properties: vec![
1573 FbxProperty::I64(target.channel_id),
1574 FbxProperty::String(name_class(name, "SubDeformer")),
1575 FbxProperty::String("BlendShapeChannel".to_string()),
1576 ],
1577 children: vec![
1578 value_node(
1579 "DeformPercent",
1580 FbxProperty::F64(source.default_weight as f64),
1581 ),
1582 value_node(
1583 "FullWeights",
1584 FbxProperty::F64Array(vec![source.full_weight as f64]),
1585 ),
1586 ],
1587 });
1588 nodes.push(FbxNode {
1589 name: "Geometry".to_string(),
1590 properties: vec![
1591 FbxProperty::I64(target.shape_geometry_id),
1592 FbxProperty::String(name_class(name, "Geometry")),
1593 FbxProperty::String("Shape".to_string()),
1594 ],
1595 children: vec![
1596 value_node(
1597 "Indexes",
1598 FbxProperty::I32Array(
1599 source
1600 .control_point_indices
1601 .iter()
1602 .map(|&index| index as i32)
1603 .collect(),
1604 ),
1605 ),
1606 value_node(
1607 "Vertices",
1608 FbxProperty::F64Array(
1609 source
1610 .position_deltas
1611 .iter()
1612 .flat_map(|delta| delta.iter().copied())
1613 .map(f64::from)
1614 .collect(),
1615 ),
1616 ),
1617 ],
1618 });
1619 }
1620
1621 nodes
1622}
1623
1624fn geometry_node(mesh_data: &MeshData) -> FbxNode {
1625 let mut children = vec![value_node("GeometryVersion", FbxProperty::I32(124))];
1626
1627 let vertices = mesh_data
1634 .control_points
1635 .as_deref()
1636 .unwrap_or(&mesh_data.vertices);
1637 children.push(value_node(
1638 "Vertices",
1639 FbxProperty::F64Array(vertices.to_vec()),
1640 ));
1641
1642 let polygon_indices = mesh_data
1643 .polygon_vertex_indices
1644 .as_deref()
1645 .unwrap_or(&mesh_data.indices);
1646 children.push(value_node(
1647 "PolygonVertexIndex",
1648 FbxProperty::I32Array(polygon_indices.to_vec()),
1649 ));
1650
1651 if mesh_data.normal_sets.is_empty() {
1653 if let Some(normals) = &mesh_data.normals {
1654 children.push(layer_element_normal_node(normals));
1655 }
1656 } else {
1657 for normal_set in &mesh_data.normal_sets {
1658 children.push(layer_element_normal_set_node(normal_set));
1659 }
1660 }
1661
1662 if !mesh_data.edges.is_empty() {
1665 children.push(value_node(
1666 "Edges",
1667 FbxProperty::I32Array(mesh_data.edges.clone()),
1668 ));
1669 }
1670
1671 for color_set in &mesh_data.color_sets {
1673 children.push(layer_element_color_set_node(color_set));
1674 }
1675
1676 for set in &mesh_data.tangent_sets {
1679 children.push(layer_element_tangent_set_node("LayerElementTangent", set));
1680 }
1681 for set in &mesh_data.binormal_sets {
1682 children.push(layer_element_tangent_set_node("LayerElementBinormal", set));
1683 }
1684
1685 for layer in &mesh_data.smoothing_layers {
1688 children.push(layer_element_smoothing_node(layer));
1689 }
1690 for layer in &mesh_data.crease_layers {
1691 children.push(layer_element_crease_node(layer));
1692 }
1693
1694 if mesh_data.uv_sets.is_empty() {
1696 if let Some(uvs) = &mesh_data.uvs {
1697 children.push(layer_element_uv_node(uvs));
1698 }
1699 } else {
1700 for uv_set in &mesh_data.uv_sets {
1701 children.push(layer_element_uv_set_node(uv_set));
1702 }
1703 }
1704
1705 if !mesh_data.material_indices.is_empty() {
1706 let per_polygon = collapse_material_indices_to_polygons(
1710 &mesh_data.material_indices,
1711 mesh_data.polygon_vertex_indices.as_deref(),
1712 );
1713 children.push(layer_element_material_node(&per_polygon));
1714 }
1715
1716 if let Some(layer) = layer_node(mesh_data) {
1717 children.push(layer);
1718 }
1719
1720 FbxNode {
1721 name: "Geometry".to_string(),
1722 properties: vec![
1723 FbxProperty::I64(mesh_data.geometry_id),
1724 FbxProperty::String(name_class(&mesh_data.name, "Geometry")),
1725 FbxProperty::String("Mesh".to_string()),
1726 ],
1727 children,
1728 }
1729}
1730
1731fn layer_node(mesh_data: &MeshData) -> Option<FbxNode> {
1738 let uses_layers = mesh_data.normals.is_some()
1739 || !mesh_data.normal_sets.is_empty()
1740 || mesh_data.uvs.is_some()
1741 || !mesh_data.uv_sets.is_empty()
1742 || !mesh_data.color_sets.is_empty()
1743 || !mesh_data.tangent_sets.is_empty()
1744 || !mesh_data.binormal_sets.is_empty()
1745 || !mesh_data.smoothing_layers.is_empty()
1746 || !mesh_data.crease_layers.is_empty()
1747 || !mesh_data.material_indices.is_empty();
1748 if !uses_layers {
1749 return None;
1750 }
1751
1752 let mut children = vec![value_node("Version", FbxProperty::I32(100))];
1753 if mesh_data.normal_sets.is_empty() && mesh_data.normals.is_some() {
1754 children.push(layer_element_node("LayerElementNormal", 0));
1755 } else {
1756 for index in 0..mesh_data.normal_sets.len() {
1757 children.push(layer_element_node("LayerElementNormal", index as i32));
1758 }
1759 }
1760 if mesh_data.uv_sets.is_empty() && mesh_data.uvs.is_some() {
1761 children.push(layer_element_node("LayerElementUV", 0));
1762 } else {
1763 for index in 0..mesh_data.uv_sets.len() {
1764 children.push(layer_element_node("LayerElementUV", index as i32));
1765 }
1766 }
1767 for index in 0..mesh_data.color_sets.len() {
1768 children.push(layer_element_node("LayerElementColor", index as i32));
1769 }
1770 for index in 0..mesh_data.tangent_sets.len() {
1771 children.push(layer_element_node("LayerElementTangent", index as i32));
1772 }
1773 for index in 0..mesh_data.binormal_sets.len() {
1774 children.push(layer_element_node("LayerElementBinormal", index as i32));
1775 }
1776 for index in 0..mesh_data.smoothing_layers.len() {
1777 children.push(layer_element_node("LayerElementSmoothing", index as i32));
1778 }
1779 for (index, layer) in mesh_data.crease_layers.iter().enumerate() {
1780 let element = match layer.kind {
1781 crate::fbx_scene::FbxCreaseKind::Edge => "LayerElementEdgeCrease",
1782 crate::fbx_scene::FbxCreaseKind::Vertex => "LayerElementVertexCrease",
1783 };
1784 children.push(layer_element_node(element, index as i32));
1785 }
1786 if !mesh_data.material_indices.is_empty() {
1787 children.push(layer_element_node("LayerElementMaterial", 0));
1788 }
1789
1790 Some(FbxNode {
1791 name: "Layer".to_string(),
1792 properties: Vec::new(),
1793 children,
1794 })
1795}
1796
1797fn value_node(name: &str, value: FbxProperty) -> FbxNode {
1799 FbxNode {
1800 name: name.to_string(),
1801 properties: vec![value],
1802 children: Vec::new(),
1803 }
1804}
1805
1806fn layer_element_node(type_name: &str, index: i32) -> FbxNode {
1809 FbxNode {
1810 name: "LayerElement".to_string(),
1811 properties: Vec::new(),
1812 children: vec![
1813 value_node("Type", FbxProperty::String(type_name.to_string())),
1814 value_node("TypedIndex", FbxProperty::I32(index)),
1815 ],
1816 }
1817}
1818
1819fn layer_header(layer_name: &str, mapping: &str, reference: &str) -> Vec<FbxNode> {
1824 vec![
1825 value_node("Version", FbxProperty::I32(101)),
1826 value_node("Name", FbxProperty::String(layer_name.to_string())),
1827 value_node(
1828 "MappingInformationType",
1829 FbxProperty::String(mapping.to_string()),
1830 ),
1831 value_node(
1832 "ReferenceInformationType",
1833 FbxProperty::String(reference.to_string()),
1834 ),
1835 ]
1836}
1837
1838fn flatten_f64<const N: usize>(values: &[[f32; N]], components: usize) -> Vec<f64> {
1840 values
1841 .iter()
1842 .flat_map(|value| value[..components].iter().map(|c| f64::from(*c)))
1843 .collect()
1844}
1845
1846fn index_array_node(name: &str, reference: Option<&str>, indices: &[i32]) -> Option<FbxNode> {
1849 (reference == Some("IndexToDirect") && !indices.is_empty())
1850 .then(|| value_node(name, FbxProperty::I32Array(indices.to_vec())))
1851}
1852
1853fn layer_element_normal_node(normals: &[f64]) -> FbxNode {
1854 let mut children = layer_header("", "ByVertice", "Direct");
1855 children.push(value_node(
1856 "Normals",
1857 FbxProperty::F64Array(normals.to_vec()),
1858 ));
1859 FbxNode {
1860 name: "LayerElementNormal".to_string(),
1861 properties: Vec::new(),
1862 children,
1863 }
1864}
1865
1866fn layer_element_normal_set_node(set: &crate::fbx_scene::FbxNormalSet) -> FbxNode {
1867 let mut children = layer_header(
1868 set.name.as_deref().unwrap_or("NormalSet0"),
1869 set.mapping.as_deref().unwrap_or("ByPolygonVertex"),
1870 set.reference.as_deref().unwrap_or("Direct"),
1871 );
1872 children.push(value_node(
1873 "Normals",
1874 FbxProperty::F64Array(flatten_f64(&set.values, 3)),
1875 ));
1876 children.extend(index_array_node(
1877 "NormalIndex",
1878 set.reference.as_deref(),
1879 &set.indices,
1880 ));
1881 FbxNode {
1882 name: "LayerElementNormal".to_string(),
1883 properties: Vec::new(),
1884 children,
1885 }
1886}
1887
1888fn layer_element_uv_node(uvs: &[f64]) -> FbxNode {
1889 let mut children = layer_header("", "ByVertice", "Direct");
1890 children.push(value_node("UV", FbxProperty::F64Array(uvs.to_vec())));
1891 FbxNode {
1892 name: "LayerElementUV".to_string(),
1893 properties: Vec::new(),
1894 children,
1895 }
1896}
1897
1898fn layer_element_uv_set_node(set: &crate::fbx_scene::FbxUvSet) -> FbxNode {
1899 let mut children = layer_header(
1900 set.name.as_deref().unwrap_or("UVSet0"),
1901 set.mapping.as_deref().unwrap_or("ByPolygonVertex"),
1902 set.reference.as_deref().unwrap_or("IndexToDirect"),
1903 );
1904 children.push(value_node(
1905 "UV",
1906 FbxProperty::F64Array(flatten_f64(&set.values, 2)),
1907 ));
1908 children.extend(index_array_node(
1909 "UVIndex",
1910 set.reference.as_deref(),
1911 &set.indices,
1912 ));
1913 FbxNode {
1914 name: "LayerElementUV".to_string(),
1915 properties: Vec::new(),
1916 children,
1917 }
1918}
1919
1920fn layer_element_color_set_node(set: &crate::fbx_scene::FbxColorSet) -> FbxNode {
1921 let mut children = layer_header(
1922 set.name.as_deref().unwrap_or("Col"),
1923 set.mapping.as_deref().unwrap_or("ByPolygonVertex"),
1924 set.reference.as_deref().unwrap_or("Direct"),
1925 );
1926 children.push(value_node(
1927 "Colors",
1928 FbxProperty::F64Array(flatten_f64(&set.values, 4)),
1929 ));
1930 children.extend(index_array_node(
1931 "ColorIndex",
1932 set.reference.as_deref(),
1933 &set.indices,
1934 ));
1935 FbxNode {
1936 name: "LayerElementColor".to_string(),
1937 properties: Vec::new(),
1938 children,
1939 }
1940}
1941
1942fn layer_element_tangent_set_node(element: &str, set: &crate::fbx_scene::FbxTangentSet) -> FbxNode {
1948 let (values_node, handedness_node, index_node) = if element == "LayerElementBinormal" {
1949 ("Binormals", "BinormalsW", "BinormalIndex")
1950 } else {
1951 ("Tangents", "TangentsW", "TangentIndex")
1952 };
1953 let mut children = layer_header(
1954 set.layer.name.as_deref().unwrap_or(""),
1955 set.layer.mapping.as_deref().unwrap_or("ByPolygonVertex"),
1956 set.layer.reference.as_deref().unwrap_or("Direct"),
1957 );
1958 children.push(value_node(
1959 values_node,
1960 FbxProperty::F64Array(flatten_f64(&set.layer.values, 3)),
1961 ));
1962 if set.has_handedness {
1963 let signs: Vec<f64> = set
1964 .layer
1965 .values
1966 .iter()
1967 .map(|value| f64::from(value[3]))
1968 .collect();
1969 children.push(value_node(handedness_node, FbxProperty::F64Array(signs)));
1970 }
1971 children.extend(index_array_node(
1972 index_node,
1973 set.layer.reference.as_deref(),
1974 &set.layer.indices,
1975 ));
1976 FbxNode {
1977 name: element.to_string(),
1978 properties: Vec::new(),
1979 children,
1980 }
1981}
1982
1983fn layer_element_smoothing_node(layer: &crate::fbx_scene::FbxSmoothingLayer) -> FbxNode {
1988 FbxNode {
1989 name: "LayerElementSmoothing".to_string(),
1990 properties: Vec::new(),
1991 children: vec![
1992 value_node("Version", FbxProperty::I32(102)),
1993 value_node("Name", FbxProperty::String(String::new())),
1994 value_node(
1995 "MappingInformationType",
1996 FbxProperty::String(
1997 layer
1998 .mapping
1999 .clone()
2000 .unwrap_or_else(|| "ByEdge".to_string()),
2001 ),
2002 ),
2003 value_node(
2004 "ReferenceInformationType",
2005 FbxProperty::String("Direct".to_string()),
2006 ),
2007 value_node("Smoothing", FbxProperty::I32Array(layer.values.clone())),
2008 ],
2009 }
2010}
2011
2012fn layer_element_crease_node(layer: &crate::fbx_scene::FbxCreaseLayer) -> FbxNode {
2015 let (element, data_node, default_mapping) = match layer.kind {
2016 crate::fbx_scene::FbxCreaseKind::Edge => ("LayerElementEdgeCrease", "EdgeCrease", "ByEdge"),
2017 crate::fbx_scene::FbxCreaseKind::Vertex => {
2018 ("LayerElementVertexCrease", "VertexCrease", "ByVertice")
2019 }
2020 };
2021 let mut children = layer_header(
2022 "",
2023 layer.mapping.as_deref().unwrap_or(default_mapping),
2024 "Direct",
2025 );
2026 children.push(value_node(
2027 data_node,
2028 FbxProperty::F64Array(layer.values.clone()),
2029 ));
2030 FbxNode {
2031 name: element.to_string(),
2032 properties: Vec::new(),
2033 children,
2034 }
2035}
2036
2037fn collapse_material_indices_to_polygons(
2048 material_indices: &[i32],
2049 polygon_vertex_indices: Option<&[i32]>,
2050) -> Vec<i32> {
2051 let Some(polygons) = polygon_vertex_indices else {
2052 return material_indices.to_vec();
2053 };
2054
2055 let mut per_polygon = Vec::new();
2056 let mut triangle = 0usize;
2057 let mut corners = 0usize;
2058 for &encoded in polygons {
2059 corners += 1;
2060 if encoded >= 0 {
2061 continue;
2062 }
2063 let triangles = corners.saturating_sub(2);
2066 per_polygon.push(material_indices.get(triangle).copied().unwrap_or(0));
2067 triangle += triangles;
2068 corners = 0;
2069 }
2070 per_polygon
2071}
2072
2073fn layer_element_material_node(material_indices: &[i32]) -> FbxNode {
2075 let mut children = layer_header("", "ByPolygon", "IndexToDirect");
2076 children.push(value_node(
2077 "Materials",
2078 FbxProperty::I32Array(material_indices.to_vec()),
2079 ));
2080 FbxNode {
2081 name: "LayerElementMaterial".to_string(),
2082 properties: Vec::new(),
2083 children,
2084 }
2085}
2086
2087fn material_node(material_data: &MaterialData) -> FbxNode {
2088 let declared_shading = material_data.source.shading_model.as_deref();
2092 let shading = declared_shading.unwrap_or("Phong");
2093
2094 let mut properties = Vec::new();
2095 if let Some(shading) = declared_shading {
2096 properties.push(string_property_node("ShadingModel", shading));
2097 }
2098 let source = &material_data.source;
2101 for property in [
2102 source
2103 .diffuse
2104 .map(|v| color_property_node("DiffuseColor", v)),
2105 source
2106 .diffuse_factor
2107 .map(|v| scalar_property_node("DiffuseFactor", v as f64)),
2108 source
2109 .specular
2110 .map(|v| color_property_node("SpecularColor", v)),
2111 source
2112 .specular_factor
2113 .map(|v| scalar_property_node("SpecularFactor", v as f64)),
2114 source
2115 .shininess
2116 .map(|v| scalar_property_node("Shininess", v as f64)),
2117 source
2118 .emissive
2119 .map(|v| color_property_node("EmissiveColor", v)),
2120 source
2121 .emissive_factor
2122 .map(|v| scalar_property_node("EmissiveFactor", v as f64)),
2123 source
2124 .ambient
2125 .map(|v| color_property_node("AmbientColor", v)),
2126 source
2127 .reflection_factor
2128 .map(|v| scalar_property_node("ReflectionFactor", v as f64)),
2129 source
2130 .transparency_factor
2131 .map(|v| scalar_property_node("TransparencyFactor", v as f64)),
2132 source
2133 .opacity
2134 .map(|v| scalar_property_node("Opacity", v as f64)),
2135 source
2136 .bump_factor
2137 .map(|v| scalar_property_node("BumpFactor", v as f64)),
2138 ] {
2139 properties.extend(property);
2140 }
2141
2142 let name = source
2143 .name
2144 .clone()
2145 .unwrap_or_else(|| "Material".to_string());
2146 FbxNode {
2147 name: "Material".to_string(),
2148 properties: vec![
2149 FbxProperty::I64(material_data.material_id),
2150 FbxProperty::String(name_class(&name, "Material")),
2151 FbxProperty::String(String::new()),
2152 ],
2153 children: vec![
2154 value_node("Version", FbxProperty::I32(102)),
2155 properties70_node(properties),
2156 value_node("ShadingModel", FbxProperty::String(shading.to_string())),
2158 ],
2159 }
2160}
2161
2162fn texture_node(texture_data: &TextureData) -> FbxNode {
2163 let name = texture_data.source.name.clone().unwrap_or_default();
2168 let filename = texture_data.source.filename.clone().unwrap_or_default();
2169 FbxNode {
2170 name: "Texture".to_string(),
2171 properties: vec![
2172 FbxProperty::I64(texture_data.texture_id),
2173 FbxProperty::String(name_class(&name, "Texture")),
2174 FbxProperty::String(String::new()),
2175 ],
2176 children: vec![
2177 value_node("Media", FbxProperty::String(name)),
2178 value_node("FileName", FbxProperty::String(filename.clone())),
2179 value_node("RelativeFilename", FbxProperty::String(filename)),
2180 ],
2181 }
2182}
2183
2184fn video_node(texture_data: &TextureData) -> FbxNode {
2185 let name = texture_data.source.name.clone().unwrap_or_default();
2187 let filename = texture_data.source.filename.clone().unwrap_or_default();
2188 let mut children = vec![
2189 value_node("Filename", FbxProperty::String(filename.clone())),
2190 value_node("RelativeFilename", FbxProperty::String(filename)),
2191 ];
2192 if let Some(content) = &texture_data.source.content {
2193 children.push(value_node("Content", FbxProperty::Raw(content.clone())));
2194 }
2195 FbxNode {
2196 name: "Video".to_string(),
2197 properties: vec![
2198 FbxProperty::I64(texture_data.video_id),
2199 FbxProperty::String(name_class(&name, "Video")),
2200 FbxProperty::String("Clip".to_string()),
2201 ],
2202 children,
2203 }
2204}
2205
2206fn animation_stack_nodes(stack: &AnimStackData) -> Vec<FbxNode> {
2209 const KTIME: i64 = 46_186_158_000;
2211 let stop = (stack.duration.max(0.0) as f64 * KTIME as f64) as i64;
2212 let name = stack
2213 .name
2214 .clone()
2215 .unwrap_or_else(|| "AnimStack".to_string());
2216
2217 let mut nodes = vec![
2218 FbxNode {
2219 name: "AnimationStack".to_string(),
2220 properties: vec![
2221 FbxProperty::I64(stack.stack_id),
2222 FbxProperty::String(name_class(&name, "AnimStack")),
2223 FbxProperty::String(String::new()),
2224 ],
2225 children: vec![properties70_node(vec![timestamp_property_node(
2226 "LocalStop",
2227 stop,
2228 )])],
2229 },
2230 FbxNode {
2231 name: "AnimationLayer".to_string(),
2232 properties: vec![
2233 FbxProperty::I64(stack.layer_id),
2234 FbxProperty::String(name_class(&name, "AnimLayer")),
2235 FbxProperty::String(String::new()),
2236 ],
2237 children: Vec::new(),
2238 },
2239 ];
2240 for channel in &stack.channels {
2241 nodes.extend(animation_curve_nodes(stack.stack_id, channel));
2242 }
2243 nodes
2244}
2245
2246fn animation_curve_nodes(
2248 stack_id: i64,
2249 channel: &crate::fbx_scene::FbxAnimChannel,
2250) -> Vec<FbxNode> {
2251 let id = anim_object_id(
2254 stack_id,
2255 channel.node_id,
2256 channel.path,
2257 channel.morph_target_index,
2258 "node",
2259 );
2260
2261 let defaults: Vec<FbxNode> = ["d|X", "d|Y", "d|Z"]
2264 .into_iter()
2265 .take(channel.path.component_count())
2266 .map(|suffix| scalar_property_node(suffix, 0.0))
2267 .collect();
2268
2269 let mut nodes = vec![FbxNode {
2270 name: "AnimationCurveNode".to_string(),
2271 properties: vec![
2272 FbxProperty::I64(id),
2273 FbxProperty::String(name_class("", "AnimCurveNode")),
2274 FbxProperty::String(String::new()),
2275 ],
2276 children: vec![properties70_node(defaults)],
2277 }];
2278
2279 for component in 0u32..channel.path.component_count() as u32 {
2281 nodes.push(animation_curve_node(id, component, channel));
2282 }
2283 nodes
2284}
2285
2286fn animation_curve_node(
2287 curve_node_id: i64,
2288 component: u32,
2289 channel: &crate::fbx_scene::FbxAnimChannel,
2290) -> FbxNode {
2291 const KTIME: f64 = 46_186_158_000.0;
2292 let components = channel.path.component_count();
2293 let keys = channel.sampler.input.len();
2294
2295 let mut key_times = Vec::with_capacity(keys);
2296 let mut key_values = Vec::with_capacity(keys);
2297 for key in 0..keys {
2298 key_times.push((channel.sampler.input[key] as f64 * KTIME) as i64);
2299 let value_index = key * components + component as usize;
2300 key_values.push(
2301 channel
2302 .sampler
2303 .output
2304 .get(value_index)
2305 .copied()
2306 .unwrap_or(0.0),
2307 );
2308 }
2309
2310 let scale = if channel.path == crate::fbx_scene::FbxAnimChannelPath::Rotation {
2318 180.0 / std::f64::consts::PI
2319 } else {
2320 1.0
2321 };
2322 let degrees = |value: f32| (f64::from(value) * scale) as f32;
2323
2324 let flags_value = channel.sampler.interpolation.to_key_attr_flags();
2328 let cubic = channel.sampler.interpolation == crate::fbx_scene::FbxAnimInterpolation::Cubic;
2329 let flags = if cubic {
2330 vec![flags_value; keys]
2331 } else {
2332 vec![flags_value]
2333 };
2334
2335 let in_tangents = channel.sampler.in_tangents.as_deref();
2336 let out_tangents = channel.sampler.out_tangents.as_deref();
2337 let mut datafloat = Vec::with_capacity(if cubic { keys * 4 } else { 4 });
2338 for key in 0..if cubic { keys } else { 1 } {
2339 let component_index = key * components + component as usize;
2340 let right = degrees(
2341 out_tangents
2342 .and_then(|values| values.get(component_index))
2343 .copied()
2344 .unwrap_or(0.0),
2345 );
2346 let next_left = degrees(
2347 in_tangents
2348 .and_then(|values| values.get(component_index + components))
2349 .copied()
2350 .unwrap_or(0.0),
2351 );
2352 datafloat.extend([right, next_left, 0.0, 0.0]);
2353 }
2354
2355 let refcount = if cubic {
2356 vec![1; keys]
2357 } else {
2358 vec![keys as i32]
2359 };
2360
2361 FbxNode {
2362 name: "AnimationCurve".to_string(),
2363 properties: vec![
2364 FbxProperty::I64(anim_curve_id(curve_node_id, component)),
2365 FbxProperty::String(name_class("", "AnimCurve")),
2366 FbxProperty::String(String::new()),
2367 ],
2368 children: vec![
2369 value_node("Default", FbxProperty::F64(0.0)),
2370 value_node("KeyVer", FbxProperty::I32(4009)),
2371 value_node("KeyTime", FbxProperty::I64Array(key_times)),
2372 value_node(
2373 "KeyValueFloat",
2374 FbxProperty::F32Array(key_values.iter().copied().map(degrees).collect()),
2375 ),
2376 value_node("KeyAttrFlags", FbxProperty::I32Array(flags)),
2377 value_node("KeyAttrDataFloat", FbxProperty::F32Array(datafloat)),
2378 value_node("KeyAttrRefCount", FbxProperty::I32Array(refcount)),
2379 ],
2380 }
2381}
2382
2383fn anim_object_id(
2385 stack_id: i64,
2386 node_id: crate::fbx_scene::FbxNodeId,
2387 path: crate::fbx_scene::FbxAnimChannelPath,
2388 morph_target_index: Option<u32>,
2389 kind: &str,
2390) -> i64 {
2391 let mut hash: u64 = 2_000_000;
2394 hash = hash.wrapping_mul(131).wrapping_add(stack_id as u64);
2395 hash = hash.wrapping_mul(131).wrapping_add(node_id.0 as u64);
2396 hash = hash
2397 .wrapping_mul(131)
2398 .wrapping_add(path.property_name().len() as u64);
2399 hash = hash
2400 .wrapping_mul(131)
2401 .wrapping_add(u64::from(morph_target_index.unwrap_or(0)));
2402 hash = hash.wrapping_mul(131).wrapping_add(kind.len() as u64);
2403 hash as i64
2404}
2405
2406fn anim_curve_id(curve_node_id: i64, component: u32) -> i64 {
2407 curve_node_id
2408 .wrapping_mul(131)
2409 .wrapping_add((component as i64) + 1_000_000)
2410}
2411
2412#[allow(clippy::too_many_arguments)]
2414fn connections_node(
2415 models: &[ModelData],
2416 meshes: &[MeshData],
2417 textures: &[TextureData],
2418 anim: &[AnimStackData],
2419 pending: &[PendingConnection],
2420 skins: &[SkinData],
2421 morphs: &[MorphData],
2422) -> FbxNode {
2423 let mut edges = Vec::new();
2424 {
2425 for model_data in models {
2427 edges.push(connection_node(
2428 "OO",
2429 model_data.model_id,
2430 model_data.parent_id.unwrap_or(0),
2431 None,
2432 ));
2433 if model_data.has_attribute() {
2434 edges.push(connection_node(
2435 "OO",
2436 node_attribute_id(model_data.model_id),
2437 model_data.model_id,
2438 None,
2439 ));
2440 }
2441 }
2442 for mesh_data in meshes {
2444 edges.push(connection_node(
2445 "OO",
2446 mesh_data.geometry_id,
2447 mesh_data.model_id,
2448 None,
2449 ));
2450 }
2451 for model_data in models {
2454 for &material_id in &model_data.material_ids {
2455 edges.push(connection_node(
2456 "OO",
2457 material_id,
2458 model_data.model_id,
2459 None,
2460 ));
2461 }
2462 }
2463 for morph in morphs {
2464 edges.push(connection_node(
2465 "OO",
2466 morph.blend_shape_id,
2467 morph.geometry_id,
2468 None,
2469 ));
2470 for target in &morph.targets {
2471 edges.push(connection_node(
2472 "OO",
2473 target.channel_id,
2474 morph.blend_shape_id,
2475 None,
2476 ));
2477 edges.push(connection_node(
2478 "OO",
2479 target.shape_geometry_id,
2480 target.channel_id,
2481 None,
2482 ));
2483 }
2484 }
2485 for texture_data in textures {
2487 edges.push(connection_node(
2488 "OO",
2489 texture_data.video_id,
2490 texture_data.texture_id,
2491 None,
2492 ));
2493 }
2494 for conn in pending {
2495 edges.push(connection_node(
2496 conn.kind,
2497 conn.child,
2498 conn.parent,
2499 conn.property.as_deref(),
2500 ));
2501 }
2502 for skin in skins {
2506 edges.push(connection_node("OO", skin.skin_id, skin.geometry_id, None));
2507 for cluster in &skin.clusters {
2508 edges.push(connection_node(
2509 "OO",
2510 cluster.cluster_id,
2511 skin.skin_id,
2512 None,
2513 ));
2514 if let Some(joint) = models
2515 .iter()
2516 .find(|model| model.scene_node_id == Some(cluster.source.joint_node_id))
2517 {
2518 edges.push(connection_node(
2519 "OO",
2520 joint.model_id,
2521 cluster.cluster_id,
2522 None,
2523 ));
2524 }
2525 }
2526 }
2527 for stack in anim {
2531 edges.push(connection_node("OO", stack.stack_id, 0, None));
2532 edges.push(connection_node("OO", stack.layer_id, stack.stack_id, None));
2533 for channel in &stack.channels {
2534 let acnode_id = anim_object_id(
2535 stack.stack_id,
2536 channel.node_id,
2537 channel.path,
2538 channel.morph_target_index,
2539 "node",
2540 );
2541 edges.push(connection_node("OO", acnode_id, stack.layer_id, None));
2542 let scene_model_id = models
2544 .iter()
2545 .find(|m| m.scene_node_id == Some(channel.node_id))
2546 .map(|model| model.model_id);
2547 let target = if channel.path == crate::fbx_scene::FbxAnimChannelPath::MorphWeight {
2548 channel.morph_target_index.and_then(|target_index| {
2549 morphs.iter().find_map(|morph| {
2550 if Some(morph.model_id) != scene_model_id {
2551 return None;
2552 }
2553 morph
2554 .targets
2555 .get(target_index as usize)
2556 .map(|target| (target.channel_id, true))
2557 })
2558 })
2559 } else {
2560 scene_model_id.map(|model_id| (model_id, false))
2561 };
2562 if let Some((target_id, _is_morph)) = target {
2563 edges.push(connection_node(
2564 "OP",
2565 acnode_id,
2566 target_id,
2567 Some(channel.path.property_name()),
2568 ));
2569 }
2570 for component in 0u32..channel.path.component_count() as u32 {
2571 let curve_id = anim_curve_id(acnode_id, component);
2572 let suffix = match component {
2573 0 => "d|X",
2574 1 => "d|Y",
2575 _ => "d|Z",
2576 };
2577 edges.push(connection_node("OP", curve_id, acnode_id, Some(suffix)));
2578 }
2579 }
2580 }
2581 }
2582
2583 FbxNode {
2584 name: "Connections".to_string(),
2585 properties: Vec::new(),
2586 children: edges,
2587 }
2588}
2589
2590fn connection_node(kind: &str, child: i64, parent: i64, property: Option<&str>) -> FbxNode {
2592 let mut properties = vec![
2593 FbxProperty::String(kind.to_string()),
2594 FbxProperty::I64(child),
2595 FbxProperty::I64(parent),
2596 ];
2597 if let Some(property) = property {
2598 properties.push(FbxProperty::String(property.to_string()));
2599 }
2600 FbxNode {
2601 name: "C".to_string(),
2602 properties,
2603 children: Vec::new(),
2604 }
2605}
2606
2607fn color_property_node(name: &str, values: [f32; 3]) -> FbxNode {
2608 property_node(
2609 name,
2610 "Color",
2611 "",
2612 "A",
2613 values
2614 .into_iter()
2615 .map(|value| FbxProperty::F64(value as f64))
2616 .collect(),
2617 )
2618}
2619
2620fn scalar_property_node(name: &str, value: f64) -> FbxNode {
2621 property_node(name, "Number", "", "A", vec![FbxProperty::F64(value)])
2622}
2623
2624fn string_property_node(name: &str, value: &str) -> FbxNode {
2625 property_node(
2626 name,
2627 "KString",
2628 "",
2629 "A",
2630 vec![FbxProperty::String(value.to_string())],
2631 )
2632}
2633
2634fn timestamp_property_node(name: &str, value: i64) -> FbxNode {
2635 property_node(name, "KTime", "Time", "", vec![FbxProperty::I64(value)])
2636}
2637
2638fn validate_supported_fbx_attributes(mesh: &Mesh) -> io::Result<()> {
2643 for i in 0..mesh.num_attributes() {
2644 let attribute_type = mesh.attribute(i).attribute_type();
2645 match attribute_type {
2646 GeometryAttributeType::Position
2647 | GeometryAttributeType::Normal
2648 | GeometryAttributeType::TexCoord
2649 | GeometryAttributeType::Color => {}
2650 _ => {
2651 return Err(io::Error::new(
2652 io::ErrorKind::InvalidInput,
2653 format!(
2654 "FBX writer currently supports only Position, Normal, TexCoord and Color attributes; {:?} is not written",
2655 attribute_type
2656 ),
2657 ));
2658 }
2659 }
2660 }
2661 Ok(())
2662}
2663
2664fn extract_vertices(mesh: &Mesh) -> Vec<f64> {
2665 let pos_att_id = mesh.named_attribute_id(GeometryAttributeType::Position);
2666 if pos_att_id < 0 {
2667 return Vec::new();
2668 }
2669
2670 let att = mesh.attribute(pos_att_id);
2671 let byte_stride = att.byte_stride() as usize;
2672 let buffer = att.buffer();
2673 let mut vertices = Vec::with_capacity(mesh.num_points() * 3);
2674
2675 for i in 0..mesh.num_points() {
2676 let mut bytes = [0u8; 12];
2677 buffer.read(i * byte_stride, &mut bytes);
2678 let x = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as f64;
2679 let y = f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]) as f64;
2680 let z = f32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]) as f64;
2681 vertices.push(x);
2682 vertices.push(y);
2683 vertices.push(z);
2684 }
2685 vertices
2686}
2687
2688fn extract_polygon_indices(mesh: &Mesh) -> Vec<i32> {
2689 let mut indices = Vec::with_capacity(mesh.num_faces() * 3);
2690 for i in 0..mesh.num_faces() as u32 {
2691 let face = mesh.face(FaceIndex(i));
2692 indices.push(face[0].0 as i32);
2693 indices.push(face[1].0 as i32);
2694 indices.push(!(face[2].0 as i32));
2696 }
2697 indices
2698}
2699
2700fn extract_vec3_attribute(mesh: &Mesh, attribute_type: GeometryAttributeType) -> Option<Vec<f64>> {
2702 let id = mesh.named_attribute_id(attribute_type);
2703 if id < 0 {
2704 return None;
2705 }
2706 let att = mesh.attribute(id);
2707 let stride = att.byte_stride() as usize;
2708 let buffer = att.buffer();
2709 let mut values = Vec::with_capacity(mesh.num_points() * 3);
2710 for i in 0..mesh.num_points() {
2711 let mut bytes = [0u8; 12];
2712 buffer.read(i * stride, &mut bytes);
2713 for c in 0..3 {
2714 values.push(f32::from_le_bytes([
2715 bytes[c * 4],
2716 bytes[c * 4 + 1],
2717 bytes[c * 4 + 2],
2718 bytes[c * 4 + 3],
2719 ]) as f64);
2720 }
2721 }
2722 Some(values)
2723}
2724
2725fn extract_normals(mesh: &Mesh) -> Option<Vec<f64>> {
2726 extract_vec3_attribute(mesh, GeometryAttributeType::Normal)
2727}
2728
2729fn extract_uvs(mesh: &Mesh) -> Option<Vec<f64>> {
2730 let id = mesh.named_attribute_id(GeometryAttributeType::TexCoord);
2731 if id < 0 {
2732 return None;
2733 }
2734 let att = mesh.attribute(id);
2735 let stride = att.byte_stride() as usize;
2736 let buffer = att.buffer();
2737 let mut values = Vec::with_capacity(mesh.num_points() * 2);
2738 for i in 0..mesh.num_points() {
2739 let mut bytes = [0u8; 8];
2740 buffer.read(i * stride, &mut bytes);
2741 let u = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as f64;
2742 let v = f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]) as f64;
2743 values.push(u);
2744 values.push(v);
2745 }
2746 Some(values)
2747}
2748
2749#[cfg(test)]
2754mod tests {
2755 use super::*;
2756 #[cfg(feature = "fbx-reader")]
2758 use crate::fbx_scene::{
2759 FbxAnimation, FbxMeshInstance, FbxMeshLayers, FbxScene, FbxSceneNode, FbxTransform,
2760 FbxTransformStack,
2761 };
2762 use draco_core::draco_types::DataType;
2763 use draco_core::geometry_attribute::PointAttribute;
2764 use draco_core::geometry_indices::PointIndex;
2765 use std::io::Cursor;
2766 use tempfile::NamedTempFile;
2767
2768 fn create_triangle_mesh() -> Mesh {
2769 let mut mesh = Mesh::new();
2770 let mut pos_att = PointAttribute::new();
2771
2772 pos_att.init(
2773 GeometryAttributeType::Position,
2774 3,
2775 DataType::Float32,
2776 false,
2777 3,
2778 );
2779 let buffer = pos_att.buffer_mut();
2780 let positions: [[f32; 3]; 3] = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
2781 for (i, pos) in positions.iter().enumerate() {
2782 let bytes: Vec<u8> = pos.iter().flat_map(|v| v.to_le_bytes()).collect();
2783 buffer.write(i * 12, &bytes);
2784 }
2785 mesh.add_attribute(pos_att);
2786
2787 mesh.set_num_faces(1);
2788 mesh.set_face(FaceIndex(0), [PointIndex(0), PointIndex(1), PointIndex(2)]);
2789
2790 mesh
2791 }
2792
2793 fn child<'a>(node: &'a FbxNode, name: &str) -> Option<&'a FbxNode> {
2795 node.children.iter().find(|child| child.name == name)
2796 }
2797
2798 #[test]
2806 fn the_document_declares_the_objects_it_writes() {
2807 let mut writer = FbxWriter::new();
2808 writer
2809 .add_mesh(&create_triangle_mesh(), Some("Tri"))
2810 .unwrap();
2811 let document = writer.build_document().unwrap();
2812
2813 let names: Vec<&str> = document.iter().map(|node| node.name.as_str()).collect();
2814 assert_eq!(
2815 names,
2816 [
2817 "FBXHeaderExtension",
2818 "GlobalSettings",
2819 "Documents",
2820 "Definitions",
2821 "Objects",
2822 "Connections",
2823 ]
2824 );
2825
2826 let definitions = &document[3];
2829 let objects = &document[4];
2830 let declared: Vec<(String, i32)> = definitions
2831 .children
2832 .iter()
2833 .filter(|node| node.name == "ObjectType")
2834 .map(|node| {
2835 let FbxProperty::String(type_name) = &node.properties[0] else {
2836 panic!("ObjectType names itself with a string");
2837 };
2838 let count = child(node, "Count").expect("every ObjectType declares a Count");
2839 let FbxProperty::I32(count) = count.properties[0] else {
2840 panic!("Count is an i32");
2841 };
2842 (type_name.clone(), count)
2843 })
2844 .collect();
2845 assert!(declared.contains(&("Geometry".to_string(), 1)));
2846 assert!(declared.contains(&("Model".to_string(), 1)));
2847 assert_eq!(
2848 child(definitions, "Count").map(|node| format!("{:?}", node.properties)),
2849 Some(format!(
2850 "{:?}",
2851 vec![FbxProperty::I32(declared.len() as i32)]
2852 )),
2853 "the Count node must be the number of ObjectType blocks"
2854 );
2855
2856 let geometry = child(objects, "Geometry").expect("the mesh writes a Geometry");
2857
2858 assert!(
2859 matches!(&geometry.properties[2], FbxProperty::String(class) if class == "Mesh"),
2860 "a Geometry's class suffix is Mesh: {:?}",
2861 geometry.properties
2862 );
2863 assert!(child(geometry, "Vertices").is_some());
2864 assert!(child(geometry, "PolygonVertexIndex").is_some());
2865 }
2866
2867 #[cfg(feature = "fbx-reader")]
2877 #[test]
2878 fn an_empty_mesh_is_still_written_as_a_geometry() {
2879 let scene = FbxScene {
2880 global_settings: None,
2881 root_nodes: vec![FbxSceneNode {
2882 id: crate::fbx_scene::FbxNodeId(1),
2883 name: Some("Empty".to_string()),
2884 transform: None,
2885 transform_stack: None,
2886 has_complex_transform_stack: false,
2887 mesh_instances: vec![FbxMeshInstance {
2888 name: Some("Nothing".to_string()),
2889 mesh: Mesh::new(),
2890 ..Default::default()
2891 }],
2892 attribute: None,
2893 children: Vec::new(),
2894 }],
2895 materials: Vec::new(),
2896 textures: Vec::new(),
2897 animations: Vec::new(),
2898 warnings: Vec::new(),
2899 };
2900
2901 let mut writer = FbxWriter::new();
2902 writer.add_scene(&scene).expect("an empty mesh is writable");
2903 let document = writer.build_document().expect("document");
2904 let objects = document
2905 .iter()
2906 .find(|node| node.name == "Objects")
2907 .expect("Objects");
2908 let geometry = child(objects, "Geometry").expect("an empty mesh writes a Geometry");
2909 assert!(
2910 matches!(child(geometry, "Vertices"), Some(node)
2911 if matches!(&node.properties[0], FbxProperty::F64Array(values) if values.is_empty())),
2912 "an empty Vertices array is written, not omitted: {:?}",
2913 geometry
2914 .children
2915 .iter()
2916 .map(|c| &c.name)
2917 .collect::<Vec<_>>()
2918 );
2919 assert!(child(geometry, "PolygonVertexIndex").is_some());
2920
2921 let reread = FbxScene::from_bytes(&scene.to_bytes().expect("write")).expect("read");
2923 assert_eq!(
2924 reread.root_nodes[0].mesh_instances.len(),
2925 1,
2926 "the empty mesh must still be there after a rewrite"
2927 );
2928 }
2929
2930 #[cfg(feature = "fbx-reader")]
2939 #[test]
2940 fn a_camera_is_declared_the_way_importers_expect() {
2941 let scene = FbxScene {
2942 root_nodes: vec![FbxSceneNode {
2943 id: crate::fbx_scene::FbxNodeId(1),
2944 name: Some("Cam".to_string()),
2945 attribute: Some(crate::fbx_scene::FbxNodeAttribute::Camera(
2946 crate::fbx_scene::FbxCamera {
2947 position: Some([1.0, 2.0, 3.0]),
2948 focal_length: Some(35.0),
2949 projection_type: Some(0),
2950 near_plane: Some(0.1),
2951 ..Default::default()
2952 },
2953 )),
2954 transform: None,
2955 transform_stack: None,
2956 has_complex_transform_stack: false,
2957 mesh_instances: Vec::new(),
2958 children: Vec::new(),
2959 }],
2960 ..FbxScene::default()
2961 };
2962 let mut writer = FbxWriter::new();
2963 writer.add_scene(&scene).unwrap();
2964 let document = writer.build_document().unwrap();
2965
2966 let objects = document
2967 .iter()
2968 .find(|node| node.name == "Objects")
2969 .expect("Objects");
2970 let model = child(objects, "Model").expect("the node writes a Model");
2971 assert!(
2972 matches!(&model.properties[2], FbxProperty::String(class) if class == "Camera"),
2973 "a camera's Model is classed Camera, not Mesh: {:?}",
2974 model.properties
2975 );
2976
2977 let attribute = child(objects, "NodeAttribute").expect("the camera writes a NodeAttribute");
2978 assert!(
2979 matches!(&attribute.properties[2], FbxProperty::String(class) if class == "Camera")
2980 );
2981 assert_eq!(
2982 child(attribute, "TypeFlags").map(|node| format!("{:?}", node.properties)),
2983 Some(format!(
2984 "{:?}",
2985 vec![FbxProperty::String("Camera".to_string())]
2986 ))
2987 );
2988
2989 let declared: Vec<(String, String)> = child(attribute, "Properties70")
2991 .expect("Properties70")
2992 .children
2993 .iter()
2994 .map(|node| match (&node.properties[0], &node.properties[1]) {
2995 (FbxProperty::String(name), FbxProperty::String(kind)) => {
2996 (name.clone(), kind.clone())
2997 }
2998 other => panic!("a P record names itself with strings: {other:?}"),
2999 })
3000 .collect();
3001 assert_eq!(
3002 declared,
3003 [
3004 ("Position".to_string(), "Vector".to_string()),
3005 ("CameraProjectionType".to_string(), "enum".to_string()),
3006 ("FocalLength".to_string(), "Number".to_string()),
3007 ("NearPlane".to_string(), "double".to_string()),
3008 ]
3009 );
3010
3011 let definitions = document
3014 .iter()
3015 .find(|node| node.name == "Definitions")
3016 .expect("Definitions");
3017 let declared_attributes = definitions
3018 .children
3019 .iter()
3020 .filter(|node| node.name == "ObjectType")
3021 .find(|node| matches!(&node.properties[0], FbxProperty::String(n) if n == "NodeAttribute"))
3022 .and_then(|node| child(node, "Count"))
3023 .map(|node| format!("{:?}", node.properties));
3024 assert_eq!(
3025 declared_attributes,
3026 Some(format!("{:?}", vec![FbxProperty::I32(1)])),
3027 "Definitions must declare the one NodeAttribute that Objects holds"
3028 );
3029 }
3030
3031 #[test]
3032 fn test_fbx_writer_new() {
3033 let writer = FbxWriter::new();
3034 assert_eq!(writer.mesh_count(), 0);
3035 assert!(!writer.is_compression_enabled());
3036 }
3037
3038 #[test]
3039 fn test_fbx_writer_with_options() {
3040 let writer = FbxWriter::new()
3041 .with_compression(true)
3042 .with_compression_threshold(64);
3043 assert!(writer.is_compression_enabled());
3044 }
3045
3046 #[test]
3047 fn test_fbx_writer_add_mesh() {
3048 let mesh = create_triangle_mesh();
3049 let mut writer = FbxWriter::new();
3050 Writer::add_mesh(&mut writer, &mesh, Some("TestMesh")).unwrap();
3051 assert_eq!(writer.mesh_count(), 1);
3052 }
3053
3054 #[test]
3055 fn test_fbx_writer_write() {
3056 let mesh = create_triangle_mesh();
3057 let mut writer = FbxWriter::new();
3058 Writer::add_mesh(&mut writer, &mesh, Some("Triangle")).unwrap();
3059
3060 let mut buffer = Cursor::new(Vec::new());
3061 writer.write_to(&mut buffer).unwrap();
3062
3063 let data = buffer.into_inner();
3064
3065 assert_eq!(&data[0..21], FBX_MAGIC);
3067 let version = u32::from_le_bytes([data[23], data[24], data[25], data[26]]);
3069 assert_eq!(version, FBX_VERSION);
3070 }
3071
3072 #[test]
3073 fn test_write_fbx_mesh_convenience() {
3074 let mesh = create_triangle_mesh();
3075 let file = NamedTempFile::new().unwrap();
3076 write_fbx_mesh(file.path(), &mesh).unwrap();
3077
3078 let metadata = std::fs::metadata(file.path()).unwrap();
3079 assert!(metadata.len() > 27);
3080 }
3081
3082 #[test]
3083 fn test_multiple_meshes() {
3084 let mesh1 = create_triangle_mesh();
3085 let mesh2 = create_triangle_mesh();
3086
3087 let mut writer = FbxWriter::new();
3088 Writer::add_mesh(&mut writer, &mesh1, Some("Mesh1")).unwrap();
3089 Writer::add_mesh(&mut writer, &mesh2, Some("Mesh2")).unwrap();
3090
3091 assert_eq!(writer.mesh_count(), 2);
3092
3093 let mut buffer = Cursor::new(Vec::new());
3094 writer.write_to(&mut buffer).unwrap();
3095
3096 let data = buffer.into_inner();
3097 assert!(!data.is_empty());
3098 }
3099
3100 #[test]
3102 #[cfg(feature = "fbx-reader")]
3103 fn scene_roundtrip_preserves_hierarchy_and_local_transforms() {
3104 use crate::{FbxMeshInstance, FbxScene, FbxSceneNode, FbxTransform};
3105
3106 let child_transform = FbxTransform {
3112 matrix: [
3113 [0.0, 0.0, 8.0, 0.0],
3114 [0.0, 3.0, 0.0, 0.0],
3115 [-2.0, 0.0, 0.0, 0.0],
3116 [1.0, 2.0, 3.0, 1.0],
3117 ],
3118 };
3119 let scene = FbxScene {
3120 global_settings: None,
3121 root_nodes: vec![FbxSceneNode {
3122 id: crate::fbx_scene::FbxNodeId(1),
3123 name: Some("Root".to_string()),
3124 transform: None,
3125 transform_stack: None,
3126 has_complex_transform_stack: false,
3127 mesh_instances: Vec::new(),
3128 attribute: None,
3129 children: vec![FbxSceneNode {
3130 id: crate::fbx_scene::FbxNodeId(2),
3131 name: Some("Child".to_string()),
3132 transform: Some(child_transform),
3133 transform_stack: None,
3134 has_complex_transform_stack: false,
3135 mesh_instances: vec![FbxMeshInstance {
3136 name: Some("Triangle".to_string()),
3137 mesh: create_triangle_mesh(),
3138 ..Default::default()
3139 }],
3140 attribute: None,
3141 children: Vec::new(),
3142 }],
3143 }],
3144 materials: Vec::new(),
3145 textures: Vec::new(),
3146 animations: Vec::new(),
3147 warnings: Vec::new(),
3148 };
3149
3150 let bytes = scene.to_bytes().unwrap();
3151 let roundtrip = FbxScene::from_bytes(&bytes).unwrap();
3152
3153 assert_eq!(roundtrip.root_nodes.len(), 1);
3154 let root = &roundtrip.root_nodes[0];
3155 assert_eq!(root.name.as_deref(), Some("Root"));
3156 assert_eq!(root.children.len(), 1);
3157 let child = &root.children[0];
3158 assert_eq!(child.name.as_deref(), Some("Child"));
3159 assert_eq!(child.mesh_instances[0].name.as_deref(), Some("Triangle"));
3160 assert_eq!(child.mesh_instances[0].mesh.num_faces(), 1);
3161 let transform = child.transform.expect("child transform should round-trip");
3162 for row in 0..4 {
3163 for column in 0..4 {
3164 assert!(
3165 (transform.matrix[row][column] - child_transform.matrix[row][column]).abs()
3166 < 1e-5
3167 );
3168 }
3169 }
3170 }
3171
3172 #[test]
3173 #[cfg(feature = "fbx-reader")]
3174 fn scene_roundtrip_preserves_model_transform_stack_properties() {
3175 let transform_stack = FbxTransformStack {
3179 translation: Some([1.25, -2.5, 3.75]),
3180 rotation: Some([12.0, -34.0, 56.0]),
3181 scaling: Some([1.0, 0.75, 1.25]),
3182 rotation_order: Some(1),
3183 rotation_active: Some(true),
3184 pre_rotation: Some([10.0, 20.0, -30.0]),
3185 post_rotation: Some([-5.0, 15.0, 25.0]),
3186 rotation_offset: Some([0.5, 1.0, -1.5]),
3187 rotation_pivot: Some([2.0, -3.0, 4.0]),
3188 scaling_offset: Some([-0.25, 0.5, 0.75]),
3189 scaling_pivot: Some([1.5, -2.5, 3.5]),
3190 inherit_type: Some(2),
3191 };
3192 let scene = FbxScene {
3193 global_settings: None,
3194 root_nodes: vec![FbxSceneNode {
3195 id: crate::fbx_scene::FbxNodeId(1),
3196 name: Some("StackedNode".to_string()),
3197 transform: Some(FbxTransform {
3198 matrix: [
3199 [1.0, 0.0, 0.0, 0.0],
3200 [0.0, 1.0, 0.0, 0.0],
3201 [0.0, 0.0, 1.0, 0.0],
3202 [1.25, -2.5, 3.75, 1.0],
3203 ],
3204 }),
3205 transform_stack: Some(transform_stack.clone()),
3206 has_complex_transform_stack: true,
3207 mesh_instances: Vec::new(),
3208 attribute: None,
3209 children: Vec::new(),
3210 }],
3211 materials: Vec::new(),
3212 textures: Vec::new(),
3213 animations: Vec::new(),
3214 warnings: Vec::new(),
3215 };
3216
3217 let output = FbxScene::from_bytes(&scene.to_bytes().unwrap()).unwrap();
3218 assert_eq!(
3219 output.root_nodes[0].transform_stack.as_ref(),
3220 Some(&transform_stack)
3221 );
3222 }
3223
3224 #[test]
3225 fn fbx_matrix_arrays_use_column_major_layout() {
3226 use crate::FbxTransform;
3234 let matrix = [
3235 [0.0, 0.0, 8.0, 0.0],
3236 [0.0, 3.0, 0.0, 0.0],
3237 [-2.0, 0.0, 0.0, 0.0],
3238 [1.0, 2.0, 3.0, 1.0],
3239 ];
3240 let column_major = super::flatten_fbx_transform(FbxTransform { matrix });
3241 assert_eq!(
3242 column_major,
3243 matrix
3244 .into_iter()
3245 .flatten()
3246 .map(f64::from)
3247 .collect::<Vec<_>>()
3248 );
3249 }
3250
3251 #[test]
3252 #[cfg(feature = "fbx-reader")]
3253 fn scene_roundtrip_preserves_skin_clusters_and_bind_pose() {
3254 let identity = crate::fbx_scene::FbxTransform {
3255 matrix: [
3256 [1.0, 0.0, 0.0, 0.0],
3257 [0.0, 1.0, 0.0, 0.0],
3258 [0.0, 0.0, 1.0, 0.0],
3259 [0.0, 0.0, 0.0, 1.0],
3260 ],
3261 };
3262 let scene = FbxScene {
3263 global_settings: None,
3264 root_nodes: vec![FbxSceneNode {
3265 id: crate::fbx_scene::FbxNodeId(1),
3266 name: Some("Armature".to_string()),
3267 transform: None,
3268 transform_stack: None,
3269 has_complex_transform_stack: false,
3270 mesh_instances: Vec::new(),
3271 attribute: None,
3272 children: vec![
3273 FbxSceneNode {
3274 id: crate::fbx_scene::FbxNodeId(2),
3275 name: Some("Bone".to_string()),
3276 transform: Some(identity),
3277 transform_stack: None,
3278 has_complex_transform_stack: false,
3279 mesh_instances: Vec::new(),
3280 attribute: None,
3281 children: Vec::new(),
3282 },
3283 FbxSceneNode {
3284 id: crate::fbx_scene::FbxNodeId(3),
3285 name: Some("Mesh".to_string()),
3286 transform: Some(identity),
3287 transform_stack: None,
3288 has_complex_transform_stack: false,
3289 mesh_instances: vec![FbxMeshInstance {
3290 name: Some("Triangle".to_string()),
3291 mesh: create_triangle_mesh(),
3292 skin: Some(crate::fbx_scene::FbxSkin {
3293 clusters: vec![crate::fbx_scene::FbxSkinCluster {
3294 joint_node_id: crate::fbx_scene::FbxNodeId(2),
3295 control_point_indices: vec![0, 1, 2],
3296 weights: vec![1.0, 0.5, 1.0],
3297 mesh_bind_transform: identity,
3298 joint_bind_transform: identity,
3299 armature_bind_transform: None,
3300 }],
3301 bind_pose: vec![
3302 (crate::fbx_scene::FbxNodeId(2), identity),
3303 (crate::fbx_scene::FbxNodeId(3), identity),
3304 ],
3305 }),
3306 morph_targets: vec![crate::fbx_scene::FbxMorphTarget {
3307 name: Some("Smile".to_string()),
3308 control_point_indices: vec![1],
3309 position_deltas: vec![[0.0, 0.25, 0.0]],
3310 normal_deltas: None,
3311 default_weight: 0.0,
3312 full_weight: 100.0,
3313 }],
3314 ..Default::default()
3315 }],
3316 attribute: None,
3317 children: Vec::new(),
3318 },
3319 ],
3320 }],
3321 materials: Vec::new(),
3322 textures: Vec::new(),
3323 animations: Vec::new(),
3324 warnings: Vec::new(),
3325 };
3326 let output = FbxScene::from_bytes(&scene.to_bytes().unwrap()).unwrap();
3327 let mesh = &output.root_nodes[0].children[1].mesh_instances[0];
3328 let skin = mesh.skin.as_ref().expect("skin must round-trip");
3329 assert_eq!(skin.clusters.len(), 1);
3330 assert_eq!(
3331 skin.clusters[0].joint_node_id,
3332 crate::fbx_scene::FbxNodeId(2)
3333 );
3334 assert_eq!(skin.clusters[0].control_point_indices, vec![0, 1, 2]);
3335 assert_eq!(skin.clusters[0].weights, vec![1.0, 0.5, 1.0]);
3336 assert_eq!(skin.bind_pose.len(), 2);
3337 assert_eq!(mesh.morph_targets.len(), 1);
3338 assert_eq!(mesh.morph_targets[0].name.as_deref(), Some("Smile"));
3339 assert_eq!(
3340 mesh.morph_targets[0].position_deltas,
3341 vec![[0.0, 0.25, 0.0]]
3342 );
3343 }
3344
3345 #[test]
3346 #[cfg(feature = "fbx-reader")]
3347 fn scene_roundtrip_preserves_cubic_tangents() {
3348 let scene = FbxScene {
3349 global_settings: None,
3350 root_nodes: vec![FbxSceneNode {
3351 id: crate::fbx_scene::FbxNodeId(1),
3352 name: Some("Root".to_string()),
3353 transform: None,
3354 transform_stack: None,
3355 has_complex_transform_stack: false,
3356 mesh_instances: Vec::new(),
3357 attribute: None,
3358 children: Vec::new(),
3359 }],
3360 materials: Vec::new(),
3361 textures: Vec::new(),
3362 animations: vec![FbxAnimation {
3363 name: Some("Cubic".to_string()),
3364 duration: 1.0,
3365 channels: vec![crate::fbx_scene::FbxAnimChannel {
3366 node_id: crate::fbx_scene::FbxNodeId(1),
3367 node_name: "Root".to_string(),
3368 path: crate::fbx_scene::FbxAnimChannelPath::Translation,
3369 morph_target_index: None,
3370 sampler: crate::fbx_scene::FbxAnimSampler {
3371 input: vec![0.0, 1.0],
3372 output: vec![0.0, 0.0, 0.0, 1.0, 2.0, 3.0],
3373 interpolation: crate::fbx_scene::FbxAnimInterpolation::Cubic,
3374 in_tangents: Some(vec![0.0, 0.0, 0.0, 0.25, 0.5, 0.75]),
3375 out_tangents: Some(vec![1.0, 2.0, 3.0, 0.0, 0.0, 0.0]),
3376 },
3377 }],
3378 }],
3379 warnings: Vec::new(),
3380 };
3381 let output = FbxScene::from_bytes(&scene.to_bytes().unwrap()).unwrap();
3382 let sampler = &output.animations[0].channels[0].sampler;
3383 assert_eq!(
3384 sampler.interpolation,
3385 crate::fbx_scene::FbxAnimInterpolation::Cubic
3386 );
3387 assert_eq!(
3388 sampler.in_tangents.as_deref(),
3389 Some(&[0.0, 0.0, 0.0, 0.25, 0.5, 0.75][..])
3390 );
3391 assert_eq!(
3392 sampler.out_tangents.as_deref(),
3393 Some(&[1.0, 2.0, 3.0, 0.0, 0.0, 0.0][..])
3394 );
3395 }
3396
3397 #[test]
3398 #[cfg(feature = "fbx-reader")]
3399 fn scene_roundtrip_preserves_per_polygon_materials_on_ngons() {
3400 let mut instance = FbxMeshInstance {
3405 name: Some("Quads".to_string()),
3406 mesh: create_triangle_mesh(),
3407 control_points: vec![
3408 [0.0, 0.0, 0.0],
3409 [1.0, 0.0, 0.0],
3410 [1.0, 1.0, 0.0],
3411 [0.0, 1.0, 0.0],
3412 [2.0, 0.0, 0.0],
3413 [3.0, 0.0, 0.0],
3414 [3.0, 1.0, 0.0],
3415 [2.0, 1.0, 0.0],
3416 ],
3417 polygon_vertex_indices: vec![0, 1, 2, !3, 4, 5, 6, !7],
3419 material_indices: vec![0, 0, 1, 1],
3420 ..Default::default()
3421 };
3422 instance.mesh = instance.to_draco_mesh();
3423
3424 let named = |name: &str| crate::fbx_scene::FbxMaterial {
3425 name: Some(name.to_string()),
3426 ..crate::fbx_scene::FbxMaterial::default()
3427 };
3428 let scene = FbxScene {
3429 root_nodes: vec![FbxSceneNode {
3430 id: crate::fbx_scene::FbxNodeId(1),
3431 name: Some("Node".to_string()),
3432 transform: None,
3433 transform_stack: None,
3434 has_complex_transform_stack: false,
3435 mesh_instances: vec![instance],
3436 attribute: None,
3437 children: Vec::new(),
3438 }],
3439 materials: vec![named("Red"), named("Blue")],
3440 ..FbxScene::default()
3441 };
3442
3443 let output = crate::FbxScene::from_bytes(&scene.to_bytes().unwrap()).unwrap();
3444 assert_eq!(
3445 output.root_nodes[0].mesh_instances[0].material_indices,
3446 vec![0, 0, 1, 1],
3447 "the second quad should keep its own material"
3448 );
3449 }
3450
3451 #[test]
3452 fn collapsing_material_indices_takes_one_entry_per_polygon() {
3453 let collapsed =
3455 collapse_material_indices_to_polygons(&[7, 7, 3], Some(&[0, 1, 2, !3, 4, 5, !6]));
3456 assert_eq!(collapsed, vec![7, 3]);
3457
3458 assert_eq!(
3460 collapse_material_indices_to_polygons(&[1, 2, 3], None),
3461 vec![1, 2, 3]
3462 );
3463 }
3464
3465 #[cfg(feature = "fbx-reader")]
3466 fn scene_with_tangents(has_handedness: bool) -> FbxScene {
3467 let set = crate::fbx_scene::FbxTangentSet {
3468 layer: crate::fbx_scene::FbxLayerSet {
3469 name: None,
3470 mapping: Some("ByPolygonVertex".to_string()),
3471 reference: Some("Direct".to_string()),
3472 values: vec![
3473 [1.0, 0.0, 0.0, -1.0],
3474 [0.0, 1.0, 0.0, -1.0],
3475 [0.0, 0.0, 1.0, -1.0],
3476 ],
3477 indices: Vec::new(),
3478 },
3479 has_handedness,
3480 };
3481 FbxScene {
3482 root_nodes: vec![FbxSceneNode {
3483 id: crate::fbx_scene::FbxNodeId(1),
3484 name: Some("Tangential".to_string()),
3485 transform: None,
3486 transform_stack: None,
3487 has_complex_transform_stack: false,
3488 mesh_instances: vec![FbxMeshInstance {
3489 name: Some("Tri".to_string()),
3490 mesh: create_triangle_mesh(),
3491 control_points: vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
3492 polygon_vertex_indices: vec![0, 1, !2],
3493 layers: FbxMeshLayers {
3494 tangent_sets: vec![set.clone()],
3495 binormal_sets: vec![set],
3496 ..Default::default()
3497 },
3498 ..Default::default()
3499 }],
3500 attribute: None,
3501 children: Vec::new(),
3502 }],
3503 ..FbxScene::default()
3504 }
3505 }
3506
3507 #[test]
3514 #[cfg(feature = "fbx-reader")]
3515 fn handedness_survives_a_round_trip_and_is_not_invented() {
3516 for has_handedness in [true, false] {
3517 let bytes = scene_with_tangents(has_handedness).to_bytes().unwrap();
3518 let output = crate::FbxScene::from_bytes(&bytes).unwrap();
3519 let instance = &output.root_nodes[0].mesh_instances[0];
3520
3521 assert_eq!(instance.layers.tangent_sets.len(), 1);
3522 assert_eq!(instance.layers.binormal_sets.len(), 1);
3523 let tangents = &instance.layers.tangent_sets[0];
3524 assert_eq!(tangents.has_handedness, has_handedness);
3525
3526 let expected_w = if has_handedness { -1.0 } else { 1.0 };
3527 assert_eq!(
3528 tangents.layer.values[0],
3529 [1.0, 0.0, 0.0, expected_w],
3530 "handedness {has_handedness}: xyz must survive and w must \
3531 {} ",
3532 if has_handedness {
3533 "be read back"
3534 } else {
3535 "default to +1"
3536 }
3537 );
3538
3539 let has_w_node = String::from_utf8_lossy(&bytes).contains("TangentsW");
3542 assert_eq!(
3543 has_w_node, has_handedness,
3544 "TangentsW node presence must match the source"
3545 );
3546 }
3547 }
3548
3549 #[test]
3554 #[cfg(feature = "fbx-reader")]
3555 fn smoothing_and_crease_layers_survive_a_round_trip() {
3556 let instance = FbxMeshInstance {
3557 name: Some("Creased".to_string()),
3558 mesh: create_triangle_mesh(),
3559 control_points: vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
3560 polygon_vertex_indices: vec![0, 1, !2],
3561 edges: vec![0, 1, 2],
3562 layers: FbxMeshLayers {
3563 smoothing_layers: vec![
3564 crate::fbx_scene::FbxSmoothingLayer {
3565 mapping: Some("ByEdge".to_string()),
3566 values: vec![1, 0, 1],
3567 },
3568 crate::fbx_scene::FbxSmoothingLayer {
3569 mapping: Some("ByPolygon".to_string()),
3570 values: vec![1],
3571 },
3572 ],
3573 crease_layers: vec![
3574 crate::fbx_scene::FbxCreaseLayer {
3575 kind: crate::fbx_scene::FbxCreaseKind::Edge,
3576 mapping: Some("ByEdge".to_string()),
3577 values: vec![0.25, 0.5, 1.0],
3579 },
3580 crate::fbx_scene::FbxCreaseLayer {
3581 kind: crate::fbx_scene::FbxCreaseKind::Vertex,
3582 mapping: Some("ByVertice".to_string()),
3583 values: vec![0.75, 0.0, 0.125],
3584 },
3585 ],
3586 ..Default::default()
3587 },
3588 ..Default::default()
3589 };
3590 let scene = FbxScene {
3591 root_nodes: vec![FbxSceneNode {
3592 id: crate::fbx_scene::FbxNodeId(1),
3593 name: Some("Node".to_string()),
3594 transform: None,
3595 transform_stack: None,
3596 has_complex_transform_stack: false,
3597 mesh_instances: vec![instance.clone()],
3598 attribute: None,
3599 children: Vec::new(),
3600 }],
3601 ..FbxScene::default()
3602 };
3603
3604 let output = crate::FbxScene::from_bytes(&scene.to_bytes().unwrap()).unwrap();
3605 let read_back = &output.root_nodes[0].mesh_instances[0];
3606 assert_eq!(
3607 read_back.layers.smoothing_layers,
3608 instance.layers.smoothing_layers
3609 );
3610 assert_eq!(
3611 read_back.layers.crease_layers,
3612 instance.layers.crease_layers
3613 );
3614 }
3615
3616 #[test]
3621 #[cfg(feature = "fbx-reader")]
3622 fn a_by_edge_layer_survives_without_an_explicit_edges_array() {
3623 let smoothing = crate::fbx_scene::FbxSmoothingLayer {
3624 mapping: Some("ByEdge".to_string()),
3625 values: vec![1, 0, 1],
3626 };
3627 let scene = FbxScene {
3628 root_nodes: vec![FbxSceneNode {
3629 id: crate::fbx_scene::FbxNodeId(1),
3630 name: Some("Node".to_string()),
3631 transform: None,
3632 transform_stack: None,
3633 has_complex_transform_stack: false,
3634 mesh_instances: vec![FbxMeshInstance {
3635 name: Some("Implicit".to_string()),
3636 mesh: create_triangle_mesh(),
3637 control_points: vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
3638 polygon_vertex_indices: vec![0, 1, !2],
3639 layers: FbxMeshLayers {
3640 smoothing_layers: vec![smoothing.clone()],
3641 ..Default::default()
3642 },
3643 ..Default::default()
3644 }],
3645 attribute: None,
3646 children: Vec::new(),
3647 }],
3648 ..FbxScene::default()
3649 };
3650
3651 let output = crate::FbxScene::from_bytes(&scene.to_bytes().unwrap()).unwrap();
3652 let read_back = &output.root_nodes[0].mesh_instances[0];
3653 assert!(read_back.edges.is_empty());
3654 assert_eq!(read_back.layers.smoothing_layers, vec![smoothing]);
3655 }
3656
3657 #[test]
3665 #[cfg(feature = "fbx-reader")]
3666 fn an_unnamed_texture_is_not_given_a_name_by_a_rewrite() {
3667 let scene = FbxScene {
3668 materials: vec![crate::fbx_scene::FbxMaterial {
3669 name: Some("M".to_string()),
3670 textures: vec![crate::fbx_scene::FbxTextureBinding {
3671 slot: crate::fbx_scene::FbxTextureSlot::Diffuse,
3672 texture_index: 0,
3673 }],
3674 ..Default::default()
3675 }],
3676 textures: vec![crate::fbx_scene::FbxTexture {
3677 name: None,
3678 content: None,
3679 filename: Some("t.png".to_string()),
3680 }],
3681 ..FbxScene::default()
3682 };
3683
3684 let output = crate::FbxScene::from_bytes(&scene.to_bytes().unwrap()).unwrap();
3685 assert_eq!(output.textures.len(), 1);
3686 assert_eq!(
3687 output.textures[0].name, None,
3688 "an unnamed texture must stay unnamed"
3689 );
3690 }
3691
3692 #[test]
3699 #[cfg(feature = "fbx-reader")]
3700 fn a_colour_only_geometry_lists_its_layer_element() {
3701 let scene = FbxScene {
3702 root_nodes: vec![FbxSceneNode {
3703 id: crate::fbx_scene::FbxNodeId(1),
3704 name: Some("Colored".to_string()),
3705 transform: None,
3706 transform_stack: None,
3707 has_complex_transform_stack: false,
3708 mesh_instances: vec![FbxMeshInstance {
3709 name: Some("Tri".to_string()),
3710 mesh: create_triangle_mesh(),
3711 control_points: vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
3712 polygon_vertex_indices: vec![0, 1, !2],
3713 layers: FbxMeshLayers {
3714 color_sets: vec![crate::fbx_scene::FbxColorSet {
3715 name: Some("Col".to_string()),
3716 mapping: Some("ByPolygonVertex".to_string()),
3717 reference: Some("Direct".to_string()),
3718 values: vec![[1.0, 0.0, 0.0, 1.0]; 3],
3719 indices: Vec::new(),
3720 }],
3721 ..Default::default()
3722 },
3723 ..Default::default()
3724 }],
3725 attribute: None,
3726 children: Vec::new(),
3727 }],
3728 ..FbxScene::default()
3729 };
3730
3731 let bytes = scene.to_bytes().unwrap();
3732 let nodes = crate::FbxReader::from_bytes(bytes)
3733 .unwrap()
3734 .read_nodes()
3735 .unwrap();
3736
3737 fn find<'a>(
3738 nodes: &'a [crate::fbx_reader::FbxNode],
3739 name: &str,
3740 ) -> Option<&'a crate::fbx_reader::FbxNode> {
3741 nodes
3742 .iter()
3743 .find(|n| n.name == name)
3744 .or_else(|| nodes.iter().find_map(|n| find(&n.children, name)))
3745 }
3746
3747 let layer = find(&nodes, "Layer").expect("colours alone must still produce a Layer node");
3748 let listed: Vec<&str> = layer
3749 .children
3750 .iter()
3751 .filter(|c| c.name == "LayerElement")
3752 .filter_map(|c| c.children.iter().find(|g| g.name == "Type"))
3753 .filter_map(|t| match t.properties.first() {
3754 Some(crate::fbx_reader::FbxProperty::String(s)) => Some(s.as_str()),
3755 _ => None,
3756 })
3757 .collect();
3758 assert!(
3759 listed.contains(&"LayerElementColor"),
3760 "Layer must reference the colour element, listed: {listed:?}"
3761 );
3762 }
3763
3764 #[test]
3765 #[cfg(feature = "fbx-reader")]
3766 fn scene_roundtrip_preserves_vertex_colors() {
3767 let colors = crate::fbx_scene::FbxColorSet {
3768 name: Some("Col".to_string()),
3769 mapping: Some("ByPolygonVertex".to_string()),
3770 reference: Some("Direct".to_string()),
3771 values: vec![
3772 [1.0, 0.0, 0.0, 1.0],
3773 [0.0, 1.0, 0.0, 1.0],
3774 [0.0, 0.0, 1.0, 0.5],
3775 ],
3776 indices: Vec::new(),
3777 };
3778 let scene = FbxScene {
3779 root_nodes: vec![FbxSceneNode {
3780 id: crate::fbx_scene::FbxNodeId(1),
3781 name: Some("Colored".to_string()),
3782 transform: None,
3783 transform_stack: None,
3784 has_complex_transform_stack: false,
3785 mesh_instances: vec![FbxMeshInstance {
3786 name: Some("Tri".to_string()),
3787 mesh: create_triangle_mesh(),
3788 control_points: vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
3789 polygon_vertex_indices: vec![0, 1, !2],
3790 layers: FbxMeshLayers {
3791 color_sets: vec![colors.clone()],
3792 ..Default::default()
3793 },
3794 ..Default::default()
3795 }],
3796 attribute: None,
3797 children: Vec::new(),
3798 }],
3799 ..FbxScene::default()
3800 };
3801
3802 let output = crate::FbxScene::from_bytes(&scene.to_bytes().unwrap()).unwrap();
3803 let instance = &output.root_nodes[0].mesh_instances[0];
3804 assert_eq!(
3805 instance.layers.color_sets.len(),
3806 1,
3807 "colour layer should survive"
3808 );
3809 let read_back = &instance.layers.color_sets[0];
3810 assert_eq!(read_back.values, colors.values);
3811 assert_eq!(read_back.mapping.as_deref(), Some("ByPolygonVertex"));
3812
3813 let render = instance.to_render_mesh();
3816 assert_eq!(render.colors.len(), 1);
3817 assert_eq!(render.colors[0].values[2], [0.0, 0.0, 1.0, 0.5]);
3818 let id = instance
3819 .mesh
3820 .named_attribute_id(draco_core::geometry_attribute::GeometryAttributeType::Color);
3821 assert!(id >= 0, "the Draco mesh should carry a Color attribute");
3822 assert_eq!(instance.mesh.attribute(id).num_components(), 4);
3823 }
3824
3825 #[test]
3826 #[cfg(feature = "fbx-reader")]
3827 fn written_files_satisfy_the_readers_strict_mode() {
3828 let mut writer = FbxWriter::new();
3832 writer
3833 .add_mesh(&create_triangle_mesh(), Some("strict"))
3834 .unwrap();
3835 let bytes = writer.write_to_vec().unwrap();
3836
3837 let scene =
3838 crate::FbxScene::from_bytes_with_options(&bytes, crate::FbxReadOptions::strict())
3839 .expect("writer output should pass strict validation");
3840 assert_eq!(scene.root_nodes.len(), 1);
3841 }
3842
3843 #[cfg(feature = "compression")]
3844 #[test]
3845 fn test_write_with_compression() {
3846 let mesh = create_triangle_mesh();
3847 let mut writer = FbxWriter::new()
3848 .with_compression(true)
3849 .with_compression_threshold(0);
3850 Writer::add_mesh(&mut writer, &mesh, None).unwrap();
3851
3852 let mut buffer = Cursor::new(Vec::new());
3853 writer.write_to(&mut buffer).unwrap();
3854
3855 let data = buffer.into_inner();
3856 assert!(!data.is_empty());
3857 }
3858}