use std::collections::HashMap;
use std::fs::{self, File};
use std::io::{self, BufReader, Cursor, Read, Seek};
use std::path::Path;
use draco_core::mesh::Mesh;
use crate::fbx_scene::push_warning;
use crate::fbx_templates::{ObjectProperties, PropertyTemplates};
use crate::fbx_transform::{
collect_transform_warnings, identity_transform, parse_transform, transform_array,
};
pub use crate::fbx_container::{FbxMemoryReader, FbxNode, FbxProperty, FbxReader};
#[derive(Debug)]
struct FbxGeometrySource {
mesh: Mesh,
material_indices: Vec<i32>,
control_points: Vec<[f32; 3]>,
polygon_vertex_indices: Vec<i32>,
layers: FbxMeshLayers,
edges: Vec<i32>,
}
#[doc(hidden)]
pub use crate::fbx_scene::{
FbxAnimChannel, FbxAnimChannelPath, FbxAnimInterpolation, FbxAnimSampler, FbxAnimation,
FbxBinormalSet, FbxColorSet, FbxCreaseKind, FbxCreaseLayer, FbxLayerSet, FbxMeshInstance,
FbxMeshLayers, FbxNodeAttribute, FbxNodeId, FbxNormalSet, FbxScene, FbxSceneNode,
FbxSmoothingLayer, FbxTangentSet, FbxTexture, FbxTextureBinding, FbxTextureSlot, FbxTransform,
FbxUvSet, FbxWarning, FbxWarningCode,
};
impl crate::traits::Reader for FbxReader<BufReader<File>> {
fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
FbxReader::open(path)
}
fn read_meshes(&mut self) -> io::Result<Vec<draco_core::mesh::Mesh>> {
FbxReader::read_meshes(self)
}
}
impl crate::traits::Reader for FbxReader<Cursor<Vec<u8>>> {
fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
Self::from_bytes(fs::read(path)?)
}
fn read_meshes(&mut self) -> io::Result<Vec<draco_core::mesh::Mesh>> {
FbxReader::read_meshes(self)
}
}
impl<R: Read + Seek> FbxReader<R> {
pub fn read_scene(&mut self) -> io::Result<FbxScene> {
let nodes = self.read_nodes()?;
let global_settings = parse_global_settings(&nodes);
let index = FbxObjectIndex::build(&nodes);
let FbxObjectIndex {
model_map,
model_order,
geometry_map,
attribute_map,
material_map,
texture_map,
video_map,
deformer_map,
pose_map,
connections,
templates,
..
} = &index;
let mut warnings = self.warnings().to_vec();
if index.name_keyed_objects > 0 {
let count = index.name_keyed_objects;
push_warning(
&mut warnings,
FbxWarningCode::NameKeyedObjectModel,
format!(
"FBX document identifies its {count} objects by name rather than by id, \
which is the pre-7000 layout; no geometry, materials or animation were \
imported from it"
),
None,
);
}
collect_transform_warnings(model_map, model_order, templates, &mut warnings);
let node_attributes = parse_node_attributes(
attribute_map,
model_map,
connections,
templates,
&mut warnings,
);
let (materials, material_index_by_id, textures) = parse_materials_and_textures(
material_map,
texture_map,
video_map,
connections,
templates,
);
let mut model_material_ids: HashMap<i64, Vec<i32>> = HashMap::new();
for conn in connections {
if conn.kind == ConnectionKind::Oo
&& material_map.contains_key(&conn.child)
&& model_map.contains_key(&conn.parent)
{
let mat_index = material_index_by_id[&conn.child] as i32;
model_material_ids
.entry(conn.parent)
.or_default()
.push(mat_index);
}
}
let mut model_children: HashMap<i64, Vec<i64>> = HashMap::new();
for conn in connections.iter() {
if model_map.contains_key(&conn.child) || model_map.contains_key(&conn.parent) {
model_children
.entry(conn.parent)
.or_default()
.push(conn.child);
}
}
let ordered_model_ids = model_order;
let model_node_ids: HashMap<i64, FbxNodeId> = ordered_model_ids
.iter()
.copied()
.enumerate()
.map(|(index, id)| (id, FbxNodeId((index + 1) as u32)))
.collect();
let mut model_mesh_instances: std::collections::HashMap<i64, Vec<FbxMeshInstance>> =
std::collections::HashMap::new();
let mut geometry_ids: Vec<i64> = geometry_map.keys().copied().collect();
geometry_ids.sort_unstable();
for geom_id in geometry_ids {
let geom_node = geometry_map[&geom_id];
if let Some(source) = geometry_to_mesh(geom_node, &mut warnings)? {
let mesh = &source.mesh;
let material_indices = source.material_indices.clone();
for conn in connections.iter() {
if conn.child == geom_id && model_map.contains_key(&conn.parent) {
let mut indices = material_indices.clone();
if let Some(model_mats) = model_material_ids.get(&conn.parent) {
if indices.is_empty() {
if !model_mats.is_empty() {
let first = model_mats[0];
indices = vec![first; mesh.num_faces()];
}
} else {
indices = indices
.into_iter()
.map(|slot| {
usize::try_from(slot)
.ok()
.and_then(|slot| model_mats.get(slot).copied())
.unwrap_or(model_mats[0])
})
.collect();
}
}
let mesh_instance = FbxMeshInstance {
name: object_name(geom_node),
mesh: source.mesh.clone(),
control_points: source.control_points.clone(),
polygon_vertex_indices: source.polygon_vertex_indices.clone(),
layers: source.layers.clone(),
edges: source.edges.clone(),
material_indices: indices,
skin: parse_skin_for_geometry(
geom_id,
deformer_map,
pose_map,
connections,
&model_node_ids,
),
morph_targets: parse_morph_targets_for_geometry(
geom_id,
geometry_map,
deformer_map,
connections,
),
};
model_mesh_instances
.entry(conn.parent)
.or_default()
.push(mesh_instance);
}
}
}
}
let model_name_map: HashMap<i64, String> = model_map
.iter()
.filter_map(|(id, node)| object_name(node).map(|name| (*id, name)))
.collect();
let animations = self.parse_animations(
&nodes,
&index,
&model_name_map,
&model_node_ids,
&morph_animation_targets(geometry_map, deformer_map, connections, model_map),
);
let mut root_nodes = Vec::new();
let top_level: Vec<i64> = ordered_model_ids
.iter()
.copied()
.filter(|id| {
!connections
.iter()
.any(|conn| conn.child == *id && model_map.contains_key(&conn.parent))
})
.collect();
let graph = ModelGraph {
models: model_map,
children: &model_children,
mesh_instances: &model_mesh_instances,
node_ids: &model_node_ids,
attributes: &node_attributes,
templates,
};
for id in top_level {
root_nodes.push(build_model_node(id, &graph, &mut Vec::new()));
}
Ok(FbxScene {
global_settings,
root_nodes,
materials,
textures,
animations,
warnings,
})
}
}
fn object_name(node: &FbxNode) -> Option<String> {
match node.properties.get(1) {
Some(FbxProperty::String(name)) => name
.split('\0')
.next()
.filter(|name| !name.is_empty())
.map(str::to_string),
_ => None,
}
}
struct ModelGraph<'a, 'n> {
models: &'a std::collections::HashMap<i64, &'n FbxNode>,
children: &'a std::collections::HashMap<i64, Vec<i64>>,
mesh_instances: &'a std::collections::HashMap<i64, Vec<FbxMeshInstance>>,
node_ids: &'a std::collections::HashMap<i64, FbxNodeId>,
attributes: &'a std::collections::HashMap<i64, FbxNodeAttribute>,
templates: &'a PropertyTemplates<'n>,
}
fn build_model_node(id: i64, graph: &ModelGraph<'_, '_>, ancestors: &mut Vec<i64>) -> FbxSceneNode {
let node_src = graph.models.get(&id).unwrap();
let mut node = FbxSceneNode::new(object_name(node_src));
node.id = graph.node_ids[&id];
if let Some((transform, transform_stack, has_complex_transform_stack)) =
parse_transform(ObjectProperties::new(node_src, graph.templates))
{
node.transform = Some(transform);
node.transform_stack = Some(transform_stack);
node.has_complex_transform_stack = has_complex_transform_stack;
}
node.attribute = graph.attributes.get(&id).cloned();
if let Some(mesh_instances) = graph.mesh_instances.get(&id) {
node.mesh_instances.extend(mesh_instances.clone());
}
const MAX_MODEL_DEPTH: usize = 256;
if ancestors.len() >= MAX_MODEL_DEPTH {
return node;
}
if let Some(children) = graph.children.get(&id) {
ancestors.push(id);
for &cid in children {
if graph.models.contains_key(&cid) && !ancestors.contains(&cid) {
node.children.push(build_model_node(cid, graph, ancestors));
}
}
ancestors.pop();
}
node
}
fn parse_global_settings(nodes: &[FbxNode]) -> Option<crate::fbx_scene::FbxGlobalSettings> {
let properties = nodes
.iter()
.find(|node| node.name == "GlobalSettings")?
.children
.iter()
.find(|node| node.name == "Properties70")?;
let integer = |property: &FbxNode| {
property.properties.iter().find_map(|value| match value {
FbxProperty::I16(value) => Some(*value as i32),
FbxProperty::I32(value) => Some(*value),
FbxProperty::I64(value) => i32::try_from(*value).ok(),
_ => None,
})
};
let number = |property: &FbxNode| {
property.properties.iter().find_map(|value| match value {
FbxProperty::F32(value) => Some(f64::from(*value)),
FbxProperty::F64(value) => Some(*value),
_ => None,
})
};
let mut result = crate::fbx_scene::FbxGlobalSettings::default();
for property in &properties.children {
let Some(FbxProperty::String(name)) = property.properties.first() else {
continue;
};
match name.as_str() {
"UpAxis" => result.up_axis = integer(property),
"UpAxisSign" => result.up_axis_sign = integer(property),
"FrontAxis" => result.front_axis = integer(property),
"FrontAxisSign" => result.front_axis_sign = integer(property),
"CoordAxis" => result.coord_axis = integer(property),
"CoordAxisSign" => result.coord_axis_sign = integer(property),
"UnitScaleFactor" => result.unit_scale_factor = number(property),
"OriginalUnitScaleFactor" => result.original_unit_scale_factor = number(property),
"TimeMode" => result.time_mode = integer(property),
_ => {}
}
}
(result != crate::fbx_scene::FbxGlobalSettings::default()).then_some(result)
}
fn child_i32_array(node: &FbxNode, child_name: &str) -> Vec<i32> {
node.children
.iter()
.find(|child| child.name == child_name)
.and_then(|child| child.properties.first())
.and_then(|value| match value {
FbxProperty::I32Array(values) => Some(values.clone()),
_ => None,
})
.unwrap_or_default()
}
fn child_f64_array(node: &FbxNode, child_name: &str) -> Vec<f64> {
node.children
.iter()
.find(|child| child.name == child_name)
.and_then(|child| child.properties.first())
.and_then(|value| match value {
FbxProperty::F64Array(values) => Some(values.clone()),
FbxProperty::F32Array(values) => Some(values.iter().copied().map(f64::from).collect()),
_ => None,
})
.unwrap_or_default()
}
fn parse_skin_for_geometry(
geometry_id: i64,
deformers: &std::collections::HashMap<i64, &FbxNode>,
poses: &std::collections::HashMap<i64, &FbxNode>,
connections: &[FbxConnection],
model_node_ids: &std::collections::HashMap<i64, FbxNodeId>,
) -> Option<crate::fbx_scene::FbxSkin> {
let skin_ids: Vec<i64> = connections
.iter()
.filter(|connection| {
connection.kind == ConnectionKind::Oo && connection.parent == geometry_id
})
.map(|connection| connection.child)
.filter(|id| {
deformers
.get(id)
.and_then(|node| deformer_type(node).map(str::to_string))
.as_deref()
== Some("Skin")
})
.collect();
if skin_ids.is_empty() {
return None;
}
let mut clusters = Vec::new();
for skin_id in skin_ids {
for cluster_id in connections
.iter()
.filter(|connection| {
connection.kind == ConnectionKind::Oo && connection.parent == skin_id
})
.map(|connection| connection.child)
{
let Some(cluster) = deformers.get(&cluster_id) else {
continue;
};
if deformer_type(cluster) != Some("Cluster") {
continue;
}
let Some(joint_model_id) = connections
.iter()
.find(|connection| {
connection.kind == ConnectionKind::Oo && connection.parent == cluster_id
})
.map(|connection| connection.child)
else {
continue;
};
let Some(&joint_node_id) = model_node_ids.get(&joint_model_id) else {
continue;
};
let indices = child_i32_array(cluster, "Indexes")
.into_iter()
.filter_map(|index| u32::try_from(index).ok())
.collect::<Vec<_>>();
let mut weights = child_f64_array(cluster, "Weights")
.into_iter()
.map(|weight| weight as f32)
.collect::<Vec<_>>();
weights.truncate(indices.len());
if weights.len() != indices.len() {
continue;
}
clusters.push(crate::fbx_scene::FbxSkinCluster {
joint_node_id,
control_point_indices: indices,
weights,
mesh_bind_transform: transform_array(cluster, "Transform")
.unwrap_or_else(identity_transform),
joint_bind_transform: transform_array(cluster, "TransformLink")
.unwrap_or_else(identity_transform),
armature_bind_transform: transform_array(cluster, "TransformAssociateModel"),
});
}
}
let mut bind_pose = Vec::new();
let mut pose_ids: Vec<i64> = poses.keys().copied().collect();
pose_ids.sort_unstable();
for pose in pose_ids.iter().map(|id| poses[id]) {
let is_bind_pose = pose
.children
.iter()
.find(|child| child.name == "Type")
.and_then(|child| child.properties.first())
.and_then(|value| match value {
FbxProperty::String(value) => Some(value == "BindPose"),
_ => None,
})
.unwrap_or(false);
if !is_bind_pose {
continue;
}
for pose_node in &pose.children {
if pose_node.name != "PoseNode" {
continue;
}
let model_id = pose_node
.children
.iter()
.find(|child| child.name == "Node")
.and_then(|child| child.properties.first())
.and_then(object_id);
let matrix = transform_array(pose_node, "Matrix");
if let (Some(_model_id), Some(matrix), Some(&node_id)) = (
model_id,
matrix,
model_id.and_then(|id| model_node_ids.get(&id)),
) {
if !bind_pose.iter().any(|(existing, _)| *existing == node_id) {
bind_pose.push((node_id, matrix));
}
}
}
}
Some(crate::fbx_scene::FbxSkin {
clusters,
bind_pose,
})
}
fn child_f64(node: &FbxNode, name: &str) -> Option<f64> {
node.children
.iter()
.find(|child| child.name == name)
.and_then(|child| child.properties.first())
.and_then(|value| match value {
FbxProperty::F64(value) => Some(*value),
FbxProperty::F32(value) => Some(*value as f64),
FbxProperty::I32(value) => Some(f64::from(*value)),
FbxProperty::I64(value) => Some(*value as f64),
_ => None,
})
}
fn parse_morph_targets_for_geometry(
geometry_id: i64,
geometries: &std::collections::HashMap<i64, &FbxNode>,
deformers: &std::collections::HashMap<i64, &FbxNode>,
connections: &[FbxConnection],
) -> Vec<crate::fbx_scene::FbxMorphTarget> {
let mut targets = Vec::new();
for blend_shape_id in connections
.iter()
.filter(|connection| {
connection.kind == ConnectionKind::Oo && connection.parent == geometry_id
})
.map(|connection| connection.child)
{
let Some(blend_shape) = deformers.get(&blend_shape_id) else {
continue;
};
if deformer_type(blend_shape) != Some("BlendShape") {
continue;
}
for channel_id in connections
.iter()
.filter(|connection| {
connection.kind == ConnectionKind::Oo && connection.parent == blend_shape_id
})
.map(|connection| connection.child)
{
let Some(channel) = deformers.get(&channel_id) else {
continue;
};
if deformer_type(channel) != Some("BlendShapeChannel") {
continue;
}
for shape_id in connections
.iter()
.filter(|connection| {
connection.kind == ConnectionKind::Oo && connection.parent == channel_id
})
.map(|connection| connection.child)
{
let Some(shape) = geometries.get(&shape_id) else {
continue;
};
let indices = child_i32_array(shape, "Indexes")
.into_iter()
.filter_map(|index| u32::try_from(index).ok())
.collect::<Vec<_>>();
let vertices = child_f64_array(shape, "Vertices");
if vertices.len() != indices.len() * 3 {
continue;
}
let position_deltas = vertices
.chunks_exact(3)
.map(|values| [values[0] as f32, values[1] as f32, values[2] as f32])
.collect();
let full_weight = child_f64_array(channel, "FullWeights")
.first()
.copied()
.unwrap_or(100.0) as f32;
targets.push(crate::fbx_scene::FbxMorphTarget {
name: match shape.properties.get(1) {
Some(FbxProperty::String(name)) => {
name.split('\0').next().map(str::to_string)
}
_ => None,
},
control_point_indices: indices,
position_deltas,
normal_deltas: None,
default_weight: child_f64(channel, "DeformPercent").unwrap_or(0.0) as f32,
full_weight,
});
}
}
}
targets
}
fn morph_animation_targets(
geometries: &std::collections::HashMap<i64, &FbxNode>,
deformers: &std::collections::HashMap<i64, &FbxNode>,
connections: &[FbxConnection],
models: &std::collections::HashMap<i64, &FbxNode>,
) -> std::collections::HashMap<i64, (i64, u32)> {
let mut result = std::collections::HashMap::new();
for geometry_id in geometries.keys().copied() {
let Some(model_id) = connections.iter().find_map(|connection| {
(connection.kind == ConnectionKind::Oo
&& connection.child == geometry_id
&& models.contains_key(&connection.parent))
.then_some(connection.parent)
}) else {
continue;
};
for blend_shape_id in connections
.iter()
.filter(|connection| {
connection.kind == ConnectionKind::Oo
&& connection.parent == geometry_id
&& deformers
.get(&connection.child)
.and_then(|node| deformer_type(node))
== Some("BlendShape")
})
.map(|connection| connection.child)
{
for (index, channel_id) in connections
.iter()
.filter(|connection| {
connection.kind == ConnectionKind::Oo
&& connection.parent == blend_shape_id
&& deformers
.get(&connection.child)
.and_then(|node| deformer_type(node))
== Some("BlendShapeChannel")
})
.map(|connection| connection.child)
.enumerate()
{
result.insert(channel_id, (model_id, index as u32));
}
}
}
result
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ConnectionKind {
Oo,
Op,
}
struct FbxObjectIndex<'a> {
model_map: HashMap<i64, &'a FbxNode>,
model_order: Vec<i64>,
geometry_map: HashMap<i64, &'a FbxNode>,
material_map: HashMap<i64, &'a FbxNode>,
texture_map: HashMap<i64, &'a FbxNode>,
video_map: HashMap<i64, &'a FbxNode>,
astack_map: HashMap<i64, &'a FbxNode>,
alayer_map: HashMap<i64, &'a FbxNode>,
acnode_map: HashMap<i64, &'a FbxNode>,
acurve_map: HashMap<i64, &'a FbxNode>,
deformer_map: HashMap<i64, &'a FbxNode>,
pose_map: HashMap<i64, &'a FbxNode>,
attribute_map: HashMap<i64, &'a FbxNode>,
connections: Vec<FbxConnection>,
name_keyed_objects: usize,
templates: PropertyTemplates<'a>,
}
impl<'a> FbxObjectIndex<'a> {
fn build(nodes: &'a [FbxNode]) -> Self {
let mut index = Self {
model_map: HashMap::new(),
model_order: Vec::new(),
geometry_map: HashMap::new(),
material_map: HashMap::new(),
texture_map: HashMap::new(),
video_map: HashMap::new(),
astack_map: HashMap::new(),
alayer_map: HashMap::new(),
acnode_map: HashMap::new(),
acurve_map: HashMap::new(),
deformer_map: HashMap::new(),
pose_map: HashMap::new(),
attribute_map: HashMap::new(),
connections: Vec::new(),
name_keyed_objects: 0,
templates: PropertyTemplates::build(nodes),
};
for node in nodes {
if node.name == "Objects" {
for child in &node.children {
let Some(id) = child.properties.first().and_then(object_id) else {
if matches!(child.properties.first(), Some(FbxProperty::String(_))) {
index.name_keyed_objects += 1;
}
continue;
};
let id = &id;
match child.name.as_str() {
"Model" => {
let first_occurrence = index.model_map.insert(*id, child).is_none();
if first_occurrence {
index.model_order.push(*id);
}
}
"Geometry" => drop(index.geometry_map.insert(*id, child)),
"Material" => drop(index.material_map.insert(*id, child)),
"Texture" => drop(index.texture_map.insert(*id, child)),
"Video" => drop(index.video_map.insert(*id, child)),
"AnimationStack" => drop(index.astack_map.insert(*id, child)),
"AnimationLayer" => drop(index.alayer_map.insert(*id, child)),
"AnimationCurveNode" => drop(index.acnode_map.insert(*id, child)),
"AnimationCurve" => drop(index.acurve_map.insert(*id, child)),
"Pose" => drop(index.pose_map.insert(*id, child)),
"NodeAttribute" => drop(index.attribute_map.insert(*id, child)),
"Deformer" => drop(index.deformer_map.insert(*id, child)),
_ => {}
}
}
} else if node.name == "Connections" {
index
.connections
.extend(node.children.iter().filter_map(FbxConnection::from_node));
}
}
index
}
}
fn float_array(property: &FbxProperty) -> Option<Vec<f32>> {
match property {
FbxProperty::F32Array(values) => Some(values.clone()),
FbxProperty::F64Array(values) => Some(values.iter().map(|v| *v as f32).collect()),
_ => None,
}
}
fn object_id(property: &FbxProperty) -> Option<i64> {
match property {
FbxProperty::I64(value) => Some(*value),
FbxProperty::I32(value) => Some(i64::from(*value)),
_ => None,
}
}
#[derive(Debug, Clone)]
struct FbxConnection {
kind: ConnectionKind,
child: i64,
parent: i64,
property: Option<String>,
}
impl FbxConnection {
fn from_node(node: &FbxNode) -> Option<Self> {
let kind = match node.properties.first() {
Some(FbxProperty::String(code)) if code == "OO" => ConnectionKind::Oo,
Some(FbxProperty::String(code)) if code == "OP" => ConnectionKind::Op,
_ => return None,
};
let child = node.properties.get(1).and_then(object_id)?;
let parent = node.properties.get(2).and_then(object_id)?;
let property = match node.properties.get(3) {
Some(FbxProperty::String(name)) => Some(name.clone()),
_ => None,
};
Some(Self {
kind,
child,
parent,
property,
})
}
}
fn parse_materials_and_textures<'a>(
material_map: &HashMap<i64, &'a FbxNode>,
texture_map: &HashMap<i64, &FbxNode>,
video_map: &HashMap<i64, &FbxNode>,
connections: &[FbxConnection],
templates: &PropertyTemplates<'a>,
) -> (
Vec<crate::fbx_scene::FbxMaterial>,
HashMap<i64, usize>,
Vec<crate::fbx_scene::FbxTexture>,
) {
let mut materials: Vec<crate::fbx_scene::FbxMaterial> = Vec::new();
let mut material_index_by_id: HashMap<i64, usize> = HashMap::new();
let mut material_ids: Vec<i64> = material_map.keys().copied().collect();
material_ids.sort_unstable();
for id in material_ids {
let mut material = parse_material(ObjectProperties::new(material_map[&id], templates));
material.textures = collect_material_texture_bindings(id, texture_map, connections);
material_index_by_id.insert(id, materials.len());
materials.push(material);
}
let mut texture_video: HashMap<i64, i64> = HashMap::new();
for conn in connections {
if conn.kind != ConnectionKind::Oo {
continue;
}
if texture_map.contains_key(&conn.child) && video_map.contains_key(&conn.parent) {
texture_video.entry(conn.child).or_insert(conn.parent);
}
if video_map.contains_key(&conn.child) && texture_map.contains_key(&conn.parent) {
texture_video.entry(conn.parent).or_insert(conn.child);
}
}
let mut textures: Vec<crate::fbx_scene::FbxTexture> = Vec::new();
let mut texture_index_by_id: HashMap<i64, usize> = HashMap::new();
let mut texture_ids: Vec<i64> = texture_map.keys().copied().collect();
texture_ids.sort_unstable();
for id in texture_ids {
let mut texture = parse_texture(texture_map[&id]);
if let Some(video) = texture_video.get(&id).and_then(|id| video_map.get(id)) {
let from_video = parse_texture(video);
texture.content = texture.content.or(from_video.content);
texture.filename = texture.filename.or(from_video.filename);
texture.name = texture.name.or(from_video.name);
}
texture_index_by_id.insert(id, textures.len());
textures.push(texture);
}
for material in &mut materials {
for binding in &mut material.textures {
let fbx_id = binding.texture_index as i64;
if let Some(&resolved) = texture_index_by_id.get(&fbx_id) {
binding.texture_index = resolved;
}
}
}
(materials, material_index_by_id, textures)
}
fn deformer_type(node: &FbxNode) -> Option<&str> {
match node.properties.get(2) {
Some(FbxProperty::String(value)) if !value.is_empty() => Some(value.as_str()),
_ => None,
}
}
fn collect_material_texture_bindings(
material_id: i64,
texture_map: &std::collections::HashMap<i64, &FbxNode>,
connections: &[FbxConnection],
) -> Vec<crate::fbx_scene::FbxTextureBinding> {
let mut bindings = Vec::new();
for conn in connections {
if conn.kind != ConnectionKind::Op || conn.parent != material_id {
continue;
}
let Some(slot_name) = conn.property.as_deref() else {
continue;
};
let Some(slot) = crate::fbx_scene::FbxTextureSlot::from_property_name(slot_name) else {
continue;
};
if !texture_map.contains_key(&conn.child) {
continue;
}
bindings.push(crate::fbx_scene::FbxTextureBinding {
slot,
texture_index: conn.child as usize,
});
}
bindings
}
fn parse_node_attributes<'a>(
attribute_map: &HashMap<i64, &'a FbxNode>,
model_map: &HashMap<i64, &FbxNode>,
connections: &[FbxConnection],
templates: &PropertyTemplates<'a>,
warnings: &mut Vec<FbxWarning>,
) -> HashMap<i64, FbxNodeAttribute> {
let mut by_model: Vec<(i64, i64)> = connections
.iter()
.filter(|conn| {
attribute_map.contains_key(&conn.child) && model_map.contains_key(&conn.parent)
})
.map(|conn| (conn.parent, conn.child))
.collect();
by_model.sort_unstable();
let mut resolved = HashMap::new();
for (model_id, attribute_id) in by_model {
let node = attribute_map[&attribute_id];
let class = match node.properties.get(2) {
Some(FbxProperty::String(class)) => class.as_str(),
_ => continue,
};
match class {
"Camera" => {
let properties = ObjectProperties::new(node, templates);
resolved.insert(model_id, FbxNodeAttribute::Camera(parse_camera(properties)));
}
"Light" => {
let properties = ObjectProperties::new(node, templates);
resolved.insert(model_id, FbxNodeAttribute::Light(parse_light(properties)));
}
"LimbNode" | "Limb" | "Null" | "Root" => {}
other => push_warning(
warnings,
FbxWarningCode::DroppedNodeAttribute,
format!(
"FBX NodeAttribute of class {other} is not represented, so its properties \
are absent from the scene"
),
Some(other),
),
}
}
resolved
}
fn parse_camera(properties: ObjectProperties<'_>) -> crate::fbx_scene::FbxCamera {
let scalar = |name: &str| properties.get(name).and_then(property_scalar);
let vector = |name: &str| properties.get(name).and_then(property_vec3);
crate::fbx_scene::FbxCamera {
position: vector("Position"),
interest_position: vector("InterestPosition"),
up_vector: vector("UpVector"),
projection_type: scalar("CameraProjectionType").map(|v| v as i32),
field_of_view: scalar("FieldOfView"),
field_of_view_x: scalar("FieldOfViewX"),
field_of_view_y: scalar("FieldOfViewY"),
focal_length: scalar("FocalLength"),
near_plane: scalar("NearPlane"),
far_plane: scalar("FarPlane"),
aspect_width: scalar("AspectWidth"),
aspect_height: scalar("AspectHeight"),
film_width: scalar("FilmWidth"),
film_height: scalar("FilmHeight"),
film_aspect_ratio: scalar("FilmAspectRatio"),
aperture_mode: scalar("ApertureMode").map(|v| v as i32),
ortho_zoom: scalar("OrthoZoom"),
}
}
fn parse_light(properties: ObjectProperties<'_>) -> crate::fbx_scene::FbxLight {
let scalar = |name: &str| properties.get(name).and_then(property_scalar);
crate::fbx_scene::FbxLight {
light_type: scalar("LightType").map(|v| v as i32),
color: properties.get("Color").and_then(property_vec3),
intensity: scalar("Intensity"),
cast_light: scalar("CastLight").map(|v| v != 0.0),
cast_shadows: scalar("CastShadows").map(|v| v != 0.0),
decay_type: scalar("DecayType").map(|v| v as i32),
decay_start: scalar("DecayStart"),
}
}
fn property_scalar(prop: &FbxNode) -> Option<f32> {
for value in prop.properties.iter().skip(4) {
match value {
FbxProperty::F64(v) => return Some(*v as f32),
FbxProperty::F32(v) => return Some(*v),
FbxProperty::I32(v) => return Some(*v as f32),
FbxProperty::I64(v) => return Some(*v as f32),
_ => {}
}
}
None
}
fn property_vec3(prop: &FbxNode) -> Option<[f32; 3]> {
let values: Vec<f32> = prop
.properties
.iter()
.skip(4)
.filter_map(|value| match value {
FbxProperty::F64(v) => Some(*v as f32),
FbxProperty::F32(v) => Some(*v),
_ => None,
})
.take(3)
.collect();
(values.len() == 3).then(|| [values[0], values[1], values[2]])
}
fn parse_material(properties: ObjectProperties<'_>) -> crate::fbx_scene::FbxMaterial {
let name = match properties.node().properties.get(1) {
Some(FbxProperty::String(raw)) => raw
.split('\0')
.next()
.filter(|s| !s.is_empty())
.map(str::to_string),
_ => None,
};
let shading_model = read_shading_model(properties);
let get_color = |name: &str| properties.get(name).and_then(property_vec3);
let get_scalar = |name: &str| properties.get(name).and_then(property_scalar);
crate::fbx_scene::FbxMaterial {
name,
shading_model,
diffuse: get_color("DiffuseColor"),
specular: get_color("SpecularColor"),
emissive: get_color("EmissiveColor"),
ambient: get_color("AmbientColor"),
diffuse_factor: get_scalar("DiffuseFactor"),
specular_factor: get_scalar("SpecularFactor"),
shininess: get_scalar("Shininess"),
emissive_factor: get_scalar("EmissiveFactor"),
reflection_factor: get_scalar("ReflectionFactor"),
transparency_factor: get_scalar("TransparencyFactor"),
opacity: get_scalar("Opacity"),
bump_factor: get_scalar("BumpFactor"),
textures: Vec::new(),
}
}
fn read_shading_model(properties: ObjectProperties<'_>) -> Option<String> {
let object = properties.node();
let from_own_properties = properties
.node()
.children
.iter()
.filter(|child| child.name == "Properties70")
.find_map(|block| crate::fbx_templates::find_property(block, "ShadingModel"))
.and_then(string_value);
let from_sibling_node = object
.children
.iter()
.find(|child| child.name == "ShadingModel")
.and_then(|child| match child.properties.first() {
Some(FbxProperty::String(model)) if !model.is_empty() => Some(model.clone()),
_ => None,
});
let from_template = properties
.template()
.and_then(|block| crate::fbx_templates::find_property(block, "ShadingModel"))
.and_then(string_value);
let from_class = match object.properties.get(2) {
Some(FbxProperty::String(raw)) if !raw.is_empty() => Some(raw.clone()),
_ => None,
};
from_own_properties
.or(from_sibling_node)
.or(from_template)
.or(from_class)
}
fn string_value(property: &FbxNode) -> Option<String> {
property
.properties
.iter()
.skip(4)
.find_map(|value| match value {
FbxProperty::String(text) => Some(text.clone()),
_ => None,
})
}
fn parse_texture(node: &FbxNode) -> crate::fbx_scene::FbxTexture {
let name = match node.properties.get(1) {
Some(FbxProperty::String(raw)) => raw
.split('\0')
.next()
.filter(|s| !s.is_empty())
.map(str::to_string),
_ => None,
};
let mut filename = None;
let mut content = None;
for child in &node.children {
match child.name.as_str() {
"RelativeFilename" | "FileName" | "Filename" if filename.is_none() => {
if let Some(FbxProperty::String(s)) = child.properties.first() {
if !s.is_empty() {
filename = Some(s.clone());
}
}
}
"Content" => {
if let Some(FbxProperty::Raw(bytes)) = child.properties.first() {
if !bytes.is_empty() {
content = Some(bytes.clone());
}
}
}
_ => {}
}
}
crate::fbx_scene::FbxTexture {
name,
content,
filename,
}
}
impl<R: Read + Seek> FbxReader<R> {
pub fn read_meshes(&mut self) -> io::Result<Vec<Mesh>> {
let nodes = self.read_nodes()?;
let mut meshes = Vec::new();
let mut warnings = Vec::new();
for node in &nodes {
if node.name == "Objects" {
for child in &node.children {
if child.name == "Geometry" {
if let Some(source) = geometry_to_mesh(child, &mut warnings)? {
meshes.push(source.mesh);
}
}
}
}
}
self.extend_warnings(warnings);
Ok(meshes)
}
}
#[derive(Default)]
struct RawLayerNodes<'a> {
normals: Vec<&'a FbxNode>,
uvs: Vec<&'a FbxNode>,
colors: Vec<&'a FbxNode>,
tangents: Vec<&'a FbxNode>,
binormals: Vec<&'a FbxNode>,
smoothing: Vec<&'a FbxNode>,
creases: Vec<(FbxCreaseKind, &'a FbxNode)>,
material: Option<&'a FbxNode>,
}
#[derive(Clone, Copy)]
struct LayerDomains {
edges: Option<usize>,
polygons: usize,
control_points: usize,
}
impl LayerDomains {
fn check(self, mapping: Option<&str>) -> DomainCheck {
match mapping {
Some("ByEdge") => match self.edges {
Some(count) => DomainCheck::Expect(count),
None => DomainCheck::Unverifiable,
},
Some("ByPolygon") => DomainCheck::Expect(self.polygons),
Some("ByVertice") | Some("ByVertex") | Some("ByControlPoint") => {
DomainCheck::Expect(self.control_points)
}
_ => DomainCheck::Unknown,
}
}
}
fn geometry_to_mesh(
geometry: &FbxNode,
warnings: &mut Vec<FbxWarning>,
) -> io::Result<Option<FbxGeometrySource>> {
let mut vertices: Option<Vec<f64>> = None;
let mut polygon_indices: Option<Vec<i32>> = None;
let mut edges: Vec<i32> = Vec::new();
let mut raw = RawLayerNodes::default();
for child in &geometry.children {
match child.name.as_str() {
"Vertices" => {
if let Some(FbxProperty::F64Array(arr)) = child.properties.first() {
vertices = Some(arr.clone());
}
}
"Edges" => {
if let Some(FbxProperty::I32Array(arr)) = child.properties.first() {
edges = arr.clone();
}
}
"PolygonVertexIndex" => {
if let Some(FbxProperty::I32Array(arr)) = child.properties.first() {
polygon_indices = Some(arr.clone());
}
}
"LayerElementNormal" => raw.normals.push(child),
"LayerElementColor" => raw.colors.push(child),
"LayerElementUV" => raw.uvs.push(child),
"LayerElementTangent" => raw.tangents.push(child),
"LayerElementBinormal" => raw.binormals.push(child),
"LayerElementSmoothing" => raw.smoothing.push(child),
"LayerElementEdgeCrease" => raw.creases.push((FbxCreaseKind::Edge, child)),
"LayerElementVertexCrease" => raw.creases.push((FbxCreaseKind::Vertex, child)),
"LayerElementMaterial" if raw.material.is_none() => {
raw.material = Some(child);
}
other if other.starts_with("LayerElement") => push_warning(
warnings,
FbxWarningCode::DroppedLayerElement,
format!("FBX {other} is not imported, so its data is absent from the scene"),
Some(other),
),
_ => {}
}
}
let vertices = match vertices {
Some(v) => v,
None => return Ok(None),
};
let polygon_indices = match polygon_indices {
Some(p) => p,
None => return Ok(None),
};
let control_points = vertices
.chunks_exact(3)
.map(|value| [value[0] as f32, value[1] as f32, value[2] as f32])
.collect::<Vec<_>>();
let mut tri_polygon_index: Vec<usize> = Vec::new();
let mut polygon_count = 0usize;
let mut corners_in_polygon = 0usize;
for &idx in &polygon_indices {
corners_in_polygon += 1;
if idx < 0 {
for _ in 0..corners_in_polygon.saturating_sub(2) {
tri_polygon_index.push(polygon_count);
}
corners_in_polygon = 0;
polygon_count += 1;
}
}
let material_indices = raw
.material
.and_then(|layer| {
let mapping = layer_string(layer, "MappingInformationType");
let reference = layer_string(layer, "ReferenceInformationType");
let data = layer_int_array(layer, "Materials");
expand_material_indices(
mapping.as_deref(),
reference.as_deref(),
data.as_deref(),
polygon_count,
&tri_polygon_index,
)
})
.unwrap_or_default();
let domains = LayerDomains {
edges: (!edges.is_empty()).then_some(edges.len()),
polygons: polygon_count,
control_points: control_points.len(),
};
let layers = parse_geometry_layers(raw, domains, warnings);
let render = crate::fbx_render_mesh::expand_to_render_mesh(
crate::fbx_render_mesh::FbxGeometryLayers::new(&control_points, &polygon_indices, &layers),
);
let mesh = crate::fbx_render_mesh::build_draco_mesh(&render);
Ok(Some(FbxGeometrySource {
mesh,
material_indices,
control_points,
polygon_vertex_indices: polygon_indices,
layers,
edges,
}))
}
fn parse_geometry_layers(
raw: RawLayerNodes<'_>,
domains: LayerDomains,
warnings: &mut Vec<FbxWarning>,
) -> FbxMeshLayers {
let uv_sets: Vec<FbxUvSet> = raw
.uvs
.into_iter()
.filter_map(|layer| {
let values = chunk_layer_values(&read_layer_floats(layer, "UV")?);
Some(layer_set(layer, values, &["UVIndex"]))
})
.collect();
let normal_sets: Vec<FbxNormalSet> = raw
.normals
.into_iter()
.filter_map(|layer| {
let values = chunk_layer_values(&read_layer_floats(layer, "Normals")?);
Some(layer_set(layer, values, &["NormalsIndex", "NormalIndex"]))
})
.collect();
for set in &uv_sets {
warn_unsupported_layer_mapping("LayerElementUV", set, warnings);
}
for set in &normal_sets {
warn_unsupported_layer_mapping("LayerElementNormal", set, warnings);
}
let color_sets: Vec<FbxColorSet> = raw
.colors
.into_iter()
.filter_map(|layer| {
let floats = read_layer_floats(layer, "Colors")?;
let values = if floats.len() % 4 == 0 {
chunk_layer_values(&floats)
} else {
floats
.chunks_exact(3)
.map(|value| [value[0], value[1], value[2], 1.0])
.collect()
};
Some(layer_set(layer, values, &["ColorIndex"]))
})
.collect();
for set in &color_sets {
warn_unsupported_layer_mapping("LayerElementColor", set, warnings);
}
let tangent_sets: Vec<FbxTangentSet> = raw
.tangents
.into_iter()
.filter_map(|layer| parse_tangent_like(layer, "Tangents", "TangentsW", "TangentIndex"))
.collect();
let binormal_sets: Vec<FbxBinormalSet> = raw
.binormals
.into_iter()
.filter_map(|layer| parse_tangent_like(layer, "Binormals", "BinormalsW", "BinormalIndex"))
.collect();
for set in &tangent_sets {
warn_unsupported_layer_mapping("LayerElementTangent", &set.layer, warnings);
}
for set in &binormal_sets {
warn_unsupported_layer_mapping("LayerElementBinormal", &set.layer, warnings);
}
let mut smoothing_layers = Vec::new();
for layer in raw.smoothing {
let mapping = layer_string(layer, "MappingInformationType");
let Some(values) = layer_int_array(layer, "Smoothing") else {
continue;
};
if domains.check(mapping.as_deref()).accepts(values.len()) {
smoothing_layers.push(FbxSmoothingLayer { mapping, values });
} else {
warn_misaligned_layer(
"LayerElementSmoothing",
mapping.as_deref(),
values.len(),
warnings,
);
}
}
let mut crease_layers = Vec::new();
for (kind, layer) in raw.creases {
let element = match kind {
FbxCreaseKind::Edge => "LayerElementEdgeCrease",
FbxCreaseKind::Vertex => "LayerElementVertexCrease",
};
let mapping = layer_string(layer, "MappingInformationType");
let Some(values) = layer_f64_array(layer, element.trim_start_matches("LayerElement"))
else {
continue;
};
match domains.check(mapping.as_deref()) {
domain if domain.accepts(values.len()) => {
crease_layers.push(FbxCreaseLayer {
kind,
mapping,
values,
});
}
_ => warn_misaligned_layer(element, mapping.as_deref(), values.len(), warnings),
}
}
FbxMeshLayers {
uv_sets,
normal_sets,
color_sets,
tangent_sets,
binormal_sets,
smoothing_layers,
crease_layers,
}
}
impl<R: Read + Seek> FbxReader<R> {
fn parse_animations(
&self,
nodes: &[FbxNode],
index: &FbxObjectIndex<'_>,
model_name_map: &HashMap<i64, String>,
model_node_ids: &HashMap<i64, FbxNodeId>,
morph_targets: &HashMap<i64, (i64, u32)>,
) -> Vec<FbxAnimation> {
let FbxObjectIndex {
connections,
astack_map,
alayer_map,
acnode_map,
acurve_map,
model_map,
..
} = index;
let fbx_ktime = fbx_ktime_for(nodes, self.version());
let ktime_f = match fbx_ktime {
0 => 1.0,
v => v as f64,
};
let mut acnode_targets: std::collections::HashMap<
i64,
(i64, i64, FbxAnimChannelPath, Option<u32>),
> = std::collections::HashMap::new();
for conn in connections {
if conn.kind != ConnectionKind::Op {
continue;
}
if !acnode_map.contains_key(&conn.child) {
continue;
}
let Some(property) = conn.property.as_deref() else {
continue;
};
let Some(path) = FbxAnimChannelPath::from_property_name(property) else {
continue;
};
let (model_id, morph_target_index) = if model_map.contains_key(&conn.parent) {
(conn.parent, None)
} else if path == FbxAnimChannelPath::MorphWeight {
let Some(&(model_id, target_index)) = morph_targets.get(&conn.parent) else {
continue;
};
(model_id, Some(target_index))
} else {
continue;
};
let mut layer_id = None;
for c2 in connections {
if c2.kind == ConnectionKind::Oo
&& c2.child == conn.child
&& alayer_map.contains_key(&c2.parent)
{
layer_id = Some(c2.parent);
break;
}
}
if let Some(layer_id) = layer_id {
acnode_targets.insert(conn.child, (layer_id, model_id, path, morph_target_index));
}
}
let mut acnode_curves: std::collections::HashMap<
i64,
std::collections::BTreeMap<u32, FbxAnimCurveData>,
> = std::collections::HashMap::new();
for conn in connections {
if conn.kind != ConnectionKind::Op {
continue;
}
if !acurve_map.contains_key(&conn.child) {
continue;
}
if !acnode_targets.contains_key(&conn.parent) {
continue;
}
let component = match conn.property.as_deref() {
Some("d|X") => 0,
Some("d|Y") => 1,
Some("d|Z") => 2,
_ => continue,
};
if let Some(curve) = parse_curve(acurve_map[&conn.child]) {
acnode_curves
.entry(conn.parent)
.or_default()
.insert(component, curve);
}
}
let mut stacks_layers: StacksLayers = std::collections::HashMap::new();
let mut acnode_ids_sorted: Vec<i64> = acnode_targets.keys().copied().collect();
acnode_ids_sorted.sort_unstable();
for acnode_id in &acnode_ids_sorted {
let (layer_id, model_id, path, morph_target_index) = &acnode_targets[acnode_id];
let mut stack_ids = Vec::new();
for c2 in connections {
if c2.kind == ConnectionKind::Oo
&& c2.child == *layer_id
&& astack_map.contains_key(&c2.parent)
{
stack_ids.push(c2.parent);
}
}
for stack_id in stack_ids {
stacks_layers
.entry(stack_id)
.or_default()
.entry(*layer_id)
.or_default()
.push((*acnode_id, *model_id, *path, *morph_target_index));
}
}
let mut animations = Vec::new();
let mut stack_ids_sorted: Vec<i64> = stacks_layers.keys().copied().collect();
stack_ids_sorted.sort_unstable();
for stack_id in stack_ids_sorted {
let layers = &stacks_layers[&stack_id];
let stack_node = astack_map.get(&stack_id);
let name = stack_node.and_then(|n| match n.properties.get(1) {
Some(FbxProperty::String(raw)) => raw
.split('\0')
.next()
.filter(|s| !s.is_empty())
.map(str::to_string),
_ => None,
});
let mut layer_ids_sorted: Vec<i64> = layers.keys().copied().collect();
layer_ids_sorted.sort_unstable();
let multiple_layers = layer_ids_sorted.len() > 1;
for (layer_index, layer_id) in layer_ids_sorted.iter().copied().enumerate() {
let mut channels = Vec::new();
let mut max_time = 0.0f32;
let entries = &layers[&layer_id];
let mut groups: std::collections::HashMap<
(i64, FbxAnimChannelPath, Option<u32>),
Vec<i64>,
> = std::collections::HashMap::new();
for &(acnode_id, model_id, path, morph_target_index) in entries {
groups
.entry((model_id, path, morph_target_index))
.or_default()
.push(acnode_id);
}
let mut group_keys: Vec<(i64, FbxAnimChannelPath, Option<u32>)> =
groups.keys().copied().collect();
group_keys.sort_unstable_by_key(|(model_id, path, morph_target_index)| {
(*model_id, *path as u8, *morph_target_index)
});
for (model_id, path, morph_target_index) in group_keys {
let acnode_ids = &groups[&(model_id, path, morph_target_index)];
let mut by_component: std::collections::BTreeMap<u32, FbxAnimCurveData> =
std::collections::BTreeMap::new();
for acnode_id in acnode_ids {
if let Some(curves) = acnode_curves.get(acnode_id) {
for (component, curve) in curves {
by_component
.entry(*component)
.or_insert_with(|| curve.clone());
}
}
}
let Some(channel) = flatten_curve(&by_component, path, ktime_f) else {
continue;
};
if let (Some(node_name), Some(&node_id)) =
(model_name_map.get(&model_id), model_node_ids.get(&model_id))
{
max_time =
max_time.max(channel.sampler.input.last().copied().unwrap_or(0.0));
channels.push(FbxAnimChannel {
node_id,
node_name: node_name.clone(),
path,
morph_target_index,
sampler: channel.sampler,
});
}
}
if channels.is_empty() {
continue;
}
let clip_name = if multiple_layers {
let layer_name = alayer_map
.get(&layer_id)
.and_then(|node| match node.properties.get(1) {
Some(FbxProperty::String(raw)) => raw
.split('\0')
.next()
.filter(|part| !part.is_empty())
.map(str::to_string),
_ => None,
})
.unwrap_or_else(|| format!("Layer{layer_index}"));
Some(match &name {
Some(stack) => format!("{stack}|{layer_name}"),
None => layer_name,
})
} else {
name.clone()
};
animations.push(FbxAnimation {
name: clip_name,
duration: max_time,
channels,
});
}
}
animations
}
}
type StacksLayers = std::collections::HashMap<
i64,
std::collections::HashMap<i64, Vec<(i64, i64, FbxAnimChannelPath, Option<u32>)>>,
>;
#[derive(Debug, Clone)]
struct FbxAnimCurveData {
key_times: Vec<i64>,
key_values: Vec<f32>,
key_attr_flags: Vec<i32>,
in_tangents: Vec<f32>,
out_tangents: Vec<f32>,
}
fn parse_curve(node: &FbxNode) -> Option<FbxAnimCurveData> {
let mut key_times = None;
let mut key_values = None;
let mut key_attr_flags = None;
let mut key_attr_data = None;
let mut key_attr_ref_count = None;
for child in &node.children {
match child.name.as_str() {
"KeyTime" => {
if let Some(FbxProperty::I64Array(arr)) = child.properties.first() {
key_times = Some(arr.clone());
}
}
"KeyValueFloat" => {
key_values = child.properties.first().and_then(float_array);
}
"KeyAttrFlags" => {
if let Some(FbxProperty::I32Array(arr)) = child.properties.first() {
key_attr_flags = Some(arr.clone());
}
}
"KeyAttrDataFloat" => {
key_attr_data = child.properties.first().and_then(float_array);
}
"KeyAttrRefCount" => {
if let Some(FbxProperty::I32Array(arr)) = child.properties.first() {
key_attr_ref_count = Some(arr.clone());
}
}
_ => {}
}
}
let key_times = key_times?;
let key_values = key_values?;
if key_times.is_empty() || key_values.len() != key_times.len() {
return None;
}
let mut expanded_flags = Vec::with_capacity(key_times.len());
let mut expanded_attrs = Vec::with_capacity(key_times.len());
if let (Some(flags), Some(data), Some(refs)) =
(key_attr_flags, key_attr_data, key_attr_ref_count)
{
if flags.len() == refs.len() && data.len() == refs.len() * 4 {
for ((flag, count), attrs) in flags.into_iter().zip(refs).zip(data.chunks_exact(4)) {
for _ in 0..count.max(0) {
expanded_flags.push(flag);
expanded_attrs.push([attrs[0], attrs[1]]);
}
}
}
}
if expanded_flags.len() != key_times.len() {
expanded_flags = vec![0x4; key_times.len()];
expanded_attrs = vec![[0.0, 0.0]; key_times.len()];
}
let mut in_tangents = vec![0.0; key_times.len()];
let mut out_tangents = vec![0.0; key_times.len()];
for (index, attrs) in expanded_attrs.iter().enumerate() {
out_tangents[index] = attrs[0];
if index + 1 < in_tangents.len() {
in_tangents[index + 1] = attrs[1];
}
}
Some(FbxAnimCurveData {
key_times,
key_values,
key_attr_flags: expanded_flags,
in_tangents,
out_tangents,
})
}
fn flatten_curve(
by_component: &std::collections::BTreeMap<u32, FbxAnimCurveData>,
path: FbxAnimChannelPath,
ktime_f: f64,
) -> Option<FbxAnimChannel> {
let time_axis = by_component
.get(&0)
.or_else(|| by_component.get(&1))
.or_else(|| by_component.get(&2))?;
let n = time_axis.key_times.len();
let mut input = Vec::with_capacity(n);
let component_count = path.component_count();
let mut output = Vec::with_capacity(n * component_count);
let mut in_tangents = Vec::with_capacity(n * component_count);
let mut out_tangents = Vec::with_capacity(n * component_count);
let flags = time_axis.key_attr_flags.first().copied().unwrap_or(0);
let interpolation = FbxAnimInterpolation::from_key_attr_flags(flags);
for i in 0..n {
input.push((time_axis.key_times[i] as f64 / ktime_f) as f32);
for component in 0..component_count as u32 {
let value = by_component.get(&component).and_then(|curve| {
if i < curve.key_values.len() {
Some(curve.key_values[i])
} else {
None
}
});
output.push(value.unwrap_or(0.0));
in_tangents.push(
by_component
.get(&component)
.and_then(|curve| curve.in_tangents.get(i))
.copied()
.unwrap_or(0.0),
);
out_tangents.push(
by_component
.get(&component)
.and_then(|curve| curve.out_tangents.get(i))
.copied()
.unwrap_or(0.0),
);
}
}
let radians = |value: f32| f64::from(value).to_radians() as f32;
if path == FbxAnimChannelPath::Rotation {
for chunk in output.chunks_mut(3) {
for value in chunk.iter_mut() {
*value = radians(*value);
}
}
for chunk in in_tangents.chunks_mut(3) {
for value in chunk.iter_mut() {
*value = radians(*value);
}
}
for chunk in out_tangents.chunks_mut(3) {
for value in chunk.iter_mut() {
*value = radians(*value);
}
}
}
Some(FbxAnimChannel {
node_id: FbxNodeId(0),
node_name: String::new(),
path,
morph_target_index: None,
sampler: FbxAnimSampler {
input,
output,
interpolation,
in_tangents: (interpolation == FbxAnimInterpolation::Cubic).then_some(in_tangents),
out_tangents: (interpolation == FbxAnimInterpolation::Cubic).then_some(out_tangents),
},
})
}
fn fbx_ktime_for(nodes: &[FbxNode], version: u32) -> u64 {
const KTIME_V7: u64 = 46_186_158_000;
const KTIME_V8: u64 = 141_120_000;
if version >= 8000 {
return KTIME_V8;
}
if version >= 7700 {
for n in nodes {
if n.name != "FBXHeaderExtension" {
continue;
}
let mut header_version = 0;
let mut other_flags: Option<&FbxNode> = None;
for child in &n.children {
if child.name == "FBXHeaderVersion" {
if let Some(FbxProperty::I32(v)) = child.properties.first() {
header_version = *v;
}
} else if child.name == "OtherFlags" && other_flags.is_none() {
other_flags = Some(child);
}
}
if header_version >= 1004 {
if let Some(flags) = other_flags {
for flag in &flags.children {
if flag.name == "TCDefinition" {
if let Some(FbxProperty::I32(v)) = flag.properties.first() {
return if *v == 127 { KTIME_V7 } else { KTIME_V8 };
}
}
}
}
}
}
return KTIME_V7;
}
KTIME_V7
}
fn layer_string(layer: &FbxNode, name: &str) -> Option<String> {
for child in &layer.children {
if child.name == name {
if let Some(FbxProperty::String(s)) = child.properties.first() {
return Some(s.clone());
}
}
}
None
}
fn layer_int_array(layer: &FbxNode, name: &str) -> Option<Vec<i32>> {
for child in &layer.children {
if child.name == name {
if let Some(FbxProperty::I32Array(arr)) = child.properties.first() {
return Some(arr.clone());
}
}
}
None
}
fn warn_unsupported_layer_mapping<const N: usize>(
element: &str,
set: &FbxLayerSet<N>,
warnings: &mut Vec<FbxWarning>,
) {
const KNOWN_MAPPINGS: [&str; 7] = [
"ByPolygonVertex",
"ByPolygon",
"ByVertice",
"ByVertex",
"ByControlPoint",
"AllSame",
"AllSameOrPolygon",
];
if let Some(mapping) = set.mapping.as_deref() {
if !KNOWN_MAPPINGS.contains(&mapping) {
let subject = format!("{element}/{mapping}");
push_warning(
warnings,
FbxWarningCode::UnsupportedLayerMapping,
format!(
"FBX {element} uses mapping {mapping}, which was resolved on the \
control-point domain"
),
Some(&subject),
);
}
}
if let Some(reference) = set.reference.as_deref() {
if reference != "Direct" && reference != "IndexToDirect" {
let subject = format!("{element}/{reference}");
push_warning(
warnings,
FbxWarningCode::UnsupportedLayerMapping,
format!("FBX {element} uses reference mode {reference}, which was read as Direct"),
Some(&subject),
);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DomainCheck {
Expect(usize),
Unverifiable,
Unknown,
}
impl DomainCheck {
fn accepts(self, len: usize) -> bool {
match self {
DomainCheck::Expect(expected) => expected == len,
DomainCheck::Unverifiable => true,
DomainCheck::Unknown => false,
}
}
}
fn warn_misaligned_layer(
element: &str,
mapping: Option<&str>,
len: usize,
warnings: &mut Vec<FbxWarning>,
) {
let mapping = mapping.unwrap_or("no mapping");
let subject = format!("{element}/{mapping}");
push_warning(
warnings,
FbxWarningCode::UnsupportedLayerMapping,
format!(
"FBX {element} has {len} values, which does not match the domain \
{mapping} addresses, so the layer was dropped"
),
Some(&subject),
);
}
fn layer_f64_array(layer: &FbxNode, name: &str) -> Option<Vec<f64>> {
for child in &layer.children {
if child.name == name {
return match child.properties.first() {
Some(FbxProperty::F64Array(arr)) => Some(arr.clone()),
Some(FbxProperty::F32Array(arr)) => {
Some(arr.iter().map(|v| f64::from(*v)).collect())
}
_ => None,
};
}
}
None
}
fn parse_tangent_like(
layer: &FbxNode,
values_node: &str,
handedness_node: &str,
index_node: &str,
) -> Option<FbxTangentSet> {
let vectors: Vec<[f32; 3]> = chunk_layer_values(&read_layer_floats(layer, values_node)?);
let handedness = read_layer_floats(layer, handedness_node)
.filter(|signs| signs.len() == vectors.len())
.unwrap_or_default();
let has_handedness = !handedness.is_empty();
let values = vectors
.iter()
.enumerate()
.map(|(index, v)| {
let sign = handedness.get(index).copied().unwrap_or(1.0);
[v[0], v[1], v[2], sign]
})
.collect();
Some(FbxTangentSet {
layer: layer_set(layer, values, &[index_node]),
has_handedness,
})
}
fn layer_set<const N: usize>(
layer: &FbxNode,
values: Vec<[f32; N]>,
index_nodes: &[&str],
) -> FbxLayerSet<N> {
FbxLayerSet {
name: layer_string(layer, "Name"),
mapping: layer_string(layer, "MappingInformationType"),
reference: layer_string(layer, "ReferenceInformationType"),
values,
indices: index_nodes
.iter()
.find_map(|name| layer_int_array(layer, name))
.unwrap_or_default(),
}
}
fn chunk_layer_values<const N: usize>(raw: &[f32]) -> Vec<[f32; N]> {
raw.chunks_exact(N)
.map(|value| std::array::from_fn(|i| value[i]))
.collect()
}
fn read_layer_floats(layer: &FbxNode, name: &str) -> Option<Vec<f32>> {
for child in &layer.children {
if child.name == name {
if let Some(FbxProperty::F64Array(arr)) = child.properties.first() {
return Some(arr.iter().map(|v| *v as f32).collect());
}
if let Some(FbxProperty::F32Array(arr)) = child.properties.first() {
return Some(arr.clone());
}
}
}
None
}
fn expand_material_indices(
mapping: Option<&str>,
reference: Option<&str>,
data: Option<&[i32]>,
polygon_count: usize,
tri_polygon_index: &[usize],
) -> Option<Vec<i32>> {
let mapping = mapping.unwrap_or("AllSame");
let data = data?;
let _ = reference;
let per_polygon: Vec<i32> = match mapping {
"AllSame" => {
let value = data.first().copied().unwrap_or(0);
vec![value; polygon_count.max(1)]
}
"ByPolygon" | "ByPolygonSide" => data.to_vec(),
"ByPolygonVertex" => {
let value = data.first().copied().unwrap_or(0);
vec![value; polygon_count.max(1)]
}
_ => return None,
};
if per_polygon.is_empty() {
return Some(Vec::new());
}
let mut out = Vec::with_capacity(tri_polygon_index.len());
for &polygon_index in tri_polygon_index {
let value = per_polygon
.get(polygon_index)
.copied()
.unwrap_or(per_polygon[0]);
out.push(value);
}
Some(out)
}