//! Global "which resource has N vertices?" index over every `.xpr` container in //! an extracted `resource3d` directory, answered for the vcounts a capture log //! actually drew. //! //! Companion to `invert_capture`: that one asks the question inside a single //! stage container, this one asks it across ALL containers — so a draw whose //! geometry lives in `Common.xpr`, a `rou_*` weapon pack or a `BG_*` backdrop is //! still identified instead of coming back "no resource". //! //! Usage: //! cargo run --release --example vcount_index -- [top_n] //! cargo run --release --example vcount_index -- --vcounts 10891,6000 use sylpheed_formats::mesh::{xbg7_resource_names, Xbg7Model}; use sylpheed_formats::ship_capture::{parse_capture, parse_drawlog}; use std::collections::{HashMap, HashSet}; fn main() { let args: Vec = std::env::args().collect(); if args.len() < 3 { eprintln!("usage: vcount_index [top_n]"); std::process::exit(2); } let dir = &args[1]; // Which vertex counts are we asking about, and how often was each drawn? let mut draw_count: HashMap = HashMap::new(); let mut bufs: HashMap> = HashMap::new(); if args[2] == "--vcounts" { for v in args[3].split(',').filter_map(|s| s.trim().parse::().ok()) { draw_count.insert(v, 0); } } else { let text = std::fs::read_to_string(&args[2]).expect("read log"); let mut draws = parse_capture(&text); if draws.is_empty() { draws = parse_drawlog(&text); } eprintln!("parsed {} draws", draws.len()); for d in &draws { *draw_count.entry(d.vcount).or_default() += 1; bufs.entry(d.vcount).or_default().insert(d.vbase); } } let top_n: usize = args.get(3).and_then(|s| s.parse().ok()).unwrap_or(usize::MAX); // Decode every container once; keep only the vcount → names mapping. let mut by_vcount: HashMap> = HashMap::new(); let mut files: Vec = std::fs::read_dir(dir) .expect("read dir") .filter_map(|e| e.ok().map(|e| e.path())) .filter(|p| p.extension().is_some_and(|e| e == "xpr")) .collect(); files.sort(); let mut total_res = 0usize; for f in &files { let Ok(bytes) = std::fs::read(f) else { continue }; let names = xbg7_resource_names(&bytes); if names.is_empty() { continue; } let want: HashSet = names.iter().cloned().collect(); let models = Xbg7Model::models_named(&bytes, &want, &|| false); let container = f.file_stem().unwrap().to_string_lossy().to_string(); for m in &models { total_res += 1; let whole: usize = m.meshes.iter().map(|s| s.positions.len()).sum(); by_vcount.entry(whole as u32).or_default().push(format!("{container}:{}", m.name)); if m.meshes.len() > 1 { for (i, s) in m.meshes.iter().enumerate() { by_vcount .entry(s.positions.len() as u32) .or_default() .push(format!("{container}:{}#{i}", m.name)); } } } } eprintln!("indexed {} resources from {} containers", total_res, files.len()); let mut vcounts: Vec = draw_count.keys().copied().collect(); vcounts.sort_unstable_by(|a, b| b.cmp(a)); println!("\nvcount draws bufs resources anywhere in resource3d/"); for v in vcounts.into_iter().take(top_n) { let n = draw_count[&v]; let b = bufs.get(&v).map(|s| s.len()).unwrap_or(0); let hit = by_vcount.get(&v).cloned().unwrap_or_default(); let label = if hit.is_empty() { "— NONE".to_string() } else { let mut h = hit.clone(); h.sort(); let shown = h.len().min(8); format!("{}{}", h[..shown].join(", "), if h.len() > shown { format!(", … ({} total)", h.len()) } else { String::new() }) }; println!("{v:6} {n:5} {b:4} {label}"); } }