diff --git a/crates/sylpheed-formats/examples/ship_render.rs b/crates/sylpheed-formats/examples/ship_render.rs new file mode 100644 index 0000000..6b8e4cd --- /dev/null +++ b/crates/sylpheed-formats/examples/ship_render.rs @@ -0,0 +1,125 @@ +//! Diagnostic: assemble a ship from the BAKED capture table (or the static +//! scene graph with --static) and render orthographic PNGs + print per-part +//! world bounds, to see exactly what the viewer shows. +//! cargo run --release --example ship_render -- [--static] +use std::collections::HashSet; +use sylpheed_formats::mesh::{ScenePart, Xbg7Model}; +use sylpheed_formats::ship::assemble_ship; +use sylpheed_formats::ship_capture::{embedded_placement, to_scene_parts}; + +fn main() { + let a: Vec = std::env::args().collect(); + let use_static = a.iter().any(|s| s == "--static"); + let (path, id, out) = (&a[1], &a[2], &a[3]); + let bytes = std::fs::read(path).unwrap(); + + let placed: Vec = if use_static { + assemble_ship(&bytes, id, true) + } else { + let cap = embedded_placement(id).expect("ship not in baked table"); + to_scene_parts(&cap) + }; + let want: HashSet = placed.iter().map(|p| p.resource.clone()).collect(); + let models = Xbg7Model::models_named(&bytes, &want, &|| false); + + // World triangles + per-part bounds. + let mut tris: Vec<[[f32; 3]; 3]> = Vec::new(); + for p in &placed { + let Some(m) = models.iter().find(|m| m.name == p.resource) else { continue }; + let mut lo = [f32::MAX; 3]; + let mut hi = [f32::MIN; 3]; + for sub in &m.meshes { + let w: Vec<[f32; 3]> = sub.positions.iter().map(|v| p.apply(*v)).collect(); + for v in &w { + for k in 0..3 { + lo[k] = lo[k].min(v[k]); + hi[k] = hi[k].max(v[k]); + } + } + for t in sub.indices.chunks_exact(3) { + tris.push([w[t[0] as usize], w[t[1] as usize], w[t[2] as usize]]); + } + } + println!( + "{:20} X[{:8.1},{:8.1}] Y[{:8.1},{:8.1}] Z[{:8.1},{:8.1}]", + p.resource, lo[0], hi[0], lo[1], hi[1], lo[2], hi[2] + ); + } + println!("total {} tris from {} placements", tris.len(), placed.len()); + + // Orthographic z-buffered flat renders: top (X/Z, look down −Y) and side (Z/Y, look down −X). + for (name, ax, ay, az) in [("top", 0usize, 2usize, 1usize), ("side", 2usize, 1usize, 0usize)] { + let size = 900usize; + let (mut lo, mut hi) = ([f32::MAX; 3], [f32::MIN; 3]); + for t in &tris { + for v in t { + for k in 0..3 { + lo[k] = lo[k].min(v[k]); + hi[k] = hi[k].max(v[k]); + } + } + } + let cx = (lo[ax] + hi[ax]) * 0.5; + let cy = (lo[ay] + hi[ay]) * 0.5; + let ext = (hi[ax] - lo[ax]).max(hi[ay] - lo[ay]) * 0.55; + let scale = (size as f32 * 0.5) / ext; + let mut img = vec![0u8; size * size * 3]; + let mut zbuf = vec![f32::MIN; size * size]; + for t in &tris { + // screen coords + let p: Vec<[f32; 3]> = t + .iter() + .map(|v| { + [ + (v[ax] - cx) * scale + size as f32 * 0.5, + (cy - v[ay]) * scale + size as f32 * 0.5, + v[az], + ] + }) + .collect(); + // flat shade by triangle normal Z-ish + let e1 = [t[1][0] - t[0][0], t[1][1] - t[0][1], t[1][2] - t[0][2]]; + let e2 = [t[2][0] - t[0][0], t[2][1] - t[0][1], t[2][2] - t[0][2]]; + let n = [ + e1[1] * e2[2] - e1[2] * e2[1], + e1[2] * e2[0] - e1[0] * e2[2], + e1[0] * e2[1] - e1[1] * e2[0], + ]; + let nl = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt().max(1e-6); + let lum = (n[az].abs() / nl * 200.0 + 40.0) as u8; + // bbox raster + let minx = p.iter().map(|v| v[0]).fold(f32::MAX, f32::min).max(0.0) as usize; + let maxx = (p.iter().map(|v| v[0]).fold(f32::MIN, f32::max).min(size as f32 - 1.0)) as usize; + let miny = p.iter().map(|v| v[1]).fold(f32::MAX, f32::min).max(0.0) as usize; + let maxy = (p.iter().map(|v| v[1]).fold(f32::MIN, f32::max).min(size as f32 - 1.0)) as usize; + let det = (p[1][0] - p[0][0]) * (p[2][1] - p[0][1]) - (p[2][0] - p[0][0]) * (p[1][1] - p[0][1]); + if det.abs() < 1e-6 { + continue; + } + for y in miny..=maxy { + for x in minx..=maxx { + let (fx, fy) = (x as f32 + 0.5, y as f32 + 0.5); + let w0 = ((p[1][0] - fx) * (p[2][1] - fy) - (p[2][0] - fx) * (p[1][1] - fy)) / det; + let w1 = ((p[2][0] - fx) * (p[0][1] - fy) - (p[0][0] - fx) * (p[2][1] - fy)) / det; + let w2 = 1.0 - w0 - w1; + if w0 < 0.0 || w1 < 0.0 || w2 < 0.0 { + continue; + } + let z = w0 * p[0][2] + w1 * p[1][2] + w2 * p[2][2]; + let idx = y * size + x; + if z > zbuf[idx] { + zbuf[idx] = z; + img[idx * 3] = lum; + img[idx * 3 + 1] = lum; + img[idx * 3 + 2] = lum; + } + } + } + } + let file = format!("{out}_{name}.ppm"); + let mut ppm = format!("P6\n{size} {size}\n255\n").into_bytes(); + ppm.extend_from_slice(&img); + std::fs::write(&file, ppm).unwrap(); + println!("wrote {file}"); + } +}