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