Skip to main content

tameimp/
tameimp.rs

1use cuneus::prelude::*;
2use cuneus::{Core, RenderKit, ShaderApp, ShaderManager, UniformProvider};
3
4#[repr(C)]
5#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
6struct ExperimentParams {
7
8    col_bg: [f32; 4],
9    col_line: [f32; 4],
10    col_core: [f32; 4],
11    col_amber: [f32; 4],
12
13    ball_offset_x: f32,
14    ball_offset_y: f32,
15    ball_sink: f32,
16    
17    distortion_amt: f32,
18    noise_amt: f32,
19    stream_width: f32,
20    
21    speed: f32,
22    scale: f32,
23    angle: f32,
24    line_freq: f32,
25    
26    cam_height: f32,
27    cam_distance: f32,
28    cam_fov: f32,
29    
30    rim_intensity: f32,
31    spotlight_intensity: f32,
32    spotlight_height: f32,
33    
34    gamma: f32,
35    saturation: f32,
36    contrast: f32,
37    
38    orbital_enabled: u32,
39    orbital_speed: f32,
40    orbital_radius: f32,
41    _pad: [f32; 2],
42}
43
44impl Default for ExperimentParams {
45    fn default() -> Self {
46        Self {
47            col_bg: [0.05, 0.02, 0.10, 1.0],
48            col_line: [0.55, 0.40, 0.85, 1.0],
49            col_core: [1.0, 0.1, 0.2, 1.0],
50            col_amber: [1.0, 0.6, 0.1, 1.0],
51
52            ball_offset_x: 0.0,
53            ball_offset_y: 0.0,
54            ball_sink: 1.0,
55            
56            distortion_amt: 50.0,
57            noise_amt: 300.0,
58            stream_width: 0.08,
59            
60            speed: 1.0,
61            scale: 1.35,
62            angle: -1.785398,
63            line_freq: 100.0,
64            
65            cam_height: 3.58,
66            cam_distance: 6.0,
67            cam_fov: 1.7,
68            
69            rim_intensity: 1.2,
70            spotlight_intensity: 1.5,
71            spotlight_height: 1.0,
72            
73            gamma: 0.66,
74            saturation: 0.71,
75            contrast: 1.1,
76            
77            orbital_enabled: 1,
78            orbital_speed: 0.3,
79            orbital_radius: 2.12,
80            _pad: [0.0; 2],
81        }
82    }
83}
84
85impl UniformProvider for ExperimentParams {
86    fn as_bytes(&self) -> &[u8] {
87        bytemuck::bytes_of(self)
88    }
89}
90
91struct ExperimentShader {
92    base: RenderKit,
93    compute_shader: ComputeShader,
94    current_params: ExperimentParams,
95    orbital_enabled: bool,
96}
97
98impl ShaderManager for ExperimentShader {
99    fn init(core: &Core) -> Self {
100        let base = RenderKit::new(core);
101        let initial_params = ExperimentParams::default();
102
103        let config = ComputeShader::builder()
104            .with_entry_point("main")
105            .with_custom_uniforms::<ExperimentParams>()
106            .with_workgroup_size([16, 16, 1])
107            .with_texture_format(wgpu::TextureFormat::Rgba16Float)
108            .with_label("Currents Port")
109            .build();
110
111        let compute_shader = cuneus::compute_shader!(core, "shaders/tameimp.wgsl", config);
112
113        compute_shader.set_custom_params(initial_params, &core.queue);
114
115        Self {
116            base,
117            compute_shader,
118            current_params: initial_params,
119            orbital_enabled: true,
120        }
121    }
122
123    fn update(&mut self, core: &Core) {
124        self.compute_shader.handle_export(core, &mut self.base);
125    }
126
127    fn render(&mut self, core: &Core) -> Result<(), cuneus::SurfaceError> {
128        let mut frame = self.base.begin_frame(core)?;
129
130        let mut params = self.current_params;
131        let mut changed = false;
132        let mut should_start_export = false;
133        let mut export_request = self.base.export_manager.get_ui_request();
134        let mut controls_request = self
135            .base
136            .controls
137            .get_ui_request(&self.base.start_time, &core.size, self.base.fps_tracker.fps());
138        
139        
140        let mut orbital_enabled = self.orbital_enabled;
141
142        let full_output = if self.base.key_handler.show_ui {
143            self.base.render_ui(core, |ctx| {
144                ctx.global_style_mut(|style| {
145                    style.visuals.window_fill = egui::Color32::from_black_alpha(220);
146                    style.visuals.window_stroke = egui::Stroke::new(1.0, egui::Color32::from_gray(60));
147                    style.text_styles.get_mut(&egui::TextStyle::Body).unwrap().size = 11.0;
148                    style.text_styles.get_mut(&egui::TextStyle::Button).unwrap().size = 10.0;
149                    style.text_styles.get_mut(&egui::TextStyle::Small).unwrap().size = 9.0;
150                    style.text_styles.get_mut(&egui::TextStyle::Heading).unwrap().size = 12.0;
151                    style.spacing.slider_width = 140.0;
152                    style.spacing.item_spacing = egui::vec2(4.0, 3.0);
153                });
154
155                egui::Window::new("Currents")
156                    .default_width(220.0)
157                    .show(ctx, |ui| {
158                        
159                        egui::CollapsingHeader::new("🔮 ball").default_open(true).show(ui, |ui| {
160                            ui.horizontal(|ui| {
161                                ui.label("X");
162                                changed |= ui.add(egui::Slider::new(&mut params.ball_offset_x, -1.0..=1.0).show_value(true)).changed();
163                            });
164                            ui.horizontal(|ui| {
165                                ui.label("Y");
166                                changed |= ui.add(egui::Slider::new(&mut params.ball_offset_y, -1.0..=1.0).show_value(true)).changed();
167                            });
168                            ui.horizontal(|ui| {
169                                ui.label("Sink");
170                                changed |= ui.add(egui::Slider::new(&mut params.ball_sink, -3.2..=3.2).show_value(true)).changed();
171                            });
172                            ui.horizontal(|ui| {
173                                ui.label("Angle");
174                                changed |= ui.add(egui::Slider::new(&mut params.angle, -3.14..=3.14).show_value(true)).changed();
175                            });
176                        });
177
178                        egui::CollapsingHeader::new("🌊 noise").default_open(false).show(ui, |ui| {
179                            ui.horizontal(|ui| {
180                                ui.label("Distortion");
181                                changed |= ui.add(egui::Slider::new(&mut params.distortion_amt, 0.0..=75.0).show_value(true)).changed();
182                            });
183                            ui.horizontal(|ui| {
184                                ui.label("Stream W");
185                                changed |= ui.add(egui::Slider::new(&mut params.stream_width, 0.01..=0.3).show_value(true)).changed();
186                            });
187                            ui.horizontal(|ui| {
188                                ui.label("Line Freq");
189                                changed |= ui.add(egui::Slider::new(&mut params.line_freq, 20.0..=200.0).show_value(true)).changed();
190                            });
191                        });
192                        egui::CollapsingHeader::new("📷 cam").default_open(false).show(ui, |ui| {
193                            ui.horizontal(|ui| {
194                                if ui.checkbox(&mut orbital_enabled, "🔄 Orbital").changed() {
195                                    changed = true;
196                                }
197                            });
198                            if orbital_enabled {
199                                ui.horizontal(|ui| {
200                                    ui.label("Orb Speed");
201                                    changed |= ui.add(egui::Slider::new(&mut params.orbital_speed, 0.01..=2.0).show_value(true)).changed();
202                                });
203                                ui.horizontal(|ui| {
204                                    ui.label("Orb Radius");
205                                    changed |= ui.add(egui::Slider::new(&mut params.orbital_radius, 0.1..=3.0).show_value(true)).changed();
206                                });
207                            }
208                            
209                            ui.separator();
210                            
211                            ui.horizontal(|ui| {
212                                ui.label("Height");
213                                changed |= ui.add(egui::Slider::new(&mut params.cam_height, 0.5..=5.0).show_value(true)).changed();
214                            });
215                            ui.horizontal(|ui| {
216                                ui.label("FOV");
217                                changed |= ui.add(egui::Slider::new(&mut params.cam_fov, 0.5..=2.5).show_value(true)).changed();
218                            });
219                            ui.horizontal(|ui| {
220                                ui.label("Scale");
221                                changed |= ui.add(egui::Slider::new(&mut params.scale, 0.3..=3.0).show_value(true)).changed();
222                            });
223                        });
224
225                        egui::CollapsingHeader::new("💡 light").default_open(false).show(ui, |ui| {
226                            ui.horizontal(|ui| {
227                                ui.label("Spot Int");
228                                changed |= ui.add(egui::Slider::new(&mut params.spotlight_intensity, 0.0..=5.0).show_value(true)).changed();
229                            });
230                            ui.horizontal(|ui| {
231                                ui.label("Spot H");
232                                changed |= ui.add(egui::Slider::new(&mut params.spotlight_height, 0.2..=3.0).show_value(true)).changed();
233                            });
234                            ui.horizontal(|ui| {
235                                ui.label("fog");
236                                changed |= ui.add(egui::Slider::new(&mut params.cam_distance, 3.0..=8.0).show_value(true)).changed();
237                            });
238                        });
239
240                        egui::CollapsingHeader::new("✨ post").default_open(false).show(ui, |ui| {
241                            ui.horizontal(|ui| {
242                                ui.label("Gamma");
243                                changed |= ui.add(egui::Slider::new(&mut params.gamma, 0.2..=2.0).show_value(true)).changed();
244                            });
245                            ui.horizontal(|ui| {
246                                ui.label("Saturation");
247                                changed |= ui.add(egui::Slider::new(&mut params.saturation, 0.0..=2.5).show_value(true)).changed();
248                            });
249                            ui.horizontal(|ui| {
250                                ui.label("Contrast");
251                                changed |= ui.add(egui::Slider::new(&mut params.contrast, 0.5..=2.0).show_value(true)).changed();
252                            });
253                        });
254                        egui::CollapsingHeader::new("🎨 cols").default_open(false).show(ui, |ui| {
255                            egui::Grid::new("colors_grid")
256                                .num_columns(2)
257                                .spacing([10.0, 4.0])
258                                .striped(true)
259                                .show(ui, |ui| {
260                                    ui.label("Background");
261                                    let mut rgb = [params.col_bg[0], params.col_bg[1], params.col_bg[2]];
262                                    if ui.color_edit_button_rgb(&mut rgb).changed() {
263                                        params.col_bg[0] = rgb[0];
264                                        params.col_bg[1] = rgb[1];
265                                        params.col_bg[2] = rgb[2];
266                                        changed = true;
267                                    }
268                                    ui.end_row();
269                                    ui.label("Line");
270                                    let mut rgb = [params.col_line[0], params.col_line[1], params.col_line[2]];
271                                    if ui.color_edit_button_rgb(&mut rgb).changed() {
272                                        params.col_line[0] = rgb[0];
273                                        params.col_line[1] = rgb[1];
274                                        params.col_line[2] = rgb[2];
275                                        changed = true;
276                                    }
277                                    ui.end_row();
278                                    ui.label("Stream Core");
279                                    let mut rgb = [params.col_core[0], params.col_core[1], params.col_core[2]];
280                                    if ui.color_edit_button_rgb(&mut rgb).changed() {
281                                        params.col_core[0] = rgb[0];
282                                        params.col_core[1] = rgb[1];
283                                        params.col_core[2] = rgb[2];
284                                        changed = true;
285                                    }
286                                    ui.end_row();
287                                    ui.label("Stream Amber");
288                                    let mut rgb = [params.col_amber[0], params.col_amber[1], params.col_amber[2]];
289                                    if ui.color_edit_button_rgb(&mut rgb).changed() {
290                                        params.col_amber[0] = rgb[0];
291                                        params.col_amber[1] = rgb[1];
292                                        params.col_amber[2] = rgb[2];
293                                        changed = true;
294                                    }
295                                    ui.end_row();
296                                });
297                        });
298                        egui::CollapsingHeader::new("⏱").default_open(false).show(ui, |ui| {
299                            ui.horizontal(|ui| {
300                                ui.label("Speed");
301                                changed |= ui.add(egui::Slider::new(&mut params.speed, 0.0..=2.0).show_value(true)).changed();
302                            });
303                        });
304
305                        ui.separator();
306                        
307                        egui::CollapsingHeader::new("▶").default_open(false).show(ui, |ui| {
308                            cuneus::ShaderControls::render_controls_widget(ui, &mut controls_request);
309                        });
310
311                        egui::CollapsingHeader::new("💾").default_open(false).show(ui, |ui| {
312                            should_start_export = cuneus::ExportManager::render_export_ui_widget(ui, &mut export_request);
313                        });
314
315                        ui.separator();
316                        ui.horizontal(|ui| {
317                            ui.separator();
318                            ui.small("H: toggle UI");
319                        });
320                    });
321            })
322        } else {
323            self.base.render_ui(core, |_ctx| {})
324        };
325
326        // Update orbital state
327        self.orbital_enabled = orbital_enabled;
328        params.orbital_enabled = if orbital_enabled { 1 } else { 0 };
329
330        self.base.export_manager.apply_ui_request(export_request);
331        self.base.apply_control_request(controls_request);
332
333        let current_time = self.base.controls.get_time(&self.base.start_time);
334        self.compute_shader
335            .set_time(current_time, 1.0 / 60.0, &core.queue);
336
337        if changed {
338            self.current_params = params;
339            self.compute_shader.set_custom_params(params, &core.queue);
340        }
341
342        if should_start_export {
343            self.base.export_manager.start_export();
344        }
345
346        self.compute_shader.dispatch(&mut frame.encoder, core);
347
348        self.base.renderer.render_to_view(&mut frame.encoder, &frame.view, &self.compute_shader.get_output_texture().bind_group);
349
350        self.base.end_frame(core, frame, full_output);
351        Ok(())
352    }
353
354    fn resize(&mut self, core: &Core) {
355        self.base.default_resize(core, &mut self.compute_shader);
356    }
357
358    fn handle_input(&mut self, core: &Core, event: &WindowEvent) -> bool {
359        self.base.default_handle_input(core, event)
360    }
361}
362
363fn main() -> Result<(), Box<dyn std::error::Error>> {
364    env_logger::init();
365    let (app, event_loop) = ShaderApp::new("Currents", 600, 800);
366    app.run(event_loop, ExperimentShader::init)
367}