1use 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(), }
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 pub fn center(&self, width: f64, height: f64) -> Point2<f64> {
208 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 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 xmax * (-1.0 + offset_x), 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}