1use cgmath::num_traits::ToPrimitive;
2use cgmath::*;
3use core::f32;
4use std::f32::consts::FRAC_PI_2;
5use std::time::Duration;
6use winit::event::*;
7use winit::keyboard::KeyCode;
8use winit::{dpi::PhysicalPosition, keyboard::PhysicalKey};
9
10#[rustfmt::skip]
11pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::from_cols(
12 cgmath::Vector4::new(1.0, 0.0, 0.0, 0.0),
13 cgmath::Vector4::new(0.0, 1.0, 0.0, 0.0),
14 cgmath::Vector4::new(0.0, 0.0, 0.5, 0.0),
15 cgmath::Vector4::new(0.0, 0.0, 0.5, 1.0),
16);
17
18const SAFE_FRAC_PI_2: f32 = FRAC_PI_2 - 0.0001;
19
20fn screen_to_ndc(mouse_x: f32, mouse_y: f32, width: f32, height: f32) -> cgmath::Vector3<f32> {
21 let x = (2.0 * mouse_x / width) - 1.0;
22 let y = 1.0 - (2.0 * mouse_y) / height; let z = 1.0;
24 Vector3::new(x, y, z)
25}
26
27#[derive(Debug)]
28pub struct Ray {
29 pub origin: Point3<f32>,
30 pub direction: Vector3<f32>,
31}
32
33impl Ray {
35 pub fn intersect_with_floor(&self) -> Option<Point2<f32>> {
41 if self.direction.y.abs() < f32::EPSILON {
42 return None;
43 }
44 let t = -self.origin.y / self.direction.y;
45 if t < 0.0 {
46 return None;
47 }
48 let intersection_point = self.origin + self.direction * t;
49 Some(Point2::new(intersection_point.x, intersection_point.z))
50 }
51}
52
53#[repr(C)]
54#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
55pub struct CameraUniform {
56 view_position: [f32; 4],
57 view_proj: [[f32; 4]; 4],
58}
59
60impl CameraUniform {
61 pub fn new() -> Self {
62 Self {
63 view_position: [0.0; 4],
64 view_proj: cgmath::Matrix4::identity().into(),
65 }
66 }
67
68 pub fn update_view_proj(&mut self, camera: &Camera, projection: &Projection) {
69 self.view_position = camera.position.to_homogeneous().into();
70 self.view_proj = (projection.calc_matrix() * camera.calc_matrix()).into();
71 }
72}
73
74#[derive(Debug, Clone)]
75pub struct Camera {
76 pub position: Point3<f32>,
77 yaw: Rad<f32>,
78 pitch: Rad<f32>,
79}
80
81impl Camera {
82 pub fn new<V: Into<Point3<f32>>, Y: Into<Rad<f32>>, P: Into<Rad<f32>>>(
83 position: V,
84 yaw: Y,
85 pitch: P,
86 ) -> Self {
87 Self {
88 position: position.into(),
89 yaw: yaw.into(),
90 pitch: pitch.into(),
91 }
92 }
93
94 pub fn calc_matrix(&self) -> Matrix4<f32> {
95 let (sin_pitch, cos_pitch) = self.pitch.0.sin_cos();
96 let (sin_yaw, cos_yaw) = self.yaw.0.sin_cos();
97
98 Matrix4::look_to_rh(
99 self.position,
100 Vector3::new(cos_pitch * cos_yaw, sin_pitch, cos_pitch * sin_yaw).normalize(),
101 Vector3::unit_y(),
102 )
103 }
104
105 pub fn cast_ray_from_mouse(
109 &self,
110 position: PhysicalPosition<f64>,
111 width: f32,
112 height: f32,
113 projection: &Projection,
114 ) -> Ray {
115 let (mouse_x, mouse_y) = position.into();
116 let ndc = screen_to_ndc(mouse_x, mouse_y, width, height);
117
118 let clip = cgmath::Vector4::new(ndc.x, ndc.y, 1.0, 1.0);
119
120 let inv_proj_view = (projection.calc_matrix() * self.calc_matrix())
121 .invert()
122 .unwrap();
123
124 let mut world_coords = inv_proj_view * clip;
125
126 world_coords /= world_coords.w;
127 let world_point = Point3::new(world_coords.x, world_coords.y, world_coords.z);
129
130 let ray_origin = self.position;
131 let ray_direction = (world_point - ray_origin).normalize();
132
133 Ray {
134 origin: ray_origin,
135 direction: ray_direction,
136 }
137 }
138}
139
140#[derive(Debug)]
141pub struct Projection {
142 aspect: f32,
143 fovy: Rad<f32>,
144 pub znear: f32,
145 pub zfar: f32,
146}
147
148impl Projection {
149 pub fn new<F: Into<Rad<f32>>>(
150 width: u32,
151 height: u32,
152 fovy: F,
153 znear: f32,
154 zfar: f32,
155 ) -> Result<Self, anyhow::Error> {
156 let width = width.to_f32().ok_or(anyhow::anyhow!(
157 "Width value {} is too large to be represented as f32.",
158 width
159 ))?;
160 let height = height.to_f32().ok_or(
161 anyhow::anyhow!(
162 "Height value {} is too large to be represented as f32.",
163 height
164 )
165 )?;
166 let aspect = width / height;
167 Ok(Self {
168 aspect,
169 fovy: fovy.into(),
170 znear,
171 zfar,
172 })
173 }
174
175 pub fn resize(&mut self, width: u32, height: u32) {
176 let width = width.to_f32().unwrap_or(f32::MAX);
177 let height = height.to_f32().unwrap_or(f32::MAX);
178 self.aspect = width as f32 / height as f32;
179 }
180
181 pub fn calc_matrix(&self) -> Matrix4<f32> {
182 OPENGL_TO_WGPU_MATRIX * perspective(self.fovy, self.aspect, self.znear, self.zfar)
183 }
184}
185
186#[derive(Debug, Clone)]
187pub struct CameraController {
188 amount_left: f32,
189 amount_right: f32,
190 amount_forward: f32,
191 amount_backward: f32,
192 amount_up: f32,
193 amount_down: f32,
194 rotate_horizontal: f32,
195 rotate_vertical: f32,
196 scroll: f32,
197 speed: f32,
198 sensitivity: f32,
199}
200
201impl CameraController {
202 pub fn new(speed: f32, sensitivity: f32) -> Self {
203 Self {
204 amount_left: 0.0,
205 amount_right: 0.0,
206 amount_forward: 0.0,
207 amount_backward: 0.0,
208 amount_up: 0.0,
209 amount_down: 0.0,
210 rotate_horizontal: 0.0,
211 rotate_vertical: 0.0,
212 scroll: 0.0,
213 speed,
214 sensitivity,
215 }
216 }
217
218 pub fn handle_window_events(&mut self, event: &WindowEvent) -> bool {
219 if let WindowEvent::KeyboardInput {
220 event:
221 KeyEvent {
222 physical_key: PhysicalKey::Code(key),
223 state: key_state,
224 ..
225 },
226 ..
227 } = event
228 {
229 let amount = if key_state.is_pressed() { 1.0 } else { 0.0 };
230 match key {
231 KeyCode::KeyW | KeyCode::ArrowUp => {
232 self.amount_forward = amount;
233 true
234 }
235 KeyCode::KeyS | KeyCode::ArrowDown => {
236 self.amount_backward = amount;
237 true
238 }
239 KeyCode::KeyA | KeyCode::ArrowLeft => {
240 self.amount_left = amount;
241 true
242 }
243 KeyCode::KeyD | KeyCode::ArrowRight => {
244 self.amount_right = amount;
245 true
246 }
247 KeyCode::Space => {
248 self.amount_up = amount;
249 true
250 }
251 KeyCode::ShiftLeft => {
252 self.amount_down = amount;
253 true
254 }
255 _ => false,
256 }
257 } else {
258 false
259 }
260 }
261
262 pub fn handle_mouse(&mut self, mouse_dx: f64, mouse_dy: f64) {
263 let dx = mouse_dx as f32;
264 let dy = mouse_dy as f32;
265 if dx.is_finite() && dy.is_finite() {
267 self.rotate_horizontal = dx;
268 self.rotate_vertical = dy;
269 } else {
270 log::warn!(
271 "Mouse coordinates of ({}, {}) are out of bounds and are not updated. The maximum supported coordinate value is {}.",
272 mouse_dx,
273 mouse_dy,
274 f32::MAX
275 );
276 }
277 }
278
279 pub fn handle_scroll(&mut self, delta: &MouseScrollDelta) {
280 self.scroll = match delta {
281 MouseScrollDelta::LineDelta(_, scroll) => -scroll * 0.5,
282 MouseScrollDelta::PixelDelta(PhysicalPosition { y: scroll, .. }) => -*scroll as f32,
283 };
284 }
285
286 pub fn update(&mut self, camera: &mut Camera, dt: Duration) {
287 let dt = dt.as_secs_f32();
288
289 let (yaw_sin, yaw_cos) = camera.yaw.0.sin_cos();
290 let forward = Vector3::new(yaw_cos, 0.0, yaw_sin).normalize();
291 let right = Vector3::new(-yaw_sin, 0.0, yaw_cos).normalize();
292 camera.position += forward * (self.amount_forward - self.amount_backward) * self.speed * dt;
293 camera.position += right * (self.amount_right - self.amount_left) * self.speed * dt;
294
295 let (pitch_sin, pitch_cos) = camera.pitch.0.sin_cos();
300 let scrollward =
301 Vector3::new(pitch_cos * yaw_cos, pitch_sin, pitch_cos * yaw_sin).normalize();
302 camera.position += scrollward * self.scroll * self.speed * self.sensitivity * dt;
303 self.scroll = 0.0;
304
305 camera.position.y += (self.amount_up - self.amount_down) * self.speed * dt;
308
309 camera.yaw += (Rad(self.rotate_horizontal) * self.speed * self.sensitivity * dt) / 10.0;
311 camera.pitch += (Rad(-self.rotate_vertical) * self.speed * self.sensitivity * dt) / 10.0;
312
313 self.rotate_horizontal = 0.0;
317 self.rotate_vertical = 0.0;
318
319 if camera.pitch < -Rad(SAFE_FRAC_PI_2) {
321 camera.pitch = -Rad(SAFE_FRAC_PI_2);
322 } else if camera.pitch > Rad(SAFE_FRAC_PI_2) {
323 camera.pitch = Rad(SAFE_FRAC_PI_2);
324 }
325 }
326}
327
328#[derive(Debug)]
329pub struct CameraResources {
330 pub camera: Camera,
331 pub controller: CameraController,
332 pub uniform: CameraUniform,
333 pub buffer: wgpu::Buffer,
334 pub bind_group: wgpu::BindGroup,
335 pub bind_group_layout: wgpu::BindGroupLayout,
336}