extern crate nalgebra as na;
extern crate nalgebra_glm as glm;
use log::warn;
use winit::{
dpi::PhysicalPosition,
event::{MouseButton, MouseScrollDelta, Touch},
};
use crate::{
components::{CamController, CamMode, PosLookat, Projection, ProjectionWithIntrinsics, TargetResolution, TargetResolutionUpdate},
scene::Scene,
};
use gloss_hecs::Entity;
#[repr(C)]
#[derive(Clone)]
pub struct Camera {
pub entity: Entity,
}
impl Camera {
#[allow(clippy::missing_panics_doc)] pub fn new(name: &str, scene: &mut Scene, initialize: bool) -> Self {
let entity = scene
.get_or_create_hidden_entity(name)
.insert(CamController::default())
.insert(TargetResolution::default())
.entity();
if initialize {
scene.world_mut().insert_one(entity, PosLookat::default()).ok();
scene.world_mut().insert_one(entity, Projection::default()).ok();
}
Self { entity }
}
pub fn from_entity(entity: Entity) -> Self {
Self { entity }
}
#[allow(clippy::missing_panics_doc)]
pub fn is_initialized(&self, scene: &Scene) -> bool {
scene.world().has::<PosLookat>(self.entity).unwrap()
&& (scene.world().has::<Projection>(self.entity).unwrap() || scene.world().has::<ProjectionWithIntrinsics>(self.entity).unwrap())
}
pub fn view_matrix(&self, scene: &Scene) -> na::Matrix4<f32> {
let pos_lookat = scene.get_comp::<&PosLookat>(&self.entity).unwrap();
pos_lookat.view_matrix()
}
pub fn proj_matrix(&self, scene: &Scene) -> na::Matrix4<f32> {
let proj = scene.get_comp::<&Projection>(&self.entity).unwrap();
let (width, height) = self.get_target_res(scene);
proj.proj_matrix(width, height)
}
pub fn proj_matrix_reverse_z(&self, scene: &Scene) -> na::Matrix4<f32> {
let proj = scene.get_comp::<&Projection>(&self.entity).unwrap();
let (width, height) = self.get_target_res(scene);
proj.proj_matrix_reverse_z(width, height)
}
pub fn two_axis_rotation(
cam_axis_x: na::Vector3<f32>,
viewport_size: na::Vector2<f32>,
speed: f32,
prev_mouse: na::Vector2<f32>,
current_mouse: na::Vector2<f32>,
) -> (na::Rotation3<f32>, na::Rotation3<f32>) {
let angle_y = std::f32::consts::PI * (prev_mouse.x - current_mouse.x) / viewport_size.x * speed;
let rot_y = na::Rotation3::from_axis_angle(&na::Vector3::y_axis(), angle_y);
let axis_x = cam_axis_x;
let axis_x = na::Unit::new_normalize(axis_x);
let angle_x = std::f32::consts::PI * (prev_mouse.y - current_mouse.y) / viewport_size.y * speed;
let rot_x = na::Rotation3::from_axis_angle(&axis_x, angle_x);
(rot_y, rot_x)
}
pub fn project(
&self,
point_world: na::Point3<f32>,
view: na::Matrix4<f32>,
proj: na::Matrix4<f32>,
viewport_size: na::Vector2<f32>,
) -> na::Vector3<f32> {
let p_view = view * point_world.to_homogeneous();
let mut p_proj = proj * p_view;
p_proj = p_proj / p_proj.w;
p_proj = p_proj * 0.5 + na::Vector4::<f32>::new(0.5, 0.5, 0.5, 0.5);
p_proj.x *= viewport_size.x;
p_proj.y *= viewport_size.y;
p_proj.fixed_rows::<3>(0).clone_owned()
}
pub fn unproject(
&self,
win: na::Point3<f32>,
view: na::Matrix4<f32>,
proj: na::Matrix4<f32>,
viewport_size: na::Vector2<f32>,
) -> na::Vector3<f32> {
let inv = (proj * view).try_inverse().unwrap();
let mut tmp = win.to_homogeneous();
tmp.x /= viewport_size.x;
tmp.y /= viewport_size.y;
tmp = tmp * 2.0 - na::Vector4::<f32>::new(-1.0, -1.0, -1.0, -1.0);
let mut obj = inv * tmp;
obj = obj / obj.w;
let scene = obj.fixed_rows::<3>(0).clone_owned();
scene
}
pub fn clear_click(&self, scene: &mut Scene) {
let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
cam_control.is_last_press_click = false;
}
pub fn is_click(&self, scene: &Scene) -> bool {
let cam_control = scene.get_comp::<&CamController>(&self.entity).unwrap();
cam_control.is_last_press_click
}
pub fn touch_pressed(&mut self, touch_event: &Touch, scene: &mut Scene) {
let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
cam_control.id2active_touches.insert(touch_event.id, *touch_event);
cam_control.mouse_pressed = true;
cam_control.prev_mouse_pos_valid = false;
cam_control.last_press = Some(wasm_timer::Instant::now());
}
pub fn touch_released(&mut self, touch_event: &Touch, scene: &mut Scene) {
let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
cam_control.id2active_touches.remove(&touch_event.id);
if cam_control.id2active_touches.is_empty() {
cam_control.mouse_pressed = false;
cam_control.prev_mouse_pos_valid = false;
}
cam_control.prev_mouse_pos_valid = false;
cam_control.decide_if_click();
cam_control.mouse_moved_while_pressed = false; }
pub fn reset_all_touch_presses(&mut self, scene: &mut Scene) {
let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
cam_control.id2active_touches.clear();
cam_control.mouse_pressed = false;
cam_control.prev_mouse_pos_valid = false;
cam_control.prev_mouse_pos_valid = false;
cam_control.mouse_moved_while_pressed = false;
}
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_lossless)]
pub fn process_touch_move(&mut self, touch_event: &Touch, viewport_width: u32, viewport_height: u32, scene: &mut Scene) {
let touches = {
let cam_control = scene.get_comp::<&CamController>(&self.entity).unwrap();
let all_touches: Vec<Touch> = cam_control.id2active_touches.values().copied().collect();
all_touches
};
if touches.len() == 1 {
{
let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
cam_control.mouse_mode = CamMode::Rotation;
}
self.process_mouse_move(touch_event.location.x, touch_event.location.y, viewport_width, viewport_height, scene);
let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
cam_control.id2active_touches.insert(touch_event.id, *touch_event);
} else if touches.len() == 2 {
let pos0 = touches[0].location;
let pos1 = touches[1].location;
let pos0_na = na::Vector2::<f64>::new(pos0.x / viewport_width as f64, pos0.y / viewport_height as f64);
let pos1_na = na::Vector2::<f64>::new(pos1.x / viewport_width as f64, pos1.y / viewport_height as f64);
let diff_prev = (pos0_na - pos1_na).norm();
let current_touches = {
let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
cam_control.id2active_touches.insert(touch_event.id, *touch_event);
let current_touches: Vec<Touch> = cam_control.id2active_touches.values().copied().collect();
current_touches
};
let pos0 = current_touches[0].location;
let pos1 = current_touches[1].location;
let pos0_na = na::Vector2::<f64>::new(pos0.x / viewport_width as f64, pos0.y / viewport_height as f64);
let pos1_na = na::Vector2::<f64>::new(pos1.x / viewport_width as f64, pos1.y / viewport_height as f64);
let diff_cur = (pos0_na - pos1_na).norm();
{
let inc = diff_cur - diff_prev;
let speed = 1.0;
let mut pos_lookat = scene.get_comp::<&mut PosLookat>(&self.entity).unwrap();
pos_lookat.dolly(inc as f32 * speed);
}
{
let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
cam_control.mouse_mode = CamMode::Translation;
}
let center = na::Vector2::<f64>::new((pos0.x + pos1.x) / 2.0, (pos0.y + pos1.y) / 2.0);
self.process_mouse_move(center.x, center.y, viewport_width, viewport_height, scene);
}
}
pub fn mouse_pressed(&mut self, mouse_button: &MouseButton, scene: &mut Scene) {
let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
match mouse_button {
MouseButton::Left => {
cam_control.last_press = Some(wasm_timer::Instant::now());
cam_control.mouse_mode = CamMode::Rotation;
cam_control.mouse_pressed = true;
}
MouseButton::Right => {
cam_control.mouse_mode = CamMode::Translation;
cam_control.mouse_pressed = true;
}
_ => {
cam_control.mouse_pressed = false;
}
}
}
pub fn mouse_released(&mut self, scene: &mut Scene) {
let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
cam_control.mouse_pressed = false;
cam_control.prev_mouse_pos_valid = false;
cam_control.decide_if_click();
cam_control.mouse_moved_while_pressed = false; }
pub fn process_mouse_move(&mut self, position_x: f64, position_y: f64, viewport_width: u32, viewport_height: u32, scene: &mut Scene) {
let proj = self.proj_matrix(scene);
let mut pos_lookat = scene.get_comp::<&mut PosLookat>(&self.entity).unwrap();
let mut cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
cam_control.cursor_position = Some(winit::dpi::PhysicalPosition::new(position_x, position_y));
#[allow(clippy::cast_possible_truncation)]
let (x, y) = (position_x as f32, position_y as f32);
let current_mouse = na::Vector2::<f32>::new(x, y);
#[allow(clippy::cast_precision_loss)] let viewport_size = na::Vector2::<f32>::new(viewport_width as f32, viewport_height as f32);
if cam_control.mouse_pressed {
if cam_control.prev_mouse_pos_valid {
cam_control.mouse_moved_while_pressed = true;
}
if cam_control.mouse_mode == CamMode::Rotation && cam_control.prev_mouse_pos_valid {
let speed = 2.0;
let (rot_y, mut rot_x) = Self::two_axis_rotation(
na::Vector3::from(pos_lookat.cam_axes().column(0)),
viewport_size,
speed,
cam_control.prev_mouse,
current_mouse,
);
let mut new_pos_lookat = pos_lookat.clone();
new_pos_lookat.orbit(rot_x);
let dot_up = new_pos_lookat.direction().dot(&na::Vector3::<f32>::y_axis());
let angle_vertical = dot_up.acos();
if let Some(max_vertical_angle) = cam_control.limit_max_vertical_angle {
if angle_vertical > max_vertical_angle || new_pos_lookat.up == na::Vector3::<f32>::new(0.0, -1.0, 0.0) {
rot_x = na::Rotation3::<f32>::identity();
}
}
if let Some(min_vertical_angle) = cam_control.limit_min_vertical_angle {
if angle_vertical < min_vertical_angle {
rot_x = na::Rotation3::<f32>::identity();
}
}
let rot = rot_y * rot_x;
pos_lookat.orbit(rot);
} else if cam_control.mouse_mode == CamMode::Translation && cam_control.prev_mouse_pos_valid {
let view = pos_lookat.view_matrix();
let coord = self.project(pos_lookat.lookat, view, proj, viewport_size);
let down_mouse_z = coord.z;
let pos1 = self.unproject(na::Point3::<f32>::new(x, viewport_size.y - y, down_mouse_z), view, proj, viewport_size);
let pos0 = self.unproject(
na::Point3::<f32>::new(cam_control.prev_mouse.x, viewport_size.y - cam_control.prev_mouse.y, down_mouse_z),
view,
proj,
viewport_size,
);
let diff = pos1 - pos0;
let new_pos = pos_lookat.position - diff;
pos_lookat.shift_cam(new_pos);
}
cam_control.prev_mouse = current_mouse;
cam_control.prev_mouse_pos_valid = true;
}
}
pub fn process_mouse_scroll(&mut self, delta: &MouseScrollDelta, scene: &mut Scene) {
let mut pos_lookat = scene.get_comp::<&mut PosLookat>(&self.entity).unwrap();
let cam_control = scene.get_comp::<&mut CamController>(&self.entity).unwrap();
let scroll = match delta {
MouseScrollDelta::LineDelta(_, scroll) => f64::from(scroll * 0.5),
#[allow(clippy::cast_precision_loss)] MouseScrollDelta::PixelDelta(PhysicalPosition { y: scroll, .. }) => *scroll,
};
let mut s = if scroll > 0.0 { 0.1 } else { -0.1 };
if let Some(max_dist) = cam_control.limit_max_dist {
let cur_dist = pos_lookat.dist_lookat();
if cur_dist > max_dist && s < 0.0 {
s = 0.0;
}
}
pos_lookat.dolly(s);
}
pub fn set_aspect_ratio(&mut self, val: f32, scene: &mut Scene) {
let mut proj = scene.get_comp::<&mut Projection>(&self.entity).unwrap();
if let Projection::WithFov(ref mut proj) = *proj {
proj.aspect_ratio = val;
}
}
pub fn set_aspect_ratio_maybe(&mut self, val: f32, scene: &mut Scene) {
if let Ok(mut proj) = scene.get_comp::<&mut Projection>(&self.entity) {
if let Projection::WithFov(ref mut proj) = *proj {
proj.aspect_ratio = val;
}
} else if scene.nr_renderables() != 0 {
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");
}
}
pub fn near_far(&self, scene: &mut Scene) -> (f32, f32) {
let proj = scene.get_comp::<&Projection>(&self.entity).unwrap();
proj.near_far()
}
pub fn set_target_res(&mut self, width: u32, height: u32, scene: &mut Scene) {
{
let mut res = scene.get_comp::<&mut TargetResolution>(&self.entity).unwrap();
res.width = width;
res.height = height;
}
#[allow(clippy::cast_precision_loss)]
self.set_aspect_ratio_maybe(width as f32 / height as f32, scene);
}
pub fn on_window_resize(&mut self, width: u32, height: u32, scene: &mut Scene) {
{
let mut res = scene.get_comp::<&mut TargetResolution>(&self.entity).unwrap();
if res.update_mode == TargetResolutionUpdate::WindowSize {
res.width = width;
res.height = height;
}
}
#[allow(clippy::cast_precision_loss)]
self.set_aspect_ratio_maybe(width as f32 / height as f32, scene);
}
pub fn get_target_res(&self, scene: &Scene) -> (u32, u32) {
let res = scene.get_comp::<&TargetResolution>(&self.entity).unwrap();
(res.width, res.height)
}
}