Skip to main content

threecrate_visualization/
interactive_viewer.rs

1//! Interactive 3D viewer with UI controls
2//! 
3//! This module provides a simplified interactive viewer for 3D data
4
5use std::sync::Arc;
6use winit::{
7    application::ApplicationHandler,
8    event::{WindowEvent, ElementState, MouseButton},
9    event_loop::{EventLoop, ActiveEventLoop},
10    window::{Window, WindowId},
11    keyboard::Key,
12    dpi::PhysicalPosition,
13};
14
15use threecrate_core::{PointCloud, TriangleMesh, Result, Point3f, ColoredPoint3f, Error};
16use threecrate_gpu::{
17    PointCloudRenderer, RenderConfig, PointVertex,
18    MeshRenderer, MeshRenderConfig, ShadingMode, mesh_to_gpu_mesh,
19};
20use threecrate_algorithms::{ICPResult, PlaneSegmentationResult};
21use crate::camera::Camera;
22
23use nalgebra::{Vector3, Point3};
24
25/// Types of data that can be displayed
26#[derive(Debug, Clone)]
27pub enum ViewData {
28    Empty,
29    PointCloud(PointCloud<Point3f>),
30    ColoredPointCloud(PointCloud<ColoredPoint3f>),
31    Mesh(TriangleMesh),
32}
33
34/// Camera control modes
35#[derive(Debug, Clone, Copy, PartialEq)]
36pub enum CameraMode {
37    Orbit,
38    Pan,
39    Zoom,
40}
41
42/// Pipeline processing type
43#[derive(Debug, Clone, Copy, PartialEq)]
44pub enum PipelineType {
45    Cpu,
46    Gpu,
47}
48
49/// ICP algorithm parameters
50#[derive(Debug, Clone)]
51pub struct ICPParams {
52    pub max_iterations: usize,
53    pub convergence_threshold: f32,
54    pub max_correspondence_distance: f32,
55}
56
57impl Default for ICPParams {
58    fn default() -> Self {
59        Self {
60            max_iterations: 50,
61            convergence_threshold: 0.001,
62            max_correspondence_distance: 1.0,
63        }
64    }
65}
66
67/// RANSAC algorithm parameters
68#[derive(Debug, Clone)]
69pub struct RANSACParams {
70    pub max_iterations: usize,
71    pub distance_threshold: f32,
72}
73
74impl Default for RANSACParams {
75    fn default() -> Self {
76        Self {
77            max_iterations: 1000,
78            distance_threshold: 0.1,
79        }
80    }
81}
82
83/// UI state for all panels and controls (kept for future use)
84#[derive(Debug)]
85pub struct UIState {
86    pub render_panel_open: bool,
87    pub algorithm_panel_open: bool,
88    pub camera_panel_open: bool,
89    pub stats_panel_open: bool,
90    pub icp_params: ICPParams,
91    pub ransac_params: RANSACParams,
92    pub source_cloud: Option<PointCloud<Point3f>>,
93    pub target_cloud: Option<PointCloud<Point3f>>,
94    pub icp_result: Option<ICPResult>,
95    pub ransac_result: Option<PlaneSegmentationResult>,
96}
97
98impl Default for UIState {
99    fn default() -> Self {
100        Self {
101            render_panel_open: false,
102            algorithm_panel_open: false,
103            camera_panel_open: false,
104            stats_panel_open: false,
105            icp_params: ICPParams::default(),
106            ransac_params: RANSACParams::default(),
107            source_cloud: None,
108            target_cloud: None,
109            icp_result: None,
110            ransac_result: None,
111        }
112    }
113}
114
115/// Interactive 3D viewer with comprehensive UI controls
116pub struct InteractiveViewer {
117    current_data: ViewData,
118    camera: Camera,
119    camera_mode: CameraMode,
120    last_mouse_pos: Option<PhysicalPosition<f64>>,
121    mouse_pressed: bool,
122    right_mouse_pressed: bool,
123    debug_frame_count: usize,
124    vertices_dirty: bool,
125}
126
127impl InteractiveViewer {
128    /// Create a new interactive viewer
129    pub fn new() -> Result<Self> {
130        let camera = Camera::new(
131            Point3::new(5.0, 5.0, 5.0),
132            Point3::new(0.0, 0.0, 0.0),
133            Vector3::new(0.0, 1.0, 0.0),
134            45.0,
135            1.0,
136            0.1,
137            100.0,
138        );
139
140        Ok(Self {
141            current_data: ViewData::Empty,
142            camera,
143            camera_mode: CameraMode::Orbit,
144            last_mouse_pos: None,
145            mouse_pressed: false,
146            right_mouse_pressed: false,
147            debug_frame_count: 0,
148            vertices_dirty: true,
149        })
150    }
151
152    /// Set point cloud data
153    pub fn set_point_cloud(&mut self, cloud: &PointCloud<Point3f>) {
154        self.current_data = ViewData::PointCloud(cloud.clone());
155        self.vertices_dirty = true;
156        println!("Set point cloud with {} points", cloud.len());
157    }
158
159    /// Set colored point cloud data
160    pub fn set_colored_point_cloud(&mut self, cloud: &PointCloud<ColoredPoint3f>) {
161        self.current_data = ViewData::ColoredPointCloud(cloud.clone());
162        self.vertices_dirty = true;
163        println!("Set colored point cloud with {} points", cloud.len());
164    }
165
166    /// Set mesh data
167    pub fn set_mesh(&mut self, mesh: &TriangleMesh) {
168        self.current_data = ViewData::Mesh(mesh.clone());
169        self.vertices_dirty = true;
170        println!("Set mesh with {} vertices and {} faces", mesh.vertices.len(), mesh.faces.len());
171    }
172
173    /// Run the interactive viewer
174    pub fn run(self) -> Result<()> {
175        println!("Starting threecrate Interactive Viewer...");
176
177        // Create event loop
178        let event_loop = EventLoop::new().map_err(|e| Error::Io(std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to create event loop: {}", e))))?;
179
180        // Create application handler
181        let mut app = ViewerApp {
182            viewer: self,
183            window: None,
184            point_renderer: None,
185            mesh_renderer: None,
186            cached_vertices: Vec::new(),
187        };
188
189        // Run the event loop
190        event_loop.run_app(&mut app).map_err(|e| Error::Io(std::io::Error::new(std::io::ErrorKind::Other, format!("Event loop error: {}", e))))?;
191
192        Ok(())
193    }
194}
195
196impl Default for InteractiveViewer {
197    fn default() -> Self {
198        Self::new().expect("Failed to create InteractiveViewer")
199    }
200}
201
202/// Application handler for the viewer
203struct ViewerApp {
204    viewer: InteractiveViewer,
205    window: Option<Arc<Window>>,
206    point_renderer: Option<PointCloudRenderer<'static>>,
207    mesh_renderer: Option<MeshRenderer<'static>>,
208    cached_vertices: Vec<PointVertex>,
209}
210
211impl ApplicationHandler for ViewerApp {
212    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
213        if self.window.is_none() {
214            println!("Creating window and initializing renderers...");
215
216            // Create window with attributes
217            let window_attrs = Window::default_attributes()
218                .with_title("threecrate Interactive Viewer")
219                .with_inner_size(winit::dpi::LogicalSize::new(1200.0, 800.0));
220
221            let window = match event_loop.create_window(window_attrs) {
222                Ok(w) => Arc::new(w),
223                Err(e) => {
224                    eprintln!("Failed to create window: {}", e);
225                    event_loop.exit();
226                    return;
227                }
228            };
229
230            // Update camera aspect ratio
231            let size = window.inner_size();
232            self.viewer.camera.aspect_ratio = size.width as f32 / size.height as f32;
233
234            // Leak the Arc to get a 'static reference
235            // This is safe because the window will live for the duration of the program
236            let window_ref: &'static Window = unsafe {
237                std::mem::transmute::<&Window, &'static Window>(window.as_ref())
238            };
239
240            // Initialize renderers using the static reference
241            let pc_config = RenderConfig::default();
242            let point_renderer = match pollster::block_on(PointCloudRenderer::new(window_ref, pc_config)) {
243                Ok(r) => r,
244                Err(e) => {
245                    eprintln!("Failed to create point cloud renderer: {}", e);
246                    event_loop.exit();
247                    return;
248                }
249            };
250
251            let mesh_config = MeshRenderConfig::default();
252            let mesh_renderer = match pollster::block_on(MeshRenderer::new(window_ref, mesh_config)) {
253                Ok(r) => r,
254                Err(e) => {
255                    eprintln!("Failed to create mesh renderer: {}", e);
256                    event_loop.exit();
257                    return;
258                }
259            };
260
261            self.window = Some(window);
262            self.point_renderer = Some(point_renderer);
263            self.mesh_renderer = Some(mesh_renderer);
264
265            println!("Viewer initialized successfully. Window should now be visible.");
266        }
267    }
268
269    fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
270        let Some(window) = &self.window else { return; };
271        let Some(point_renderer) = &mut self.point_renderer else { return; };
272        let Some(mesh_renderer) = &mut self.mesh_renderer else { return; };
273
274        match event {
275            WindowEvent::CloseRequested => {
276                event_loop.exit();
277            }
278            WindowEvent::Resized(new_size) => {
279                point_renderer.resize(new_size);
280                mesh_renderer.resize(new_size);
281                self.viewer.camera.aspect_ratio = new_size.width as f32 / new_size.height as f32;
282            }
283            WindowEvent::MouseInput { state, button, .. } => {
284                match button {
285                    MouseButton::Left => {
286                        self.viewer.mouse_pressed = state == ElementState::Pressed;
287                    }
288                    MouseButton::Right => {
289                        self.viewer.right_mouse_pressed = state == ElementState::Pressed;
290                    }
291                    _ => {}
292                }
293            }
294            WindowEvent::CursorMoved { position, .. } => {
295                if let Some(last_pos) = self.viewer.last_mouse_pos {
296                    let delta_x = position.x - last_pos.x;
297                    let delta_y = position.y - last_pos.y;
298
299                    if self.viewer.mouse_pressed {
300                        match self.viewer.camera_mode {
301                            CameraMode::Orbit => {
302                                self.viewer.camera.orbit(delta_x as f32 * 0.01, delta_y as f32 * 0.01);
303                            }
304                            CameraMode::Pan => {
305                                self.viewer.camera.pan(delta_x as f32 * 0.01, delta_y as f32 * 0.01);
306                            }
307                            _ => {}
308                        }
309                    }
310                }
311                self.viewer.last_mouse_pos = Some(position);
312            }
313            WindowEvent::MouseWheel { delta, .. } => {
314                let scroll_delta = match delta {
315                    winit::event::MouseScrollDelta::LineDelta(_, y) => y,
316                    winit::event::MouseScrollDelta::PixelDelta(pos) => pos.y as f32 / 100.0,
317                };
318                self.viewer.camera.zoom(scroll_delta * 0.1);
319            }
320            WindowEvent::KeyboardInput { event, .. } => {
321                if event.state == ElementState::Pressed {
322                    match &event.logical_key {
323                        Key::Character(c) => {
324                            match c.as_str() {
325                                "o" | "O" => {
326                                    self.viewer.camera_mode = CameraMode::Orbit;
327                                    println!("Switched to Orbit mode");
328                                }
329                                "p" | "P" => {
330                                    self.viewer.camera_mode = CameraMode::Pan;
331                                    println!("Switched to Pan mode");
332                                }
333                                "z" | "Z" => {
334                                    self.viewer.camera_mode = CameraMode::Zoom;
335                                    println!("Switched to Zoom mode");
336                                }
337                                "r" | "R" => {
338                                    self.viewer.camera.reset();
339                                    println!("Reset camera");
340                                }
341                                _ => {}
342                            }
343                        }
344                        _ => {}
345                    }
346                }
347            }
348            WindowEvent::RedrawRequested => {
349                // Update camera matrices
350                let view_matrix = self.viewer.camera.view_matrix();
351                let proj_matrix = self.viewer.camera.projection_matrix();
352                let camera_pos = self.viewer.camera.position.coords;
353                point_renderer.update_camera(view_matrix, proj_matrix, camera_pos);
354                mesh_renderer.update_camera(view_matrix, proj_matrix, camera_pos);
355
356                // Rebuild quad vertices only when data has changed
357                if self.viewer.vertices_dirty {
358                    self.cached_vertices = match &self.viewer.current_data {
359                        ViewData::PointCloud(cloud) => {
360                            let mut vertices = Vec::with_capacity(cloud.len() * 6);
361                            for point in cloud.iter() {
362                                let size = 0.02;
363                                let pos = [point.x, point.y, point.z];
364                                let color = [1.0, 1.0, 1.0];
365                                let normal = [0.0, 0.0, 1.0];
366
367                                let v1 = PointVertex::from_point(&Point3f::new(pos[0] - size, pos[1] - size, pos[2]), color, 16.0, normal);
368                                let v2 = PointVertex::from_point(&Point3f::new(pos[0] + size, pos[1] - size, pos[2]), color, 16.0, normal);
369                                let v3 = PointVertex::from_point(&Point3f::new(pos[0] + size, pos[1] + size, pos[2]), color, 16.0, normal);
370                                let v4 = PointVertex::from_point(&Point3f::new(pos[0] - size, pos[1] + size, pos[2]), color, 16.0, normal);
371
372                                vertices.push(v1);
373                                vertices.push(v2);
374                                vertices.push(v3);
375                                vertices.push(v1);
376                                vertices.push(v3);
377                                vertices.push(v4);
378                            }
379                            vertices
380                        }
381                        ViewData::ColoredPointCloud(cloud) => {
382                            let mut vertices = Vec::with_capacity(cloud.len() * 6);
383                            for point in cloud.iter() {
384                                let size = 0.02;
385                                let pos = [point.position.x, point.position.y, point.position.z];
386                                let color = [
387                                    point.color[0] as f32 / 255.0,
388                                    point.color[1] as f32 / 255.0,
389                                    point.color[2] as f32 / 255.0,
390                                ];
391                                let normal = [0.0, 0.0, 1.0];
392
393                                let v1 = PointVertex::from_point(&Point3f::new(pos[0] - size, pos[1] - size, pos[2]), color, 16.0, normal);
394                                let v2 = PointVertex::from_point(&Point3f::new(pos[0] + size, pos[1] - size, pos[2]), color, 16.0, normal);
395                                let v3 = PointVertex::from_point(&Point3f::new(pos[0] + size, pos[1] + size, pos[2]), color, 16.0, normal);
396                                let v4 = PointVertex::from_point(&Point3f::new(pos[0] - size, pos[1] + size, pos[2]), color, 16.0, normal);
397
398                                vertices.push(v1);
399                                vertices.push(v2);
400                                vertices.push(v3);
401                                vertices.push(v1);
402                                vertices.push(v3);
403                                vertices.push(v4);
404                            }
405                            vertices
406                        }
407                        ViewData::Mesh(_) | ViewData::Empty => vec![],
408                    };
409                    self.viewer.vertices_dirty = false;
410                }
411
412                // Debug: Print vertex count periodically
413                if !self.cached_vertices.is_empty() {
414                    if self.viewer.debug_frame_count % 60 == 0 {
415                        println!("Rendering {} vertices", self.cached_vertices.len());
416                    }
417                }
418                self.viewer.debug_frame_count += 1;
419
420                match &self.viewer.current_data {
421                    ViewData::PointCloud(_) | ViewData::ColoredPointCloud(_) => {
422                        if !self.cached_vertices.is_empty() {
423                            if let Err(e) = point_renderer.render(&self.cached_vertices) {
424                                eprintln!("Render error: {}", e);
425                            }
426                        }
427                    }
428                    ViewData::Mesh(mesh) => {
429                        if !mesh.vertices.is_empty() && !mesh.faces.is_empty() {
430                            // Build index buffer from faces
431                            let indices: Vec<u32> = mesh
432                                .faces
433                                .iter()
434                                .flat_map(|f| [f[0] as u32, f[1] as u32, f[2] as u32])
435                                .collect();
436
437                            // Prepare optional normals
438                            let normals_opt = mesh.normals.as_ref().map(|n| n.as_slice());
439
440                            // Prepare optional colors as f32
441                            let colors_f32: Option<Vec<[f32; 3]>> = mesh.colors.as_ref().map(|cols| {
442                                cols.iter()
443                                    .map(|c| [c[0] as f32 / 255.0, c[1] as f32 / 255.0, c[2] as f32 / 255.0])
444                                    .collect()
445                            });
446                            let colors_opt = colors_f32.as_ref().map(|c| c.as_slice());
447
448                            // Convert to GPU mesh
449                            let gpu_mesh = mesh_to_gpu_mesh(
450                                &mesh.vertices,
451                                &indices,
452                                normals_opt,
453                                colors_opt,
454                                None,
455                            );
456
457                            if let Err(e) = mesh_renderer.render(&gpu_mesh, ShadingMode::Flat) {
458                                eprintln!("Mesh render error: {}", e);
459                            }
460                        }
461                    }
462                    ViewData::Empty => {}
463                }
464
465                // Request next frame
466                window.request_redraw();
467            }
468            _ => {}
469        }
470    }
471
472    fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
473        if let Some(window) = &self.window {
474            window.request_redraw();
475        }
476    }
477}
478