maplibre/render/
camera.rs

1//! Main camera
2
3use cgmath::{num_traits::clamp, prelude::*, *};
4
5use crate::util::SignificantlyDifferent;
6
7#[rustfmt::skip]
8pub const OPENGL_TO_WGPU_MATRIX: Matrix4<f64> = Matrix4::new(
9    1.0, 0.0, 0.0, 0.0,
10    0.0, 1.0, 0.0, 0.0,
11    0.0, 0.0, 0.5, 0.0,
12    0.0, 0.0, 0.5, 1.0,
13);
14
15#[rustfmt::skip]
16pub const FLIP_Y: Matrix4<f64> = Matrix4::new(
17    1.0, 0.0, 0.0, 0.0, 
18    0.0, -1.0, 0.0, 0.0, 
19    0.0, 0.0, 1.0, 0.0, 
20    0.0, 0.0, 0.0, 1.0,
21);
22
23#[derive(Debug, Clone, Copy)]
24pub struct ViewProjection(pub Matrix4<f64>);
25
26impl ViewProjection {
27    #[tracing::instrument(skip_all)]
28    pub fn invert(&self) -> InvertedViewProjection {
29        InvertedViewProjection(self.0.invert().expect("Unable to invert view projection"))
30    }
31
32    pub fn project(&self, vector: Vector4<f64>) -> Vector4<f64> {
33        self.0 * vector
34    }
35
36    #[tracing::instrument(skip_all)]
37    pub fn to_model_view_projection(&self, projection: Matrix4<f64>) -> ModelViewProjection {
38        ModelViewProjection(self.0 * projection)
39    }
40
41    pub fn downcast(&self) -> Matrix4<f32> {
42        self.0
43            .cast::<f32>()
44            .expect("Unable to cast view projection to f32")
45    }
46}
47
48pub struct InvertedViewProjection(Matrix4<f64>);
49
50impl InvertedViewProjection {
51    pub fn project(&self, vector: Vector4<f64>) -> Vector4<f64> {
52        self.0 * vector
53    }
54}
55
56pub struct ModelViewProjection(Matrix4<f64>);
57
58impl ModelViewProjection {
59    pub fn downcast(&self) -> Matrix4<f32> {
60        self.0
61            .cast::<f32>()
62            .expect("Unable to cast view projection to f32")
63    }
64
65    pub fn get(&self) -> Matrix4<f64> {
66        self.0
67    }
68
69    pub fn project(&self, vector: Vector4<f64>) -> Vector4<f64> {
70        self.0 * vector
71    }
72}
73
74const MIN_PITCH: Deg<f64> = Deg(-30.0);
75const MAX_PITCH: Deg<f64> = Deg(30.0);
76
77const MIN_YAW: Deg<f64> = Deg(-30.0);
78const MAX_YAW: Deg<f64> = Deg(30.0);
79
80#[derive(Debug, Clone)]
81pub struct Camera {
82    position: Point2<f64>,
83    yaw: Rad<f64>,
84    pitch: Rad<f64>,
85    roll: Rad<f64>,
86}
87
88impl SignificantlyDifferent for Camera {
89    type Epsilon = f64;
90
91    fn ne(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
92        self.position.abs_diff_ne(&other.position, epsilon)
93            || self.yaw.abs_diff_ne(&other.yaw, epsilon)
94            || self.pitch.abs_diff_ne(&other.pitch, epsilon)
95            || self.roll.abs_diff_ne(&other.roll, epsilon)
96    }
97}
98
99impl Camera {
100    pub fn new<V: Into<Point2<f64>>, Y: Into<Rad<f64>>, P: Into<Rad<f64>>>(
101        position: V,
102        yaw: Y,
103        pitch: P,
104    ) -> Self {
105        Self {
106            position: position.into(),
107            yaw: yaw.into(),
108            pitch: pitch.into(),
109            roll: Rad::zero(), // TODO: initialize
110        }
111    }
112
113    pub fn calc_matrix(&self, camera_height: f64) -> Matrix4<f64> {
114        Matrix4::from_translation(Vector3::new(0.0, 0.0, -camera_height))
115            * Matrix4::from_angle_x(self.pitch)
116            * Matrix4::from_angle_y(self.yaw)
117            * Matrix4::from_angle_z(self.roll)
118            * Matrix4::from_translation(Vector3::new(-self.position.x, -self.position.y, 0.0))
119    }
120
121    pub fn position(&self) -> Point2<f64> {
122        self.position
123    }
124
125    pub fn get_yaw(&self) -> Rad<f64> {
126        self.yaw
127    }
128
129    pub fn yaw<P: Into<Rad<f64>>>(&mut self, delta: P) {
130        let new_yaw = self.yaw + delta.into();
131
132        if new_yaw <= MAX_YAW.into() && new_yaw >= MIN_YAW.into() {
133            self.yaw = new_yaw;
134        }
135    }
136
137    pub fn get_roll(&self) -> Rad<f64> {
138        self.roll
139    }
140
141    pub fn roll<P: Into<Rad<f64>>>(&mut self, delta: P) {
142        self.roll += delta.into();
143    }
144
145    pub fn get_pitch(&self) -> Rad<f64> {
146        self.pitch
147    }
148
149    pub fn pitch<P: Into<Rad<f64>>>(&mut self, delta: P) {
150        let new_pitch = self.pitch + delta.into();
151
152        if new_pitch <= MAX_PITCH.into() && new_pitch >= MIN_PITCH.into() {
153            self.pitch = new_pitch;
154        }
155    }
156
157    pub fn move_relative(&mut self, delta: Vector2<f64>) {
158        self.position += delta;
159    }
160
161    pub fn move_to(&mut self, new_position: Point2<f64>) {
162        self.position = new_position;
163    }
164
165    pub fn position_vector(&self) -> Vector2<f64> {
166        self.position.to_vec()
167    }
168
169    pub fn to_3d(&self, camera_height: f64) -> Point3<f64> {
170        Point3::new(self.position.x, self.position.y, camera_height)
171    }
172    pub fn set_yaw<P: Into<Rad<f64>>>(&mut self, yaw: P) {
173        let new_yaw = yaw.into();
174        let max: Rad<_> = MAX_YAW.into();
175        let min: Rad<_> = MIN_YAW.into();
176        self.yaw = Rad(new_yaw.0.min(max.0).max(min.0))
177    }
178    pub fn set_pitch<P: Into<Rad<f64>>>(&mut self, pitch: P) {
179        let new_pitch = pitch.into();
180        let max: Rad<_> = MAX_PITCH.into();
181        let min: Rad<_> = MIN_PITCH.into();
182        self.pitch = Rad(new_pitch.0.min(max.0).max(min.0))
183    }
184    pub fn set_roll<P: Into<Rad<f64>>>(&mut self, roll: P) {
185        self.roll = roll.into();
186    }
187}
188
189#[derive(PartialEq, Copy, Clone, Default)]
190pub struct EdgeInsets {
191    pub top: f64,
192    pub bottom: f64,
193    pub left: f64,
194    pub right: f64,
195}
196
197impl EdgeInsets {
198    /**
199     * Utility method that computes the new apprent center or vanishing point after applying insets.
200     * This is in pixels and with the top left being (0.0) and +y being downwards.
201     *
202     * @param {number} width the width
203     * @param {number} height the height
204     * @returns {Point} the point
205     * @memberof EdgeInsets
206     */
207    pub fn center(&self, width: f64, height: f64) -> Point2<f64> {
208        // Clamp insets so they never overflow width/height and always calculate a valid center
209        let x = clamp((self.left + width - self.right) / 2.0, 0.0, width);
210        let y = clamp((self.top + height - self.bottom) / 2.0, 0.0, height);
211
212        return Point2::new(x, y);
213    }
214}
215
216#[derive(Clone)]
217pub struct Perspective {
218    fovy: Rad<f64>,
219}
220
221impl Perspective {
222    pub fn new<F: Into<Rad<f64>>>(fovy: F) -> Self {
223        let rad = fovy.into();
224        Self { fovy: rad }
225    }
226
227    pub fn fovy(&self) -> Rad<f64> {
228        self.fovy
229    }
230    pub fn fovx(&self, width: f64, height: f64) -> Rad<f64> {
231        let aspect = width / height;
232        Rad(2.0 * ((self.fovy / 2.0).tan() * aspect).atan())
233    }
234
235    pub fn y_tan(&self) -> f64 {
236        let half_fovy = self.fovy / 2.0;
237        half_fovy.tan()
238    }
239    pub fn x_tan(&self, width: f64, height: f64) -> f64 {
240        let half_fovx = self.fovx(width, height) / 2.0;
241        half_fovx.tan()
242    }
243
244    pub fn offset_x(&self, center_offset: Point2<f64>, width: f64) -> f64 {
245        center_offset.x * 2.0 / width
246    }
247
248    pub fn offset_y(&self, center_offset: Point2<f64>, height: f64) -> f64 {
249        center_offset.y * 2.0 / height
250    }
251
252    pub fn calc_matrix(&self, aspect: f64, near_z: f64, far_z: f64) -> Matrix4<f64> {
253        perspective(self.fovy, aspect, near_z, far_z)
254    }
255
256    pub fn calc_matrix_with_center(
257        &self,
258        width: f64,
259        height: f64,
260        near_z: f64,
261        far_z: f64,
262        center_offset: Point2<f64>,
263    ) -> Matrix4<f64> {
264        let ymax = near_z * self.y_tan();
265
266        //TODO maybe just: let xmax = ymax * aspect;
267        let xmax = near_z * self.x_tan(width, height);
268
269        let offset_x = self.offset_x(center_offset, width);
270        let offset_y = self.offset_y(center_offset, height);
271        frustum(
272            // https://webglfundamentals.org/webgl/lessons/webgl-qna-how-can-i-move-the-perspective-vanishing-point-from-the-center-of-the-canvas-.html
273            xmax * (-1.0 + offset_x), /* = -xmax + (center_offset.x * screen_to_near_factor_x)
274                                                 where:
275                                                  screen_to_near_factor_x = near_width / width
276                                                  where:
277                                                    near_width = xmax * 2.0
278                                      */
279            xmax * (1.0 + offset_x),
280            ymax * (-1.0 + offset_y),
281            ymax * (1.0 + offset_y),
282            near_z,
283            far_z,
284        )
285    }
286}