1use glam::{Mat4, Vec2, Vec3, Vec4};
31
32#[derive(Debug, Clone, Copy, PartialEq)]
34pub enum ProjectionMode {
35 Orthographic {
37 left: f32,
38 right: f32,
39 bottom: f32,
40 top: f32,
41 near: f32,
42 far: f32,
43 },
44 Perspective {
46 fov_y_radians: f32,
47 aspect_ratio: f32,
48 near: f32,
49 far: f32,
50 },
51}
52
53pub struct Camera {
55 position: Vec3,
57 target: Vec3,
59 up: Vec3,
61 projection: ProjectionMode,
63 view_matrix: Mat4,
65 projection_matrix: Mat4,
67 view_projection_matrix: Mat4,
69 dirty: bool,
71}
72
73impl Camera {
74 pub fn orthographic(width: f32, height: f32, near: f32, far: f32) -> Self {
83 let half_width = width / 2.0;
84 let half_height = height / 2.0;
85
86 let projection = ProjectionMode::Orthographic {
87 left: -half_width,
88 right: half_width,
89 bottom: -half_height,
90 top: half_height,
91 near,
92 far,
93 };
94
95 let mut camera = Self {
96 position: Vec3::new(0.0, 0.0, 1.0),
97 target: Vec3::ZERO,
98 up: Vec3::Y,
99 projection,
100 view_matrix: Mat4::IDENTITY,
101 projection_matrix: Mat4::IDENTITY,
102 view_projection_matrix: Mat4::IDENTITY,
103 dirty: true,
104 };
105
106 camera.update_matrices();
107 camera
108 }
109
110 pub fn orthographic_custom(
112 left: f32,
113 right: f32,
114 bottom: f32,
115 top: f32,
116 near: f32,
117 far: f32,
118 ) -> Self {
119 let projection = ProjectionMode::Orthographic {
120 left,
121 right,
122 bottom,
123 top,
124 near,
125 far,
126 };
127
128 let mut camera = Self {
129 position: Vec3::new(0.0, 0.0, 1.0),
130 target: Vec3::ZERO,
131 up: Vec3::Y,
132 projection,
133 view_matrix: Mat4::IDENTITY,
134 projection_matrix: Mat4::IDENTITY,
135 view_projection_matrix: Mat4::IDENTITY,
136 dirty: true,
137 };
138
139 camera.update_matrices();
140 camera
141 }
142
143 pub fn perspective(fov_y_degrees: f32, aspect_ratio: f32, near: f32, far: f32) -> Self {
152 let projection = ProjectionMode::Perspective {
153 fov_y_radians: fov_y_degrees.to_radians(),
154 aspect_ratio,
155 near,
156 far,
157 };
158
159 let mut camera = Self {
160 position: Vec3::new(0.0, 5.0, 10.0),
161 target: Vec3::ZERO,
162 up: Vec3::Y,
163 projection,
164 view_matrix: Mat4::IDENTITY,
165 projection_matrix: Mat4::IDENTITY,
166 view_projection_matrix: Mat4::IDENTITY,
167 dirty: true,
168 };
169
170 camera.update_matrices();
171 camera
172 }
173
174 pub fn look_at(&mut self, eye: Vec3, target: Vec3, up: Vec3) {
176 self.position = eye;
177 self.target = target;
178 self.up = up;
179 self.dirty = true;
180 }
181
182 pub fn set_position(&mut self, position: Vec3) {
184 self.position = position;
185 self.dirty = true;
186 }
187
188 pub fn position(&self) -> Vec3 {
190 self.position
191 }
192
193 pub fn set_target(&mut self, target: Vec3) {
195 self.target = target;
196 self.dirty = true;
197 }
198
199 pub fn target(&self) -> Vec3 {
201 self.target
202 }
203
204 pub fn set_up(&mut self, up: Vec3) {
206 self.up = up;
207 self.dirty = true;
208 }
209
210 pub fn up(&self) -> Vec3 {
212 self.up
213 }
214
215 pub fn forward(&self) -> Vec3 {
217 (self.target - self.position).normalize()
218 }
219
220 pub fn right(&self) -> Vec3 {
222 self.forward().cross(self.up).normalize()
223 }
224
225 pub fn set_projection(&mut self, projection: ProjectionMode) {
227 self.projection = projection;
228 self.dirty = true;
229 }
230
231 pub fn projection(&self) -> ProjectionMode {
233 self.projection
234 }
235
236 pub fn set_aspect_ratio(&mut self, aspect_ratio: f32) {
238 if let ProjectionMode::Perspective {
239 fov_y_radians,
240 near,
241 far,
242 ..
243 } = self.projection
244 {
245 self.projection = ProjectionMode::Perspective {
246 fov_y_radians,
247 aspect_ratio,
248 near,
249 far,
250 };
251 self.dirty = true;
252 }
253 }
254
255 pub fn view_matrix(&mut self) -> Mat4 {
257 if self.dirty {
258 self.update_matrices();
259 }
260 self.view_matrix
261 }
262
263 pub fn projection_matrix(&mut self) -> Mat4 {
265 if self.dirty {
266 self.update_matrices();
267 }
268 self.projection_matrix
269 }
270
271 pub fn view_projection_matrix(&mut self) -> Mat4 {
273 if self.dirty {
274 self.update_matrices();
275 }
276 self.view_projection_matrix
277 }
278
279 pub fn screen_to_world(&mut self, screen_pos: Vec2, viewport_size: Vec2, depth: f32) -> Vec3 {
291 let ndc_x = (screen_pos.x / viewport_size.x) * 2.0 - 1.0;
293 let ndc_y = 1.0 - (screen_pos.y / viewport_size.y) * 2.0; let ndc = Vec4::new(ndc_x, ndc_y, depth, 1.0);
295
296 let view_proj = self.view_projection_matrix();
298 let inv_view_proj = view_proj.inverse();
299 let world = inv_view_proj * ndc;
300
301 Vec3::new(world.x / world.w, world.y / world.w, world.z / world.w)
303 }
304
305 pub fn world_to_screen(&mut self, world_pos: Vec3, viewport_size: Vec2) -> (Vec2, f32) {
316 let view_proj = self.view_projection_matrix();
317 let clip = view_proj * Vec4::new(world_pos.x, world_pos.y, world_pos.z, 1.0);
318
319 let ndc = Vec3::new(clip.x / clip.w, clip.y / clip.w, clip.z / clip.w);
321
322 let screen_x = (ndc.x + 1.0) * 0.5 * viewport_size.x;
324 let screen_y = (1.0 - ndc.y) * 0.5 * viewport_size.y; (Vec2::new(screen_x, screen_y), ndc.z)
327 }
328
329 fn update_matrices(&mut self) {
331 self.view_matrix = Mat4::look_at_rh(self.position, self.target, self.up);
333
334 self.projection_matrix = match self.projection {
336 ProjectionMode::Orthographic {
337 left,
338 right,
339 bottom,
340 top,
341 near,
342 far,
343 } => Mat4::orthographic_rh(left, right, bottom, top, near, far),
344 ProjectionMode::Perspective {
345 fov_y_radians,
346 aspect_ratio,
347 near,
348 far,
349 } => Mat4::perspective_rh(fov_y_radians, aspect_ratio, near, far),
350 };
351
352 self.view_projection_matrix = self.projection_matrix * self.view_matrix;
354
355 self.dirty = false;
356 }
357}
358
359#[repr(C)]
372#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
373pub struct CameraUniform {
374 pub view_proj: [[f32; 4]; 4],
376 pub view: [[f32; 4]; 4],
378 pub projection: [[f32; 4]; 4],
380 pub position: [f32; 3],
382 pub _padding: f32,
384}
385
386impl CameraUniform {
387 pub fn from_camera(camera: &mut Camera) -> Self {
389 Self {
390 view_proj: camera.view_projection_matrix().to_cols_array_2d(),
391 view: camera.view_matrix().to_cols_array_2d(),
392 projection: camera.projection_matrix().to_cols_array_2d(),
393 position: camera.position().to_array(),
394 _padding: 0.0,
395 }
396 }
397}
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402
403 #[test]
404 fn test_orthographic_camera() {
405 let mut camera = Camera::orthographic(800.0, 600.0, 0.1, 100.0);
406 camera.look_at(Vec3::ZERO, Vec3::new(0.0, 0.0, -1.0), Vec3::Y);
407
408 let view_proj = camera.view_projection_matrix();
409 assert!(!view_proj.is_nan());
410 }
411
412 #[test]
413 fn test_perspective_camera() {
414 let mut camera = Camera::perspective(60.0, 16.0 / 9.0, 0.1, 100.0);
415 camera.look_at(Vec3::new(0.0, 5.0, 10.0), Vec3::ZERO, Vec3::Y);
416
417 let view_proj = camera.view_projection_matrix();
418 assert!(!view_proj.is_nan());
419 }
420
421 #[test]
422 fn test_screen_to_world() {
423 let mut camera = Camera::orthographic(800.0, 600.0, 0.1, 100.0);
424 camera.look_at(Vec3::new(0.0, 0.0, 1.0), Vec3::ZERO, Vec3::Y);
425
426 let world_pos =
427 camera.screen_to_world(Vec2::new(400.0, 300.0), Vec2::new(800.0, 600.0), 0.0);
428
429 assert!((world_pos.x.abs()) < 0.1);
431 assert!((world_pos.y.abs()) < 0.1);
432 }
433
434 #[test]
435 fn test_world_to_screen() {
436 let mut camera = Camera::orthographic(800.0, 600.0, 0.1, 100.0);
437 camera.look_at(Vec3::new(0.0, 0.0, 1.0), Vec3::ZERO, Vec3::Y);
438
439 let (screen_pos, _depth) = camera.world_to_screen(Vec3::ZERO, Vec2::new(800.0, 600.0));
440
441 assert!((screen_pos.x - 400.0).abs() < 1.0);
443 assert!((screen_pos.y - 300.0).abs() < 1.0);
444 }
445}