gloss_renderer/components/
cam_comps.rs

1// use gloss_hecs::Bundle;
2
3/// Duration of a click in milliseconds, a press followed by release within this duration is considered a click
4pub const CLICK_DURATION_MS: u128 = 250;
5
6use std::collections::HashMap;
7use wasm_timer::Instant;
8use winit::event::Touch;
9
10extern crate nalgebra as na;
11extern crate nalgebra_glm as glm;
12
13#[derive(Eq, PartialEq, Debug, Default)]
14pub enum TargetResolutionUpdate {
15    Fixed,
16    #[default]
17    WindowSize,
18}
19
20#[derive(Default)]
21pub struct TargetResolution {
22    pub width: u32,
23    pub height: u32,
24    pub update_mode: TargetResolutionUpdate,
25}
26
27#[derive(Eq, PartialEq, Debug)]
28pub enum CamMode {
29    Rotation,
30    Translation,
31}
32
33/// Component usually used on camera or lights. Defines a position and a lookat.
34/// This can be used to convert to a `view_matrix`
35#[derive(Clone)]
36pub struct PosLookat {
37    pub position: na::Point3<f32>, //position in world coordinates
38    pub lookat: na::Point3<f32>,
39    pub up: na::Vector3<f32>,
40}
41
42// #[derive(Default)]
43pub enum Projection {
44    // #[default]
45    WithFov(ProjectionWithFov),
46    WithIntrinsics(ProjectionWithIntrinsics),
47}
48
49/// Component usually used on camera on lights. Defines a projection matrix
50#[derive(Clone)]
51pub struct ProjectionWithFov {
52    pub aspect_ratio: f32,
53    pub fovy: f32, //radians
54    pub near: f32,
55    pub far: f32,
56}
57
58#[derive(Clone)]
59pub struct ProjectionWithIntrinsics {
60    pub fx: f32,
61    pub fy: f32, //radians
62    pub cx: f32,
63    pub cy: f32,
64    // pub height: f32,
65    // pub width: f32,
66    pub near: f32,
67    pub far: f32,
68}
69
70/// Component usually used on camera, allows to keep track of camera state while
71/// handling mouse events
72#[allow(clippy::struct_excessive_bools)]
73pub struct CamController {
74    pub mouse_mode: CamMode,
75    pub mouse_pressed: bool,
76    pub prev_mouse_pos_valid: bool,
77    pub prev_mouse: na::Vector2<f32>,
78    pub limit_max_dist: Option<f32>,
79    pub limit_max_vertical_angle: Option<f32>,
80    pub limit_min_vertical_angle: Option<f32>,
81    pub id2active_touches: HashMap<u64, Touch>, /* when we have multiple touch events each fingers gets an unique id. This maps from the unique id
82                                                 * to an "active" or touching finger */
83    // Store some parameters for camera interaction
84    pub cursor_position: Option<winit::dpi::PhysicalPosition<f64>>,
85    pub last_press: Option<Instant>,
86    pub is_last_press_click: bool,
87    pub mouse_moved_while_pressed: bool,
88}
89
90//implementations
91//PosLookAt
92impl Default for PosLookat {
93    fn default() -> Self {
94        Self {
95            position: na::Point3::<f32>::new(0.0, 1.0, 3.0),
96            lookat: na::Point3::<f32>::new(0.0, 0.0, 0.0),
97            up: na::Vector3::<f32>::new(0.0, 1.0, 0.0),
98        }
99    }
100}
101impl PosLookat {
102    pub fn new(position: na::Point3<f32>, lookat: na::Point3<f32>) -> Self {
103        Self {
104            position,
105            lookat,
106            ..Default::default()
107        }
108    }
109
110    /// Initializes from a model matrix and a distance to lookat. Assumes the up
111    /// vector is (0,1,0)
112    pub fn new_from_model_matrix(model_matrix: na::SimilarityMatrix3<f32>, dist_lookat: f32) -> Self {
113        let position = model_matrix.isometry.translation.vector;
114        let mat = model_matrix.isometry.to_matrix();
115        let rot: na::Matrix3<f32> = mat.fixed_view::<3, 3>(0, 0).into();
116        let axis_lookat = rot.column(1); //Y axis
117        let lookat = position + axis_lookat * dist_lookat;
118
119        let position = na::Point3::<f32>::from(position);
120        let lookat = na::Point3::<f32>::from(lookat);
121
122        Self {
123            position,
124            lookat,
125            up: na::Vector3::<f32>::new(0.0, 1.0, 0.0),
126        }
127    }
128
129    /// Get view matrix as a mat4x4. View matrix maps from world to camera
130    /// coordinates
131    pub fn view_matrix(&self) -> na::Matrix4<f32> {
132        self.view_matrix_isometry().to_matrix()
133    }
134
135    /// Get view matrix as a isometry matrix. View matrix maps from world to
136    /// camera coordinates. Isometry matrix allows for faster inverse than a
137    /// mat4x4.
138    pub fn view_matrix_isometry(&self) -> na::IsometryMatrix3<f32> {
139        na::IsometryMatrix3::<f32>::look_at_rh(&self.position, &self.lookat, &self.up)
140    }
141
142    /// Direction in which we are looking at in world coordinates
143    pub fn direction(&self) -> na::Vector3<f32> {
144        (self.lookat - self.position).normalize()
145    }
146
147    /// Get ``model_matrix`` as a isometry matrix. Model matrix maps from camera
148    /// coordinates to world coordinates. Isometry matrix allows for faster
149    /// inverse than a mat4x4
150    pub fn model_matrix_isometry(&self) -> na::IsometryMatrix3<f32> {
151        self.view_matrix_isometry().inverse()
152    }
153
154    /// Get ``model_matrix`` matrix as a isometry matrix. Model matrix maps from
155    /// camera coordinates to world coordinates.
156    pub fn model_matrix(&self) -> na::Matrix4<f32> {
157        self.model_matrix_isometry().to_matrix()
158    }
159
160    /// Cam axes as columns of a 3x3 matrix. The columns represent a right
161    /// handed coordinate system where x is towards right, y is up and z is
162    /// outwards from the screen.
163    pub fn cam_axes(&self) -> na::Matrix3<f32> {
164        let model_matrix = self.model_matrix();
165        let rot = model_matrix.fixed_view::<3, 3>(0, 0);
166        rot.into()
167    }
168
169    /// Moves the camera along the direction of lookat
170    pub fn dolly(&mut self, s: f32) {
171        let eye_look_vec = self.lookat - self.position; //just a vector from eye to lookat
172        let movement = eye_look_vec * s;
173        self.position += movement;
174    }
175
176    /// Rotates the camera around the lookat point.
177    pub fn orbit(&mut self, rot: na::Rotation3<f32>) {
178        //we apply rotations around the lookat point so we have to substract, apply
179        // rotation and then add back the lookat point
180        let model_matrix = self.model_matrix_isometry();
181
182        let trans_to_look_at = na::Translation3::from(-self.lookat);
183        let trans_back = na::Translation3::from(self.lookat);
184
185        let model_matrix_rotated = trans_back * rot * trans_to_look_at * model_matrix;
186
187        //set the new position
188        self.position = model_matrix_rotated.translation.vector.into();
189
190        //fix the issue of the camera rotation above the object so that it becomes
191        // upside down. When the camera is upside down then it's up vector shouldn't be
192        // 0,1,0 anymore but rather 0,-1,0. This is because look_at_rh does a cross
193        // product to get the Right vector and using 0,1,0 as up vector would mean that
194        // we flip the right vector. Here we check if we are upside down and flip the up
195        // vector accordingly.
196        let model_matrix_rotated_mat = model_matrix_rotated.to_matrix();
197        let cam_axes_after = model_matrix_rotated_mat.fixed_view::<3, 3>(0, 0);
198        let up_cam_axis = cam_axes_after.column(1);
199        let dot_up = self.up.dot(&up_cam_axis);
200        if dot_up < 0.0 {
201            self.up = -self.up;
202        }
203    }
204
205    //convenience function to rotate just around the y axis by x degrees
206    pub fn orbit_y(&mut self, degrees: f32) {
207        let axis = na::Vector3::y_axis();
208        let rot = na::Rotation3::from_axis_angle(&axis, degrees.to_radians());
209
210        self.orbit(rot);
211    }
212
213    /// Moves the camera position to be at a new position and also rigidly
214    /// shifts the lookat point, without rotating camera
215    pub fn shift_cam(&mut self, pos: na::Point3<f32>) {
216        let displacement = pos - self.position;
217        self.position += displacement;
218        self.lookat += displacement;
219    }
220
221    /// Moves the lookat at a new position and also rigidly shifts the cam
222    /// point, without rotating camera
223    pub fn shift_lookat(&mut self, pos: na::Point3<f32>) {
224        let displacement = pos - self.lookat;
225        self.position += displacement;
226        self.lookat += displacement;
227    }
228
229    /// Returns the distance from camera to the lookat point
230    pub fn dist_lookat(&self) -> f32 {
231        (self.position - self.lookat).norm()
232    }
233}
234
235impl Default for Projection {
236    fn default() -> Self {
237        Self::WithFov(ProjectionWithFov::default())
238    }
239}
240impl Projection {
241    /// # Panics
242    /// Will panic if the ``Projection`` component does not exist for this
243    /// entity
244    pub fn proj_matrix(&self, width: u32, height: u32) -> na::Matrix4<f32> {
245        match self {
246            Projection::WithFov(proj) => proj.proj_matrix(),
247            Projection::WithIntrinsics(proj) => proj.proj_matrix(width, height),
248        }
249    }
250    /// # Panics
251    /// Will panic if the ``Projection`` component does not exist for this
252    /// entity
253    pub fn proj_matrix_reverse_z(&self, width: u32, height: u32) -> na::Matrix4<f32> {
254        match self {
255            Projection::WithFov(proj) => proj.proj_matrix_reverse_z(),
256            Projection::WithIntrinsics(proj) => proj.proj_matrix_reverse_z(width, height),
257        }
258    }
259    pub fn near_far(&self) -> (f32, f32) {
260        match self {
261            Projection::WithFov(proj) => (proj.near, proj.far),
262            Projection::WithIntrinsics(proj) => (proj.near, proj.far),
263        }
264    }
265    pub fn set_near(&mut self, val: f32) {
266        match self {
267            Projection::WithFov(ref mut proj) => proj.near = val,
268            Projection::WithIntrinsics(ref mut proj) => proj.near = val,
269        }
270    }
271    pub fn set_far(&mut self, val: f32) {
272        match self {
273            Projection::WithFov(ref mut proj) => proj.far = val,
274            Projection::WithIntrinsics(ref mut proj) => proj.far = val,
275        }
276    }
277}
278
279//ProjectionWithFov
280impl Default for ProjectionWithFov {
281    fn default() -> Self {
282        Self {
283            aspect_ratio: 1.6,
284            fovy: 0.7, //radians
285            near: 0.01,
286            far: 100.0,
287        }
288    }
289}
290impl ProjectionWithFov {
291    // right hand perspective-view frustum with a depth range of 0 to 1
292    // https://github.com/toji/gl-matrix/issues/369
293    pub fn proj_matrix(&self) -> na::Matrix4<f32> {
294        glm::perspective_rh_zo(self.aspect_ratio, self.fovy, self.near, self.far)
295    }
296    /// Creates an infinite reverse right-handed perspective projection matrix
297    /// with `[0,1]` depth range
298    /// <https://docs.rs/glam/latest/src/glam/f32/sse2/mat4.rs.html#969-982>
299    /// <https://github.com/bevyengine/bevy/blob/main/crates/bevy_render/src/camera/projection.rs#L172>
300    pub fn proj_matrix_reverse_z(&self) -> na::Matrix4<f32> {
301        //infinite Zfar
302        // let mut mat = glm::TMat4::zeros();
303        // let f = 1.0 / (0.5 * self.fovy).tan();
304        // mat[(0, 0)] = f / (self.aspect_ratio);
305        // mat[(1, 1)] = f;
306        // mat[(2, 3)] = self.near;
307        // mat[(3, 2)] = -1.0;
308        // mat
309
310        let mat = self.proj_matrix();
311
312        // let mut depth_remap = glm::TMat4::identity();
313        // depth_remap[(2, 2)] = -1.0;
314        // depth_remap[(2, 3)] = 1.0;
315
316        // depth_remap * mat
317        // let mut mat = self.proj_matrix(width, height);
318
319        let mut depth_remap = glm::TMat4::identity();
320        depth_remap[(2, 2)] = -1.0;
321        depth_remap[(2, 3)] = 1.0;
322
323        depth_remap * mat
324        // mat[(2, 2)] *= -1.0;
325        // mat[(2, 3)] *= -1.0;
326        // mat
327    }
328}
329
330impl ProjectionWithIntrinsics {
331    #[allow(clippy::cast_precision_loss)]
332    pub fn proj_matrix(&self, width: u32, height: u32) -> na::Matrix4<f32> {
333        let mut projection_matrix = na::Matrix4::<f32>::zeros();
334
335        // Calculate the projection matrix with the given fx, fy, and normalised cx, cy
336        projection_matrix[(0, 0)] = 2.0 * self.fx / width as f32;
337        projection_matrix[(1, 1)] = 2.0 * self.fy / height as f32;
338        projection_matrix[(0, 2)] = 1.0 - (2.0 * self.cx / width as f32);
339        projection_matrix[(1, 2)] = (2.0 * self.cy / height as f32) - 1.0;
340        // projection_matrix[(2, 2)] = -(self.far + self.near) / (self.far - self.near);
341        // projection_matrix[(2, 3)] = -2.0 * self.far * self.near / (self.far -
342        // self.near); projection_matrix[(3, 2)] = -1.0;
343        projection_matrix[(2, 2)] = -self.far / (self.far - self.near);
344        projection_matrix[(2, 3)] = -self.far * self.near / (self.far - self.near);
345        projection_matrix[(3, 2)] = -1.0;
346        projection_matrix
347    }
348
349    pub fn proj_matrix_reverse_z(&self, width: u32, height: u32) -> na::Matrix4<f32> {
350        let mat = self.proj_matrix(width, height);
351
352        let mut depth_remap = glm::TMat4::identity();
353        depth_remap[(2, 2)] = -1.0;
354        depth_remap[(2, 3)] = 1.0;
355
356        depth_remap * mat
357        // mat[(2, 2)] *= -1.0;
358        // mat[(2, 3)] *= -1.0;
359        // mat
360    }
361}
362//CamController
363impl Default for CamController {
364    fn default() -> Self {
365        Self {
366            mouse_mode: CamMode::Rotation,
367            mouse_pressed: false,
368            prev_mouse_pos_valid: false,
369            prev_mouse: na::Vector2::<f32>::zeros(),
370            limit_max_dist: None,
371            limit_max_vertical_angle: None,
372            limit_min_vertical_angle: None,
373            id2active_touches: HashMap::new(),
374            cursor_position: None,
375            last_press: None,
376            is_last_press_click: false,
377            mouse_moved_while_pressed: false,
378        }
379    }
380}
381impl CamController {
382    pub fn new(limit_max_dist: Option<f32>, limit_max_vertical_angle: Option<f32>, limit_min_vertical_angle: Option<f32>) -> Self {
383        Self {
384            limit_max_dist,
385            limit_max_vertical_angle,
386            limit_min_vertical_angle,
387            ..Default::default()
388        }
389    }
390    pub fn decide_if_click(&mut self) {
391        if let Some(last_press) = self.last_press {
392            if last_press.elapsed().as_millis() < CLICK_DURATION_MS {
393                //&& !self.mouse_moved_while_pressed {
394                self.is_last_press_click = true;
395            }
396        }
397    }
398}