//! Does every part of a ship sit inside the envelope its siblings describe? //! //! `slab_screen` compared *scale* and could not see the `e106` slab. What the eye //! used when the render exposed it was **relationship**: a blocky mass sitting //! apart from the hull. This measures that — assemble a ship, and for each part //! ask how far its world box protrudes beyond the box of all the OTHER parts, //! relative to the ship's own size. A mis-anchored block sticks out; a real part, //! however big, is part of the silhouette. //! //! Usage: envelope_screen [protrusion_fraction] use sylpheed_formats::mesh::Xbg7Model; use sylpheed_formats::ship::{assemble_ship, ship_id_of}; use std::collections::{BTreeSet, HashSet}; fn main() { let dir = std::env::args().nth(1).expect("resource3d dir"); let limit: f32 = std::env::args().nth(2).and_then(|s| s.parse().ok()).unwrap_or(0.35); let mut files: Vec<_> = std::fs::read_dir(&dir) .unwrap() .flatten() .map(|e| e.path()) .filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr")) .collect(); files.sort(); let mut flagged = 0usize; for f in &files { let Ok(bytes) = std::fs::read(f) else { continue }; let ids: BTreeSet = Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false) .iter() .filter_map(|m| ship_id_of(&m.name).map(|s| s.to_string())) .collect(); for id in &ids { let placed = assemble_ship(&bytes, id, true); if placed.len() < 3 { continue; } let want: HashSet = placed.iter().map(|p| p.resource.clone()).collect(); let models = Xbg7Model::models_named(&bytes, &want, &|| false); // World box per placement. let mut boxes: Vec<(String, [f32; 3], [f32; 3])> = Vec::new(); for p in &placed { let Some(m) = models.iter().find(|m| m.name == p.resource) else { continue }; let (mut lo, mut hi) = ([f32::MAX; 3], [f32::MIN; 3]); for s in &m.meshes { for q in &s.positions { let w = p.apply(*q); for k in 0..3 { lo[k] = lo[k].min(w[k]); hi[k] = hi[k].max(w[k]); } } } if lo[0] != f32::MAX { boxes.push((p.resource.clone(), lo, hi)); } } if boxes.len() < 3 { continue; } for i in 0..boxes.len() { // Envelope of every OTHER part. let (mut lo, mut hi) = ([f32::MAX; 3], [f32::MIN; 3]); for (j, b) in boxes.iter().enumerate() { if i == j { continue; } for k in 0..3 { lo[k] = lo[k].min(b.1[k]); hi[k] = hi[k].max(b.2[k]); } } // Per-AXIS: a bow legitimately extends the ship along its long // axis, so protrusion only means something measured against the // envelope's size IN THAT AXIS. The e106 slab stuck ~1 500 out in // Y where its siblings spanned ~800. let mut worst = 0.0f32; let mut worst_out = 0.0f32; for k in 0..3 { let size_k = hi[k] - lo[k]; if size_k <= 1.0 { continue; } let out_k = (lo[k] - boxes[i].1[k]).max(boxes[i].2[k] - hi[k]).max(0.0); if out_k / size_k > worst { worst = out_k / size_k; worst_out = out_k; } } let (out, size) = (worst_out, 1.0f32); let _ = size; if worst > limit { flagged += 1; println!( "{:<20} {:<22} protrudes {:>7.0} beyond its siblings ({:.0}% of the ship)", f.file_name().unwrap().to_string_lossy(), boxes[i].0, out, 100.0 * worst ); } } } } println!("{flagged} parts protrude more than {:.0}% of their ship's size", 100.0 * limit); }