1extern crate nalgebra as na;
2extern crate nalgebra_glm as glm;
3
4use log::warn;
5use winit::{
6 dpi::PhysicalPosition,
7 event::{MouseButton, MouseScrollDelta, Touch},
8};
9
10use crate::{
11 components::{CamController, CamMode, PosLookat, Projection, ProjectionWithIntrinsics, TargetResolution, TargetResolutionUpdate},
12 scene::Scene,
13};
14use gloss_hecs::Entity;
15
16#[repr(C)]
21pub struct Camera {
22 pub entity: Entity,
23}
24
25impl Camera {
26 #[allow(clippy::missing_panics_doc)] pub fn new(name: &str, scene: &mut Scene, initialize: bool) -> Self {
29 let entity = scene
30 .get_or_create_hidden_entity(name)
31 .insert(CamController::default())
32 .insert(TargetResolution::default())
33 .entity();
34 if initialize {
35 scene.world.insert_one(entity, PosLookat::default()).ok();
36 scene.world.insert_one(entity, Projection::default()).ok();
37 }
38 Self { entity }
39 }
40
41 pub fn from_entity(entity: Entity) -> Self {
42 Self { entity }
43 }
44
45 #[allow(clippy::missing_panics_doc)]
46 pub fn is_initialized(&self, scene: &Scene) -> bool {
47 scene.world.has::<PosLookat>(self.entity).unwrap()
48 && (scene.world.has::<Projection>(self.entity).unwrap() || scene.world.has::<ProjectionWithIntrinsics>(self.entity).unwrap())
49 }
50
51 pub fn view_matrix(&self, scene: &Scene) -> na::Matrix4<f32> {
54 let pos_lookat = scene.get_comp::<&PosLookat>(&self.entity).unwrap();
55 pos_lookat.view_matrix()
56 }
57
58 pub fn proj_matrix(&self, scene: &Scene) -> na::Matrix4<f32> {
62 let proj = scene.get_comp::<&Projection>(&self.entity).unwrap();
63 let (width, height) = self.get_target_res(scene);
64 proj.proj_matrix(width, height)
65 }
66
67 pub fn proj_matrix_reverse_z(&self, scene: &Scene) -> na::Matrix4<f32> {
71 let proj = scene.get_comp::<&Projection>(&self.entity).unwrap();
72 let (width, height) = self.get_target_res(scene);
73 proj.proj_matrix_reverse_z(width, height)
74 }
75
76 pub fn two_axis_rotation(
80 cam_axis_x: na::Vector3<f32>,
81 viewport_size: na::Vector2<f32>,
82 speed: f32,
83 prev_mouse: na::Vector2<f32>,
84 current_mouse: na::Vector2<f32>,
85 ) -> (na::Rotation3<f32>, na::Rotation3<f32>) {
86 let angle_y = std::f32::consts::PI * (prev_mouse.x - current_mouse.x) / viewport_size.x * speed;
88 let rot_y = na::Rotation3::from_axis_angle(&na::Vector3::y_axis(), angle_y);
89
90 let axis_x = cam_axis_x;
92 let axis_x = na::Unit::new_normalize(axis_x);
93 let angle_x = std::f32::consts::PI * (prev_mouse.y - current_mouse.y) / viewport_size.y * speed;
94 let rot_x = na::Rotation3::from_axis_angle(&axis_x, angle_x);
95
96 (rot_y, rot_x)
97 }
98
99 pub fn project(
102 &self,
103 point_world: na::Point3<f32>,
104 view: na::Matrix4<f32>,
105 proj: na::Matrix4<f32>,
106 viewport_size: na::Vector2<f32>,
107 ) -> na::Vector3<f32> {
108 let p_view = view * point_world.to_homogeneous();
110 let mut p_proj = proj * p_view;
111 p_proj = p_proj / p_proj.w;
112 p_proj = p_proj * 0.5 + na::Vector4::<f32>::new(0.5, 0.5, 0.5, 0.5);
113 p_proj.x *= viewport_size.x;
114 p_proj.y *= viewport_size.y;
115
116 p_proj.fixed_rows::<3>(0).clone_owned()
117 }
118
119 pub fn unproject(
123 &self,
124 win: na::Point3<f32>,
125 view: na::Matrix4<f32>,
126 proj: na::Matrix4<f32>,
127 viewport_size: na::Vector2<f32>,
128 ) -> na::Vector3<f32> {
129 let inv = (proj * view).try_inverse().unwrap();
130
131 let mut tmp = win.to_homogeneous();
132 tmp.x /= viewport_size.x;
133 tmp.y /= viewport_size.y;
134 tmp = tmp * 2.0 - na::Vector4::<f32>::new(-1.0, -1.0, -1.0, -1.0);
135
136 let mut obj = inv * tmp;
137 obj = obj / obj.w;
138
139 let scene = obj.fixed_rows::<3>(0).clone_owned();
140
141 scene
142 }
143
144 pub fn touch_pressed(&mut self, touch_event: &Touch, scene: &mut Scene) {
149 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
151
152 cam_control.id2active_touches.insert(touch_event.id, *touch_event);
153
154 cam_control.mouse_pressed = true;
155
156 cam_control.prev_mouse_pos_valid = false;
161 }
162
163 pub fn touch_released(&mut self, touch_event: &Touch, scene: &mut Scene) {
168 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
170
171 cam_control.id2active_touches.remove(&touch_event.id);
172
173 if cam_control.id2active_touches.is_empty() {
175 cam_control.mouse_pressed = false;
176 cam_control.prev_mouse_pos_valid = false;
177 }
178 cam_control.prev_mouse_pos_valid = false;
183 }
184
185 pub fn reset_all_touch_presses(&mut self, scene: &mut Scene) {
189 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
190
191 cam_control.id2active_touches.clear();
192
193 cam_control.mouse_pressed = false;
194 cam_control.prev_mouse_pos_valid = false;
195 cam_control.prev_mouse_pos_valid = false;
196 }
197
198 #[allow(clippy::cast_possible_truncation)]
202 #[allow(clippy::cast_lossless)]
203 pub fn process_touch_move(&mut self, touch_event: &Touch, viewport_width: u32, viewport_height: u32, scene: &mut Scene) {
204 let touches = {
205 let cam_control = scene.get_comp::<&CamController>(&self.entity).unwrap();
206 let all_touches: Vec<Touch> = cam_control.id2active_touches.values().copied().collect();
207 all_touches
208 };
209
210 if touches.len() == 1 {
212 {
213 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
214 cam_control.mouse_mode = CamMode::Rotation;
215 }
216 self.process_mouse_move(
217 touch_event.location.x as f32,
218 touch_event.location.y as f32,
219 viewport_width,
220 viewport_height,
221 scene,
222 );
223 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
225 cam_control.id2active_touches.insert(touch_event.id, *touch_event);
226 } else if touches.len() == 2 {
227 let pos0 = touches[0].location;
230 let pos1 = touches[1].location;
231 let pos0_na = na::Vector2::<f64>::new(pos0.x / viewport_width as f64, pos0.y / viewport_height as f64);
232 let pos1_na = na::Vector2::<f64>::new(pos1.x / viewport_width as f64, pos1.y / viewport_height as f64);
233 let diff_prev = (pos0_na - pos1_na).norm();
234 let current_touches = {
236 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
237 cam_control.id2active_touches.insert(touch_event.id, *touch_event);
238 let current_touches: Vec<Touch> = cam_control.id2active_touches.values().copied().collect();
239 current_touches
240 };
241 let pos0 = current_touches[0].location;
243 let pos1 = current_touches[1].location;
244 let pos0_na = na::Vector2::<f64>::new(pos0.x / viewport_width as f64, pos0.y / viewport_height as f64);
245 let pos1_na = na::Vector2::<f64>::new(pos1.x / viewport_width as f64, pos1.y / viewport_height as f64);
246 let diff_cur = (pos0_na - pos1_na).norm();
247
248 {
250 let inc = diff_cur - diff_prev;
251 let speed = 1.0;
252 let mut pos_lookat = scene.get_comp::<&mut PosLookat>(&self.entity).unwrap();
253 pos_lookat.dolly(inc as f32 * speed);
254 }
255
256 {
258 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
259 cam_control.mouse_mode = CamMode::Translation;
260 }
261 let center = na::Vector2::<f64>::new((pos0.x + pos1.x) / 2.0, (pos0.y + pos1.y) / 2.0);
262 self.process_mouse_move(center.x as f32, center.y as f32, viewport_width, viewport_height, scene);
263 }
264 }
265
266 pub fn mouse_pressed(&mut self, mouse_button: &MouseButton, scene: &mut Scene) {
271 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
273
274 match mouse_button {
275 MouseButton::Left => {
276 cam_control.mouse_mode = CamMode::Rotation;
277 cam_control.mouse_pressed = true;
278 }
279 MouseButton::Right => {
280 cam_control.mouse_mode = CamMode::Translation;
281 cam_control.mouse_pressed = true;
282 }
283 _ => {
284 cam_control.mouse_pressed = false;
285 }
286 }
287 }
288
289 pub fn mouse_released(&mut self, scene: &mut Scene) {
294 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
296
297 cam_control.mouse_pressed = false;
299 cam_control.prev_mouse_pos_valid = false;
300 }
301
302 pub fn process_mouse_move(&mut self, x: f32, y: f32, viewport_width: u32, viewport_height: u32, scene: &mut Scene) {
307 let proj = self.proj_matrix(scene);
308 let mut pos_lookat = scene.get_comp::<&mut PosLookat>(&self.entity).unwrap();
309 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
310
311 let current_mouse = na::Vector2::<f32>::new(x, y);
312 #[allow(clippy::cast_precision_loss)] let viewport_size = na::Vector2::<f32>::new(viewport_width as f32, viewport_height as f32);
314
315 if cam_control.mouse_pressed {
316 if cam_control.mouse_mode == CamMode::Rotation && cam_control.prev_mouse_pos_valid {
317 let speed = 2.0;
318 let (rot_y, mut rot_x) = Self::two_axis_rotation(
319 na::Vector3::from(pos_lookat.cam_axes().column(0)),
320 viewport_size,
321 speed,
322 cam_control.prev_mouse,
323 current_mouse,
324 );
325
326 let mut new_pos_lookat = pos_lookat.clone();
328 new_pos_lookat.orbit(rot_x);
329 let dot_up = new_pos_lookat.direction().dot(&na::Vector3::<f32>::y_axis());
331 let angle_vertical = dot_up.acos();
332 if let Some(max_vertical_angle) = cam_control.limit_max_vertical_angle {
334 if angle_vertical > max_vertical_angle || new_pos_lookat.up == na::Vector3::<f32>::new(0.0, -1.0, 0.0) {
335 rot_x = na::Rotation3::<f32>::identity();
336 }
337 }
338 if let Some(min_vertical_angle) = cam_control.limit_min_vertical_angle {
339 if angle_vertical < min_vertical_angle {
340 rot_x = na::Rotation3::<f32>::identity();
341 }
342 }
343
344 let rot = rot_y * rot_x;
345 pos_lookat.orbit(rot);
346 } else if cam_control.mouse_mode == CamMode::Translation && cam_control.prev_mouse_pos_valid {
347 let view = pos_lookat.view_matrix();
348
349 let coord = self.project(pos_lookat.lookat, view, proj, viewport_size);
350 let down_mouse_z = coord.z;
351
352 let pos1 = self.unproject(na::Point3::<f32>::new(x, viewport_size.y - y, down_mouse_z), view, proj, viewport_size);
353 let pos0 = self.unproject(
354 na::Point3::<f32>::new(cam_control.prev_mouse.x, viewport_size.y - cam_control.prev_mouse.y, down_mouse_z),
355 view,
356 proj,
357 viewport_size,
358 );
359 let diff = pos1 - pos0;
360 let new_pos = pos_lookat.position - diff;
362 pos_lookat.shift_cam(new_pos);
363 }
364 cam_control.prev_mouse = current_mouse;
365 cam_control.prev_mouse_pos_valid = true;
366 }
367 }
368
369 pub fn process_mouse_scroll(&mut self, delta: &MouseScrollDelta, scene: &mut Scene) {
374 let mut pos_lookat = scene.get_comp::<&mut PosLookat>(&self.entity).unwrap();
375 let cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
376
377 let scroll = match delta {
378 MouseScrollDelta::LineDelta(_, scroll) => f64::from(scroll * 0.5),
380 #[allow(clippy::cast_precision_loss)] MouseScrollDelta::PixelDelta(PhysicalPosition { y: scroll, .. }) => *scroll,
382 };
383
384 let mut s = if scroll > 0.0 { 0.1 } else { -0.1 };
385
386 if let Some(max_dist) = cam_control.limit_max_dist {
387 let cur_dist = pos_lookat.dist_lookat();
388 if cur_dist > max_dist && s < 0.0 {
389 s = 0.0;
390 }
391 }
392
393 pos_lookat.dolly(s);
395 }
396
397 pub fn set_aspect_ratio(&mut self, val: f32, scene: &mut Scene) {
402 let mut proj = scene.get_comp::<&mut Projection>(&self.entity).unwrap();
404 if let Projection::WithFov(ref mut proj) = *proj {
405 proj.aspect_ratio = val;
406 }
407 }
408
409 pub fn set_aspect_ratio_maybe(&mut self, val: f32, scene: &mut Scene) {
412 if let Ok(mut proj) = scene.get_comp::<&mut Projection>(&self.entity) {
413 if let Projection::WithFov(ref mut proj) = *proj {
414 proj.aspect_ratio = val;
415 }
416 } else if scene.nr_renderables() != 0 {
417 warn!("No Projection component yet so we couldn't set aspect ratio. This may not be an issue since the prepass might fix this. Ideally this warning should only appear at most once");
421 }
422 }
423
424 pub fn near_far(&self, scene: &mut Scene) -> (f32, f32) {
425 let proj = scene.get_comp::<&Projection>(&self.entity).unwrap();
426 proj.near_far()
427 }
428
429 pub fn set_target_res(&mut self, width: u32, height: u32, scene: &mut Scene) {
431 {
432 let mut res = scene.get_comp::<&mut TargetResolution>(&self.entity).unwrap();
433 res.width = width;
434 res.height = height;
435 }
436
437 #[allow(clippy::cast_precision_loss)]
439 self.set_aspect_ratio_maybe(width as f32 / height as f32, scene);
440 }
441
442 pub fn on_window_resize(&mut self, width: u32, height: u32, scene: &mut Scene) {
445 {
446 let mut res = scene.get_comp::<&mut TargetResolution>(&self.entity).unwrap();
447 if res.update_mode == TargetResolutionUpdate::WindowSize {
448 res.width = width;
449 res.height = height;
450 }
451 }
452
453 #[allow(clippy::cast_precision_loss)]
455 self.set_aspect_ratio_maybe(width as f32 / height as f32, scene);
456 }
457
458 pub fn get_target_res(&self, scene: &Scene) -> (u32, u32) {
459 let res = scene.get_comp::<&TargetResolution>(&self.entity).unwrap();
460 (res.width, res.height)
461 }
462}