1pub mod camera;
7pub mod colormap;
8pub mod renderer;
9pub mod volume;
10
11pub use camera::Camera;
12pub use renderer::VolumeRenderer;
13pub use volume::{VolumeData, VolumeFormat};
14
15use anyhow::Result;
16use std::sync::Arc;
17use wgpu::{Device, Queue, Surface, SurfaceConfiguration};
18use winit::{
19 application::ApplicationHandler,
20 event::*,
21 event_loop::{ActiveEventLoop, EventLoop},
22 keyboard::{KeyCode, PhysicalKey},
23 window::{Window, WindowAttributes, WindowId},
24};
25
26pub struct Viz3DApp {
28 surface: Surface<'static>,
29 device: Device,
30 queue: Queue,
31 config: SurfaceConfiguration,
32 renderer: VolumeRenderer,
33 camera: Camera,
34 window: Arc<Window>,
35}
36
37impl Viz3DApp {
38 pub async fn new_with_window(window: Arc<Window>) -> Result<Self> {
40 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
41 backends: wgpu::Backends::all(),
42 ..Default::default()
43 });
44
45 let surface = instance.create_surface(window.clone())?;
46
47 let adapter = instance
48 .request_adapter(&wgpu::RequestAdapterOptions {
49 power_preference: wgpu::PowerPreference::HighPerformance,
50 compatible_surface: Some(&surface),
51 force_fallback_adapter: false,
52 })
53 .await
54 .ok_or_else(|| anyhow::anyhow!("Failed to find suitable adapter"))?;
55
56 let (device, queue) = adapter
57 .request_device(
58 &wgpu::DeviceDescriptor {
59 label: Some("XDL 3D Device"),
60 required_features: wgpu::Features::empty(),
61 required_limits: wgpu::Limits::default(),
62 memory_hints: wgpu::MemoryHints::default(),
63 },
64 None,
65 )
66 .await?;
67
68 let surface_caps = surface.get_capabilities(&adapter);
69 let surface_format = surface_caps
70 .formats
71 .iter()
72 .copied()
73 .find(|f| f.is_srgb())
74 .unwrap_or(surface_caps.formats[0]);
75
76 let size = window.inner_size();
77 let config = SurfaceConfiguration {
78 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
79 format: surface_format,
80 width: size.width,
81 height: size.height,
82 present_mode: wgpu::PresentMode::Fifo,
83 alpha_mode: surface_caps.alpha_modes[0],
84 view_formats: vec![],
85 desired_maximum_frame_latency: 2,
86 };
87 surface.configure(&device, &config);
88
89 let mut renderer = VolumeRenderer::new(&device, &config)?;
90 renderer.init_colormap(&queue); let camera = Camera::new(
93 glam::Vec3::new(0.0, 0.0, 3.0),
94 glam::Vec3::ZERO,
95 size.width as f32 / size.height as f32,
96 );
97
98 Ok(Self {
99 surface,
100 device,
101 queue,
102 config,
103 renderer,
104 camera,
105 window,
106 })
107 }
108
109 pub fn load_volume(&mut self, volume: VolumeData) -> Result<()> {
111 self.renderer.load_volume(&self.device, &self.queue, volume)
112 }
113
114 pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
116 if new_size.width > 0 && new_size.height > 0 {
117 self.config.width = new_size.width;
118 self.config.height = new_size.height;
119 self.surface.configure(&self.device, &self.config);
120 self.camera
121 .set_aspect(new_size.width as f32 / new_size.height as f32);
122 }
123 }
124
125 pub fn input(&mut self, event: &WindowEvent) -> bool {
127 self.camera.handle_input(event)
128 }
129
130 pub fn update(&mut self) {
132 self.camera.update();
133 }
134
135 pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
137 let output = self.surface.get_current_texture()?;
138 let view = output
139 .texture
140 .create_view(&wgpu::TextureViewDescriptor::default());
141
142 self.renderer
143 .render(&self.device, &self.queue, &view, &self.camera)?;
144
145 output.present();
146 Ok(())
147 }
148
149 pub fn device(&self) -> &Device {
151 &self.device
152 }
153
154 pub fn queue(&self) -> &Queue {
156 &self.queue
157 }
158
159 pub fn window(&self) -> &Arc<Window> {
161 &self.window
162 }
163
164 pub fn renderer_mut(&mut self) -> &mut VolumeRenderer {
166 &mut self.renderer
167 }
168
169 pub fn set_colormap(&mut self, colormap: colormap::Colormap) {
171 self.renderer
172 .set_colormap(&self.device, &self.queue, colormap);
173 }
174}
175
176struct Viz3DHandler {
178 app: Option<Viz3DApp>,
179 volume_data: Option<VolumeData>,
180 colormap: colormap::Colormap,
181 title: String,
182}
183
184impl ApplicationHandler for Viz3DHandler {
185 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
186 if self.app.is_none() {
188 println!("Creating visualization window...");
189
190 let window_attrs = WindowAttributes::default()
191 .with_title(&self.title)
192 .with_inner_size(winit::dpi::PhysicalSize::new(1280, 720));
193
194 let window = match event_loop.create_window(window_attrs) {
195 Ok(w) => Arc::new(w),
196 Err(e) => {
197 eprintln!("Failed to create window: {}", e);
198 event_loop.exit();
199 return;
200 }
201 };
202
203 let app = match pollster::block_on(Viz3DApp::new_with_window(window)) {
205 Ok(mut a) => {
206 if let Some(volume) = self.volume_data.take() {
208 if let Err(e) = a.load_volume(volume) {
209 eprintln!("Failed to load volume: {}", e);
210 event_loop.exit();
211 return;
212 }
213 }
214 a.set_colormap(self.colormap);
215 a
216 }
217 Err(e) => {
218 eprintln!("Failed to create app: {}", e);
219 event_loop.exit();
220 return;
221 }
222 };
223
224 app.window().request_redraw();
225 self.app = Some(app);
226 println!("Window created successfully");
227 println!(
228 "Window is visible: {}",
229 self.app
230 .as_ref()
231 .unwrap()
232 .window()
233 .is_visible()
234 .unwrap_or(false)
235 );
236 } else if let Some(app) = &self.app {
237 app.window().request_redraw();
238 }
239 }
240
241 fn new_events(&mut self, _event_loop: &ActiveEventLoop, _cause: winit::event::StartCause) {
242 }
244
245 fn user_event(&mut self, _event_loop: &ActiveEventLoop, _event: ()) {
246 }
248
249 fn device_event(
250 &mut self,
251 _event_loop: &ActiveEventLoop,
252 _device_id: winit::event::DeviceId,
253 _event: winit::event::DeviceEvent,
254 ) {
255 }
257
258 fn window_event(
259 &mut self,
260 event_loop: &ActiveEventLoop,
261 window_id: WindowId,
262 event: WindowEvent,
263 ) {
264 let Some(app) = self.app.as_mut() else { return };
265 if window_id != app.window().id() {
266 return;
267 }
268
269 if !app.input(&event) {
270 match event {
271 WindowEvent::CloseRequested => {
272 println!("Close requested - exiting");
273 event_loop.exit();
274 }
275 WindowEvent::KeyboardInput {
276 event:
277 KeyEvent {
278 physical_key: PhysicalKey::Code(KeyCode::Escape),
279 state: ElementState::Pressed,
280 ..
281 },
282 ..
283 } => {
284 event_loop.exit();
285 }
286 WindowEvent::Resized(physical_size) => {
287 app.resize(physical_size);
288 }
289 WindowEvent::RedrawRequested => {
290 app.update();
291 match app.render() {
292 Ok(_) => {}
293 Err(wgpu::SurfaceError::Lost) => {
294 println!("Surface lost, reconfiguring...");
295 app.resize(app.window().inner_size());
296 }
297 Err(wgpu::SurfaceError::OutOfMemory) => {
298 eprintln!("Out of memory!");
299 event_loop.exit();
300 }
301 Err(e) => {
302 eprintln!("Render error: {:?}", e);
303 event_loop.exit();
304 }
305 }
306 app.window().request_redraw();
307 }
308 _ => {}
309 }
310 }
311 }
312
313 fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
314 if let Some(app) = &self.app {
315 app.window().request_redraw();
316 }
317 }
318}
319
320pub fn run(
322 volume_data: Option<VolumeData>,
323 colormap: colormap::Colormap,
324 title: String,
325 event_loop: EventLoop<()>,
326) -> Result<()> {
327 let mut handler = Viz3DHandler {
328 app: None,
329 volume_data,
330 colormap,
331 title,
332 };
333 event_loop.run_app(&mut handler)?;
334 Ok(())
335}
336
337use std::sync::Mutex;
338use std::sync::OnceLock;
339
340static EVENT_LOOP_CREATED: OnceLock<Mutex<bool>> = OnceLock::new();
341
342pub fn launch_visualization(
351 volume_data: Vec<f32>,
352 dimensions: [usize; 3],
353 colormap_name: &str,
354 title: Option<&str>,
355) -> Result<()> {
356 use colormap::Colormap;
357
358 let already_used = EVENT_LOOP_CREATED
360 .get_or_init(|| Mutex::new(false))
361 .lock()
362 .map(|mut guard| {
363 let used = *guard;
364 *guard = true;
365 used
366 })
367 .unwrap_or(false);
368
369 if already_used {
370 return Err(anyhow::anyhow!(
371 "Cannot create multiple visualization windows in the same process. \
372 This is a limitation of the windowing system (winit). \
373 Please run each visualization in a separate XDL script execution."
374 ));
375 }
376
377 let event_loop = EventLoop::new()?;
378
379 let colormap = match colormap_name.to_uppercase().as_str() {
381 "VIRIDIS" => Colormap::Viridis,
382 "RAINBOW" => Colormap::Rainbow,
383 "PLASMA" => Colormap::Plasma,
384 "INFERNO" => Colormap::Inferno,
385 "TURBO" => Colormap::Turbo,
386 "GRAYSCALE" | "GRAY" => Colormap::Grayscale,
387 _ => Colormap::Viridis, };
389
390 let volume = VolumeData::new(volume_data, dimensions);
392
393 let window_title = title.unwrap_or("XDL 3D Visualization").to_string();
395
396 run(Some(volume), colormap, window_title, event_loop)
399}