use std::env;
use std::path::{Path, PathBuf};
use draco_io::{FbxScene, FbxSceneNode};
#[derive(Default)]
struct Summary {
counts: Vec<String>,
meshes: Vec<String>,
transforms: Vec<String>,
transform_stacks: Vec<String>,
channels: Vec<String>,
material_names: Vec<String>,
texture_names: Vec<String>,
global_settings: String,
}
fn visit(node: &FbxSceneNode, out: &mut Summary) {
let name = node.name.clone().unwrap_or_default();
out.transforms.push(format!(
"{name}|{:?}",
node.transform.as_ref().map(|t| t
.matrix
.map(|row| row.map(|v| (f64::from(v) * 1e4).round() as i64)))
));
out.transform_stacks
.push(format!("{name}|{:?}", node.transform_stack));
for mesh in &node.mesh_instances {
out.meshes.push(format!(
"{name}|{:?}|points={}|corners={}|uv={}|colour={}|normal={}|edges={}",
mesh.name,
mesh.control_points.len(),
mesh.polygon_vertex_indices.len(),
mesh.layers.uv_sets.len(),
mesh.layers.color_sets.len(),
mesh.layers.normal_sets.len(),
mesh.edges.len(),
));
}
for child in &node.children {
visit(child, out);
}
}
fn summarize(scene: &FbxScene) -> Summary {
let mut out = Summary {
global_settings: format!("{:?}", scene.global_settings),
..Summary::default()
};
for root in &scene.root_nodes {
visit(root, &mut out);
}
out.counts = vec![
format!("materials={}", scene.materials.len()),
format!("textures={}", scene.textures.len()),
format!("animations={}", scene.animations.len()),
];
for clip in &scene.animations {
for channel in &clip.channels {
let sampler = &channel.sampler;
out.channels.push(format!(
"{}|{:?}|{:?}|keys={}|first={:?}|last={:?}|interp={:?}",
channel.node_name,
channel.path,
channel.morph_target_index,
sampler.input.len(),
sampler.output.first(),
sampler.output.last(),
sampler.interpolation,
));
}
}
out.material_names = scene
.materials
.iter()
.map(|material| format!("{:?}", material.name))
.collect();
out.texture_names = scene
.textures
.iter()
.map(|texture| format!("{:?}", texture.name))
.collect();
for list in [
&mut out.meshes,
&mut out.transforms,
&mut out.transform_stacks,
&mut out.channels,
&mut out.material_names,
&mut out.texture_names,
] {
list.sort();
}
out
}
fn report(label: &str, left: &[String], right: &[String]) -> usize {
if left == right {
return 0;
}
print!("{label}: {} vs {} entries", left.len(), right.len());
match left.iter().zip(right).find(|(a, b)| a != b) {
Some((a, b)) => println!("\n left {a}\n right {b}"),
None => println!(" (one is a prefix of the other)"),
}
1
}
fn twin_of(path: &Path) -> Option<PathBuf> {
let name = path.file_name()?.to_str()?;
let twin = if name.contains("_ascii") {
name.replace("_ascii", "_binary")
} else {
name.replace("_binary", "_ascii")
};
(twin != name).then(|| path.with_file_name(twin))
}
fn load(path: &Path) -> Result<FbxScene, Box<dyn std::error::Error>> {
let bytes = std::fs::read(path)?;
let scene =
FbxScene::from_bytes(&bytes).map_err(|error| format!("{}: {error}", path.display()))?;
println!(
"{}: {} bytes, {} root nodes, {} warnings",
path.display(),
bytes.len(),
scene.root_nodes.len(),
scene.warnings.len(),
);
Ok(scene)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = env::args().skip(1).collect();
let (left, right) = match args.as_slice() {
[one] => {
let left = PathBuf::from(one);
let right =
twin_of(&left).ok_or("cannot derive a twin name: pass both files explicitly")?;
(left, right)
}
[one, two] => (PathBuf::from(one), PathBuf::from(two)),
_ => return Err("usage: fbx_twin_diff <ascii.fbx> [binary.fbx]".into()),
};
let left_summary = summarize(&load(&left)?);
let right_summary = summarize(&load(&right)?);
let mut differing = report("counts", &left_summary.counts, &right_summary.counts);
differing += report("meshes", &left_summary.meshes, &right_summary.meshes);
differing += report(
"transforms",
&left_summary.transforms,
&right_summary.transforms,
);
differing += report(
"transform_stacks",
&left_summary.transform_stacks,
&right_summary.transform_stacks,
);
differing += report("channels", &left_summary.channels, &right_summary.channels);
differing += report(
"material_names",
&left_summary.material_names,
&right_summary.material_names,
);
differing += report(
"texture_names",
&left_summary.texture_names,
&right_summary.texture_names,
);
if left_summary.global_settings != right_summary.global_settings {
differing += 1;
println!(
"global_settings:\n left {}\n right {}",
left_summary.global_settings, right_summary.global_settings
);
}
if differing == 0 {
println!("IDENTICAL");
} else {
println!("{differing} families differ");
}
Ok(())
}