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(1)),
830 int_property_node(
831 "UpAxisSign",
832 "int",
833 "Integer",
834 "",
835 source.up_axis_sign.unwrap_or(1),
836 ),
837 int_property_node(
838 "FrontAxis",
839 "int",
840 "Integer",
841 "",
842 source.front_axis.unwrap_or(2),
843 ),
844 int_property_node(
845 "FrontAxisSign",
846 "int",
847 "Integer",
848 "",
849 source.front_axis_sign.unwrap_or(1),
850 ),
851 int_property_node(
852 "CoordAxis",
853 "int",
854 "Integer",
855 "",
856 source.coord_axis.unwrap_or(0),
857 ),
858 int_property_node(
859 "CoordAxisSign",
860 "int",
861 "Integer",
862 "",
863 source.coord_axis_sign.unwrap_or(1),
864 ),
865 f64_property_node(
874 "UnitScaleFactor",
875 "double",
876 "Number",
877 "",
878 source.unit_scale_factor.unwrap_or(100.0),
879 ),
880 f64_property_node(
881 "OriginalUnitScaleFactor",
882 "double",
883 "Number",
884 "",
885 source.original_unit_scale_factor.unwrap_or(100.0),
886 ),
887 ];
888 if let Some(time_mode) = source.time_mode {
889 properties.push(int_property_node("TimeMode", "enum", "", "", time_mode));
890 }
891
892 FbxNode {
893 name: "GlobalSettings".to_string(),
894 properties: Vec::new(),
895 children: vec![
896 value_node("Version", FbxProperty::I32(1000)),
897 properties70_node(properties),
898 ],
899 }
900}
901
902fn properties70_node(properties: Vec<FbxNode>) -> FbxNode {
904 FbxNode {
905 name: "Properties70".to_string(),
906 properties: Vec::new(),
907 children: properties,
908 }
909}
910
911fn property_node(
917 name: &str,
918 type1: &str,
919 type2: &str,
920 flags: &str,
921 values: Vec<FbxProperty>,
922) -> FbxNode {
923 let mut properties = vec![
924 FbxProperty::String(name.to_string()),
925 FbxProperty::String(type1.to_string()),
926 FbxProperty::String(type2.to_string()),
927 FbxProperty::String(flags.to_string()),
928 ];
929 properties.extend(values);
930 FbxNode {
931 name: "P".to_string(),
932 properties,
933 children: Vec::new(),
934 }
935}
936
937fn int_property_node(name: &str, type1: &str, type2: &str, flags: &str, value: i32) -> FbxNode {
938 property_node(name, type1, type2, flags, vec![FbxProperty::I32(value)])
939}
940
941fn bool_property_node(name: &str, value: bool) -> FbxNode {
942 property_node(
945 name,
946 "bool",
947 "",
948 "",
949 vec![FbxProperty::I32(i32::from(value))],
950 )
951}
952
953fn f64_property_node(name: &str, type1: &str, type2: &str, flags: &str, value: f64) -> FbxNode {
954 property_node(name, type1, type2, flags, vec![FbxProperty::F64(value)])
955}
956
957fn vector_property_node(name: &str, values: [f32; 3]) -> FbxNode {
960 property_node(
961 name,
962 "Vector",
963 "",
964 "A",
965 values
966 .into_iter()
967 .map(|value| FbxProperty::F64(f64::from(value)))
968 .collect(),
969 )
970}
971
972fn enum_property_node(name: &str, value: i32) -> FbxNode {
973 int_property_node(name, "enum", "", "", value)
974}
975
976fn vec3_property_node(name: &str, values: [f64; 3]) -> FbxNode {
982 property_node(
983 name,
984 name,
985 "",
986 "A",
987 values.into_iter().map(FbxProperty::F64).collect(),
988 )
989}
990
991fn decompose_transform(
992 transform: crate::fbx_scene::FbxTransform,
993) -> io::Result<([f64; 3], [f64; 3], [f64; 3])> {
994 let matrix = transform.matrix.map(|row| row.map(f64::from));
995 if !matrix.iter().flatten().all(|value| value.is_finite()) {
996 return Err(io::Error::new(
997 io::ErrorKind::InvalidInput,
998 "FBX transform contains a non-finite value",
999 ));
1000 }
1001 if matrix[0][3].abs() > 1e-5
1002 || matrix[1][3].abs() > 1e-5
1003 || matrix[2][3].abs() > 1e-5
1004 || (matrix[3][3] - 1.0).abs() > 1e-5
1005 {
1006 return Err(io::Error::new(
1007 io::ErrorKind::InvalidInput,
1008 "FBX scene export requires an affine transform matrix",
1009 ));
1010 }
1011
1012 let mut rotation = [
1015 [matrix[0][0], matrix[1][0], matrix[2][0]],
1016 [matrix[0][1], matrix[1][1], matrix[2][1]],
1017 [matrix[0][2], matrix[1][2], matrix[2][2]],
1018 ];
1019 let mut scaling = [
1020 (rotation[0][0].powi(2) + rotation[1][0].powi(2) + rotation[2][0].powi(2)).sqrt(),
1021 (rotation[0][1].powi(2) + rotation[1][1].powi(2) + rotation[2][1].powi(2)).sqrt(),
1022 (rotation[0][2].powi(2) + rotation[1][2].powi(2) + rotation[2][2].powi(2)).sqrt(),
1023 ];
1024 if scaling.iter().any(|value| *value < 1e-8) {
1025 return Err(io::Error::new(
1026 io::ErrorKind::InvalidInput,
1027 "FBX scene export cannot decompose a zero-scale transform",
1028 ));
1029 }
1030 for column in 0..3 {
1031 for row in &mut rotation {
1032 row[column] /= scaling[column];
1033 }
1034 }
1035
1036 let determinant = rotation[0][0]
1037 * (rotation[1][1] * rotation[2][2] - rotation[1][2] * rotation[2][1])
1038 - rotation[0][1] * (rotation[1][0] * rotation[2][2] - rotation[1][2] * rotation[2][0])
1039 + rotation[0][2] * (rotation[1][0] * rotation[2][1] - rotation[1][1] * rotation[2][0]);
1040 if determinant < 0.0 {
1041 scaling[0] = -scaling[0];
1042 for row in &mut rotation {
1043 row[0] = -row[0];
1044 }
1045 }
1046
1047 let dot01 = rotation[0][0] * rotation[0][1]
1048 + rotation[1][0] * rotation[1][1]
1049 + rotation[2][0] * rotation[2][1];
1050 let dot02 = rotation[0][0] * rotation[0][2]
1051 + rotation[1][0] * rotation[1][2]
1052 + rotation[2][0] * rotation[2][2];
1053 let dot12 = rotation[0][1] * rotation[0][2]
1054 + rotation[1][1] * rotation[1][2]
1055 + rotation[2][1] * rotation[2][2];
1056 if dot01.abs() > 1e-4 || dot02.abs() > 1e-4 || dot12.abs() > 1e-4 {
1057 return Err(io::Error::new(
1058 io::ErrorKind::InvalidInput,
1059 "FBX scene export cannot represent transform shear",
1060 ));
1061 }
1062
1063 let y = (-rotation[2][0]).asin();
1064 let (x, z) = if y.cos().abs() > 1e-6 {
1065 (
1066 rotation[2][1].atan2(rotation[2][2]),
1067 rotation[1][0].atan2(rotation[0][0]),
1068 )
1069 } else {
1070 ((-rotation[1][2]).atan2(rotation[1][1]), 0.0)
1071 };
1072 Ok((
1073 [matrix[3][0], matrix[3][1], matrix[3][2]],
1074 [x.to_degrees(), y.to_degrees(), z.to_degrees()],
1075 scaling,
1076 ))
1077}
1078
1079fn documents_node() -> FbxNode {
1080 FbxNode {
1081 name: "Documents".to_string(),
1082 properties: Vec::new(),
1083 children: vec![
1084 value_node("Count", FbxProperty::I32(1)),
1085 FbxNode {
1086 name: "Document".to_string(),
1087 properties: vec![
1088 FbxProperty::I64(0), FbxProperty::String(String::new()),
1090 FbxProperty::String("Scene".to_string()),
1091 ],
1092 children: Vec::new(),
1093 },
1094 ],
1095 }
1096}
1097
1098#[allow(clippy::too_many_arguments)]
1105fn definitions_node(
1106 meshes: &[MeshData],
1107 models: &[ModelData],
1108 materials: &[MaterialData],
1109 textures: &[TextureData],
1110 anim: &[AnimStackData],
1111 skins: &[SkinData],
1112 morphs: &[MorphData],
1113) -> FbxNode {
1114 let node_attributes = models.iter().filter(|model| model.has_attribute()).count();
1119 let shape_count = morphs
1120 .iter()
1121 .map(|morph| morph.targets.len())
1122 .sum::<usize>();
1123 let curve_nodes: usize = anim.iter().map(|stack| stack.channels.len()).sum();
1124 let curve_count: usize = anim
1126 .iter()
1127 .flat_map(|stack| &stack.channels)
1128 .map(|channel| channel.path.component_count())
1129 .sum();
1130
1131 let mut object_types = vec![
1132 object_type_node("Geometry", (meshes.len() + shape_count) as i32),
1133 object_type_node("Model", models.len() as i32),
1134 ];
1135 if node_attributes > 0 {
1136 object_types.push(object_type_node("NodeAttribute", node_attributes as i32));
1137 }
1138 object_types.push(object_type_node("Material", materials.len() as i32));
1139 object_types.push(object_type_node("Texture", textures.len() as i32));
1140 object_types.push(object_type_node("Video", textures.len() as i32));
1141 object_types.push(object_type_node("AnimationStack", anim.len() as i32));
1142 object_types.push(object_type_node("AnimationLayer", anim.len() as i32));
1144 object_types.push(object_type_node("AnimationCurveNode", curve_nodes as i32));
1145 object_types.push(object_type_node("AnimationCurve", curve_count as i32));
1146 if !skins.is_empty() || !morphs.is_empty() {
1147 let deformer_count = skins.len()
1148 + skins.iter().map(|skin| skin.clusters.len()).sum::<usize>()
1149 + morphs.len()
1150 + morphs
1151 .iter()
1152 .map(|morph| morph.targets.len())
1153 .sum::<usize>();
1154 object_types.push(object_type_node("Deformer", deformer_count as i32));
1155 object_types.push(object_type_node("Pose", skins.len() as i32));
1156 }
1157
1158 let mut children = vec![
1159 value_node("Version", FbxProperty::I32(100)),
1160 value_node("Count", FbxProperty::I32(object_types.len() as i32)),
1161 ];
1162 children.extend(object_types);
1163 FbxNode {
1164 name: "Definitions".to_string(),
1165 properties: Vec::new(),
1166 children,
1167 }
1168}
1169
1170fn object_type_node(type_name: &str, count: i32) -> FbxNode {
1171 FbxNode {
1172 name: "ObjectType".to_string(),
1173 properties: vec![FbxProperty::String(type_name.to_string())],
1174 children: vec![value_node("Count", FbxProperty::I32(count))],
1175 }
1176}
1177
1178#[allow(clippy::too_many_arguments)]
1180fn objects_node(
1181 meshes: &[MeshData],
1182 models: &[ModelData],
1183 materials: &[MaterialData],
1184 textures: &[TextureData],
1185 anim: &[AnimStackData],
1186 skins: &[SkinData],
1187 morphs: &[MorphData],
1188) -> io::Result<FbxNode> {
1189 let mut children = Vec::new();
1190 for mesh_data in meshes {
1191 children.push(geometry_node(mesh_data));
1192 }
1193 for model_data in models {
1194 children.push(model_node(model_data)?);
1195 }
1196 children.extend(models.iter().filter_map(node_attribute_node));
1197 for material_data in materials {
1198 children.push(material_node(material_data));
1199 }
1200 for texture_data in textures {
1201 children.push(texture_node(texture_data));
1202 children.push(video_node(texture_data));
1203 }
1204 for stack in anim {
1205 children.extend(animation_stack_nodes(stack));
1206 }
1207 for skin in skins {
1208 children.extend(skin_nodes(skin, models));
1209 }
1210 for morph in morphs {
1211 children.extend(morph_nodes(morph));
1212 }
1213
1214 Ok(FbxNode {
1215 name: "Objects".to_string(),
1216 properties: Vec::new(),
1217 children,
1218 })
1219}
1220
1221fn model_node(model_data: &ModelData) -> io::Result<FbxNode> {
1222 let mut properties = Vec::new();
1223 if let Some(transform) = model_data.transform {
1224 let as_f64 = |value: [f32; 3]| value.map(f64::from);
1225 if let Some(stack) = model_data.transform_stack.as_ref() {
1226 for (name, value) in [
1231 ("Lcl Translation", stack.translation),
1232 ("Lcl Rotation", stack.rotation),
1233 ("Lcl Scaling", stack.scaling),
1234 ] {
1235 if let Some(value) = value {
1236 properties.push(vec3_property_node(name, as_f64(value)));
1237 }
1238 }
1239 if let Some(value) = stack.rotation_order {
1240 properties.push(int_property_node("RotationOrder", "enum", "", "", value));
1241 }
1242 if let Some(value) = stack.rotation_active {
1243 properties.push(bool_property_node("RotationActive", value));
1244 }
1245 for (name, value) in [
1246 ("PreRotation", stack.pre_rotation),
1247 ("PostRotation", stack.post_rotation),
1248 ("RotationOffset", stack.rotation_offset),
1249 ("RotationPivot", stack.rotation_pivot),
1250 ("ScalingOffset", stack.scaling_offset),
1251 ("ScalingPivot", stack.scaling_pivot),
1252 ] {
1253 if let Some(value) = value {
1254 properties.push(vec3_property_node(name, as_f64(value)));
1255 }
1256 }
1257 if let Some(value) = stack.inherit_type {
1258 properties.push(int_property_node("InheritType", "enum", "", "", value));
1259 }
1260 } else {
1261 let (translation, rotation, scaling) = decompose_transform(transform)?;
1268 properties.push(vec3_property_node("Lcl Translation", translation));
1269 properties.push(vec3_property_node("Lcl Rotation", rotation));
1270 properties.push(vec3_property_node("Lcl Scaling", scaling));
1271 }
1272 }
1273
1274 Ok(FbxNode {
1275 name: "Model".to_string(),
1276 properties: vec![
1277 FbxProperty::I64(model_data.model_id),
1278 FbxProperty::String(name_class(&model_data.name, "Model")),
1279 FbxProperty::String(model_data.class.to_string()),
1280 ],
1281 children: vec![
1282 value_node("Version", FbxProperty::I32(232)),
1283 properties70_node(properties),
1284 value_node("Shading", FbxProperty::Bool(true)),
1289 value_node("Culling", FbxProperty::String("CullingOff".to_string())),
1290 ],
1291 })
1292}
1293
1294fn node_attribute_node(model_data: &ModelData) -> Option<FbxNode> {
1307 let id = node_attribute_id(model_data.model_id);
1308 let name = name_class(&model_data.name, "NodeAttribute");
1309
1310 let (class, children) = match &model_data.attribute {
1311 Some(FbxNodeAttribute::Camera(camera)) => {
1312 let mut properties = Vec::new();
1313 for (property_name, value) in [
1314 ("Position", camera.position),
1315 ("UpVector", camera.up_vector),
1316 ("InterestPosition", camera.interest_position),
1317 ] {
1318 if let Some(value) = value {
1319 properties.push(vector_property_node(property_name, value));
1320 }
1321 }
1322 if let Some(value) = camera.projection_type {
1323 properties.push(enum_property_node("CameraProjectionType", value));
1324 }
1325 for (property_name, value) in [
1326 ("FieldOfView", camera.field_of_view),
1327 ("FieldOfViewX", camera.field_of_view_x),
1328 ("FieldOfViewY", camera.field_of_view_y),
1329 ("FocalLength", camera.focal_length),
1330 ("OrthoZoom", camera.ortho_zoom),
1331 ("FilmWidth", camera.film_width),
1335 ("FilmHeight", camera.film_height),
1336 ] {
1337 if let Some(value) = value {
1338 properties.push(scalar_property_node(property_name, f64::from(value)));
1339 }
1340 }
1341 if let Some(value) = camera.aperture_mode {
1342 properties.push(enum_property_node("ApertureMode", value));
1343 }
1344 for (property_name, value) in [
1345 ("NearPlane", camera.near_plane),
1346 ("FarPlane", camera.far_plane),
1347 ("AspectWidth", camera.aspect_width),
1348 ("AspectHeight", camera.aspect_height),
1349 ("FilmAspectRatio", camera.film_aspect_ratio),
1350 ] {
1351 if let Some(value) = value {
1352 properties.push(f64_property_node(
1353 property_name,
1354 "double",
1355 "Number",
1356 "",
1357 f64::from(value),
1358 ));
1359 }
1360 }
1361 (
1362 "Camera",
1363 vec![
1364 properties70_node(properties),
1365 value_node("TypeFlags", FbxProperty::String("Camera".to_string())),
1366 value_node("GeometryVersion", FbxProperty::I32(124)),
1367 ],
1368 )
1369 }
1370 Some(FbxNodeAttribute::Light(light)) => {
1371 let mut properties = Vec::new();
1372 if let Some(value) = light.light_type {
1373 properties.push(enum_property_node("LightType", value));
1374 }
1375 if let Some(value) = light.cast_light {
1376 properties.push(bool_property_node("CastLight", value));
1377 }
1378 if let Some(value) = light.color {
1379 properties.push(color_property_node("Color", value));
1380 }
1381 if let Some(value) = light.intensity {
1382 properties.push(scalar_property_node("Intensity", f64::from(value)));
1383 }
1384 if let Some(value) = light.cast_shadows {
1385 properties.push(bool_property_node("CastShadows", value));
1386 }
1387 if let Some(value) = light.decay_type {
1388 properties.push(enum_property_node("DecayType", value));
1389 }
1390 if let Some(value) = light.decay_start {
1391 properties.push(f64_property_node(
1392 "DecayStart",
1393 "double",
1394 "Number",
1395 "",
1396 f64::from(value),
1397 ));
1398 }
1399 (
1400 "Light",
1401 vec![
1402 value_node("GeometryVersion", FbxProperty::I32(124)),
1403 properties70_node(properties),
1404 value_node("TypeFlags", FbxProperty::String("Light".to_string())),
1405 ],
1406 )
1407 }
1408 None if model_data.class == "LimbNode" => (
1413 "LimbNode",
1414 vec![value_node(
1415 "TypeFlags",
1416 FbxProperty::String("Skeleton".to_string()),
1417 )],
1418 ),
1419 None => return None,
1420 };
1421
1422 Some(FbxNode {
1423 name: "NodeAttribute".to_string(),
1424 properties: vec![
1425 FbxProperty::I64(id),
1426 FbxProperty::String(name),
1427 FbxProperty::String(class.to_string()),
1428 ],
1429 children,
1430 })
1431}
1432
1433fn node_attribute_id(model_id: i64) -> i64 {
1434 1_000_000_000i64.saturating_add(model_id)
1438}
1439
1440fn flatten_fbx_transform(transform: crate::fbx_scene::FbxTransform) -> Vec<f64> {
1444 transform
1445 .matrix
1446 .into_iter()
1447 .flatten()
1448 .map(f64::from)
1449 .collect()
1450}
1451
1452fn skin_nodes(skin: &SkinData, models: &[ModelData]) -> Vec<FbxNode> {
1459 let mut nodes = vec![FbxNode {
1460 name: "Deformer".to_string(),
1461 properties: vec![
1462 FbxProperty::I64(skin.skin_id),
1463 FbxProperty::String(name_class("Skin", "Deformer")),
1464 FbxProperty::String("Skin".to_string()),
1465 ],
1466 children: vec![
1467 value_node("Version", FbxProperty::I32(101)),
1468 value_node("Link_DeformAcuracy", FbxProperty::F64(50.0)),
1469 ],
1470 }];
1471
1472 for cluster in &skin.clusters {
1473 let source = &cluster.source;
1474 let mut children = vec![
1475 value_node("Version", FbxProperty::I32(100)),
1476 FbxNode {
1477 name: "UserData".to_string(),
1478 properties: vec![
1479 FbxProperty::String(String::new()),
1480 FbxProperty::String(String::new()),
1481 ],
1482 children: Vec::new(),
1483 },
1484 value_node(
1485 "Indexes",
1486 FbxProperty::I32Array(
1487 source
1488 .control_point_indices
1489 .iter()
1490 .map(|&index| index as i32)
1491 .collect(),
1492 ),
1493 ),
1494 value_node(
1495 "Weights",
1496 FbxProperty::F64Array(source.weights.iter().copied().map(f64::from).collect()),
1497 ),
1498 value_node(
1499 "Transform",
1500 FbxProperty::F64Array(flatten_fbx_transform(source.mesh_bind_transform)),
1501 ),
1502 value_node(
1503 "TransformLink",
1504 FbxProperty::F64Array(flatten_fbx_transform(source.joint_bind_transform)),
1505 ),
1506 ];
1507 if let Some(armature_bind_transform) = source.armature_bind_transform {
1508 children.push(value_node(
1509 "TransformAssociateModel",
1510 FbxProperty::F64Array(flatten_fbx_transform(armature_bind_transform)),
1511 ));
1512 }
1513 nodes.push(FbxNode {
1514 name: "Deformer".to_string(),
1515 properties: vec![
1516 FbxProperty::I64(cluster.cluster_id),
1517 FbxProperty::String(name_class("Cluster", "SubDeformer")),
1518 FbxProperty::String("Cluster".to_string()),
1519 ],
1520 children,
1521 });
1522 }
1523
1524 let mut pose_children = vec![
1525 value_node("Type", FbxProperty::String("BindPose".to_string())),
1526 value_node("Version", FbxProperty::I32(100)),
1527 ];
1528 for (node_id, transform) in &skin.bind_pose {
1529 let Some(model) = models
1530 .iter()
1531 .find(|model| model.scene_node_id == Some(*node_id))
1532 else {
1533 continue;
1534 };
1535 pose_children.push(FbxNode {
1536 name: "PoseNode".to_string(),
1537 properties: Vec::new(),
1538 children: vec![
1539 value_node("Node", FbxProperty::I64(model.model_id)),
1540 value_node(
1541 "Matrix",
1542 FbxProperty::F64Array(flatten_fbx_transform(*transform)),
1543 ),
1544 ],
1545 });
1546 }
1547 nodes.push(FbxNode {
1548 name: "Pose".to_string(),
1549 properties: vec![
1550 FbxProperty::I64(skin.pose_id),
1551 FbxProperty::String(name_class("BindPose", "Pose")),
1552 FbxProperty::String("BindPose".to_string()),
1553 ],
1554 children: pose_children,
1555 });
1556
1557 nodes
1558}
1559
1560fn morph_nodes(morph: &MorphData) -> Vec<FbxNode> {
1563 let mut nodes = vec![FbxNode {
1564 name: "Deformer".to_string(),
1565 properties: vec![
1566 FbxProperty::I64(morph.blend_shape_id),
1567 FbxProperty::String(name_class("BlendShape", "Deformer")),
1568 FbxProperty::String("BlendShape".to_string()),
1569 ],
1570 children: Vec::new(),
1571 }];
1572
1573 for target in &morph.targets {
1574 let source = &target.source;
1575 let name = source.name.as_deref().unwrap_or("MorphTarget");
1576 nodes.push(FbxNode {
1577 name: "Deformer".to_string(),
1578 properties: vec![
1579 FbxProperty::I64(target.channel_id),
1580 FbxProperty::String(name_class(name, "SubDeformer")),
1581 FbxProperty::String("BlendShapeChannel".to_string()),
1582 ],
1583 children: vec![
1584 value_node(
1585 "DeformPercent",
1586 FbxProperty::F64(source.default_weight as f64),
1587 ),
1588 value_node(
1589 "FullWeights",
1590 FbxProperty::F64Array(vec![source.full_weight as f64]),
1591 ),
1592 ],
1593 });
1594 nodes.push(FbxNode {
1595 name: "Geometry".to_string(),
1596 properties: vec![
1597 FbxProperty::I64(target.shape_geometry_id),
1598 FbxProperty::String(name_class(name, "Geometry")),
1599 FbxProperty::String("Shape".to_string()),
1600 ],
1601 children: vec![
1602 value_node(
1603 "Indexes",
1604 FbxProperty::I32Array(
1605 source
1606 .control_point_indices
1607 .iter()
1608 .map(|&index| index as i32)
1609 .collect(),
1610 ),
1611 ),
1612 value_node(
1613 "Vertices",
1614 FbxProperty::F64Array(
1615 source
1616 .position_deltas
1617 .iter()
1618 .flat_map(|delta| delta.iter().copied())
1619 .map(f64::from)
1620 .collect(),
1621 ),
1622 ),
1623 ],
1624 });
1625 }
1626
1627 nodes
1628}
1629
1630fn geometry_node(mesh_data: &MeshData) -> FbxNode {
1631 let mut children = vec![value_node("GeometryVersion", FbxProperty::I32(124))];
1632
1633 let vertices = mesh_data
1640 .control_points
1641 .as_deref()
1642 .unwrap_or(&mesh_data.vertices);
1643 children.push(value_node(
1644 "Vertices",
1645 FbxProperty::F64Array(vertices.to_vec()),
1646 ));
1647
1648 let polygon_indices = mesh_data
1649 .polygon_vertex_indices
1650 .as_deref()
1651 .unwrap_or(&mesh_data.indices);
1652 children.push(value_node(
1653 "PolygonVertexIndex",
1654 FbxProperty::I32Array(polygon_indices.to_vec()),
1655 ));
1656
1657 if mesh_data.normal_sets.is_empty() {
1659 if let Some(normals) = &mesh_data.normals {
1660 children.push(layer_element_normal_node(normals));
1661 }
1662 } else {
1663 for normal_set in &mesh_data.normal_sets {
1664 children.push(layer_element_normal_set_node(normal_set));
1665 }
1666 }
1667
1668 if !mesh_data.edges.is_empty() {
1671 children.push(value_node(
1672 "Edges",
1673 FbxProperty::I32Array(mesh_data.edges.clone()),
1674 ));
1675 }
1676
1677 for color_set in &mesh_data.color_sets {
1679 children.push(layer_element_color_set_node(color_set));
1680 }
1681
1682 for set in &mesh_data.tangent_sets {
1685 children.push(layer_element_tangent_set_node("LayerElementTangent", set));
1686 }
1687 for set in &mesh_data.binormal_sets {
1688 children.push(layer_element_tangent_set_node("LayerElementBinormal", set));
1689 }
1690
1691 for layer in &mesh_data.smoothing_layers {
1694 children.push(layer_element_smoothing_node(layer));
1695 }
1696 for layer in &mesh_data.crease_layers {
1697 children.push(layer_element_crease_node(layer));
1698 }
1699
1700 if mesh_data.uv_sets.is_empty() {
1702 if let Some(uvs) = &mesh_data.uvs {
1703 children.push(layer_element_uv_node(uvs));
1704 }
1705 } else {
1706 for uv_set in &mesh_data.uv_sets {
1707 children.push(layer_element_uv_set_node(uv_set));
1708 }
1709 }
1710
1711 if !mesh_data.material_indices.is_empty() {
1712 let per_polygon = collapse_material_indices_to_polygons(
1716 &mesh_data.material_indices,
1717 mesh_data.polygon_vertex_indices.as_deref(),
1718 );
1719 children.push(layer_element_material_node(&per_polygon));
1720 }
1721
1722 if let Some(layer) = layer_node(mesh_data) {
1723 children.push(layer);
1724 }
1725
1726 FbxNode {
1727 name: "Geometry".to_string(),
1728 properties: vec![
1729 FbxProperty::I64(mesh_data.geometry_id),
1730 FbxProperty::String(name_class(&mesh_data.name, "Geometry")),
1731 FbxProperty::String("Mesh".to_string()),
1732 ],
1733 children,
1734 }
1735}
1736
1737fn layer_node(mesh_data: &MeshData) -> Option<FbxNode> {
1744 let uses_layers = mesh_data.normals.is_some()
1745 || !mesh_data.normal_sets.is_empty()
1746 || mesh_data.uvs.is_some()
1747 || !mesh_data.uv_sets.is_empty()
1748 || !mesh_data.color_sets.is_empty()
1749 || !mesh_data.tangent_sets.is_empty()
1750 || !mesh_data.binormal_sets.is_empty()
1751 || !mesh_data.smoothing_layers.is_empty()
1752 || !mesh_data.crease_layers.is_empty()
1753 || !mesh_data.material_indices.is_empty();
1754 if !uses_layers {
1755 return None;
1756 }
1757
1758 let mut children = vec![value_node("Version", FbxProperty::I32(100))];
1759 if mesh_data.normal_sets.is_empty() && mesh_data.normals.is_some() {
1760 children.push(layer_element_node("LayerElementNormal", 0));
1761 } else {
1762 for index in 0..mesh_data.normal_sets.len() {
1763 children.push(layer_element_node("LayerElementNormal", index as i32));
1764 }
1765 }
1766 if mesh_data.uv_sets.is_empty() && mesh_data.uvs.is_some() {
1767 children.push(layer_element_node("LayerElementUV", 0));
1768 } else {
1769 for index in 0..mesh_data.uv_sets.len() {
1770 children.push(layer_element_node("LayerElementUV", index as i32));
1771 }
1772 }
1773 for index in 0..mesh_data.color_sets.len() {
1774 children.push(layer_element_node("LayerElementColor", index as i32));
1775 }
1776 for index in 0..mesh_data.tangent_sets.len() {
1777 children.push(layer_element_node("LayerElementTangent", index as i32));
1778 }
1779 for index in 0..mesh_data.binormal_sets.len() {
1780 children.push(layer_element_node("LayerElementBinormal", index as i32));
1781 }
1782 for index in 0..mesh_data.smoothing_layers.len() {
1783 children.push(layer_element_node("LayerElementSmoothing", index as i32));
1784 }
1785 for (index, layer) in mesh_data.crease_layers.iter().enumerate() {
1786 let element = match layer.kind {
1787 crate::fbx_scene::FbxCreaseKind::Edge => "LayerElementEdgeCrease",
1788 crate::fbx_scene::FbxCreaseKind::Vertex => "LayerElementVertexCrease",
1789 };
1790 children.push(layer_element_node(element, index as i32));
1791 }
1792 if !mesh_data.material_indices.is_empty() {
1793 children.push(layer_element_node("LayerElementMaterial", 0));
1794 }
1795
1796 Some(FbxNode {
1797 name: "Layer".to_string(),
1798 properties: Vec::new(),
1799 children,
1800 })
1801}
1802
1803fn value_node(name: &str, value: FbxProperty) -> FbxNode {
1805 FbxNode {
1806 name: name.to_string(),
1807 properties: vec![value],
1808 children: Vec::new(),
1809 }
1810}
1811
1812fn layer_element_node(type_name: &str, index: i32) -> FbxNode {
1815 FbxNode {
1816 name: "LayerElement".to_string(),
1817 properties: Vec::new(),
1818 children: vec![
1819 value_node("Type", FbxProperty::String(type_name.to_string())),
1820 value_node("TypedIndex", FbxProperty::I32(index)),
1821 ],
1822 }
1823}
1824
1825fn layer_header(layer_name: &str, mapping: &str, reference: &str) -> Vec<FbxNode> {
1830 vec![
1831 value_node("Version", FbxProperty::I32(101)),
1832 value_node("Name", FbxProperty::String(layer_name.to_string())),
1833 value_node(
1834 "MappingInformationType",
1835 FbxProperty::String(mapping.to_string()),
1836 ),
1837 value_node(
1838 "ReferenceInformationType",
1839 FbxProperty::String(reference.to_string()),
1840 ),
1841 ]
1842}
1843
1844fn flatten_f64<const N: usize>(values: &[[f32; N]], components: usize) -> Vec<f64> {
1846 values
1847 .iter()
1848 .flat_map(|value| value[..components].iter().map(|c| f64::from(*c)))
1849 .collect()
1850}
1851
1852fn index_array_node(name: &str, reference: Option<&str>, indices: &[i32]) -> Option<FbxNode> {
1855 (reference == Some("IndexToDirect") && !indices.is_empty())
1856 .then(|| value_node(name, FbxProperty::I32Array(indices.to_vec())))
1857}
1858
1859fn layer_element_normal_node(normals: &[f64]) -> FbxNode {
1860 let mut children = layer_header("", "ByVertice", "Direct");
1861 children.push(value_node(
1862 "Normals",
1863 FbxProperty::F64Array(normals.to_vec()),
1864 ));
1865 FbxNode {
1866 name: "LayerElementNormal".to_string(),
1867 properties: Vec::new(),
1868 children,
1869 }
1870}
1871
1872fn layer_element_normal_set_node(set: &crate::fbx_scene::FbxNormalSet) -> FbxNode {
1873 let mut children = layer_header(
1874 set.name.as_deref().unwrap_or("NormalSet0"),
1875 set.mapping.as_deref().unwrap_or("ByPolygonVertex"),
1876 set.reference.as_deref().unwrap_or("Direct"),
1877 );
1878 children.push(value_node(
1879 "Normals",
1880 FbxProperty::F64Array(flatten_f64(&set.values, 3)),
1881 ));
1882 children.extend(index_array_node(
1883 "NormalIndex",
1884 set.reference.as_deref(),
1885 &set.indices,
1886 ));
1887 FbxNode {
1888 name: "LayerElementNormal".to_string(),
1889 properties: Vec::new(),
1890 children,
1891 }
1892}
1893
1894fn layer_element_uv_node(uvs: &[f64]) -> FbxNode {
1895 let mut children = layer_header("", "ByVertice", "Direct");
1896 children.push(value_node("UV", FbxProperty::F64Array(uvs.to_vec())));
1897 FbxNode {
1898 name: "LayerElementUV".to_string(),
1899 properties: Vec::new(),
1900 children,
1901 }
1902}
1903
1904fn layer_element_uv_set_node(set: &crate::fbx_scene::FbxUvSet) -> FbxNode {
1905 let mut children = layer_header(
1906 set.name.as_deref().unwrap_or("UVSet0"),
1907 set.mapping.as_deref().unwrap_or("ByPolygonVertex"),
1908 set.reference.as_deref().unwrap_or("IndexToDirect"),
1909 );
1910 children.push(value_node(
1911 "UV",
1912 FbxProperty::F64Array(flatten_f64(&set.values, 2)),
1913 ));
1914 children.extend(index_array_node(
1915 "UVIndex",
1916 set.reference.as_deref(),
1917 &set.indices,
1918 ));
1919 FbxNode {
1920 name: "LayerElementUV".to_string(),
1921 properties: Vec::new(),
1922 children,
1923 }
1924}
1925
1926fn layer_element_color_set_node(set: &crate::fbx_scene::FbxColorSet) -> FbxNode {
1927 let mut children = layer_header(
1928 set.name.as_deref().unwrap_or("Col"),
1929 set.mapping.as_deref().unwrap_or("ByPolygonVertex"),
1930 set.reference.as_deref().unwrap_or("Direct"),
1931 );
1932 children.push(value_node(
1933 "Colors",
1934 FbxProperty::F64Array(flatten_f64(&set.values, 4)),
1935 ));
1936 children.extend(index_array_node(
1937 "ColorIndex",
1938 set.reference.as_deref(),
1939 &set.indices,
1940 ));
1941 FbxNode {
1942 name: "LayerElementColor".to_string(),
1943 properties: Vec::new(),
1944 children,
1945 }
1946}
1947
1948fn layer_element_tangent_set_node(element: &str, set: &crate::fbx_scene::FbxTangentSet) -> FbxNode {
1954 let (values_node, handedness_node, index_node) = if element == "LayerElementBinormal" {
1955 ("Binormals", "BinormalsW", "BinormalIndex")
1956 } else {
1957 ("Tangents", "TangentsW", "TangentIndex")
1958 };
1959 let mut children = layer_header(
1960 set.layer.name.as_deref().unwrap_or(""),
1961 set.layer.mapping.as_deref().unwrap_or("ByPolygonVertex"),
1962 set.layer.reference.as_deref().unwrap_or("Direct"),
1963 );
1964 children.push(value_node(
1965 values_node,
1966 FbxProperty::F64Array(flatten_f64(&set.layer.values, 3)),
1967 ));
1968 if set.has_handedness {
1969 let signs: Vec<f64> = set
1970 .layer
1971 .values
1972 .iter()
1973 .map(|value| f64::from(value[3]))
1974 .collect();
1975 children.push(value_node(handedness_node, FbxProperty::F64Array(signs)));
1976 }
1977 children.extend(index_array_node(
1978 index_node,
1979 set.layer.reference.as_deref(),
1980 &set.layer.indices,
1981 ));
1982 FbxNode {
1983 name: element.to_string(),
1984 properties: Vec::new(),
1985 children,
1986 }
1987}
1988
1989fn layer_element_smoothing_node(layer: &crate::fbx_scene::FbxSmoothingLayer) -> FbxNode {
1994 FbxNode {
1995 name: "LayerElementSmoothing".to_string(),
1996 properties: Vec::new(),
1997 children: vec![
1998 value_node("Version", FbxProperty::I32(102)),
1999 value_node("Name", FbxProperty::String(String::new())),
2000 value_node(
2001 "MappingInformationType",
2002 FbxProperty::String(
2003 layer
2004 .mapping
2005 .clone()
2006 .unwrap_or_else(|| "ByEdge".to_string()),
2007 ),
2008 ),
2009 value_node(
2010 "ReferenceInformationType",
2011 FbxProperty::String("Direct".to_string()),
2012 ),
2013 value_node("Smoothing", FbxProperty::I32Array(layer.values.clone())),
2014 ],
2015 }
2016}
2017
2018fn layer_element_crease_node(layer: &crate::fbx_scene::FbxCreaseLayer) -> FbxNode {
2021 let (element, data_node, default_mapping) = match layer.kind {
2022 crate::fbx_scene::FbxCreaseKind::Edge => ("LayerElementEdgeCrease", "EdgeCrease", "ByEdge"),
2023 crate::fbx_scene::FbxCreaseKind::Vertex => {
2024 ("LayerElementVertexCrease", "VertexCrease", "ByVertice")
2025 }
2026 };
2027 let mut children = layer_header(
2028 "",
2029 layer.mapping.as_deref().unwrap_or(default_mapping),
2030 "Direct",
2031 );
2032 children.push(value_node(
2033 data_node,
2034 FbxProperty::F64Array(layer.values.clone()),
2035 ));
2036 FbxNode {
2037 name: element.to_string(),
2038 properties: Vec::new(),
2039 children,
2040 }
2041}
2042
2043fn collapse_material_indices_to_polygons(
2054 material_indices: &[i32],
2055 polygon_vertex_indices: Option<&[i32]>,
2056) -> Vec<i32> {
2057 let Some(polygons) = polygon_vertex_indices else {
2058 return material_indices.to_vec();
2059 };
2060
2061 let mut per_polygon = Vec::new();
2062 let mut triangle = 0usize;
2063 let mut corners = 0usize;
2064 for &encoded in polygons {
2065 corners += 1;
2066 if encoded >= 0 {
2067 continue;
2068 }
2069 let triangles = corners.saturating_sub(2);
2072 per_polygon.push(material_indices.get(triangle).copied().unwrap_or(0));
2073 triangle += triangles;
2074 corners = 0;
2075 }
2076 per_polygon
2077}
2078
2079fn layer_element_material_node(material_indices: &[i32]) -> FbxNode {
2081 let mut children = layer_header("", "ByPolygon", "IndexToDirect");
2082 children.push(value_node(
2083 "Materials",
2084 FbxProperty::I32Array(material_indices.to_vec()),
2085 ));
2086 FbxNode {
2087 name: "LayerElementMaterial".to_string(),
2088 properties: Vec::new(),
2089 children,
2090 }
2091}
2092
2093fn material_node(material_data: &MaterialData) -> FbxNode {
2094 let declared_shading = material_data.source.shading_model.as_deref();
2098 let shading = declared_shading.unwrap_or("Phong");
2099
2100 let mut properties = Vec::new();
2101 if let Some(shading) = declared_shading {
2102 properties.push(string_property_node("ShadingModel", shading));
2103 }
2104 let source = &material_data.source;
2107 for property in [
2108 source
2109 .diffuse
2110 .map(|v| color_property_node("DiffuseColor", v)),
2111 source
2112 .diffuse_factor
2113 .map(|v| scalar_property_node("DiffuseFactor", v as f64)),
2114 source
2115 .specular
2116 .map(|v| color_property_node("SpecularColor", v)),
2117 source
2118 .specular_factor
2119 .map(|v| scalar_property_node("SpecularFactor", v as f64)),
2120 source
2121 .shininess
2122 .map(|v| scalar_property_node("Shininess", v as f64)),
2123 source
2124 .emissive
2125 .map(|v| color_property_node("EmissiveColor", v)),
2126 source
2127 .emissive_factor
2128 .map(|v| scalar_property_node("EmissiveFactor", v as f64)),
2129 source
2130 .ambient
2131 .map(|v| color_property_node("AmbientColor", v)),
2132 source
2133 .reflection_factor
2134 .map(|v| scalar_property_node("ReflectionFactor", v as f64)),
2135 source
2136 .transparency_factor
2137 .map(|v| scalar_property_node("TransparencyFactor", v as f64)),
2138 source
2139 .opacity
2140 .map(|v| scalar_property_node("Opacity", v as f64)),
2141 source
2142 .bump_factor
2143 .map(|v| scalar_property_node("BumpFactor", v as f64)),
2144 ] {
2145 properties.extend(property);
2146 }
2147
2148 let name = source
2149 .name
2150 .clone()
2151 .unwrap_or_else(|| "Material".to_string());
2152 FbxNode {
2153 name: "Material".to_string(),
2154 properties: vec![
2155 FbxProperty::I64(material_data.material_id),
2156 FbxProperty::String(name_class(&name, "Material")),
2157 FbxProperty::String(String::new()),
2158 ],
2159 children: vec![
2160 value_node("Version", FbxProperty::I32(102)),
2161 properties70_node(properties),
2162 value_node("ShadingModel", FbxProperty::String(shading.to_string())),
2164 ],
2165 }
2166}
2167
2168fn texture_node(texture_data: &TextureData) -> FbxNode {
2169 let name = texture_data.source.name.clone().unwrap_or_default();
2174 let filename = texture_data.source.filename.clone().unwrap_or_default();
2175 FbxNode {
2176 name: "Texture".to_string(),
2177 properties: vec![
2178 FbxProperty::I64(texture_data.texture_id),
2179 FbxProperty::String(name_class(&name, "Texture")),
2180 FbxProperty::String(String::new()),
2181 ],
2182 children: vec![
2183 value_node("Media", FbxProperty::String(name)),
2184 value_node("FileName", FbxProperty::String(filename.clone())),
2185 value_node("RelativeFilename", FbxProperty::String(filename)),
2186 ],
2187 }
2188}
2189
2190fn video_node(texture_data: &TextureData) -> FbxNode {
2191 let name = texture_data.source.name.clone().unwrap_or_default();
2193 let filename = texture_data.source.filename.clone().unwrap_or_default();
2194 let mut children = vec![
2195 value_node("Filename", FbxProperty::String(filename.clone())),
2196 value_node("RelativeFilename", FbxProperty::String(filename)),
2197 ];
2198 if let Some(content) = &texture_data.source.content {
2199 children.push(value_node("Content", FbxProperty::Raw(content.clone())));
2200 }
2201 FbxNode {
2202 name: "Video".to_string(),
2203 properties: vec![
2204 FbxProperty::I64(texture_data.video_id),
2205 FbxProperty::String(name_class(&name, "Video")),
2206 FbxProperty::String("Clip".to_string()),
2207 ],
2208 children,
2209 }
2210}
2211
2212fn animation_stack_nodes(stack: &AnimStackData) -> Vec<FbxNode> {
2215 const KTIME: i64 = 46_186_158_000;
2217 let stop = (stack.duration.max(0.0) as f64 * KTIME as f64) as i64;
2218 let name = stack
2219 .name
2220 .clone()
2221 .unwrap_or_else(|| "AnimStack".to_string());
2222
2223 let mut nodes = vec![
2224 FbxNode {
2225 name: "AnimationStack".to_string(),
2226 properties: vec![
2227 FbxProperty::I64(stack.stack_id),
2228 FbxProperty::String(name_class(&name, "AnimStack")),
2229 FbxProperty::String(String::new()),
2230 ],
2231 children: vec![properties70_node(vec![timestamp_property_node(
2232 "LocalStop",
2233 stop,
2234 )])],
2235 },
2236 FbxNode {
2237 name: "AnimationLayer".to_string(),
2238 properties: vec![
2239 FbxProperty::I64(stack.layer_id),
2240 FbxProperty::String(name_class(&name, "AnimLayer")),
2241 FbxProperty::String(String::new()),
2242 ],
2243 children: Vec::new(),
2244 },
2245 ];
2246 for channel in &stack.channels {
2247 nodes.extend(animation_curve_nodes(stack.stack_id, channel));
2248 }
2249 nodes
2250}
2251
2252fn animation_curve_nodes(
2254 stack_id: i64,
2255 channel: &crate::fbx_scene::FbxAnimChannel,
2256) -> Vec<FbxNode> {
2257 let id = anim_object_id(
2260 stack_id,
2261 channel.node_id,
2262 channel.path,
2263 channel.morph_target_index,
2264 "node",
2265 );
2266
2267 let defaults: Vec<FbxNode> = ["d|X", "d|Y", "d|Z"]
2270 .into_iter()
2271 .take(channel.path.component_count())
2272 .map(|suffix| scalar_property_node(suffix, 0.0))
2273 .collect();
2274
2275 let mut nodes = vec![FbxNode {
2276 name: "AnimationCurveNode".to_string(),
2277 properties: vec![
2278 FbxProperty::I64(id),
2279 FbxProperty::String(name_class("", "AnimCurveNode")),
2280 FbxProperty::String(String::new()),
2281 ],
2282 children: vec![properties70_node(defaults)],
2283 }];
2284
2285 for component in 0u32..channel.path.component_count() as u32 {
2287 nodes.push(animation_curve_node(id, component, channel));
2288 }
2289 nodes
2290}
2291
2292fn animation_curve_node(
2293 curve_node_id: i64,
2294 component: u32,
2295 channel: &crate::fbx_scene::FbxAnimChannel,
2296) -> FbxNode {
2297 const KTIME: f64 = 46_186_158_000.0;
2298 let components = channel.path.component_count();
2299 let keys = channel.sampler.input.len();
2300
2301 let mut key_times = Vec::with_capacity(keys);
2302 let mut key_values = Vec::with_capacity(keys);
2303 for key in 0..keys {
2304 key_times.push((channel.sampler.input[key] as f64 * KTIME) as i64);
2305 let value_index = key * components + component as usize;
2306 key_values.push(
2307 channel
2308 .sampler
2309 .output
2310 .get(value_index)
2311 .copied()
2312 .unwrap_or(0.0),
2313 );
2314 }
2315
2316 let scale = if channel.path == crate::fbx_scene::FbxAnimChannelPath::Rotation {
2324 180.0 / std::f64::consts::PI
2325 } else {
2326 1.0
2327 };
2328 let degrees = |value: f32| (f64::from(value) * scale) as f32;
2329
2330 let flags_value = channel.sampler.interpolation.to_key_attr_flags();
2334 let cubic = channel.sampler.interpolation == crate::fbx_scene::FbxAnimInterpolation::Cubic;
2335 let flags = if cubic {
2336 vec![flags_value; keys]
2337 } else {
2338 vec![flags_value]
2339 };
2340
2341 let in_tangents = channel.sampler.in_tangents.as_deref();
2342 let out_tangents = channel.sampler.out_tangents.as_deref();
2343 let mut datafloat = Vec::with_capacity(if cubic { keys * 4 } else { 4 });
2344 for key in 0..if cubic { keys } else { 1 } {
2345 let component_index = key * components + component as usize;
2346 let right = degrees(
2347 out_tangents
2348 .and_then(|values| values.get(component_index))
2349 .copied()
2350 .unwrap_or(0.0),
2351 );
2352 let next_left = degrees(
2353 in_tangents
2354 .and_then(|values| values.get(component_index + components))
2355 .copied()
2356 .unwrap_or(0.0),
2357 );
2358 datafloat.extend([right, next_left, 0.0, 0.0]);
2359 }
2360
2361 let refcount = if cubic {
2362 vec![1; keys]
2363 } else {
2364 vec![keys as i32]
2365 };
2366
2367 FbxNode {
2368 name: "AnimationCurve".to_string(),
2369 properties: vec![
2370 FbxProperty::I64(anim_curve_id(curve_node_id, component)),
2371 FbxProperty::String(name_class("", "AnimCurve")),
2372 FbxProperty::String(String::new()),
2373 ],
2374 children: vec![
2375 value_node("Default", FbxProperty::F64(0.0)),
2376 value_node("KeyVer", FbxProperty::I32(4009)),
2377 value_node("KeyTime", FbxProperty::I64Array(key_times)),
2378 value_node(
2379 "KeyValueFloat",
2380 FbxProperty::F32Array(key_values.iter().copied().map(degrees).collect()),
2381 ),
2382 value_node("KeyAttrFlags", FbxProperty::I32Array(flags)),
2383 value_node("KeyAttrDataFloat", FbxProperty::F32Array(datafloat)),
2384 value_node("KeyAttrRefCount", FbxProperty::I32Array(refcount)),
2385 ],
2386 }
2387}
2388
2389fn anim_object_id(
2391 stack_id: i64,
2392 node_id: crate::fbx_scene::FbxNodeId,
2393 path: crate::fbx_scene::FbxAnimChannelPath,
2394 morph_target_index: Option<u32>,
2395 kind: &str,
2396) -> i64 {
2397 let mut hash: u64 = 2_000_000;
2400 hash = hash.wrapping_mul(131).wrapping_add(stack_id as u64);
2401 hash = hash.wrapping_mul(131).wrapping_add(node_id.0 as u64);
2402 hash = hash
2403 .wrapping_mul(131)
2404 .wrapping_add(path.property_name().len() as u64);
2405 hash = hash
2406 .wrapping_mul(131)
2407 .wrapping_add(u64::from(morph_target_index.unwrap_or(0)));
2408 hash = hash.wrapping_mul(131).wrapping_add(kind.len() as u64);
2409 hash as i64
2410}
2411
2412fn anim_curve_id(curve_node_id: i64, component: u32) -> i64 {
2413 curve_node_id
2414 .wrapping_mul(131)
2415 .wrapping_add((component as i64) + 1_000_000)
2416}
2417
2418#[allow(clippy::too_many_arguments)]
2420fn connections_node(
2421 models: &[ModelData],
2422 meshes: &[MeshData],
2423 textures: &[TextureData],
2424 anim: &[AnimStackData],
2425 pending: &[PendingConnection],
2426 skins: &[SkinData],
2427 morphs: &[MorphData],
2428) -> FbxNode {
2429 let mut edges = Vec::new();
2430 {
2431 for model_data in models {
2433 edges.push(connection_node(
2434 "OO",
2435 model_data.model_id,
2436 model_data.parent_id.unwrap_or(0),
2437 None,
2438 ));
2439 if model_data.has_attribute() {
2440 edges.push(connection_node(
2441 "OO",
2442 node_attribute_id(model_data.model_id),
2443 model_data.model_id,
2444 None,
2445 ));
2446 }
2447 }
2448 for mesh_data in meshes {
2450 edges.push(connection_node(
2451 "OO",
2452 mesh_data.geometry_id,
2453 mesh_data.model_id,
2454 None,
2455 ));
2456 }
2457 for model_data in models {
2460 for &material_id in &model_data.material_ids {
2461 edges.push(connection_node(
2462 "OO",
2463 material_id,
2464 model_data.model_id,
2465 None,
2466 ));
2467 }
2468 }
2469 for morph in morphs {
2470 edges.push(connection_node(
2471 "OO",
2472 morph.blend_shape_id,
2473 morph.geometry_id,
2474 None,
2475 ));
2476 for target in &morph.targets {
2477 edges.push(connection_node(
2478 "OO",
2479 target.channel_id,
2480 morph.blend_shape_id,
2481 None,
2482 ));
2483 edges.push(connection_node(
2484 "OO",
2485 target.shape_geometry_id,
2486 target.channel_id,
2487 None,
2488 ));
2489 }
2490 }
2491 for texture_data in textures {
2493 edges.push(connection_node(
2494 "OO",
2495 texture_data.video_id,
2496 texture_data.texture_id,
2497 None,
2498 ));
2499 }
2500 for conn in pending {
2501 edges.push(connection_node(
2502 conn.kind,
2503 conn.child,
2504 conn.parent,
2505 conn.property.as_deref(),
2506 ));
2507 }
2508 for skin in skins {
2512 edges.push(connection_node("OO", skin.skin_id, skin.geometry_id, None));
2513 for cluster in &skin.clusters {
2514 edges.push(connection_node(
2515 "OO",
2516 cluster.cluster_id,
2517 skin.skin_id,
2518 None,
2519 ));
2520 if let Some(joint) = models
2521 .iter()
2522 .find(|model| model.scene_node_id == Some(cluster.source.joint_node_id))
2523 {
2524 edges.push(connection_node(
2525 "OO",
2526 joint.model_id,
2527 cluster.cluster_id,
2528 None,
2529 ));
2530 }
2531 }
2532 }
2533 for stack in anim {
2537 edges.push(connection_node("OO", stack.stack_id, 0, None));
2538 edges.push(connection_node("OO", stack.layer_id, stack.stack_id, None));
2539 for channel in &stack.channels {
2540 let acnode_id = anim_object_id(
2541 stack.stack_id,
2542 channel.node_id,
2543 channel.path,
2544 channel.morph_target_index,
2545 "node",
2546 );
2547 edges.push(connection_node("OO", acnode_id, stack.layer_id, None));
2548 let scene_model_id = models
2550 .iter()
2551 .find(|m| m.scene_node_id == Some(channel.node_id))
2552 .map(|model| model.model_id);
2553 let target = if channel.path == crate::fbx_scene::FbxAnimChannelPath::MorphWeight {
2554 channel.morph_target_index.and_then(|target_index| {
2555 morphs.iter().find_map(|morph| {
2556 if Some(morph.model_id) != scene_model_id {
2557 return None;
2558 }
2559 morph
2560 .targets
2561 .get(target_index as usize)
2562 .map(|target| (target.channel_id, true))
2563 })
2564 })
2565 } else {
2566 scene_model_id.map(|model_id| (model_id, false))
2567 };
2568 if let Some((target_id, _is_morph)) = target {
2569 edges.push(connection_node(
2570 "OP",
2571 acnode_id,
2572 target_id,
2573 Some(channel.path.property_name()),
2574 ));
2575 }
2576 for component in 0u32..channel.path.component_count() as u32 {
2577 let curve_id = anim_curve_id(acnode_id, component);
2578 let suffix = match component {
2579 0 => "d|X",
2580 1 => "d|Y",
2581 _ => "d|Z",
2582 };
2583 edges.push(connection_node("OP", curve_id, acnode_id, Some(suffix)));
2584 }
2585 }
2586 }
2587 }
2588
2589 FbxNode {
2590 name: "Connections".to_string(),
2591 properties: Vec::new(),
2592 children: edges,
2593 }
2594}
2595
2596fn connection_node(kind: &str, child: i64, parent: i64, property: Option<&str>) -> FbxNode {
2598 let mut properties = vec![
2599 FbxProperty::String(kind.to_string()),
2600 FbxProperty::I64(child),
2601 FbxProperty::I64(parent),
2602 ];
2603 if let Some(property) = property {
2604 properties.push(FbxProperty::String(property.to_string()));
2605 }
2606 FbxNode {
2607 name: "C".to_string(),
2608 properties,
2609 children: Vec::new(),
2610 }
2611}
2612
2613fn color_property_node(name: &str, values: [f32; 3]) -> FbxNode {
2614 property_node(
2615 name,
2616 "Color",
2617 "",
2618 "A",
2619 values
2620 .into_iter()
2621 .map(|value| FbxProperty::F64(value as f64))
2622 .collect(),
2623 )
2624}
2625
2626fn scalar_property_node(name: &str, value: f64) -> FbxNode {
2627 property_node(name, "Number", "", "A", vec![FbxProperty::F64(value)])
2628}
2629
2630fn string_property_node(name: &str, value: &str) -> FbxNode {
2631 property_node(
2632 name,
2633 "KString",
2634 "",
2635 "A",
2636 vec![FbxProperty::String(value.to_string())],
2637 )
2638}
2639
2640fn timestamp_property_node(name: &str, value: i64) -> FbxNode {
2641 property_node(name, "KTime", "Time", "", vec![FbxProperty::I64(value)])
2642}
2643
2644fn validate_supported_fbx_attributes(mesh: &Mesh) -> io::Result<()> {
2649 for i in 0..mesh.num_attributes() {
2650 let attribute_type = mesh.attribute(i).attribute_type();
2651 match attribute_type {
2652 GeometryAttributeType::Position
2653 | GeometryAttributeType::Normal
2654 | GeometryAttributeType::TexCoord
2655 | GeometryAttributeType::Color => {}
2656 _ => {
2657 return Err(io::Error::new(
2658 io::ErrorKind::InvalidInput,
2659 format!(
2660 "FBX writer currently supports only Position, Normal, TexCoord and Color attributes; {:?} is not written",
2661 attribute_type
2662 ),
2663 ));
2664 }
2665 }
2666 }
2667 Ok(())
2668}
2669
2670fn extract_vertices(mesh: &Mesh) -> Vec<f64> {
2671 let pos_att_id = mesh.named_attribute_id(GeometryAttributeType::Position);
2672 if pos_att_id < 0 {
2673 return Vec::new();
2674 }
2675
2676 let att = mesh.attribute(pos_att_id);
2677 let byte_stride = att.byte_stride() as usize;
2678 let buffer = att.buffer();
2679 let mut vertices = Vec::with_capacity(mesh.num_points() * 3);
2680
2681 for i in 0..mesh.num_points() {
2682 let mut bytes = [0u8; 12];
2683 buffer.read(i * byte_stride, &mut bytes);
2684 let x = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as f64;
2685 let y = f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]) as f64;
2686 let z = f32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]) as f64;
2687 vertices.push(x);
2688 vertices.push(y);
2689 vertices.push(z);
2690 }
2691 vertices
2692}
2693
2694fn extract_polygon_indices(mesh: &Mesh) -> Vec<i32> {
2695 let mut indices = Vec::with_capacity(mesh.num_faces() * 3);
2696 for i in 0..mesh.num_faces() as u32 {
2697 let face = mesh.face(FaceIndex(i));
2698 indices.push(face[0].0 as i32);
2699 indices.push(face[1].0 as i32);
2700 indices.push(!(face[2].0 as i32));
2702 }
2703 indices
2704}
2705
2706fn extract_vec3_attribute(mesh: &Mesh, attribute_type: GeometryAttributeType) -> Option<Vec<f64>> {
2708 let id = mesh.named_attribute_id(attribute_type);
2709 if id < 0 {
2710 return None;
2711 }
2712 let att = mesh.attribute(id);
2713 let stride = att.byte_stride() as usize;
2714 let buffer = att.buffer();
2715 let mut values = Vec::with_capacity(mesh.num_points() * 3);
2716 for i in 0..mesh.num_points() {
2717 let mut bytes = [0u8; 12];
2718 buffer.read(i * stride, &mut bytes);
2719 for c in 0..3 {
2720 values.push(f32::from_le_bytes([
2721 bytes[c * 4],
2722 bytes[c * 4 + 1],
2723 bytes[c * 4 + 2],
2724 bytes[c * 4 + 3],
2725 ]) as f64);
2726 }
2727 }
2728 Some(values)
2729}
2730
2731fn extract_normals(mesh: &Mesh) -> Option<Vec<f64>> {
2732 extract_vec3_attribute(mesh, GeometryAttributeType::Normal)
2733}
2734
2735fn extract_uvs(mesh: &Mesh) -> Option<Vec<f64>> {
2736 let id = mesh.named_attribute_id(GeometryAttributeType::TexCoord);
2737 if id < 0 {
2738 return None;
2739 }
2740 let att = mesh.attribute(id);
2741 let stride = att.byte_stride() as usize;
2742 let buffer = att.buffer();
2743 let mut values = Vec::with_capacity(mesh.num_points() * 2);
2744 for i in 0..mesh.num_points() {
2745 let mut bytes = [0u8; 8];
2746 buffer.read(i * stride, &mut bytes);
2747 let u = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as f64;
2748 let v = f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]) as f64;
2749 values.push(u);
2750 values.push(v);
2751 }
2752 Some(values)
2753}
2754
2755#[cfg(test)]
2760mod tests {
2761 use super::*;
2762 #[cfg(feature = "fbx-reader")]
2764 use crate::fbx_scene::{
2765 FbxAnimation, FbxMeshInstance, FbxMeshLayers, FbxScene, FbxSceneNode, FbxTransform,
2766 FbxTransformStack,
2767 };
2768 use draco_core::draco_types::DataType;
2769 use draco_core::geometry_attribute::PointAttribute;
2770 use draco_core::geometry_indices::PointIndex;
2771 use std::io::Cursor;
2772 use tempfile::NamedTempFile;
2773
2774 fn create_triangle_mesh() -> Mesh {
2775 let mut mesh = Mesh::new();
2776 let mut pos_att = PointAttribute::new();
2777
2778 pos_att.init(
2779 GeometryAttributeType::Position,
2780 3,
2781 DataType::Float32,
2782 false,
2783 3,
2784 );
2785 let buffer = pos_att.buffer_mut();
2786 let positions: [[f32; 3]; 3] = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
2787 for (i, pos) in positions.iter().enumerate() {
2788 let bytes: Vec<u8> = pos.iter().flat_map(|v| v.to_le_bytes()).collect();
2789 buffer.write(i * 12, &bytes);
2790 }
2791 mesh.add_attribute(pos_att);
2792
2793 mesh.set_num_faces(1);
2794 mesh.set_face(FaceIndex(0), [PointIndex(0), PointIndex(1), PointIndex(2)]);
2795
2796 mesh
2797 }
2798
2799 fn child<'a>(node: &'a FbxNode, name: &str) -> Option<&'a FbxNode> {
2801 node.children.iter().find(|child| child.name == name)
2802 }
2803
2804 #[test]
2812 fn the_document_declares_the_objects_it_writes() {
2813 let mut writer = FbxWriter::new();
2814 writer
2815 .add_mesh(&create_triangle_mesh(), Some("Tri"))
2816 .unwrap();
2817 let document = writer.build_document().unwrap();
2818
2819 let names: Vec<&str> = document.iter().map(|node| node.name.as_str()).collect();
2820 assert_eq!(
2821 names,
2822 [
2823 "FBXHeaderExtension",
2824 "GlobalSettings",
2825 "Documents",
2826 "Definitions",
2827 "Objects",
2828 "Connections",
2829 ]
2830 );
2831
2832 let definitions = &document[3];
2835 let objects = &document[4];
2836 let declared: Vec<(String, i32)> = definitions
2837 .children
2838 .iter()
2839 .filter(|node| node.name == "ObjectType")
2840 .map(|node| {
2841 let FbxProperty::String(type_name) = &node.properties[0] else {
2842 panic!("ObjectType names itself with a string");
2843 };
2844 let count = child(node, "Count").expect("every ObjectType declares a Count");
2845 let FbxProperty::I32(count) = count.properties[0] else {
2846 panic!("Count is an i32");
2847 };
2848 (type_name.clone(), count)
2849 })
2850 .collect();
2851 assert!(declared.contains(&("Geometry".to_string(), 1)));
2852 assert!(declared.contains(&("Model".to_string(), 1)));
2853 assert_eq!(
2854 child(definitions, "Count").map(|node| format!("{:?}", node.properties)),
2855 Some(format!(
2856 "{:?}",
2857 vec![FbxProperty::I32(declared.len() as i32)]
2858 )),
2859 "the Count node must be the number of ObjectType blocks"
2860 );
2861
2862 let geometry = child(objects, "Geometry").expect("the mesh writes a Geometry");
2863
2864 assert!(
2865 matches!(&geometry.properties[2], FbxProperty::String(class) if class == "Mesh"),
2866 "a Geometry's class suffix is Mesh: {:?}",
2867 geometry.properties
2868 );
2869 assert!(child(geometry, "Vertices").is_some());
2870 assert!(child(geometry, "PolygonVertexIndex").is_some());
2871 }
2872
2873 #[cfg(feature = "fbx-reader")]
2883 #[test]
2884 fn an_empty_mesh_is_still_written_as_a_geometry() {
2885 let scene = FbxScene {
2886 global_settings: None,
2887 root_nodes: vec![FbxSceneNode {
2888 id: crate::fbx_scene::FbxNodeId(1),
2889 name: Some("Empty".to_string()),
2890 transform: None,
2891 transform_stack: None,
2892 has_complex_transform_stack: false,
2893 mesh_instances: vec![FbxMeshInstance {
2894 name: Some("Nothing".to_string()),
2895 mesh: Mesh::new(),
2896 ..Default::default()
2897 }],
2898 attribute: None,
2899 children: Vec::new(),
2900 }],
2901 materials: Vec::new(),
2902 textures: Vec::new(),
2903 animations: Vec::new(),
2904 warnings: Vec::new(),
2905 };
2906
2907 let mut writer = FbxWriter::new();
2908 writer.add_scene(&scene).expect("an empty mesh is writable");
2909 let document = writer.build_document().expect("document");
2910 let objects = document
2911 .iter()
2912 .find(|node| node.name == "Objects")
2913 .expect("Objects");
2914 let geometry = child(objects, "Geometry").expect("an empty mesh writes a Geometry");
2915 assert!(
2916 matches!(child(geometry, "Vertices"), Some(node)
2917 if matches!(&node.properties[0], FbxProperty::F64Array(values) if values.is_empty())),
2918 "an empty Vertices array is written, not omitted: {:?}",
2919 geometry
2920 .children
2921 .iter()
2922 .map(|c| &c.name)
2923 .collect::<Vec<_>>()
2924 );
2925 assert!(child(geometry, "PolygonVertexIndex").is_some());
2926
2927 let reread = FbxScene::from_bytes(&scene.to_bytes().expect("write")).expect("read");
2929 assert_eq!(
2930 reread.root_nodes[0].mesh_instances.len(),
2931 1,
2932 "the empty mesh must still be there after a rewrite"
2933 );
2934 }
2935
2936 #[cfg(feature = "fbx-reader")]
2945 #[test]
2946 fn a_camera_is_declared_the_way_importers_expect() {
2947 let scene = FbxScene {
2948 root_nodes: vec![FbxSceneNode {
2949 id: crate::fbx_scene::FbxNodeId(1),
2950 name: Some("Cam".to_string()),
2951 attribute: Some(crate::fbx_scene::FbxNodeAttribute::Camera(
2952 crate::fbx_scene::FbxCamera {
2953 position: Some([1.0, 2.0, 3.0]),
2954 focal_length: Some(35.0),
2955 projection_type: Some(0),
2956 near_plane: Some(0.1),
2957 ..Default::default()
2958 },
2959 )),
2960 transform: None,
2961 transform_stack: None,
2962 has_complex_transform_stack: false,
2963 mesh_instances: Vec::new(),
2964 children: Vec::new(),
2965 }],
2966 ..FbxScene::default()
2967 };
2968 let mut writer = FbxWriter::new();
2969 writer.add_scene(&scene).unwrap();
2970 let document = writer.build_document().unwrap();
2971
2972 let objects = document
2973 .iter()
2974 .find(|node| node.name == "Objects")
2975 .expect("Objects");
2976 let model = child(objects, "Model").expect("the node writes a Model");
2977 assert!(
2978 matches!(&model.properties[2], FbxProperty::String(class) if class == "Camera"),
2979 "a camera's Model is classed Camera, not Mesh: {:?}",
2980 model.properties
2981 );
2982
2983 let attribute = child(objects, "NodeAttribute").expect("the camera writes a NodeAttribute");
2984 assert!(
2985 matches!(&attribute.properties[2], FbxProperty::String(class) if class == "Camera")
2986 );
2987 assert_eq!(
2988 child(attribute, "TypeFlags").map(|node| format!("{:?}", node.properties)),
2989 Some(format!(
2990 "{:?}",
2991 vec![FbxProperty::String("Camera".to_string())]
2992 ))
2993 );
2994
2995 let declared: Vec<(String, String)> = child(attribute, "Properties70")
2997 .expect("Properties70")
2998 .children
2999 .iter()
3000 .map(|node| match (&node.properties[0], &node.properties[1]) {
3001 (FbxProperty::String(name), FbxProperty::String(kind)) => {
3002 (name.clone(), kind.clone())
3003 }
3004 other => panic!("a P record names itself with strings: {other:?}"),
3005 })
3006 .collect();
3007 assert_eq!(
3008 declared,
3009 [
3010 ("Position".to_string(), "Vector".to_string()),
3011 ("CameraProjectionType".to_string(), "enum".to_string()),
3012 ("FocalLength".to_string(), "Number".to_string()),
3013 ("NearPlane".to_string(), "double".to_string()),
3014 ]
3015 );
3016
3017 let definitions = document
3020 .iter()
3021 .find(|node| node.name == "Definitions")
3022 .expect("Definitions");
3023 let declared_attributes = definitions
3024 .children
3025 .iter()
3026 .filter(|node| node.name == "ObjectType")
3027 .find(|node| matches!(&node.properties[0], FbxProperty::String(n) if n == "NodeAttribute"))
3028 .and_then(|node| child(node, "Count"))
3029 .map(|node| format!("{:?}", node.properties));
3030 assert_eq!(
3031 declared_attributes,
3032 Some(format!("{:?}", vec![FbxProperty::I32(1)])),
3033 "Definitions must declare the one NodeAttribute that Objects holds"
3034 );
3035 }
3036
3037 #[test]
3038 fn test_fbx_writer_new() {
3039 let writer = FbxWriter::new();
3040 assert_eq!(writer.mesh_count(), 0);
3041 assert!(!writer.is_compression_enabled());
3042 }
3043
3044 #[test]
3045 fn test_fbx_writer_with_options() {
3046 let writer = FbxWriter::new()
3047 .with_compression(true)
3048 .with_compression_threshold(64);
3049 assert!(writer.is_compression_enabled());
3050 }
3051
3052 #[test]
3053 fn test_fbx_writer_add_mesh() {
3054 let mesh = create_triangle_mesh();
3055 let mut writer = FbxWriter::new();
3056 Writer::add_mesh(&mut writer, &mesh, Some("TestMesh")).unwrap();
3057 assert_eq!(writer.mesh_count(), 1);
3058 }
3059
3060 #[test]
3061 fn test_fbx_writer_write() {
3062 let mesh = create_triangle_mesh();
3063 let mut writer = FbxWriter::new();
3064 Writer::add_mesh(&mut writer, &mesh, Some("Triangle")).unwrap();
3065
3066 let mut buffer = Cursor::new(Vec::new());
3067 writer.write_to(&mut buffer).unwrap();
3068
3069 let data = buffer.into_inner();
3070
3071 assert_eq!(&data[0..21], FBX_MAGIC);
3073 let version = u32::from_le_bytes([data[23], data[24], data[25], data[26]]);
3075 assert_eq!(version, FBX_VERSION);
3076 }
3077
3078 #[test]
3079 fn test_write_fbx_mesh_convenience() {
3080 let mesh = create_triangle_mesh();
3081 let file = NamedTempFile::new().unwrap();
3082 write_fbx_mesh(file.path(), &mesh).unwrap();
3083
3084 let metadata = std::fs::metadata(file.path()).unwrap();
3085 assert!(metadata.len() > 27);
3086 }
3087
3088 #[test]
3089 fn test_multiple_meshes() {
3090 let mesh1 = create_triangle_mesh();
3091 let mesh2 = create_triangle_mesh();
3092
3093 let mut writer = FbxWriter::new();
3094 Writer::add_mesh(&mut writer, &mesh1, Some("Mesh1")).unwrap();
3095 Writer::add_mesh(&mut writer, &mesh2, Some("Mesh2")).unwrap();
3096
3097 assert_eq!(writer.mesh_count(), 2);
3098
3099 let mut buffer = Cursor::new(Vec::new());
3100 writer.write_to(&mut buffer).unwrap();
3101
3102 let data = buffer.into_inner();
3103 assert!(!data.is_empty());
3104 }
3105
3106 #[test]
3108 #[cfg(feature = "fbx-reader")]
3109 fn scene_roundtrip_preserves_hierarchy_and_local_transforms() {
3110 use crate::{FbxMeshInstance, FbxScene, FbxSceneNode, FbxTransform};
3111
3112 let child_transform = FbxTransform {
3118 matrix: [
3119 [0.0, 0.0, 8.0, 0.0],
3120 [0.0, 3.0, 0.0, 0.0],
3121 [-2.0, 0.0, 0.0, 0.0],
3122 [1.0, 2.0, 3.0, 1.0],
3123 ],
3124 };
3125 let scene = FbxScene {
3126 global_settings: None,
3127 root_nodes: vec![FbxSceneNode {
3128 id: crate::fbx_scene::FbxNodeId(1),
3129 name: Some("Root".to_string()),
3130 transform: None,
3131 transform_stack: None,
3132 has_complex_transform_stack: false,
3133 mesh_instances: Vec::new(),
3134 attribute: None,
3135 children: vec![FbxSceneNode {
3136 id: crate::fbx_scene::FbxNodeId(2),
3137 name: Some("Child".to_string()),
3138 transform: Some(child_transform),
3139 transform_stack: None,
3140 has_complex_transform_stack: false,
3141 mesh_instances: vec![FbxMeshInstance {
3142 name: Some("Triangle".to_string()),
3143 mesh: create_triangle_mesh(),
3144 ..Default::default()
3145 }],
3146 attribute: None,
3147 children: Vec::new(),
3148 }],
3149 }],
3150 materials: Vec::new(),
3151 textures: Vec::new(),
3152 animations: Vec::new(),
3153 warnings: Vec::new(),
3154 };
3155
3156 let bytes = scene.to_bytes().unwrap();
3157 let roundtrip = FbxScene::from_bytes(&bytes).unwrap();
3158
3159 assert_eq!(roundtrip.root_nodes.len(), 1);
3160 let root = &roundtrip.root_nodes[0];
3161 assert_eq!(root.name.as_deref(), Some("Root"));
3162 assert_eq!(root.children.len(), 1);
3163 let child = &root.children[0];
3164 assert_eq!(child.name.as_deref(), Some("Child"));
3165 assert_eq!(child.mesh_instances[0].name.as_deref(), Some("Triangle"));
3166 assert_eq!(child.mesh_instances[0].mesh.num_faces(), 1);
3167 let transform = child.transform.expect("child transform should round-trip");
3168 for row in 0..4 {
3169 for column in 0..4 {
3170 assert!(
3171 (transform.matrix[row][column] - child_transform.matrix[row][column]).abs()
3172 < 1e-5
3173 );
3174 }
3175 }
3176 }
3177
3178 #[test]
3179 #[cfg(feature = "fbx-reader")]
3180 fn scene_roundtrip_preserves_model_transform_stack_properties() {
3181 let transform_stack = FbxTransformStack {
3185 translation: Some([1.25, -2.5, 3.75]),
3186 rotation: Some([12.0, -34.0, 56.0]),
3187 scaling: Some([1.0, 0.75, 1.25]),
3188 rotation_order: Some(1),
3189 rotation_active: Some(true),
3190 pre_rotation: Some([10.0, 20.0, -30.0]),
3191 post_rotation: Some([-5.0, 15.0, 25.0]),
3192 rotation_offset: Some([0.5, 1.0, -1.5]),
3193 rotation_pivot: Some([2.0, -3.0, 4.0]),
3194 scaling_offset: Some([-0.25, 0.5, 0.75]),
3195 scaling_pivot: Some([1.5, -2.5, 3.5]),
3196 inherit_type: Some(2),
3197 };
3198 let scene = FbxScene {
3199 global_settings: None,
3200 root_nodes: vec![FbxSceneNode {
3201 id: crate::fbx_scene::FbxNodeId(1),
3202 name: Some("StackedNode".to_string()),
3203 transform: Some(FbxTransform {
3204 matrix: [
3205 [1.0, 0.0, 0.0, 0.0],
3206 [0.0, 1.0, 0.0, 0.0],
3207 [0.0, 0.0, 1.0, 0.0],
3208 [1.25, -2.5, 3.75, 1.0],
3209 ],
3210 }),
3211 transform_stack: Some(transform_stack.clone()),
3212 has_complex_transform_stack: true,
3213 mesh_instances: Vec::new(),
3214 attribute: None,
3215 children: Vec::new(),
3216 }],
3217 materials: Vec::new(),
3218 textures: Vec::new(),
3219 animations: Vec::new(),
3220 warnings: Vec::new(),
3221 };
3222
3223 let output = FbxScene::from_bytes(&scene.to_bytes().unwrap()).unwrap();
3224 assert_eq!(
3225 output.root_nodes[0].transform_stack.as_ref(),
3226 Some(&transform_stack)
3227 );
3228 }
3229
3230 #[test]
3231 fn fbx_matrix_arrays_use_column_major_layout() {
3232 use crate::FbxTransform;
3240 let matrix = [
3241 [0.0, 0.0, 8.0, 0.0],
3242 [0.0, 3.0, 0.0, 0.0],
3243 [-2.0, 0.0, 0.0, 0.0],
3244 [1.0, 2.0, 3.0, 1.0],
3245 ];
3246 let column_major = super::flatten_fbx_transform(FbxTransform { matrix });
3247 assert_eq!(
3248 column_major,
3249 matrix
3250 .into_iter()
3251 .flatten()
3252 .map(f64::from)
3253 .collect::<Vec<_>>()
3254 );
3255 }
3256
3257 #[test]
3258 #[cfg(feature = "fbx-reader")]
3259 fn scene_roundtrip_preserves_skin_clusters_and_bind_pose() {
3260 let identity = crate::fbx_scene::FbxTransform {
3261 matrix: [
3262 [1.0, 0.0, 0.0, 0.0],
3263 [0.0, 1.0, 0.0, 0.0],
3264 [0.0, 0.0, 1.0, 0.0],
3265 [0.0, 0.0, 0.0, 1.0],
3266 ],
3267 };
3268 let scene = FbxScene {
3269 global_settings: None,
3270 root_nodes: vec![FbxSceneNode {
3271 id: crate::fbx_scene::FbxNodeId(1),
3272 name: Some("Armature".to_string()),
3273 transform: None,
3274 transform_stack: None,
3275 has_complex_transform_stack: false,
3276 mesh_instances: Vec::new(),
3277 attribute: None,
3278 children: vec![
3279 FbxSceneNode {
3280 id: crate::fbx_scene::FbxNodeId(2),
3281 name: Some("Bone".to_string()),
3282 transform: Some(identity),
3283 transform_stack: None,
3284 has_complex_transform_stack: false,
3285 mesh_instances: Vec::new(),
3286 attribute: None,
3287 children: Vec::new(),
3288 },
3289 FbxSceneNode {
3290 id: crate::fbx_scene::FbxNodeId(3),
3291 name: Some("Mesh".to_string()),
3292 transform: Some(identity),
3293 transform_stack: None,
3294 has_complex_transform_stack: false,
3295 mesh_instances: vec![FbxMeshInstance {
3296 name: Some("Triangle".to_string()),
3297 mesh: create_triangle_mesh(),
3298 skin: Some(crate::fbx_scene::FbxSkin {
3299 clusters: vec![crate::fbx_scene::FbxSkinCluster {
3300 joint_node_id: crate::fbx_scene::FbxNodeId(2),
3301 control_point_indices: vec![0, 1, 2],
3302 weights: vec![1.0, 0.5, 1.0],
3303 mesh_bind_transform: identity,
3304 joint_bind_transform: identity,
3305 armature_bind_transform: None,
3306 }],
3307 bind_pose: vec![
3308 (crate::fbx_scene::FbxNodeId(2), identity),
3309 (crate::fbx_scene::FbxNodeId(3), identity),
3310 ],
3311 }),
3312 morph_targets: vec![crate::fbx_scene::FbxMorphTarget {
3313 name: Some("Smile".to_string()),
3314 control_point_indices: vec![1],
3315 position_deltas: vec![[0.0, 0.25, 0.0]],
3316 normal_deltas: None,
3317 default_weight: 0.0,
3318 full_weight: 100.0,
3319 }],
3320 ..Default::default()
3321 }],
3322 attribute: None,
3323 children: Vec::new(),
3324 },
3325 ],
3326 }],
3327 materials: Vec::new(),
3328 textures: Vec::new(),
3329 animations: Vec::new(),
3330 warnings: Vec::new(),
3331 };
3332 let output = FbxScene::from_bytes(&scene.to_bytes().unwrap()).unwrap();
3333 let mesh = &output.root_nodes[0].children[1].mesh_instances[0];
3334 let skin = mesh.skin.as_ref().expect("skin must round-trip");
3335 assert_eq!(skin.clusters.len(), 1);
3336 assert_eq!(
3337 skin.clusters[0].joint_node_id,
3338 crate::fbx_scene::FbxNodeId(2)
3339 );
3340 assert_eq!(skin.clusters[0].control_point_indices, vec![0, 1, 2]);
3341 assert_eq!(skin.clusters[0].weights, vec![1.0, 0.5, 1.0]);
3342 assert_eq!(skin.bind_pose.len(), 2);
3343 assert_eq!(mesh.morph_targets.len(), 1);
3344 assert_eq!(mesh.morph_targets[0].name.as_deref(), Some("Smile"));
3345 assert_eq!(
3346 mesh.morph_targets[0].position_deltas,
3347 vec![[0.0, 0.25, 0.0]]
3348 );
3349 }
3350
3351 #[test]
3352 #[cfg(feature = "fbx-reader")]
3353 fn scene_roundtrip_preserves_cubic_tangents() {
3354 let scene = FbxScene {
3355 global_settings: None,
3356 root_nodes: vec![FbxSceneNode {
3357 id: crate::fbx_scene::FbxNodeId(1),
3358 name: Some("Root".to_string()),
3359 transform: None,
3360 transform_stack: None,
3361 has_complex_transform_stack: false,
3362 mesh_instances: Vec::new(),
3363 attribute: None,
3364 children: Vec::new(),
3365 }],
3366 materials: Vec::new(),
3367 textures: Vec::new(),
3368 animations: vec![FbxAnimation {
3369 name: Some("Cubic".to_string()),
3370 duration: 1.0,
3371 channels: vec![crate::fbx_scene::FbxAnimChannel {
3372 node_id: crate::fbx_scene::FbxNodeId(1),
3373 node_name: "Root".to_string(),
3374 path: crate::fbx_scene::FbxAnimChannelPath::Translation,
3375 morph_target_index: None,
3376 sampler: crate::fbx_scene::FbxAnimSampler {
3377 input: vec![0.0, 1.0],
3378 output: vec![0.0, 0.0, 0.0, 1.0, 2.0, 3.0],
3379 interpolation: crate::fbx_scene::FbxAnimInterpolation::Cubic,
3380 in_tangents: Some(vec![0.0, 0.0, 0.0, 0.25, 0.5, 0.75]),
3381 out_tangents: Some(vec![1.0, 2.0, 3.0, 0.0, 0.0, 0.0]),
3382 },
3383 }],
3384 }],
3385 warnings: Vec::new(),
3386 };
3387 let output = FbxScene::from_bytes(&scene.to_bytes().unwrap()).unwrap();
3388 let sampler = &output.animations[0].channels[0].sampler;
3389 assert_eq!(
3390 sampler.interpolation,
3391 crate::fbx_scene::FbxAnimInterpolation::Cubic
3392 );
3393 assert_eq!(
3394 sampler.in_tangents.as_deref(),
3395 Some(&[0.0, 0.0, 0.0, 0.25, 0.5, 0.75][..])
3396 );
3397 assert_eq!(
3398 sampler.out_tangents.as_deref(),
3399 Some(&[1.0, 2.0, 3.0, 0.0, 0.0, 0.0][..])
3400 );
3401 }
3402
3403 #[test]
3404 #[cfg(feature = "fbx-reader")]
3405 fn scene_roundtrip_preserves_per_polygon_materials_on_ngons() {
3406 let mut instance = FbxMeshInstance {
3411 name: Some("Quads".to_string()),
3412 mesh: create_triangle_mesh(),
3413 control_points: vec![
3414 [0.0, 0.0, 0.0],
3415 [1.0, 0.0, 0.0],
3416 [1.0, 1.0, 0.0],
3417 [0.0, 1.0, 0.0],
3418 [2.0, 0.0, 0.0],
3419 [3.0, 0.0, 0.0],
3420 [3.0, 1.0, 0.0],
3421 [2.0, 1.0, 0.0],
3422 ],
3423 polygon_vertex_indices: vec![0, 1, 2, !3, 4, 5, 6, !7],
3425 material_indices: vec![0, 0, 1, 1],
3426 ..Default::default()
3427 };
3428 instance.mesh = instance.to_draco_mesh();
3429
3430 let named = |name: &str| crate::fbx_scene::FbxMaterial {
3431 name: Some(name.to_string()),
3432 ..crate::fbx_scene::FbxMaterial::default()
3433 };
3434 let scene = FbxScene {
3435 root_nodes: vec![FbxSceneNode {
3436 id: crate::fbx_scene::FbxNodeId(1),
3437 name: Some("Node".to_string()),
3438 transform: None,
3439 transform_stack: None,
3440 has_complex_transform_stack: false,
3441 mesh_instances: vec![instance],
3442 attribute: None,
3443 children: Vec::new(),
3444 }],
3445 materials: vec![named("Red"), named("Blue")],
3446 ..FbxScene::default()
3447 };
3448
3449 let output = crate::FbxScene::from_bytes(&scene.to_bytes().unwrap()).unwrap();
3450 assert_eq!(
3451 output.root_nodes[0].mesh_instances[0].material_indices,
3452 vec![0, 0, 1, 1],
3453 "the second quad should keep its own material"
3454 );
3455 }
3456
3457 #[test]
3458 fn collapsing_material_indices_takes_one_entry_per_polygon() {
3459 let collapsed =
3461 collapse_material_indices_to_polygons(&[7, 7, 3], Some(&[0, 1, 2, !3, 4, 5, !6]));
3462 assert_eq!(collapsed, vec![7, 3]);
3463
3464 assert_eq!(
3466 collapse_material_indices_to_polygons(&[1, 2, 3], None),
3467 vec![1, 2, 3]
3468 );
3469 }
3470
3471 #[cfg(feature = "fbx-reader")]
3472 fn scene_with_tangents(has_handedness: bool) -> FbxScene {
3473 let set = crate::fbx_scene::FbxTangentSet {
3474 layer: crate::fbx_scene::FbxLayerSet {
3475 name: None,
3476 mapping: Some("ByPolygonVertex".to_string()),
3477 reference: Some("Direct".to_string()),
3478 values: vec![
3479 [1.0, 0.0, 0.0, -1.0],
3480 [0.0, 1.0, 0.0, -1.0],
3481 [0.0, 0.0, 1.0, -1.0],
3482 ],
3483 indices: Vec::new(),
3484 },
3485 has_handedness,
3486 };
3487 FbxScene {
3488 root_nodes: vec![FbxSceneNode {
3489 id: crate::fbx_scene::FbxNodeId(1),
3490 name: Some("Tangential".to_string()),
3491 transform: None,
3492 transform_stack: None,
3493 has_complex_transform_stack: false,
3494 mesh_instances: vec![FbxMeshInstance {
3495 name: Some("Tri".to_string()),
3496 mesh: create_triangle_mesh(),
3497 control_points: vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
3498 polygon_vertex_indices: vec![0, 1, !2],
3499 layers: FbxMeshLayers {
3500 tangent_sets: vec![set.clone()],
3501 binormal_sets: vec![set],
3502 ..Default::default()
3503 },
3504 ..Default::default()
3505 }],
3506 attribute: None,
3507 children: Vec::new(),
3508 }],
3509 ..FbxScene::default()
3510 }
3511 }
3512
3513 #[test]
3520 #[cfg(feature = "fbx-reader")]
3521 fn handedness_survives_a_round_trip_and_is_not_invented() {
3522 for has_handedness in [true, false] {
3523 let bytes = scene_with_tangents(has_handedness).to_bytes().unwrap();
3524 let output = crate::FbxScene::from_bytes(&bytes).unwrap();
3525 let instance = &output.root_nodes[0].mesh_instances[0];
3526
3527 assert_eq!(instance.layers.tangent_sets.len(), 1);
3528 assert_eq!(instance.layers.binormal_sets.len(), 1);
3529 let tangents = &instance.layers.tangent_sets[0];
3530 assert_eq!(tangents.has_handedness, has_handedness);
3531
3532 let expected_w = if has_handedness { -1.0 } else { 1.0 };
3533 assert_eq!(
3534 tangents.layer.values[0],
3535 [1.0, 0.0, 0.0, expected_w],
3536 "handedness {has_handedness}: xyz must survive and w must \
3537 {} ",
3538 if has_handedness {
3539 "be read back"
3540 } else {
3541 "default to +1"
3542 }
3543 );
3544
3545 let has_w_node = String::from_utf8_lossy(&bytes).contains("TangentsW");
3548 assert_eq!(
3549 has_w_node, has_handedness,
3550 "TangentsW node presence must match the source"
3551 );
3552 }
3553 }
3554
3555 #[test]
3560 #[cfg(feature = "fbx-reader")]
3561 fn smoothing_and_crease_layers_survive_a_round_trip() {
3562 let instance = FbxMeshInstance {
3563 name: Some("Creased".to_string()),
3564 mesh: create_triangle_mesh(),
3565 control_points: vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
3566 polygon_vertex_indices: vec![0, 1, !2],
3567 edges: vec![0, 1, 2],
3568 layers: FbxMeshLayers {
3569 smoothing_layers: vec![
3570 crate::fbx_scene::FbxSmoothingLayer {
3571 mapping: Some("ByEdge".to_string()),
3572 values: vec![1, 0, 1],
3573 },
3574 crate::fbx_scene::FbxSmoothingLayer {
3575 mapping: Some("ByPolygon".to_string()),
3576 values: vec![1],
3577 },
3578 ],
3579 crease_layers: vec![
3580 crate::fbx_scene::FbxCreaseLayer {
3581 kind: crate::fbx_scene::FbxCreaseKind::Edge,
3582 mapping: Some("ByEdge".to_string()),
3583 values: vec![0.25, 0.5, 1.0],
3585 },
3586 crate::fbx_scene::FbxCreaseLayer {
3587 kind: crate::fbx_scene::FbxCreaseKind::Vertex,
3588 mapping: Some("ByVertice".to_string()),
3589 values: vec![0.75, 0.0, 0.125],
3590 },
3591 ],
3592 ..Default::default()
3593 },
3594 ..Default::default()
3595 };
3596 let scene = FbxScene {
3597 root_nodes: vec![FbxSceneNode {
3598 id: crate::fbx_scene::FbxNodeId(1),
3599 name: Some("Node".to_string()),
3600 transform: None,
3601 transform_stack: None,
3602 has_complex_transform_stack: false,
3603 mesh_instances: vec![instance.clone()],
3604 attribute: None,
3605 children: Vec::new(),
3606 }],
3607 ..FbxScene::default()
3608 };
3609
3610 let output = crate::FbxScene::from_bytes(&scene.to_bytes().unwrap()).unwrap();
3611 let read_back = &output.root_nodes[0].mesh_instances[0];
3612 assert_eq!(
3613 read_back.layers.smoothing_layers,
3614 instance.layers.smoothing_layers
3615 );
3616 assert_eq!(
3617 read_back.layers.crease_layers,
3618 instance.layers.crease_layers
3619 );
3620 }
3621
3622 #[test]
3627 #[cfg(feature = "fbx-reader")]
3628 fn a_by_edge_layer_survives_without_an_explicit_edges_array() {
3629 let smoothing = crate::fbx_scene::FbxSmoothingLayer {
3630 mapping: Some("ByEdge".to_string()),
3631 values: vec![1, 0, 1],
3632 };
3633 let scene = FbxScene {
3634 root_nodes: vec![FbxSceneNode {
3635 id: crate::fbx_scene::FbxNodeId(1),
3636 name: Some("Node".to_string()),
3637 transform: None,
3638 transform_stack: None,
3639 has_complex_transform_stack: false,
3640 mesh_instances: vec![FbxMeshInstance {
3641 name: Some("Implicit".to_string()),
3642 mesh: create_triangle_mesh(),
3643 control_points: vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
3644 polygon_vertex_indices: vec![0, 1, !2],
3645 layers: FbxMeshLayers {
3646 smoothing_layers: vec![smoothing.clone()],
3647 ..Default::default()
3648 },
3649 ..Default::default()
3650 }],
3651 attribute: None,
3652 children: Vec::new(),
3653 }],
3654 ..FbxScene::default()
3655 };
3656
3657 let output = crate::FbxScene::from_bytes(&scene.to_bytes().unwrap()).unwrap();
3658 let read_back = &output.root_nodes[0].mesh_instances[0];
3659 assert!(read_back.edges.is_empty());
3660 assert_eq!(read_back.layers.smoothing_layers, vec![smoothing]);
3661 }
3662
3663 #[test]
3671 #[cfg(feature = "fbx-reader")]
3672 fn an_unnamed_texture_is_not_given_a_name_by_a_rewrite() {
3673 let scene = FbxScene {
3674 materials: vec![crate::fbx_scene::FbxMaterial {
3675 name: Some("M".to_string()),
3676 textures: vec![crate::fbx_scene::FbxTextureBinding {
3677 slot: crate::fbx_scene::FbxTextureSlot::Diffuse,
3678 texture_index: 0,
3679 }],
3680 ..Default::default()
3681 }],
3682 textures: vec![crate::fbx_scene::FbxTexture {
3683 name: None,
3684 content: None,
3685 filename: Some("t.png".to_string()),
3686 }],
3687 ..FbxScene::default()
3688 };
3689
3690 let output = crate::FbxScene::from_bytes(&scene.to_bytes().unwrap()).unwrap();
3691 assert_eq!(output.textures.len(), 1);
3692 assert_eq!(
3693 output.textures[0].name, None,
3694 "an unnamed texture must stay unnamed"
3695 );
3696 }
3697
3698 #[test]
3705 #[cfg(feature = "fbx-reader")]
3706 fn a_colour_only_geometry_lists_its_layer_element() {
3707 let scene = FbxScene {
3708 root_nodes: vec![FbxSceneNode {
3709 id: crate::fbx_scene::FbxNodeId(1),
3710 name: Some("Colored".to_string()),
3711 transform: None,
3712 transform_stack: None,
3713 has_complex_transform_stack: false,
3714 mesh_instances: vec![FbxMeshInstance {
3715 name: Some("Tri".to_string()),
3716 mesh: create_triangle_mesh(),
3717 control_points: vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
3718 polygon_vertex_indices: vec![0, 1, !2],
3719 layers: FbxMeshLayers {
3720 color_sets: vec![crate::fbx_scene::FbxColorSet {
3721 name: Some("Col".to_string()),
3722 mapping: Some("ByPolygonVertex".to_string()),
3723 reference: Some("Direct".to_string()),
3724 values: vec![[1.0, 0.0, 0.0, 1.0]; 3],
3725 indices: Vec::new(),
3726 }],
3727 ..Default::default()
3728 },
3729 ..Default::default()
3730 }],
3731 attribute: None,
3732 children: Vec::new(),
3733 }],
3734 ..FbxScene::default()
3735 };
3736
3737 let bytes = scene.to_bytes().unwrap();
3738 let nodes = crate::FbxReader::from_bytes(bytes)
3739 .unwrap()
3740 .read_nodes()
3741 .unwrap();
3742
3743 fn find<'a>(
3744 nodes: &'a [crate::fbx_reader::FbxNode],
3745 name: &str,
3746 ) -> Option<&'a crate::fbx_reader::FbxNode> {
3747 nodes
3748 .iter()
3749 .find(|n| n.name == name)
3750 .or_else(|| nodes.iter().find_map(|n| find(&n.children, name)))
3751 }
3752
3753 let layer = find(&nodes, "Layer").expect("colours alone must still produce a Layer node");
3754 let listed: Vec<&str> = layer
3755 .children
3756 .iter()
3757 .filter(|c| c.name == "LayerElement")
3758 .filter_map(|c| c.children.iter().find(|g| g.name == "Type"))
3759 .filter_map(|t| match t.properties.first() {
3760 Some(crate::fbx_reader::FbxProperty::String(s)) => Some(s.as_str()),
3761 _ => None,
3762 })
3763 .collect();
3764 assert!(
3765 listed.contains(&"LayerElementColor"),
3766 "Layer must reference the colour element, listed: {listed:?}"
3767 );
3768 }
3769
3770 #[test]
3771 #[cfg(feature = "fbx-reader")]
3772 fn scene_roundtrip_preserves_vertex_colors() {
3773 let colors = crate::fbx_scene::FbxColorSet {
3774 name: Some("Col".to_string()),
3775 mapping: Some("ByPolygonVertex".to_string()),
3776 reference: Some("Direct".to_string()),
3777 values: vec![
3778 [1.0, 0.0, 0.0, 1.0],
3779 [0.0, 1.0, 0.0, 1.0],
3780 [0.0, 0.0, 1.0, 0.5],
3781 ],
3782 indices: Vec::new(),
3783 };
3784 let scene = FbxScene {
3785 root_nodes: vec![FbxSceneNode {
3786 id: crate::fbx_scene::FbxNodeId(1),
3787 name: Some("Colored".to_string()),
3788 transform: None,
3789 transform_stack: None,
3790 has_complex_transform_stack: false,
3791 mesh_instances: vec![FbxMeshInstance {
3792 name: Some("Tri".to_string()),
3793 mesh: create_triangle_mesh(),
3794 control_points: vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
3795 polygon_vertex_indices: vec![0, 1, !2],
3796 layers: FbxMeshLayers {
3797 color_sets: vec![colors.clone()],
3798 ..Default::default()
3799 },
3800 ..Default::default()
3801 }],
3802 attribute: None,
3803 children: Vec::new(),
3804 }],
3805 ..FbxScene::default()
3806 };
3807
3808 let output = crate::FbxScene::from_bytes(&scene.to_bytes().unwrap()).unwrap();
3809 let instance = &output.root_nodes[0].mesh_instances[0];
3810 assert_eq!(
3811 instance.layers.color_sets.len(),
3812 1,
3813 "colour layer should survive"
3814 );
3815 let read_back = &instance.layers.color_sets[0];
3816 assert_eq!(read_back.values, colors.values);
3817 assert_eq!(read_back.mapping.as_deref(), Some("ByPolygonVertex"));
3818
3819 let render = instance.to_render_mesh();
3822 assert_eq!(render.colors.len(), 1);
3823 assert_eq!(render.colors[0].values[2], [0.0, 0.0, 1.0, 0.5]);
3824 let id = instance
3825 .mesh
3826 .named_attribute_id(draco_core::geometry_attribute::GeometryAttributeType::Color);
3827 assert!(id >= 0, "the Draco mesh should carry a Color attribute");
3828 assert_eq!(instance.mesh.attribute(id).num_components(), 4);
3829 }
3830
3831 #[test]
3832 #[cfg(feature = "fbx-reader")]
3833 fn written_files_satisfy_the_readers_strict_mode() {
3834 let mut writer = FbxWriter::new();
3838 writer
3839 .add_mesh(&create_triangle_mesh(), Some("strict"))
3840 .unwrap();
3841 let bytes = writer.write_to_vec().unwrap();
3842
3843 let scene =
3844 crate::FbxScene::from_bytes_with_options(&bytes, crate::FbxReadOptions::strict())
3845 .expect("writer output should pass strict validation");
3846 assert_eq!(scene.root_nodes.len(), 1);
3847 }
3848
3849 #[cfg(feature = "compression")]
3850 #[test]
3851 fn test_write_with_compression() {
3852 let mesh = create_triangle_mesh();
3853 let mut writer = FbxWriter::new()
3854 .with_compression(true)
3855 .with_compression_threshold(0);
3856 Writer::add_mesh(&mut writer, &mesh, None).unwrap();
3857
3858 let mut buffer = Cursor::new(Vec::new());
3859 writer.write_to(&mut buffer).unwrap();
3860
3861 let data = buffer.into_inner();
3862 assert!(!data.is_empty());
3863 }
3864}