BlueGit

golden-age paperdoll-renderer

Files

paperdoll-renderer / examples / preview2d_smoke.rs

preview2d_smoke.rs

preview2d_smoke.rs 5.2 KiB Download Raw
1//! Native GUI smoke for the rendered 2D preview tab: it loads a synthetic
2//! export through its `.ppda` + atlases (loose PNGs are deleted first).
3//! Usage: preview2d_smoke OUTPUT_DIR
4
5use eframe::App;
6use paperdoll_renderer::editor::EditorApp;
7use paperdoll_renderer::export_fixture::{Pixel, SyntheticRun};
8use paperdoll_renderer::preview2d::discover_completed_runs;
9use paperdoll_renderer::render_job::ImageBuf;
10use paperdoll_renderer::session::Session;
11use std::fs;
12use std::path::{Path, PathBuf};
13use std::time::{Duration, Instant};
14
15struct Smoke {
16 editor: EditorApp,
17 out: PathBuf,
18 started: Instant,
19 frames: u64,
20 done: bool,
21}
22
23impl App for Smoke {
24 fn update(&mut self, ctx: &eframe::egui::Context, frame: &mut eframe::Frame) {
25 self.frames += 1;
26 self.editor.update(ctx, frame);
27 if self.frames == 2 {
28 self.editor.open_preview2d();
29 }
30 let ready = self.editor.preview2d_image().is_some()
31 || self.editor.preview2d_state().diagnostic.is_some();
32 let timeout = self.started.elapsed() > Duration::from_secs(8);
33 if self.done {
34 return;
35 }
36 if (self.frames >= 12 && ready) || timeout {
37 self.done = true;
38 let state = self.editor.preview2d_state();
39 let image_ok = if let Some(img) = self.editor.preview2d_image() {
40 write_png(&self.out.join("preview2d-composed.png"), img).expect("png");
41 true
42 } else {
43 false
44 };
45 let value = serde_json::json!({
46 "success": image_ok,
47 "gui_updates": self.frames,
48 "sex": state.sex_token(),
49 "clip": state.clip_token,
50 "frame": state.frame_index(),
51 "yaw": state.yaw_deg(),
52 "pitch": state.pitch_deg(),
53 "playing": state.playing,
54 "diagnostic": state.diagnostic,
55 "elapsed_secs": state.elapsed_secs,
56 });
57 fs::write(
58 self.out.join("gui-smoke.json"),
59 serde_json::to_vec_pretty(&value).unwrap(),
60 )
61 .unwrap();
62 println!("RESULT {value}");
63 ctx.send_viewport_cmd(eframe::egui::ViewportCommand::Close);
64 }
65 }
66
67 fn on_exit(&mut self, gl: Option<&glow::Context>) {
68 self.editor.on_exit(gl);
69 }
70}
71
72fn write_png(path: &Path, img: &ImageBuf) -> Result<(), String> {
73 let file = fs::File::create(path).map_err(|e| e.to_string())?;
74 let mut encoder = png::Encoder::new(file, img.width, img.height);
75 encoder.set_color(png::ColorType::Rgba);
76 encoder.set_depth(png::BitDepth::Eight);
77 let mut writer = encoder.write_header().map_err(|e| e.to_string())?;
78 writer
79 .write_image_data(&img.rgba)
80 .map_err(|e| e.to_string())
81}
82
83/// Body export: 2 frames, every view gets a distinct colour from its
84/// pitch/yaw, written with real loose PNGs + atlases + .ppda.
85fn install_body_export(project: &Path) -> Result<(), String> {
86 let mut spec = SyntheticRun::body("smoke-preview2d", "male", b"preview2d-smoke-body");
87 spec.target = [2, 2];
88 spec.clips = vec![("walk".into(), 2, 0.5)];
89 spec.write(
90 &project.join("renders/run-preview2d-body"),
91 |_, frame, pitch, yaw| Pixel {
92 diffuse: [
93 40u8.wrapping_add(frame as u8 * 80),
94 (pitch + 90) as u8,
95 (yaw / 2) as u8,
96 255,
97 ],
98 normal: [128, 128, 255, 255],
99 specular: [0, 0, 0, 255],
100 depth: 25000 - frame as u16 * 1000,
101 },
102 )
103}
104
105fn main() -> Result<(), Box<dyn std::error::Error>> {
106 let out = PathBuf::from(
107 std::env::args()
108 .nth(1)
109 .ok_or("usage: preview2d_smoke OUTPUT_DIR")?,
110 );
111 fs::create_dir_all(&out)?;
112 let project = out.join("project");
113 let mut session = Session::new();
114 session.install_new_project(&project)?;
115 session.save()?;
116 install_body_export(&project)?;
117 // Prove the preview reads the atlases: remove every loose PNG.
118 fs::remove_dir_all(project.join("renders/run-preview2d-body/male"))?;
119 let catalog = discover_completed_runs(&project);
120 fs::write(
121 out.join("catalog.json"),
122 serde_json::to_vec_pretty(&serde_json::json!({
123 "count": catalog.len(),
124 "fps": catalog.iter().map(|e| e.fps).collect::<Vec<_>>(),
125 "sets": catalog.iter().map(|e| format!("{}:{}", e.sex, e.set)).collect::<Vec<_>>(),
126 }))?,
127 )?;
128
129 let mut editor = EditorApp::new(Box::new(paperdoll_renderer::dialogs::SystemDialogs::new()));
130 editor.open_path(project.join("character.ppd"));
131
132 eframe::run_native(
133 "Paperdoll Preview2d Smoke",
134 eframe::NativeOptions {
135 viewport: eframe::egui::ViewportBuilder::default()
136 .with_inner_size([1100.0, 720.0])
137 .with_visible(true),
138 ..Default::default()
139 },
140 Box::new(move |_cc| {
141 Ok(Box::new(Smoke {
142 editor,
143 out: out.clone(),
144 started: Instant::now(),
145 frames: 0,
146 done: false,
147 }) as Box<dyn App>)
148 }),
149 )
150 .map_err(|e| e.to_string())?;
151 Ok(())
152}