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)]
21#[derive(Clone)]
22pub struct Camera {
23 pub entity: Entity,
24}
25
26impl Camera {
27 #[allow(clippy::missing_panics_doc)] pub fn new(name: &str, scene: &mut Scene, initialize: bool) -> Self {
30 let entity = scene
31 .get_or_create_hidden_entity(name)
32 .insert(CamController::default())
33 .insert(TargetResolution::default())
34 .entity();
35 if initialize {
36 scene.world.insert_one(entity, PosLookat::default()).ok();
37 scene.world.insert_one(entity, Projection::default()).ok();
38 }
39 Self { entity }
40 }
41
42 pub fn from_entity(entity: Entity) -> Self {
43 Self { entity }
44 }
45
46 #[allow(clippy::missing_panics_doc)]
47 pub fn is_initialized(&self, scene: &Scene) -> bool {
48 scene.world.has::<PosLookat>(self.entity).unwrap()
49 && (scene.world.has::<Projection>(self.entity).unwrap() || scene.world.has::<ProjectionWithIntrinsics>(self.entity).unwrap())
50 }
51
52 pub fn view_matrix(&self, scene: &Scene) -> na::Matrix4<f32> {
55 let pos_lookat = scene.get_comp::<&PosLookat>(&self.entity).unwrap();
56 pos_lookat.view_matrix()
57 }
58
59 pub fn proj_matrix(&self, scene: &Scene) -> na::Matrix4<f32> {
63 let proj = scene.get_comp::<&Projection>(&self.entity).unwrap();
64 let (width, height) = self.get_target_res(scene);
65 proj.proj_matrix(width, height)
66 }
67
68 pub fn proj_matrix_reverse_z(&self, scene: &Scene) -> na::Matrix4<f32> {
72 let proj = scene.get_comp::<&Projection>(&self.entity).unwrap();
73 let (width, height) = self.get_target_res(scene);
74 proj.proj_matrix_reverse_z(width, height)
75 }
76
77 pub fn two_axis_rotation(
81 cam_axis_x: na::Vector3<f32>,
82 viewport_size: na::Vector2<f32>,
83 speed: f32,
84 prev_mouse: na::Vector2<f32>,
85 current_mouse: na::Vector2<f32>,
86 ) -> (na::Rotation3<f32>, na::Rotation3<f32>) {
87 let angle_y = std::f32::consts::PI * (prev_mouse.x - current_mouse.x) / viewport_size.x * speed;
89 let rot_y = na::Rotation3::from_axis_angle(&na::Vector3::y_axis(), angle_y);
90
91 let axis_x = cam_axis_x;
93 let axis_x = na::Unit::new_normalize(axis_x);
94 let angle_x = std::f32::consts::PI * (prev_mouse.y - current_mouse.y) / viewport_size.y * speed;
95 let rot_x = na::Rotation3::from_axis_angle(&axis_x, angle_x);
96
97 (rot_y, rot_x)
98 }
99
100 pub fn project(
103 &self,
104 point_world: na::Point3<f32>,
105 view: na::Matrix4<f32>,
106 proj: na::Matrix4<f32>,
107 viewport_size: na::Vector2<f32>,
108 ) -> na::Vector3<f32> {
109 let p_view = view * point_world.to_homogeneous();
111 let mut p_proj = proj * p_view;
112 p_proj = p_proj / p_proj.w;
113 p_proj = p_proj * 0.5 + na::Vector4::<f32>::new(0.5, 0.5, 0.5, 0.5);
114 p_proj.x *= viewport_size.x;
115 p_proj.y *= viewport_size.y;
116
117 p_proj.fixed_rows::<3>(0).clone_owned()
118 }
119
120 pub fn unproject(
124 &self,
125 win: na::Point3<f32>,
126 view: na::Matrix4<f32>,
127 proj: na::Matrix4<f32>,
128 viewport_size: na::Vector2<f32>,
129 ) -> na::Vector3<f32> {
130 let inv = (proj * view).try_inverse().unwrap();
131
132 let mut tmp = win.to_homogeneous();
133 tmp.x /= viewport_size.x;
134 tmp.y /= viewport_size.y;
135 tmp = tmp * 2.0 - na::Vector4::<f32>::new(-1.0, -1.0, -1.0, -1.0);
136
137 let mut obj = inv * tmp;
138 obj = obj / obj.w;
139
140 let scene = obj.fixed_rows::<3>(0).clone_owned();
141
142 scene
143 }
144
145 pub fn clear_click(&self, scene: &mut Scene) {
147 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
148 cam_control.is_last_press_click = false;
149 }
150
151 pub fn is_click(&self, scene: &Scene) -> bool {
156 let cam_control = scene.get_comp::<&CamController>(&self.entity).unwrap();
157 cam_control.is_last_press_click
158 }
159
160 pub fn touch_pressed(&mut self, touch_event: &Touch, scene: &mut Scene) {
165 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
167
168 cam_control.id2active_touches.insert(touch_event.id, *touch_event);
169
170 cam_control.mouse_pressed = true;
171
172 cam_control.prev_mouse_pos_valid = false;
177
178 cam_control.last_press = Some(wasm_timer::Instant::now());
179 }
180
181 pub fn touch_released(&mut self, touch_event: &Touch, scene: &mut Scene) {
186 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
187
188 cam_control.id2active_touches.remove(&touch_event.id);
189
190 if cam_control.id2active_touches.is_empty() {
192 cam_control.mouse_pressed = false;
193 cam_control.prev_mouse_pos_valid = false;
194 }
195 cam_control.prev_mouse_pos_valid = false;
200 cam_control.decide_if_click();
201 cam_control.mouse_moved_while_pressed = false; }
203
204 pub fn reset_all_touch_presses(&mut self, scene: &mut Scene) {
208 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
209
210 cam_control.id2active_touches.clear();
211
212 cam_control.mouse_pressed = false;
213 cam_control.prev_mouse_pos_valid = false;
214 cam_control.prev_mouse_pos_valid = false;
215 cam_control.mouse_moved_while_pressed = false;
216 }
217
218 #[allow(clippy::cast_possible_truncation)]
222 #[allow(clippy::cast_lossless)]
223 pub fn process_touch_move(&mut self, touch_event: &Touch, viewport_width: u32, viewport_height: u32, scene: &mut Scene) {
224 let touches = {
225 let cam_control = scene.get_comp::<&CamController>(&self.entity).unwrap();
226 let all_touches: Vec<Touch> = cam_control.id2active_touches.values().copied().collect();
227 all_touches
228 };
229
230 if touches.len() == 1 {
232 {
233 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
234 cam_control.mouse_mode = CamMode::Rotation;
235 }
236 self.process_mouse_move(touch_event.location.x, touch_event.location.y, viewport_width, viewport_height, scene);
237 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
239 cam_control.id2active_touches.insert(touch_event.id, *touch_event);
240 } else if touches.len() == 2 {
241 let pos0 = touches[0].location;
244 let pos1 = touches[1].location;
245 let pos0_na = na::Vector2::<f64>::new(pos0.x / viewport_width as f64, pos0.y / viewport_height as f64);
246 let pos1_na = na::Vector2::<f64>::new(pos1.x / viewport_width as f64, pos1.y / viewport_height as f64);
247 let diff_prev = (pos0_na - pos1_na).norm();
248 let current_touches = {
250 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
251 cam_control.id2active_touches.insert(touch_event.id, *touch_event);
252 let current_touches: Vec<Touch> = cam_control.id2active_touches.values().copied().collect();
253 current_touches
254 };
255 let pos0 = current_touches[0].location;
257 let pos1 = current_touches[1].location;
258 let pos0_na = na::Vector2::<f64>::new(pos0.x / viewport_width as f64, pos0.y / viewport_height as f64);
259 let pos1_na = na::Vector2::<f64>::new(pos1.x / viewport_width as f64, pos1.y / viewport_height as f64);
260 let diff_cur = (pos0_na - pos1_na).norm();
261
262 {
264 let inc = diff_cur - diff_prev;
265 let speed = 1.0;
266 let mut pos_lookat = scene.get_comp::<&mut PosLookat>(&self.entity).unwrap();
267 pos_lookat.dolly(inc as f32 * speed);
268 }
269
270 {
272 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
273 cam_control.mouse_mode = CamMode::Translation;
274 }
275 let center = na::Vector2::<f64>::new((pos0.x + pos1.x) / 2.0, (pos0.y + pos1.y) / 2.0);
276 self.process_mouse_move(center.x, center.y, viewport_width, viewport_height, scene);
277 }
278 }
279
280 pub fn mouse_pressed(&mut self, mouse_button: &MouseButton, scene: &mut Scene) {
285 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
287
288 match mouse_button {
289 MouseButton::Left => {
290 cam_control.last_press = Some(wasm_timer::Instant::now());
291 cam_control.mouse_mode = CamMode::Rotation;
292 cam_control.mouse_pressed = true;
293 }
294 MouseButton::Right => {
295 cam_control.mouse_mode = CamMode::Translation;
296 cam_control.mouse_pressed = true;
297 }
298 _ => {
299 cam_control.mouse_pressed = false;
300 }
301 }
302 }
303
304 pub fn mouse_released(&mut self, scene: &mut Scene) {
309 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
310
311 cam_control.mouse_pressed = false;
312 cam_control.prev_mouse_pos_valid = false;
313 cam_control.decide_if_click();
314 cam_control.mouse_moved_while_pressed = false; }
316
317 pub fn process_mouse_move(&mut self, position_x: f64, position_y: f64, viewport_width: u32, viewport_height: u32, scene: &mut Scene) {
322 let proj = self.proj_matrix(scene);
323 let mut pos_lookat = scene.get_comp::<&mut PosLookat>(&self.entity).unwrap();
324 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
325
326 cam_control.cursor_position = Some(winit::dpi::PhysicalPosition::new(position_x, position_y));
327
328 #[allow(clippy::cast_possible_truncation)]
329 let (x, y) = (position_x as f32, position_y as f32);
330
331 let current_mouse = na::Vector2::<f32>::new(x, y);
332 #[allow(clippy::cast_precision_loss)] let viewport_size = na::Vector2::<f32>::new(viewport_width as f32, viewport_height as f32);
334
335 if cam_control.mouse_pressed {
336 if cam_control.prev_mouse_pos_valid {
337 cam_control.mouse_moved_while_pressed = true;
338 }
339
340 if cam_control.mouse_mode == CamMode::Rotation && cam_control.prev_mouse_pos_valid {
341 let speed = 2.0;
342 let (rot_y, mut rot_x) = Self::two_axis_rotation(
343 na::Vector3::from(pos_lookat.cam_axes().column(0)),
344 viewport_size,
345 speed,
346 cam_control.prev_mouse,
347 current_mouse,
348 );
349
350 let mut new_pos_lookat = pos_lookat.clone();
352 new_pos_lookat.orbit(rot_x);
353 let dot_up = new_pos_lookat.direction().dot(&na::Vector3::<f32>::y_axis());
355 let angle_vertical = dot_up.acos();
356 if let Some(max_vertical_angle) = cam_control.limit_max_vertical_angle {
358 if angle_vertical > max_vertical_angle || new_pos_lookat.up == na::Vector3::<f32>::new(0.0, -1.0, 0.0) {
359 rot_x = na::Rotation3::<f32>::identity();
360 }
361 }
362 if let Some(min_vertical_angle) = cam_control.limit_min_vertical_angle {
363 if angle_vertical < min_vertical_angle {
364 rot_x = na::Rotation3::<f32>::identity();
365 }
366 }
367
368 let rot = rot_y * rot_x;
369 pos_lookat.orbit(rot);
370 } else if cam_control.mouse_mode == CamMode::Translation && cam_control.prev_mouse_pos_valid {
371 let view = pos_lookat.view_matrix();
372
373 let coord = self.project(pos_lookat.lookat, view, proj, viewport_size);
374 let down_mouse_z = coord.z;
375
376 let pos1 = self.unproject(na::Point3::<f32>::new(x, viewport_size.y - y, down_mouse_z), view, proj, viewport_size);
377 let pos0 = self.unproject(
378 na::Point3::<f32>::new(cam_control.prev_mouse.x, viewport_size.y - cam_control.prev_mouse.y, down_mouse_z),
379 view,
380 proj,
381 viewport_size,
382 );
383 let diff = pos1 - pos0;
384 let new_pos = pos_lookat.position - diff;
386 pos_lookat.shift_cam(new_pos);
387 }
388 cam_control.prev_mouse = current_mouse;
389 cam_control.prev_mouse_pos_valid = true;
390 }
391 }
392
393 pub fn process_mouse_scroll(&mut self, delta: &MouseScrollDelta, scene: &mut Scene) {
398 let mut pos_lookat = scene.get_comp::<&mut PosLookat>(&self.entity).unwrap();
399 let cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
400
401 let scroll = match delta {
402 MouseScrollDelta::LineDelta(_, scroll) => f64::from(scroll * 0.5),
404 #[allow(clippy::cast_precision_loss)] MouseScrollDelta::PixelDelta(PhysicalPosition { y: scroll, .. }) => *scroll,
406 };
407
408 let mut s = if scroll > 0.0 { 0.1 } else { -0.1 };
409
410 if let Some(max_dist) = cam_control.limit_max_dist {
411 let cur_dist = pos_lookat.dist_lookat();
412 if cur_dist > max_dist && s < 0.0 {
413 s = 0.0;
414 }
415 }
416
417 pos_lookat.dolly(s);
419 }
420
421 pub fn set_aspect_ratio(&mut self, val: f32, scene: &mut Scene) {
426 let mut proj = scene.get_comp::<&mut Projection>(&self.entity).unwrap();
428 if let Projection::WithFov(ref mut proj) = *proj {
429 proj.aspect_ratio = val;
430 }
431 }
432
433 pub fn set_aspect_ratio_maybe(&mut self, val: f32, scene: &mut Scene) {
436 if let Ok(mut proj) = scene.get_comp::<&mut Projection>(&self.entity) {
437 if let Projection::WithFov(ref mut proj) = *proj {
438 proj.aspect_ratio = val;
439 }
440 } else if scene.nr_renderables() != 0 {
441 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");
445 }
446 }
447
448 pub fn near_far(&self, scene: &mut Scene) -> (f32, f32) {
449 let proj = scene.get_comp::<&Projection>(&self.entity).unwrap();
450 proj.near_far()
451 }
452
453 pub fn set_target_res(&mut self, width: u32, height: u32, scene: &mut Scene) {
455 {
456 let mut res = scene.get_comp::<&mut TargetResolution>(&self.entity).unwrap();
457 res.width = width;
458 res.height = height;
459 }
460
461 #[allow(clippy::cast_precision_loss)]
463 self.set_aspect_ratio_maybe(width as f32 / height as f32, scene);
464 }
465
466 pub fn on_window_resize(&mut self, width: u32, height: u32, scene: &mut Scene) {
469 {
470 let mut res = scene.get_comp::<&mut TargetResolution>(&self.entity).unwrap();
471 if res.update_mode == TargetResolutionUpdate::WindowSize {
472 res.width = width;
473 res.height = height;
474 }
475 }
476
477 #[allow(clippy::cast_precision_loss)]
479 self.set_aspect_ratio_maybe(width as f32 / height as f32, scene);
480 }
481
482 pub fn get_target_res(&self, scene: &Scene) -> (u32, u32) {
483 let res = scene.get_comp::<&TargetResolution>(&self.entity).unwrap();
484 (res.width, res.height)
485 }
486}