use std::path::Path;
use wow_m2::M2Model;
fn validate_model(model: &M2Model) -> Vec<String> {
let mut issues = Vec::new();
if &model.header.magic != b"MD20" {
issues.push(format!("Invalid magic: {:?}", model.header.magic));
}
let version = model.header.version();
if version.is_none() {
issues.push(format!("Unknown version: {}", model.header.version));
}
let bbox_min = &model.header.bounding_box_min;
let bbox_max = &model.header.bounding_box_max;
if bbox_min[0] > bbox_max[0] || bbox_min[1] > bbox_max[1] || bbox_min[2] > bbox_max[2] {
issues.push("Invalid bounding box: min > max".to_string());
}
if model.header.bounding_sphere_radius <= 0.0 {
issues.push("Invalid bounding sphere radius".to_string());
}
if model.vertices.is_empty() {
issues.push("No vertices found".to_string());
} else {
for (i, vertex) in model.vertices.iter().enumerate() {
let weight_sum: u8 = vertex.bone_weights.iter().sum();
if weight_sum != 255 && weight_sum != 0 {
issues.push(format!(
"Vertex {i}: bone weights don't sum to 1.0 (sum={weight_sum})"
));
}
for (j, &bone_idx) in vertex.bone_indices.iter().enumerate() {
if vertex.bone_weights[j] > 0 && bone_idx as usize >= model.bones.len() {
issues.push(format!("Vertex {i}: invalid bone index {bone_idx}"));
}
}
}
}
for (i, texture) in model.textures.iter().enumerate() {
if texture.filename.is_empty() {
issues.push(format!("Texture {i}: empty filename"));
}
}
for (i, material) in model.materials.iter().enumerate() {
if material.flags.is_empty() && material.blend_mode.is_empty() {
issues.push(format!("Material {i}: has no flags or blend mode set"));
}
}
for (i, anim) in model.animations.iter().enumerate() {
if let Some(end_ts) = anim.end_timestamp {
if end_ts <= anim.start_timestamp {
issues.push(format!("Animation {i}: end timestamp <= start timestamp"));
}
} else {
if anim.start_timestamp == 0 {
issues.push(format!("Animation {i}: zero duration"));
}
}
}
for (i, bone) in model.bones.iter().enumerate() {
if bone.parent_bone != -1 {
if bone.parent_bone < 0 || bone.parent_bone as usize >= model.bones.len() {
issues.push(format!(
"Bone {}: invalid parent bone index {}",
i, bone.parent_bone
));
} else if bone.parent_bone as usize == i {
issues.push(format!("Bone {i}: references itself as parent"));
}
}
}
issues
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
eprintln!("Usage: {} <path_to_m2_file>", args[0]);
std::process::exit(1);
}
let path = &args[1];
if !Path::new(path).exists() {
eprintln!("Error: File not found: {path}");
std::process::exit(1);
}
println!("Loading model from: {path}");
let m2_format = match M2Model::load(path) {
Ok(format) => format,
Err(e) => {
eprintln!("Failed to load model: {e}");
std::process::exit(1);
}
};
let model = m2_format.model();
println!("Model loaded successfully!");
println!(
"Format: {}",
if m2_format.is_chunked() {
"Chunked (MD21)"
} else {
"Legacy (MD20)"
}
);
println!("Version: {:?}", model.header.version());
println!("\nValidating model...");
let issues = validate_model(model);
if issues.is_empty() {
println!("✅ Model validation passed! No issues found.");
} else {
println!("❌ Model validation found {} issue(s):", issues.len());
for issue in &issues {
println!(" - {issue}");
}
}
println!("\n=== Model Statistics ===");
println!("Vertices: {}", model.vertices.len());
println!("Bones: {}", model.bones.len());
println!("Textures: {}", model.textures.len());
println!("Materials: {}", model.materials.len());
println!("Animations: {}", model.animations.len());
println!("Animation Lookups: {}", model.animation_lookup.len());
if !issues.is_empty() {
std::process::exit(1);
}
Ok(())
}