gloss_renderer/
camera.rs

1extern crate nalgebra as na;
2extern crate nalgebra_glm as glm;
3
4use log::warn;
5use winit::{
6    dpi::PhysicalPosition,
7    event::{MouseButton, MouseScrollDelta, Touch},
8};
9
10use crate::{
11    components::{CamController, CamMode, PosLookat, Projection, ProjectionWithIntrinsics, TargetResolution, TargetResolutionUpdate},
12    scene::Scene,
13};
14use gloss_hecs::Entity;
15
16/// Camera implements most of the functionality related to cameras. It deals
17/// with processing of mouse events and with moving the camera in an orbit like
18/// manner. It contains a reference to the entity in the world so that changes
19/// done by the camera object will directly affect the entity.
20#[repr(C)]
21pub struct Camera {
22    pub entity: Entity,
23}
24
25impl Camera {
26    #[allow(clippy::missing_panics_doc)] //really will never panic because the entity definitelly already exists in the
27                                         // world
28    pub fn new(name: &str, scene: &mut Scene, initialize: bool) -> Self {
29        let entity = scene
30            .get_or_create_hidden_entity(name)
31            .insert(CamController::default())
32            .insert(TargetResolution::default())
33            .entity();
34        if initialize {
35            scene.world.insert_one(entity, PosLookat::default()).ok();
36            scene.world.insert_one(entity, Projection::default()).ok();
37        }
38        Self { entity }
39    }
40
41    pub fn from_entity(entity: Entity) -> Self {
42        Self { entity }
43    }
44
45    #[allow(clippy::missing_panics_doc)]
46    pub fn is_initialized(&self, scene: &Scene) -> bool {
47        scene.world.has::<PosLookat>(self.entity).unwrap()
48            && (scene.world.has::<Projection>(self.entity).unwrap() || scene.world.has::<ProjectionWithIntrinsics>(self.entity).unwrap())
49    }
50
51    /// # Panics
52    /// Will panic if the ``PosLookat`` component does not exist for this entity
53    pub fn view_matrix(&self, scene: &Scene) -> na::Matrix4<f32> {
54        let pos_lookat = scene.get_comp::<&PosLookat>(&self.entity).unwrap();
55        pos_lookat.view_matrix()
56    }
57
58    /// # Panics
59    /// Will panic if the ``Projection`` component does not exist for this
60    /// entity
61    pub fn proj_matrix(&self, scene: &Scene) -> na::Matrix4<f32> {
62        let proj = scene.get_comp::<&Projection>(&self.entity).unwrap();
63        let (width, height) = self.get_target_res(scene);
64        proj.proj_matrix(width, height)
65    }
66
67    /// # Panics
68    /// Will panic if the ``Projection`` component does not exist for this
69    /// entity
70    pub fn proj_matrix_reverse_z(&self, scene: &Scene) -> na::Matrix4<f32> {
71        let proj = scene.get_comp::<&Projection>(&self.entity).unwrap();
72        let (width, height) = self.get_target_res(scene);
73        proj.proj_matrix_reverse_z(width, height)
74    }
75
76    /// Performs a two-axis rotation of the camera by mapping the xy
77    /// coordianates of the mouse to rotation around the Y axis of the world and
78    /// the X axis of the camera.
79    pub fn two_axis_rotation(
80        cam_axis_x: na::Vector3<f32>,
81        viewport_size: na::Vector2<f32>,
82        speed: f32,
83        prev_mouse: na::Vector2<f32>,
84        current_mouse: na::Vector2<f32>,
85    ) -> (na::Rotation3<f32>, na::Rotation3<f32>) {
86        // rotate around Y axis of the world (the vector 0,1,0)
87        let angle_y = std::f32::consts::PI * (prev_mouse.x - current_mouse.x) / viewport_size.x * speed;
88        let rot_y = na::Rotation3::from_axis_angle(&na::Vector3::y_axis(), angle_y);
89
90        //rotate around x axis of the camera coordinate
91        let axis_x = cam_axis_x;
92        let axis_x = na::Unit::new_normalize(axis_x);
93        let angle_x = std::f32::consts::PI * (prev_mouse.y - current_mouse.y) / viewport_size.y * speed;
94        let rot_x = na::Rotation3::from_axis_angle(&axis_x, angle_x);
95
96        (rot_y, rot_x)
97    }
98
99    /// Projects from 3D world to 2D screen coordinates in the range [0,
100    /// `viewport_width`] and [0, `viewport_height`]
101    pub fn project(
102        &self,
103        point_world: na::Point3<f32>,
104        view: na::Matrix4<f32>,
105        proj: na::Matrix4<f32>,
106        viewport_size: na::Vector2<f32>,
107    ) -> na::Vector3<f32> {
108        //get the point from world to screen space
109        let p_view = view * point_world.to_homogeneous();
110        let mut p_proj = proj * p_view;
111        p_proj = p_proj / p_proj.w;
112        p_proj = p_proj * 0.5 + na::Vector4::<f32>::new(0.5, 0.5, 0.5, 0.5);
113        p_proj.x *= viewport_size.x;
114        p_proj.y *= viewport_size.y;
115
116        p_proj.fixed_rows::<3>(0).clone_owned()
117    }
118
119    /// Unprojects from 2D screen coordinates in range [0, `viewport_width`] and
120    /// [0, `viewport_height`] to 3D world # Panics
121    /// Will panic if the proj*view matrix is not invertable
122    pub fn unproject(
123        &self,
124        win: na::Point3<f32>,
125        view: na::Matrix4<f32>,
126        proj: na::Matrix4<f32>,
127        viewport_size: na::Vector2<f32>,
128    ) -> na::Vector3<f32> {
129        let inv = (proj * view).try_inverse().unwrap();
130
131        let mut tmp = win.to_homogeneous();
132        tmp.x /= viewport_size.x;
133        tmp.y /= viewport_size.y;
134        tmp = tmp * 2.0 - na::Vector4::<f32>::new(-1.0, -1.0, -1.0, -1.0);
135
136        let mut obj = inv * tmp;
137        obj = obj / obj.w;
138
139        let scene = obj.fixed_rows::<3>(0).clone_owned();
140
141        scene
142    }
143
144    /// Clear the click state once processed, so this doesnt keep running
145    pub fn clear_click(&self, scene: &mut Scene) {
146        let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
147        cam_control.is_last_press_click = false;
148    }
149
150    // Since we use LMB for both rotation and selection, rotations tamper with the selection
151    // which can be a little annoying. To avoid this we do -
152    // LMB click + release (within 0.25s) -> entity selection
153    // LMB click + hold + move -> camera rotation
154    pub fn is_click(&self, scene: &Scene) -> bool {
155        let cam_control = scene.get_comp::<&CamController>(&self.entity).unwrap();
156        cam_control.is_last_press_click
157    }
158
159    /// Handle the event of touching with a finger
160    /// # Panics
161    /// Will panic if the ``CamController`` component does not exist for this
162    /// entity
163    pub fn touch_pressed(&mut self, touch_event: &Touch, scene: &mut Scene) {
164        // println!("mouse pressed");
165        let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
166
167        cam_control.id2active_touches.insert(touch_event.id, *touch_event);
168
169        cam_control.mouse_pressed = true;
170
171        //adding a finger already invalidates the prev mouse point. If there was one
172        // finger before we added, then it means we switch from having the previous
173        // position be at the finger to it being in between the two fingers so we
174        // invalidate previous state
175        cam_control.prev_mouse_pos_valid = false;
176
177        cam_control.last_press = Some(wasm_timer::Instant::now());
178    }
179
180    /// Handle the event of pressing mouse
181    /// # Panics
182    /// Will panic if the ``CamController`` component does not exist for this
183    /// entity
184    pub fn touch_released(&mut self, touch_event: &Touch, scene: &mut Scene) {
185        let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
186
187        cam_control.id2active_touches.remove(&touch_event.id);
188
189        //no fingers are touching the screen
190        if cam_control.id2active_touches.is_empty() {
191            cam_control.mouse_pressed = false;
192            cam_control.prev_mouse_pos_valid = false;
193        }
194        //releasing a finger already invalidates the prev mouse point. If there was one
195        // finger before we removed, then we definitelly want to invalidate. If there
196        // were two then it means we switch from the center of the two fingers to just
197        // one finger so we also invalidate the previous pos
198        cam_control.prev_mouse_pos_valid = false;
199        cam_control.decide_if_click();
200        cam_control.mouse_moved_while_pressed = false; //do this AFTER decide_if_click because that function depends on if the mouse has moved while pressed
201    }
202
203    /// # Panics
204    /// Will panic if the ``CamController`` component does not exist for this
205    /// entity
206    pub fn reset_all_touch_presses(&mut self, scene: &mut Scene) {
207        let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
208
209        cam_control.id2active_touches.clear();
210
211        cam_control.mouse_pressed = false;
212        cam_control.prev_mouse_pos_valid = false;
213        cam_control.prev_mouse_pos_valid = false;
214        cam_control.mouse_moved_while_pressed = false;
215    }
216
217    /// # Panics
218    /// Will panic if the ``CamController`` component does not exist for this
219    /// entity
220    #[allow(clippy::cast_possible_truncation)]
221    #[allow(clippy::cast_lossless)]
222    pub fn process_touch_move(&mut self, touch_event: &Touch, viewport_width: u32, viewport_height: u32, scene: &mut Scene) {
223        let touches = {
224            let cam_control = scene.get_comp::<&CamController>(&self.entity).unwrap();
225            let all_touches: Vec<Touch> = cam_control.id2active_touches.values().copied().collect();
226            all_touches
227        };
228
229        //if we have only one finger, we rotate
230        if touches.len() == 1 {
231            {
232                let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
233                cam_control.mouse_mode = CamMode::Rotation;
234            }
235            self.process_mouse_move(touch_event.location.x, touch_event.location.y, viewport_width, viewport_height, scene);
236            //update position of this finger
237            let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
238            cam_control.id2active_touches.insert(touch_event.id, *touch_event);
239        } else if touches.len() == 2 {
240            //calculate difference between the two fingers, did it increase or decrease
241            // from the previous frame
242            let pos0 = touches[0].location;
243            let pos1 = touches[1].location;
244            let pos0_na = na::Vector2::<f64>::new(pos0.x / viewport_width as f64, pos0.y / viewport_height as f64);
245            let pos1_na = na::Vector2::<f64>::new(pos1.x / viewport_width as f64, pos1.y / viewport_height as f64);
246            let diff_prev = (pos0_na - pos1_na).norm();
247            // //update position of this finger
248            let current_touches = {
249                let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
250                cam_control.id2active_touches.insert(touch_event.id, *touch_event);
251                let current_touches: Vec<Touch> = cam_control.id2active_touches.values().copied().collect();
252                current_touches
253            };
254            //current difference between fingers
255            let pos0 = current_touches[0].location;
256            let pos1 = current_touches[1].location;
257            let pos0_na = na::Vector2::<f64>::new(pos0.x / viewport_width as f64, pos0.y / viewport_height as f64);
258            let pos1_na = na::Vector2::<f64>::new(pos1.x / viewport_width as f64, pos1.y / viewport_height as f64);
259            let diff_cur = (pos0_na - pos1_na).norm();
260
261            //check if the difference increased or not
262            {
263                let inc = diff_cur - diff_prev;
264                let speed = 1.0;
265                let mut pos_lookat = scene.get_comp::<&mut PosLookat>(&self.entity).unwrap();
266                pos_lookat.dolly(inc as f32 * speed);
267            }
268
269            //also move the camera depening on where the center of the two fingers moved
270            {
271                let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
272                cam_control.mouse_mode = CamMode::Translation;
273            }
274            let center = na::Vector2::<f64>::new((pos0.x + pos1.x) / 2.0, (pos0.y + pos1.y) / 2.0);
275            self.process_mouse_move(center.x, center.y, viewport_width, viewport_height, scene);
276        }
277    }
278
279    /// Handle the event of pressing mouse
280    /// # Panics
281    /// Will panic if the ``CamController`` component does not exist for this
282    /// entity
283    pub fn mouse_pressed(&mut self, mouse_button: &MouseButton, scene: &mut Scene) {
284        // println!("mouse pressed");
285        let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
286
287        match mouse_button {
288            MouseButton::Left => {
289                cam_control.last_press = Some(wasm_timer::Instant::now());
290                cam_control.mouse_mode = CamMode::Rotation;
291                cam_control.mouse_pressed = true;
292            }
293            MouseButton::Right => {
294                cam_control.mouse_mode = CamMode::Translation;
295                cam_control.mouse_pressed = true;
296            }
297            _ => {
298                cam_control.mouse_pressed = false;
299            }
300        }
301    }
302
303    /// Handle the event of releasing mouse
304    /// # Panics
305    /// Will panic if the ``CamController`` component does not exist for this
306    /// entity
307    pub fn mouse_released(&mut self, scene: &mut Scene) {
308        let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
309
310        cam_control.mouse_pressed = false;
311        cam_control.prev_mouse_pos_valid = false;
312        cam_control.decide_if_click();
313        cam_control.mouse_moved_while_pressed = false; //do this AFTER decide_if_click because that function depends on if the mouse has moved while pressed
314    }
315
316    /// Handle the event of dragging the mouse on the window
317    /// # Panics
318    /// Will panic if the ``PosLookat``, ``Projection``, ``CamController``
319    /// component does not exist for this entity
320    pub fn process_mouse_move(&mut self, position_x: f64, position_y: f64, viewport_width: u32, viewport_height: u32, scene: &mut Scene) {
321        let proj = self.proj_matrix(scene);
322        let mut pos_lookat = scene.get_comp::<&mut PosLookat>(&self.entity).unwrap();
323        let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
324
325        cam_control.cursor_position = Some(winit::dpi::PhysicalPosition::new(position_x, position_y));
326
327        #[allow(clippy::cast_possible_truncation)]
328        let (x, y) = (position_x as f32, position_y as f32);
329
330        let current_mouse = na::Vector2::<f32>::new(x, y);
331        #[allow(clippy::cast_precision_loss)] //it's ok, we don't have very big viewport sizes
332        let viewport_size = na::Vector2::<f32>::new(viewport_width as f32, viewport_height as f32);
333
334        if cam_control.mouse_pressed {
335            if cam_control.prev_mouse_pos_valid {
336                cam_control.mouse_moved_while_pressed = true;
337            }
338
339            if cam_control.mouse_mode == CamMode::Rotation && cam_control.prev_mouse_pos_valid {
340                let speed = 2.0;
341                let (rot_y, mut rot_x) = Self::two_axis_rotation(
342                    na::Vector3::from(pos_lookat.cam_axes().column(0)),
343                    viewport_size,
344                    speed,
345                    cam_control.prev_mouse,
346                    current_mouse,
347                );
348
349                //calculate the new position as if we apply this rotation
350                let mut new_pos_lookat = pos_lookat.clone();
351                new_pos_lookat.orbit(rot_x);
352                //calculate vertical angle
353                let dot_up = new_pos_lookat.direction().dot(&na::Vector3::<f32>::y_axis());
354                let angle_vertical = dot_up.acos();
355                // println!("angle vertical {}", angle_vertical);
356                if let Some(max_vertical_angle) = cam_control.limit_max_vertical_angle {
357                    if angle_vertical > max_vertical_angle || new_pos_lookat.up == na::Vector3::<f32>::new(0.0, -1.0, 0.0) {
358                        rot_x = na::Rotation3::<f32>::identity();
359                    }
360                }
361                if let Some(min_vertical_angle) = cam_control.limit_min_vertical_angle {
362                    if angle_vertical < min_vertical_angle {
363                        rot_x = na::Rotation3::<f32>::identity();
364                    }
365                }
366
367                let rot = rot_y * rot_x;
368                pos_lookat.orbit(rot);
369            } else if cam_control.mouse_mode == CamMode::Translation && cam_control.prev_mouse_pos_valid {
370                let view = pos_lookat.view_matrix();
371
372                let coord = self.project(pos_lookat.lookat, view, proj, viewport_size);
373                let down_mouse_z = coord.z;
374
375                let pos1 = self.unproject(na::Point3::<f32>::new(x, viewport_size.y - y, down_mouse_z), view, proj, viewport_size);
376                let pos0 = self.unproject(
377                    na::Point3::<f32>::new(cam_control.prev_mouse.x, viewport_size.y - cam_control.prev_mouse.y, down_mouse_z),
378                    view,
379                    proj,
380                    viewport_size,
381                );
382                let diff = pos1 - pos0;
383                // diff.array()*=speed_multiplier;
384                let new_pos = pos_lookat.position - diff;
385                pos_lookat.shift_cam(new_pos);
386            }
387            cam_control.prev_mouse = current_mouse;
388            cam_control.prev_mouse_pos_valid = true;
389        }
390    }
391
392    /// Handle event of scrolling the mouse wheel. It performs a zoom.
393    /// # Panics
394    /// Will panic if the ``PosLookat``, ``CamController`` component does not
395    /// exist for this entity
396    pub fn process_mouse_scroll(&mut self, delta: &MouseScrollDelta, scene: &mut Scene) {
397        let mut pos_lookat = scene.get_comp::<&mut PosLookat>(&self.entity).unwrap();
398        let cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
399
400        let scroll = match delta {
401            // I'm assuming a line is about 100 pixels
402            MouseScrollDelta::LineDelta(_, scroll) => f64::from(scroll * 0.5),
403            #[allow(clippy::cast_precision_loss)] //it's ok, we don't have very big numbers
404            MouseScrollDelta::PixelDelta(PhysicalPosition { y: scroll, .. }) => *scroll,
405        };
406
407        let mut s = if scroll > 0.0 { 0.1 } else { -0.1 };
408
409        if let Some(max_dist) = cam_control.limit_max_dist {
410            let cur_dist = pos_lookat.dist_lookat();
411            if cur_dist > max_dist && s < 0.0 {
412                s = 0.0;
413            }
414        }
415
416        // self.push_away(val);
417        pos_lookat.dolly(s);
418    }
419
420    /// Resizing the window means that the projection matrix of the camera has
421    /// to change accordingly so as to not squish the scene # Panics
422    /// Will panic if the ``Projection`` component does not exist for this
423    /// entity
424    pub fn set_aspect_ratio(&mut self, val: f32, scene: &mut Scene) {
425        //extract data
426        let mut proj = scene.get_comp::<&mut Projection>(&self.entity).unwrap();
427        if let Projection::WithFov(ref mut proj) = *proj {
428            proj.aspect_ratio = val;
429        }
430    }
431
432    /// Resizing the window means that the projection matrix of the camera has
433    /// to change accordingly so as to not squish the scene
434    pub fn set_aspect_ratio_maybe(&mut self, val: f32, scene: &mut Scene) {
435        if let Ok(mut proj) = scene.get_comp::<&mut Projection>(&self.entity) {
436            if let Projection::WithFov(ref mut proj) = *proj {
437                proj.aspect_ratio = val;
438            }
439        } else if scene.nr_renderables() != 0 {
440            //if the nr of renderabled is 0 then might not have a projection matrix and
441            // that's fine because when we add renderables, the prepass will run and add a
442            // projection matrix
443            warn!("No Projection component yet so we couldn't set aspect ratio. This may not be an issue since the prepass might fix this. Ideally this warning should only appear at most once");
444        }
445    }
446
447    pub fn near_far(&self, scene: &mut Scene) -> (f32, f32) {
448        let proj = scene.get_comp::<&Projection>(&self.entity).unwrap();
449        proj.near_far()
450    }
451
452    /// Unconditionally sets the target res, regardless of the update mode
453    pub fn set_target_res(&mut self, width: u32, height: u32, scene: &mut Scene) {
454        {
455            let mut res = scene.get_comp::<&mut TargetResolution>(&self.entity).unwrap();
456            res.width = width;
457            res.height = height;
458        }
459
460        //sync also aspect ratio
461        #[allow(clippy::cast_precision_loss)]
462        self.set_aspect_ratio_maybe(width as f32 / height as f32, scene);
463    }
464
465    /// Sets the target res on window resizing, only updates if the updatemode
466    /// is `WindowSize`
467    pub fn on_window_resize(&mut self, width: u32, height: u32, scene: &mut Scene) {
468        {
469            let mut res = scene.get_comp::<&mut TargetResolution>(&self.entity).unwrap();
470            if res.update_mode == TargetResolutionUpdate::WindowSize {
471                res.width = width;
472                res.height = height;
473            }
474        }
475
476        //sync also aspect ratio
477        #[allow(clippy::cast_precision_loss)]
478        self.set_aspect_ratio_maybe(width as f32 / height as f32, scene);
479    }
480
481    pub fn get_target_res(&self, scene: &Scene) -> (u32, u32) {
482        let res = scene.get_comp::<&TargetResolution>(&self.entity).unwrap();
483        (res.width, res.height)
484    }
485
486    // fn handle_selection_click(&self, gpu_res: &Option<GpuResources>, scene: &mut Scene, x: u32, y: u32) {
487    //     if gpu_res.is_none() {
488    //         return;
489    //     }
490
491    //     let gpu = &gpu_res.as_ref().unwrap().gpu;
492
493    //     // Get the entity id texture from the renderer
494    //     let entity_id_texture = gpu_res.as_ref().unwrap().renderer.entity_id_buffer();
495
496    //     // We only need the pixel at selection so we dont really need to download the whole thing
497    //     let single_pixel_img = pollster::block_on(entity_id_texture.download_pixel_to_cpu(
498    //         gpu.device(),
499    //         gpu.queue(),
500    //         wgpu::TextureAspect::All,
501    //         x,
502    //         y,
503    //     ));
504    //     let entity_id = single_pixel_img.as_bytes()[0];
505
506    //     // Switch off selection for previous entity using the name in the selector
507    //     // Always do this, every click regardless of where should switch off the previous selection
508    //     if let Ok(selector) = scene.get_resource::<&mut Selector>() {
509    //         if let Some(prev_entity) = scene.get_entity_with_name(&selector.current_selected) {
510    //             if let Ok(mut vis_outline) = scene.world.get::<&mut VisOutline>(prev_entity) {
511    //                 vis_outline.show_outline = false;
512    //             }
513    //         }
514    //     }
515    //     let _ = scene.remove_resource::<Selector>();
516
517    //     // For pixels with no entity, we get 0, dont do anything in that case.
518    //     // If entity_id is not 0, we can look up the entity in the scene to select
519    //     if entity_id != 0 {
520    //         // Look for an entity with given ID (internally iterates over all ents)
521    //         let entity_ref = scene.find_entity_with_id(entity_id);
522
523    //         // Modify selector and VisOutline state if entity is found
524    //         if let Some(e_ref) = entity_ref {
525    //             let name = e_ref.get::<&Name>().expect("The entity has no name").0.clone();
526    //             // Only ents with VisOutline are candidates for visual selection
527    //             if let Some(mut vis_outline) = e_ref.get::<&mut VisOutline>() {
528    //                 vis_outline.show_outline = true;
529    //             }
530    //             // Add the selector resource to the scene
531    //             scene.add_resource(Selector {
532    //                 current_selected: name.clone(),
533    //             });
534    //         }
535    //     }
536    // }
537}