//! 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] ); } // Exhaust cones at the GN_Jet/GN_SJet frames (the game's visible thrusters). for f in sylpheed_formats::ship::exhaust_frames(&bytes, id) { const SEG: usize = 12; let (r, len) = (22.0f32, 140.0f32); let ring: Vec<[f32; 3]> = (0..SEG) .map(|s| { let a = s as f32 / SEG as f32 * std::f32::consts::TAU; f.apply([a.cos() * r, a.sin() * r, 0.0]) }) .collect(); let apex = f.apply([0.0, 0.0, len]); for s in 0..SEG { tris.push([apex, ring[(s + 1) % SEG], ring[s]]); } println!(" exhaust frame {:16} T=[{:8.1}{:8.1}{:8.1}]", f.resource, f.t[0], f.t[1], f.t[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}"); } }