Raw File
camera.rs
/*
ENSnano, a 3d graphical application for DNA nanostructures.
    Copyright (C) 2021  Nicolas Levy <nicolaspierrelevy@gmail.com> and Nicolas Schabanel <nicolas.schabanel@ens-lyon.fr>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/
use super::maths_3d;
use super::{ClickMode, PhySize};
use iced_winit::winit;
use std::cell::RefCell;
use std::f32::consts::{FRAC_PI_2, PI};
use std::rc::Rc;
use std::time::Duration;
use ultraviolet::{Mat3, Mat4, Rotor3, Vec3};
use winit::dpi::PhysicalPosition;
use winit::event::*;

#[derive(Debug, Clone)]
pub struct Camera {
    /// The eye of the camera
    pub position: Vec3,
    /// The orientation of the camera.
    ///
    /// `rotor` is an object that can cast as a transformation of the world basis into the camera's
    /// basis. The camera is looking in the opposite direction of its z axis with its y axis
    /// pointing up.
    pub rotor: Rotor3,
}

pub type CameraPtr = Rc<RefCell<Camera>>;

impl Camera {
    pub fn new<V: Into<Vec3>>(position: V, rotor: Rotor3) -> Self {
        Self {
            position: position.into(),
            rotor,
        }
    }

    /// The view matrix of the camera
    pub fn calc_matrix(&self) -> Mat4 {
        let at = self.position + self.direction();
        Mat4::look_at(self.position, at, self.up_vec())
    }

    /// The direction of the camera, expressed in the world coordinates
    pub fn direction(&self) -> Vec3 {
        self.rotor.reversed() * Vec3::from([0., 0., -1.])
    }

    /// The right vector of the camera, expressed in the world coordinates
    pub fn right_vec(&self) -> Vec3 {
        self.rotor.reversed() * Vec3::from([1., 0., 0.])
    }

    /// The up vector of the camera, expressed in the world coordinates.
    pub fn up_vec(&self) -> Vec3 {
        self.right_vec().cross(self.direction())
    }

    pub fn get_basis(&self) -> maths_3d::Basis3D {
        maths_3d::Basis3D::from_vecs(self.right_vec(), self.up_vec(), -self.direction())
    }
}

#[derive(Debug)]
/// This structure holds the information needed to compute the projection matrix.
pub struct Projection {
    aspect: f32,
    /// Field of view in *radiants*
    fovy: f32,
    znear: f32,
    zfar: f32,
}

pub type ProjectionPtr = Rc<RefCell<Projection>>;

impl Projection {
    pub fn new(width: u32, height: u32, fovy: f32, znear: f32, zfar: f32) -> Self {
        Self {
            aspect: width as f32 / height as f32,
            fovy,
            znear,
            zfar,
        }
    }

    pub fn resize(&mut self, width: u32, height: u32) {
        self.aspect = width as f32 / height as f32;
    }

    /// Computes the projection matrix.
    pub fn calc_matrix(&self) -> Mat4 {
        ultraviolet::projection::rh_yup::perspective_wgpu_dx(
            self.fovy,
            self.aspect,
            self.znear,
            self.zfar,
        )
    }

    pub fn get_fovy(&self) -> f32 {
        self.fovy
    }

    pub fn get_ratio(&self) -> f32 {
        self.aspect
    }

    pub fn cube_dist(&self) -> f32 {
        2f32.sqrt() / (self.fovy / 2.).tan() * 1f32.max(1. / self.aspect)
    }
}

pub struct CameraController {
    speed: f32,
    pub sensitivity: f32,
    amount_up: f32,
    amount_down: f32,
    amount_left: f32,
    amount_right: f32,
    mouse_horizontal: f32,
    mouse_vertical: f32,
    scroll: f32,
    #[allow(dead_code)]
    last_rotor: Rotor3,
    processed_move: bool,
    camera: CameraPtr,
    cam0: Camera,
    projection: ProjectionPtr,
    pivot_point: Option<Vec3>,
    zoom_plane: Option<Plane>,
    x_scroll: f32,
    y_scroll: f32,
}

impl CameraController {
    pub fn new(speed: f32, sensitivity: f32, camera: CameraPtr, projection: ProjectionPtr) -> Self {
        Self {
            speed,
            sensitivity,
            amount_left: 0.0,
            amount_right: 0.0,
            amount_up: 0.0,
            amount_down: 0.0,
            mouse_horizontal: 0.0,
            mouse_vertical: 0.0,
            scroll: 0.0,
            last_rotor: camera.borrow().rotor,
            processed_move: false,
            camera: camera.clone(),
            cam0: camera.borrow().clone(), // clone the camera not the pointer !
            projection,
            pivot_point: None,
            zoom_plane: None,
            x_scroll: 0.,
            y_scroll: 0.,
        }
    }

    pub fn process_keyboard(&mut self, key: VirtualKeyCode, state: ElementState) -> bool {
        let amount = if state == ElementState::Pressed {
            1.0
        } else {
            0.0
        };
        match key {
            VirtualKeyCode::W | VirtualKeyCode::Up => {
                self.amount_up = amount;
                true
            }
            VirtualKeyCode::S | VirtualKeyCode::Down => {
                self.amount_down = amount;
                true
            }
            VirtualKeyCode::A | VirtualKeyCode::Left => {
                self.amount_left = amount;
                true
            }
            VirtualKeyCode::D | VirtualKeyCode::Right => {
                self.amount_right = amount;
                true
            }
            VirtualKeyCode::H if amount > 0. => {
                self.rotate_camera_around(
                    FRAC_PI_2 / 20.,
                    0.,
                    self.pivot_point.unwrap_or_else(Vec3::zero),
                );
                self.cam0 = self.camera.borrow().clone();
                true
            }
            VirtualKeyCode::L if amount > 0. => {
                self.rotate_camera_around(
                    -FRAC_PI_2 / 20.,
                    0.,
                    self.pivot_point.unwrap_or_else(Vec3::zero),
                );
                self.cam0 = self.camera.borrow().clone();
                true
            }
            VirtualKeyCode::J if amount > 0. => {
                self.rotate_camera_around(
                    0.,
                    FRAC_PI_2 / 20.,
                    self.pivot_point.unwrap_or_else(Vec3::zero),
                );
                self.cam0 = self.camera.borrow().clone();
                true
            }
            VirtualKeyCode::K if amount > 0. => {
                self.rotate_camera_around(
                    0.,
                    -FRAC_PI_2 / 20.,
                    self.pivot_point.unwrap_or_else(Vec3::zero),
                );
                self.cam0 = self.camera.borrow().clone();
                true
            }
            _ => false,
        }
    }

    pub fn is_moving(&self) -> bool {
        self.amount_down > 0.
            || self.amount_up > 0.
            || self.amount_right > 0.
            || self.amount_left > 0.
            || self.scroll.abs() > 0.
    }

    pub fn set_pivot_point(&mut self, point: Option<Vec3>) {
        if let Some(origin) = point {
            self.zoom_plane = Some(Plane {
                origin,
                normal: (self.camera.borrow().position - origin),
            });
        }
        self.pivot_point = point
    }

    pub fn get_projection(&self, origin: Vec3, x: f64, y: f64) -> Vec3 {
        let plane = Plane {
            origin,
            normal: (self.camera.borrow().position - origin),
        };
        maths_3d::unproject_point_on_plane(
            plane.origin,
            plane.normal,
            self.camera.clone(),
            self.projection.clone(),
            x as f32,
            y as f32,
        )
        .unwrap_or(origin)
    }

    pub fn process_mouse(&mut self, mouse_dx: f64, mouse_dy: f64) {
        self.mouse_horizontal = -mouse_dx as f32;
        self.mouse_vertical = -mouse_dy as f32;
        self.processed_move = true;
    }

    pub fn process_scroll(&mut self, delta: &MouseScrollDelta, x_cursor: f32, y_cursor: f32) {
        self.x_scroll = x_cursor;
        self.y_scroll = y_cursor;
        self.scroll = match delta {
            // I'm assuming a line is about 100 pixels
            MouseScrollDelta::LineDelta(_, scroll) => scroll.min(1.).max(-1.) * 10.,
            MouseScrollDelta::PixelDelta(PhysicalPosition { y: scroll, .. }) => *scroll as f32,
        };
    }

    /// Rotate the head of the camera on its yz plane and xz plane according to the values of
    /// self.mouse_horizontal and self.mouse_vertical
    fn process_angles(&mut self) {
        let xz_angle = self.mouse_horizontal * FRAC_PI_2;
        let yz_angle = self.mouse_vertical * FRAC_PI_2;

        // We want to build a rotation that will
        // first maps (1, 0, 0) to (cos(yz_angle), -sin(yz_angle), 0)
        // and then (0, 1, 0) to (0, cos(xz_angle), -sin(yz_angle))

        let rotation = Rotor3::from_rotation_xz(xz_angle) * Rotor3::from_rotation_yz(yz_angle);

        self.camera.borrow_mut().rotor = rotation * self.cam0.rotor;

        // Since we have rotated the camera we can reset those values
        self.mouse_horizontal = 0.0;
        self.mouse_vertical = 0.0;
    }

    /// Translate the camera
    fn translate_camera(&mut self) {
        let right = self.mouse_horizontal;
        let up = -self.mouse_vertical;

        let scale = if let Some(pivot) = self.pivot_point {
            (pivot - self.camera.borrow().position).dot(self.camera.borrow().direction())
        } else if let Some(origin) = self.zoom_plane.as_ref().map(|plane| plane.origin) {
            (origin - self.camera.borrow().position).dot(self.camera.borrow().direction())
        } else {
            10.
        };

        let right_vec =
            self.camera.borrow().right_vec() * scale * self.projection.borrow().get_ratio();
        let up_vec = self.camera.borrow().up_vec() * scale;

        let old_pos = self.cam0.position;
        self.camera.borrow_mut().position = old_pos + right * right_vec + up * up_vec;

        self.mouse_horizontal = 0.0;
        self.mouse_vertical = 0.0;
    }

    /// Move the camera according to the keyboard input
    fn move_camera(&mut self, dt: Duration) {
        let dt = dt.as_secs_f32();

        // Move forward/backward and left/right
        let right = self.camera.borrow().right_vec();
        let up_vec = self.camera.borrow().up_vec();

        {
            let mut camera = self.camera.borrow_mut();
            camera.position += right * (self.amount_right - self.amount_left) * self.speed * dt;
            camera.position += up_vec * (self.amount_up - self.amount_down) * self.speed * dt;
        }

        let pivot = self.zoom_plane.as_ref().and_then(|plane| {
            if self
                .camera
                .borrow()
                .direction()
                .normalized()
                .dot(-plane.normal.normalized())
                > 0.9
            {
                maths_3d::unproject_point_on_plane(
                    plane.origin,
                    plane.normal,
                    self.camera.clone(),
                    self.projection.clone(),
                    self.x_scroll,
                    self.y_scroll,
                )
            } else {
                None
            }
        });

        // Move in/out (aka. "zoom")
        // Note: this isn't an actual zoom. The camera's position
        // changes when zooming. I've added this to make it easier
        // to get closer to an object you want to focus on.
        let scrollward = if let Some(pivot) = pivot {
            let to_pivot = pivot - self.camera.borrow().position;
            let score = to_pivot
                .normalized()
                .dot(self.camera.borrow().direction().normalized());
            if score < 0. {
                self.camera.borrow().direction()
            } else if (pivot - self.camera.borrow().position).mag() > 0.1 {
                to_pivot
            } else {
                self.camera.borrow().direction()
            }
        } else {
            10. * self.camera.borrow().direction()
        };
        {
            let mut camera = self.camera.borrow_mut();
            camera.position += scrollward * self.scroll * self.speed * self.sensitivity * 33e-3;
        }
        self.cam0 = self.camera.borrow().clone();
        self.scroll = 0.;
    }

    pub fn update_camera(&mut self, dt: Duration, click_mode: ClickMode) {
        if self.processed_move {
            match click_mode {
                ClickMode::RotateCam => self.process_angles(),
                ClickMode::TranslateCam => self.translate_camera(),
            }
        }
        if self.is_moving() {
            self.move_camera(dt);
        }
    }

    pub fn init_movement(&mut self) {
        self.processed_move = false;
    }

    pub fn end_movement(&mut self) {
        self.last_rotor = self.camera.borrow().rotor;
        self.cam0 = self.camera.borrow().clone();
        self.mouse_horizontal = 0.;
        self.mouse_vertical = 0.;
        if let Some(origin) = self.pivot_point {
            self.zoom_plane = Some(Plane {
                origin,
                normal: (self.camera.borrow().position - origin),
            });
        }
    }

    pub fn teleport_camera(&mut self, position: Vec3, rotation: Rotor3) {
        let mut camera = self.camera.borrow_mut();
        camera.position = position;
        camera.rotor = rotation;
        self.last_rotor = rotation;
        self.cam0 = camera.clone();
    }

    pub fn set_camera_position(&mut self, position: Vec3) {
        let mut camera = self.camera.borrow_mut();
        camera.position = position;
        self.cam0 = camera.clone();
    }

    pub fn resize(&mut self, size: PhySize) {
        self.projection.borrow_mut().resize(size.width, size.height)
    }

    /// Swing the camera arrond `self.pivot_point`. Assumes that the pivot_point is where the
    /// camera points at.
    pub fn swing(&mut self, x: f64, y: f64) {
        let angle_yz = -(y.min(1.).max(-1.)) as f32 * PI;
        let angle_xz = x.min(1.).max(-1.) as f32 * PI;
        if let Some(pivot) = self.pivot_point {
            self.rotate_camera_around(angle_xz, angle_yz, pivot);
        } else {
            self.small_rotate_camera(angle_xz, angle_yz, None);
        }
    }

    /// Rotate the camera arround a point.
    /// `point` is given in the world's coordiantes.
    pub fn rotate_camera_around(&mut self, xz_angle: f32, yz_angle: f32, point: Vec3) {
        // We first modify the camera orientation and then position it at the correct position
        let to_point = point - self.camera.borrow().position;
        let up = to_point.dot(self.camera.borrow().up_vec());
        let right = to_point.dot(self.camera.borrow().right_vec());
        let dir = to_point.dot(self.camera.borrow().direction());
        let rotation = Rotor3::from_rotation_xz(xz_angle) * Rotor3::from_rotation_yz(yz_angle);
        self.camera.borrow_mut().rotor = rotation * self.cam0.rotor;
        let new_direction = self.camera.borrow().direction();
        let new_up = self.camera.borrow().up_vec();
        let new_right = self.camera.borrow().right_vec();
        self.camera.borrow_mut().position =
            point - dir * new_direction - up * new_up - right * new_right
    }

    /// Modify the camera's rotor so that the camera looks at `point`.
    /// `point` is given in the world's coordinates
    pub fn look_at_point(&mut self, point: Vec3, up: Vec3) {
        let new_direction = (point - self.camera.borrow().position).normalized();
        let right = new_direction.cross(up);
        let matrix = Mat3::new(right, up, -new_direction);
        let rotor = matrix.into_rotor3();
        self.camera.borrow_mut().rotor = rotor;
    }

    /// Modify the camera's rotor so that the camera looks at `self.position + point`.
    /// `point` is given in the world's coordinates
    pub fn look_at_orientation(&mut self, point: Vec3, up: Vec3, pivot: Option<Vec3>) {
        let dist = pivot.map(|p| (self.camera.borrow().position - p).mag());
        let point = self.camera.borrow().position + point;
        self.look_at_point(point, up);
        if let Some(dist) = dist {
            let new_pos = pivot.unwrap() - dist * self.camera.borrow().direction();
            self.camera.borrow_mut().position = new_pos;
            self.cam0.position = new_pos;
        }
        self.cam0.rotor = self.camera.borrow().rotor;
    }

    fn small_rotate_camera(&mut self, angle_xz: f32, angle_yz: f32, pivot: Option<Vec3>) {
        let dist = pivot.map(|p| (self.camera.borrow().position - p).mag());
        let rotation = Rotor3::from_rotation_yz(angle_yz) * Rotor3::from_rotation_xz(angle_xz);

        // and we apply this rotation to the camera
        let new_rotor = rotation * self.cam0.rotor;
        self.camera.borrow_mut().rotor = new_rotor;
        if let Some(dist) = dist {
            let new_pos = pivot.unwrap() - dist * self.camera.borrow().direction();
            self.camera.borrow_mut().position = new_pos;
            self.cam0.position = new_pos;
        }
    }

    pub fn rotate_camera(&mut self, angle_xz: f32, angle_yz: f32, pivot: Option<Vec3>) {
        let dist = pivot.map(|p| (self.camera.borrow().position - p).mag());
        let rotation = Rotor3::from_rotation_yz(angle_yz) * Rotor3::from_rotation_xz(angle_xz);

        // and we apply this rotation to the camera
        let new_rotor = rotation * self.cam0.rotor;
        self.camera.borrow_mut().rotor = new_rotor;
        self.cam0.rotor = new_rotor;
        if let Some(dist) = dist {
            let new_pos = pivot.unwrap() - dist * self.camera.borrow().direction();
            self.camera.borrow_mut().position = new_pos;
            self.cam0.position = new_pos;
        }
    }

    pub fn tilt_camera(&mut self, angle_xy: f32) {
        let rotation = Rotor3::from_rotation_xy(angle_xy);

        let new_rotor = rotation * self.cam0.rotor;
        self.camera.borrow_mut().rotor = new_rotor;
        self.cam0.rotor = new_rotor;
    }

    pub fn shift(&mut self) {
        let vec = 0.01 * self.camera.borrow().right_vec() + 0.01 * self.camera.borrow().up_vec();
        self.camera.borrow_mut().position += vec;
        self.cam0.position = self.camera.borrow().position;
        self.cam0.rotor = self.camera.borrow().rotor;
    }

    pub fn center_camera(&mut self, center: Vec3) {
        let new_position = center - 5. * self.camera.borrow().direction();
        let orientation = self.camera.borrow().rotor;
        self.teleport_camera(new_position, orientation);
    }
}

/// A plane in space defined by an origin and a normal
#[derive(Debug)]
struct Plane {
    origin: Vec3,
    normal: Vec3,
}
back to top