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 clear_click(&self, scene: &mut Scene) {
146 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
147 cam_control.is_last_press_click = false;
148 }
149
150 pub fn is_click(&self, scene: &Scene) -> bool {
155 let cam_control = scene.get_comp::<&CamController>(&self.entity).unwrap();
156 cam_control.is_last_press_click
157 }
158
159 pub fn touch_pressed(&mut self, touch_event: &Touch, scene: &mut Scene) {
164 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
166
167 cam_control.id2active_touches.insert(touch_event.id, *touch_event);
168
169 cam_control.mouse_pressed = true;
170
171 cam_control.prev_mouse_pos_valid = false;
176
177 cam_control.last_press = Some(wasm_timer::Instant::now());
178 }
179
180 pub fn touch_released(&mut self, touch_event: &Touch, scene: &mut Scene) {
185 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
186
187 cam_control.id2active_touches.remove(&touch_event.id);
188
189 if cam_control.id2active_touches.is_empty() {
191 cam_control.mouse_pressed = false;
192 cam_control.prev_mouse_pos_valid = false;
193 }
194 cam_control.prev_mouse_pos_valid = false;
199 cam_control.decide_if_click();
200 cam_control.mouse_moved_while_pressed = false; }
202
203 pub fn reset_all_touch_presses(&mut self, scene: &mut Scene) {
207 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
208
209 cam_control.id2active_touches.clear();
210
211 cam_control.mouse_pressed = false;
212 cam_control.prev_mouse_pos_valid = false;
213 cam_control.prev_mouse_pos_valid = false;
214 cam_control.mouse_moved_while_pressed = false;
215 }
216
217 #[allow(clippy::cast_possible_truncation)]
221 #[allow(clippy::cast_lossless)]
222 pub fn process_touch_move(&mut self, touch_event: &Touch, viewport_width: u32, viewport_height: u32, scene: &mut Scene) {
223 let touches = {
224 let cam_control = scene.get_comp::<&CamController>(&self.entity).unwrap();
225 let all_touches: Vec<Touch> = cam_control.id2active_touches.values().copied().collect();
226 all_touches
227 };
228
229 if touches.len() == 1 {
231 {
232 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
233 cam_control.mouse_mode = CamMode::Rotation;
234 }
235 self.process_mouse_move(touch_event.location.x, touch_event.location.y, viewport_width, viewport_height, scene);
236 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
238 cam_control.id2active_touches.insert(touch_event.id, *touch_event);
239 } else if touches.len() == 2 {
240 let pos0 = touches[0].location;
243 let pos1 = 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_prev = (pos0_na - pos1_na).norm();
247 let current_touches = {
249 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
250 cam_control.id2active_touches.insert(touch_event.id, *touch_event);
251 let current_touches: Vec<Touch> = cam_control.id2active_touches.values().copied().collect();
252 current_touches
253 };
254 let pos0 = current_touches[0].location;
256 let pos1 = current_touches[1].location;
257 let pos0_na = na::Vector2::<f64>::new(pos0.x / viewport_width as f64, pos0.y / viewport_height as f64);
258 let pos1_na = na::Vector2::<f64>::new(pos1.x / viewport_width as f64, pos1.y / viewport_height as f64);
259 let diff_cur = (pos0_na - pos1_na).norm();
260
261 {
263 let inc = diff_cur - diff_prev;
264 let speed = 1.0;
265 let mut pos_lookat = scene.get_comp::<&mut PosLookat>(&self.entity).unwrap();
266 pos_lookat.dolly(inc as f32 * speed);
267 }
268
269 {
271 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
272 cam_control.mouse_mode = CamMode::Translation;
273 }
274 let center = na::Vector2::<f64>::new((pos0.x + pos1.x) / 2.0, (pos0.y + pos1.y) / 2.0);
275 self.process_mouse_move(center.x, center.y, viewport_width, viewport_height, scene);
276 }
277 }
278
279 pub fn mouse_pressed(&mut self, mouse_button: &MouseButton, scene: &mut Scene) {
284 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
286
287 match mouse_button {
288 MouseButton::Left => {
289 cam_control.last_press = Some(wasm_timer::Instant::now());
290 cam_control.mouse_mode = CamMode::Rotation;
291 cam_control.mouse_pressed = true;
292 }
293 MouseButton::Right => {
294 cam_control.mouse_mode = CamMode::Translation;
295 cam_control.mouse_pressed = true;
296 }
297 _ => {
298 cam_control.mouse_pressed = false;
299 }
300 }
301 }
302
303 pub fn mouse_released(&mut self, scene: &mut Scene) {
308 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
309
310 cam_control.mouse_pressed = false;
311 cam_control.prev_mouse_pos_valid = false;
312 cam_control.decide_if_click();
313 cam_control.mouse_moved_while_pressed = false; }
315
316 pub fn process_mouse_move(&mut self, position_x: f64, position_y: f64, viewport_width: u32, viewport_height: u32, scene: &mut Scene) {
321 let proj = self.proj_matrix(scene);
322 let mut pos_lookat = scene.get_comp::<&mut PosLookat>(&self.entity).unwrap();
323 let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
324
325 cam_control.cursor_position = Some(winit::dpi::PhysicalPosition::new(position_x, position_y));
326
327 #[allow(clippy::cast_possible_truncation)]
328 let (x, y) = (position_x as f32, position_y as f32);
329
330 let current_mouse = na::Vector2::<f32>::new(x, y);
331 #[allow(clippy::cast_precision_loss)] let viewport_size = na::Vector2::<f32>::new(viewport_width as f32, viewport_height as f32);
333
334 if cam_control.mouse_pressed {
335 if cam_control.prev_mouse_pos_valid {
336 cam_control.mouse_moved_while_pressed = true;
337 }
338
339 if cam_control.mouse_mode == CamMode::Rotation && cam_control.prev_mouse_pos_valid {
340 let speed = 2.0;
341 let (rot_y, mut rot_x) = Self::two_axis_rotation(
342 na::Vector3::from(pos_lookat.cam_axes().column(0)),
343 viewport_size,
344 speed,
345 cam_control.prev_mouse,
346 current_mouse,
347 );
348
349 let mut new_pos_lookat = pos_lookat.clone();
351 new_pos_lookat.orbit(rot_x);
352 let dot_up = new_pos_lookat.direction().dot(&na::Vector3::<f32>::y_axis());
354 let angle_vertical = dot_up.acos();
355 if let Some(max_vertical_angle) = cam_control.limit_max_vertical_angle {
357 if angle_vertical > max_vertical_angle || new_pos_lookat.up == na::Vector3::<f32>::new(0.0, -1.0, 0.0) {
358 rot_x = na::Rotation3::<f32>::identity();
359 }
360 }
361 if let Some(min_vertical_angle) = cam_control.limit_min_vertical_angle {
362 if angle_vertical < min_vertical_angle {
363 rot_x = na::Rotation3::<f32>::identity();
364 }
365 }
366
367 let rot = rot_y * rot_x;
368 pos_lookat.orbit(rot);
369 } else if cam_control.mouse_mode == CamMode::Translation && cam_control.prev_mouse_pos_valid {
370 let view = pos_lookat.view_matrix();
371
372 let coord = self.project(pos_lookat.lookat, view, proj, viewport_size);
373 let down_mouse_z = coord.z;
374
375 let pos1 = self.unproject(na::Point3::<f32>::new(x, viewport_size.y - y, down_mouse_z), view, proj, viewport_size);
376 let pos0 = self.unproject(
377 na::Point3::<f32>::new(cam_control.prev_mouse.x, viewport_size.y - cam_control.prev_mouse.y, down_mouse_z),
378 view,
379 proj,
380 viewport_size,
381 );
382 let diff = pos1 - pos0;
383 let new_pos = pos_lookat.position - diff;
385 pos_lookat.shift_cam(new_pos);
386 }
387 cam_control.prev_mouse = current_mouse;
388 cam_control.prev_mouse_pos_valid = true;
389 }
390 }
391
392 pub fn process_mouse_scroll(&mut self, delta: &MouseScrollDelta, scene: &mut Scene) {
397 let mut pos_lookat = scene.get_comp::<&mut PosLookat>(&self.entity).unwrap();
398 let cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
399
400 let scroll = match delta {
401 MouseScrollDelta::LineDelta(_, scroll) => f64::from(scroll * 0.5),
403 #[allow(clippy::cast_precision_loss)] MouseScrollDelta::PixelDelta(PhysicalPosition { y: scroll, .. }) => *scroll,
405 };
406
407 let mut s = if scroll > 0.0 { 0.1 } else { -0.1 };
408
409 if let Some(max_dist) = cam_control.limit_max_dist {
410 let cur_dist = pos_lookat.dist_lookat();
411 if cur_dist > max_dist && s < 0.0 {
412 s = 0.0;
413 }
414 }
415
416 pos_lookat.dolly(s);
418 }
419
420 pub fn set_aspect_ratio(&mut self, val: f32, scene: &mut Scene) {
425 let mut proj = scene.get_comp::<&mut Projection>(&self.entity).unwrap();
427 if let Projection::WithFov(ref mut proj) = *proj {
428 proj.aspect_ratio = val;
429 }
430 }
431
432 pub fn set_aspect_ratio_maybe(&mut self, val: f32, scene: &mut Scene) {
435 if let Ok(mut proj) = scene.get_comp::<&mut Projection>(&self.entity) {
436 if let Projection::WithFov(ref mut proj) = *proj {
437 proj.aspect_ratio = val;
438 }
439 } else if scene.nr_renderables() != 0 {
440 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");
444 }
445 }
446
447 pub fn near_far(&self, scene: &mut Scene) -> (f32, f32) {
448 let proj = scene.get_comp::<&Projection>(&self.entity).unwrap();
449 proj.near_far()
450 }
451
452 pub fn set_target_res(&mut self, width: u32, height: u32, scene: &mut Scene) {
454 {
455 let mut res = scene.get_comp::<&mut TargetResolution>(&self.entity).unwrap();
456 res.width = width;
457 res.height = height;
458 }
459
460 #[allow(clippy::cast_precision_loss)]
462 self.set_aspect_ratio_maybe(width as f32 / height as f32, scene);
463 }
464
465 pub fn on_window_resize(&mut self, width: u32, height: u32, scene: &mut Scene) {
468 {
469 let mut res = scene.get_comp::<&mut TargetResolution>(&self.entity).unwrap();
470 if res.update_mode == TargetResolutionUpdate::WindowSize {
471 res.width = width;
472 res.height = height;
473 }
474 }
475
476 #[allow(clippy::cast_precision_loss)]
478 self.set_aspect_ratio_maybe(width as f32 / height as f32, scene);
479 }
480
481 pub fn get_target_res(&self, scene: &Scene) -> (u32, u32) {
482 let res = scene.get_comp::<&TargetResolution>(&self.entity).unwrap();
483 (res.width, res.height)
484 }
485
486 }