gloss_renderer/components/
cam_comps.rs

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