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    /// Handle the event of touching with a finger
145    /// # Panics
146    /// Will panic if the ``CamController`` component does not exist for this
147    /// entity
148    pub fn touch_pressed(&mut self, touch_event: &Touch, scene: &mut Scene) {
149        // println!("mouse pressed");
150        let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
151
152        cam_control.id2active_touches.insert(touch_event.id, *touch_event);
153
154        cam_control.mouse_pressed = true;
155
156        //adding a finger already invalidates the prev mouse point. If there was one
157        // finger before we added, then it means we switch from having the previous
158        // position be at the finger to it being in between the two fingers so we
159        // invalidate previous state
160        cam_control.prev_mouse_pos_valid = false;
161    }
162
163    /// Handle the event of pressing mouse
164    /// # Panics
165    /// Will panic if the ``CamController`` component does not exist for this
166    /// entity
167    pub fn touch_released(&mut self, touch_event: &Touch, scene: &mut Scene) {
168        // println!("mouse pressed");
169        let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
170
171        cam_control.id2active_touches.remove(&touch_event.id);
172
173        //no fingers are touching the screen
174        if cam_control.id2active_touches.is_empty() {
175            cam_control.mouse_pressed = false;
176            cam_control.prev_mouse_pos_valid = false;
177        }
178        //releasing a finger already invalidates the prev mouse point. If there was one
179        // finger before we removed, then we definitelly want to invalidate. If there
180        // were two then it means we switch from the center of the two fingers to just
181        // one finger so we also invalidate the previous pos
182        cam_control.prev_mouse_pos_valid = false;
183    }
184
185    /// # Panics
186    /// Will panic if the ``CamController`` component does not exist for this
187    /// entity
188    pub fn reset_all_touch_presses(&mut self, scene: &mut Scene) {
189        let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
190
191        cam_control.id2active_touches.clear();
192
193        cam_control.mouse_pressed = false;
194        cam_control.prev_mouse_pos_valid = false;
195        cam_control.prev_mouse_pos_valid = false;
196    }
197
198    /// # Panics
199    /// Will panic if the ``CamController`` component does not exist for this
200    /// entity
201    #[allow(clippy::cast_possible_truncation)]
202    #[allow(clippy::cast_lossless)]
203    pub fn process_touch_move(&mut self, touch_event: &Touch, viewport_width: u32, viewport_height: u32, scene: &mut Scene) {
204        let touches = {
205            let cam_control = scene.get_comp::<&CamController>(&self.entity).unwrap();
206            let all_touches: Vec<Touch> = cam_control.id2active_touches.values().copied().collect();
207            all_touches
208        };
209
210        //if we have only one finger, we rotate
211        if touches.len() == 1 {
212            {
213                let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
214                cam_control.mouse_mode = CamMode::Rotation;
215            }
216            self.process_mouse_move(
217                touch_event.location.x as f32,
218                touch_event.location.y as f32,
219                viewport_width,
220                viewport_height,
221                scene,
222            );
223            //update position of this finger
224            let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
225            cam_control.id2active_touches.insert(touch_event.id, *touch_event);
226        } else if touches.len() == 2 {
227            //calculate difference between the two fingers, did it increase or decrease
228            // from the previous frame
229            let pos0 = touches[0].location;
230            let pos1 = touches[1].location;
231            let pos0_na = na::Vector2::<f64>::new(pos0.x / viewport_width as f64, pos0.y / viewport_height as f64);
232            let pos1_na = na::Vector2::<f64>::new(pos1.x / viewport_width as f64, pos1.y / viewport_height as f64);
233            let diff_prev = (pos0_na - pos1_na).norm();
234            // //update position of this finger
235            let current_touches = {
236                let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
237                cam_control.id2active_touches.insert(touch_event.id, *touch_event);
238                let current_touches: Vec<Touch> = cam_control.id2active_touches.values().copied().collect();
239                current_touches
240            };
241            //current difference between fingers
242            let pos0 = current_touches[0].location;
243            let pos1 = current_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_cur = (pos0_na - pos1_na).norm();
247
248            //check if the difference increased or not
249            {
250                let inc = diff_cur - diff_prev;
251                let speed = 1.0;
252                let mut pos_lookat = scene.get_comp::<&mut PosLookat>(&self.entity).unwrap();
253                pos_lookat.dolly(inc as f32 * speed);
254            }
255
256            //also move the camera depening on where the center of the two fingers moved
257            {
258                let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
259                cam_control.mouse_mode = CamMode::Translation;
260            }
261            let center = na::Vector2::<f64>::new((pos0.x + pos1.x) / 2.0, (pos0.y + pos1.y) / 2.0);
262            self.process_mouse_move(center.x as f32, center.y as f32, viewport_width, viewport_height, scene);
263        }
264    }
265
266    /// Handle the event of pressing mouse
267    /// # Panics
268    /// Will panic if the ``CamController`` component does not exist for this
269    /// entity
270    pub fn mouse_pressed(&mut self, mouse_button: &MouseButton, scene: &mut Scene) {
271        // println!("mouse pressed");
272        let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
273
274        match mouse_button {
275            MouseButton::Left => {
276                cam_control.mouse_mode = CamMode::Rotation;
277                cam_control.mouse_pressed = true;
278            }
279            MouseButton::Right => {
280                cam_control.mouse_mode = CamMode::Translation;
281                cam_control.mouse_pressed = true;
282            }
283            _ => {
284                cam_control.mouse_pressed = false;
285            }
286        }
287    }
288
289    /// Handle the event of releasing mouse
290    /// # Panics
291    /// Will panic if the ``CamController`` component does not exist for this
292    /// entity
293    pub fn mouse_released(&mut self, scene: &mut Scene) {
294        // println!("mouse released");
295        let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
296
297        // println!("mouse released");
298        cam_control.mouse_pressed = false;
299        cam_control.prev_mouse_pos_valid = false;
300    }
301
302    /// Handle the event of dragging the mouse on the window
303    /// # Panics
304    /// Will panic if the ``PosLookat``, ``Projection``, ``CamController``
305    /// component does not exist for this entity
306    pub fn process_mouse_move(&mut self, x: f32, y: f32, viewport_width: u32, viewport_height: u32, scene: &mut Scene) {
307        let proj = self.proj_matrix(scene);
308        let mut pos_lookat = scene.get_comp::<&mut PosLookat>(&self.entity).unwrap();
309        let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
310
311        let current_mouse = na::Vector2::<f32>::new(x, y);
312        #[allow(clippy::cast_precision_loss)] //it's ok, we don't have very big viewport sizes
313        let viewport_size = na::Vector2::<f32>::new(viewport_width as f32, viewport_height as f32);
314
315        if cam_control.mouse_pressed {
316            if cam_control.mouse_mode == CamMode::Rotation && cam_control.prev_mouse_pos_valid {
317                let speed = 2.0;
318                let (rot_y, mut rot_x) = Self::two_axis_rotation(
319                    na::Vector3::from(pos_lookat.cam_axes().column(0)),
320                    viewport_size,
321                    speed,
322                    cam_control.prev_mouse,
323                    current_mouse,
324                );
325
326                //calculate the new position as if we apply this rotation
327                let mut new_pos_lookat = pos_lookat.clone();
328                new_pos_lookat.orbit(rot_x);
329                //calculate vertical angle
330                let dot_up = new_pos_lookat.direction().dot(&na::Vector3::<f32>::y_axis());
331                let angle_vertical = dot_up.acos();
332                // println!("angle vertical {}", angle_vertical);
333                if let Some(max_vertical_angle) = cam_control.limit_max_vertical_angle {
334                    if angle_vertical > max_vertical_angle || new_pos_lookat.up == na::Vector3::<f32>::new(0.0, -1.0, 0.0) {
335                        rot_x = na::Rotation3::<f32>::identity();
336                    }
337                }
338                if let Some(min_vertical_angle) = cam_control.limit_min_vertical_angle {
339                    if angle_vertical < min_vertical_angle {
340                        rot_x = na::Rotation3::<f32>::identity();
341                    }
342                }
343
344                let rot = rot_y * rot_x;
345                pos_lookat.orbit(rot);
346            } else if cam_control.mouse_mode == CamMode::Translation && cam_control.prev_mouse_pos_valid {
347                let view = pos_lookat.view_matrix();
348
349                let coord = self.project(pos_lookat.lookat, view, proj, viewport_size);
350                let down_mouse_z = coord.z;
351
352                let pos1 = self.unproject(na::Point3::<f32>::new(x, viewport_size.y - y, down_mouse_z), view, proj, viewport_size);
353                let pos0 = self.unproject(
354                    na::Point3::<f32>::new(cam_control.prev_mouse.x, viewport_size.y - cam_control.prev_mouse.y, down_mouse_z),
355                    view,
356                    proj,
357                    viewport_size,
358                );
359                let diff = pos1 - pos0;
360                // diff.array()*=speed_multiplier;
361                let new_pos = pos_lookat.position - diff;
362                pos_lookat.shift_cam(new_pos);
363            }
364            cam_control.prev_mouse = current_mouse;
365            cam_control.prev_mouse_pos_valid = true;
366        }
367    }
368
369    /// Handle event of scrolling the mouse wheel. It performs a zoom.
370    /// # Panics
371    /// Will panic if the ``PosLookat``, ``CamController`` component does not
372    /// exist for this entity
373    pub fn process_mouse_scroll(&mut self, delta: &MouseScrollDelta, scene: &mut Scene) {
374        let mut pos_lookat = scene.get_comp::<&mut PosLookat>(&self.entity).unwrap();
375        let cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
376
377        let scroll = match delta {
378            // I'm assuming a line is about 100 pixels
379            MouseScrollDelta::LineDelta(_, scroll) => f64::from(scroll * 0.5),
380            #[allow(clippy::cast_precision_loss)] //it's ok, we don't have very big numbers
381            MouseScrollDelta::PixelDelta(PhysicalPosition { y: scroll, .. }) => *scroll,
382        };
383
384        let mut s = if scroll > 0.0 { 0.1 } else { -0.1 };
385
386        if let Some(max_dist) = cam_control.limit_max_dist {
387            let cur_dist = pos_lookat.dist_lookat();
388            if cur_dist > max_dist && s < 0.0 {
389                s = 0.0;
390            }
391        }
392
393        // self.push_away(val);
394        pos_lookat.dolly(s);
395    }
396
397    /// Resizing the window means that the projection matrix of the camera has
398    /// to change accordingly so as to not squish the scene # Panics
399    /// Will panic if the ``Projection`` component does not exist for this
400    /// entity
401    pub fn set_aspect_ratio(&mut self, val: f32, scene: &mut Scene) {
402        //extract data
403        let mut proj = scene.get_comp::<&mut Projection>(&self.entity).unwrap();
404        if let Projection::WithFov(ref mut proj) = *proj {
405            proj.aspect_ratio = val;
406        }
407    }
408
409    /// Resizing the window means that the projection matrix of the camera has
410    /// to change accordingly so as to not squish the scene
411    pub fn set_aspect_ratio_maybe(&mut self, val: f32, scene: &mut Scene) {
412        if let Ok(mut proj) = scene.get_comp::<&mut Projection>(&self.entity) {
413            if let Projection::WithFov(ref mut proj) = *proj {
414                proj.aspect_ratio = val;
415            }
416        } else if scene.nr_renderables() != 0 {
417            //if the nr of renderabled is 0 then might not have a projection matrix and
418            // that's fine because when we add renderables, the prepass will run and add a
419            // projection matrix
420            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");
421        }
422    }
423
424    pub fn near_far(&self, scene: &mut Scene) -> (f32, f32) {
425        let proj = scene.get_comp::<&Projection>(&self.entity).unwrap();
426        proj.near_far()
427    }
428
429    /// Unconditionally sets the target res, regardless of the update mode
430    pub fn set_target_res(&mut self, width: u32, height: u32, scene: &mut Scene) {
431        {
432            let mut res = scene.get_comp::<&mut TargetResolution>(&self.entity).unwrap();
433            res.width = width;
434            res.height = height;
435        }
436
437        //sync also aspect ratio
438        #[allow(clippy::cast_precision_loss)]
439        self.set_aspect_ratio_maybe(width as f32 / height as f32, scene);
440    }
441
442    /// Sets the target res on window resizing, only updates if the updatemode
443    /// is `WindowSize`
444    pub fn on_window_resize(&mut self, width: u32, height: u32, scene: &mut Scene) {
445        {
446            let mut res = scene.get_comp::<&mut TargetResolution>(&self.entity).unwrap();
447            if res.update_mode == TargetResolutionUpdate::WindowSize {
448                res.width = width;
449                res.height = height;
450            }
451        }
452
453        //sync also aspect ratio
454        #[allow(clippy::cast_precision_loss)]
455        self.set_aspect_ratio_maybe(width as f32 / height as f32, scene);
456    }
457
458    pub fn get_target_res(&self, scene: &Scene) -> (u32, u32) {
459        let res = scene.get_comp::<&TargetResolution>(&self.entity).unwrap();
460        (res.width, res.height)
461    }
462}