2019-06-26 10:50:55 +00:00
|
|
|
use common::vol::{ReadVol, Vox};
|
2019-01-11 23:18:34 +00:00
|
|
|
use std::f32::consts::PI;
|
2020-01-08 17:09:54 +00:00
|
|
|
use treeculler::Frustum;
|
2019-01-11 23:18:34 +00:00
|
|
|
use vek::*;
|
|
|
|
|
2019-10-10 22:24:24 +00:00
|
|
|
const NEAR_PLANE: f32 = 0.5;
|
2019-11-20 09:59:27 +00:00
|
|
|
const FAR_PLANE: f32 = 100000.0;
|
2019-01-11 23:18:34 +00:00
|
|
|
|
2020-03-16 12:19:51 +00:00
|
|
|
const FIRST_PERSON_INTERP_TIME: f32 = 0.1;
|
2019-08-18 06:55:14 +00:00
|
|
|
const THIRD_PERSON_INTERP_TIME: f32 = 0.1;
|
2020-07-09 17:12:11 +00:00
|
|
|
const FREEFLY_INTERP_TIME: f32 = 0.0;
|
2020-02-17 15:28:17 +00:00
|
|
|
const LERP_ORI_RATE: f32 = 15.0;
|
2019-07-24 14:09:16 +00:00
|
|
|
pub const MIN_ZOOM: f32 = 0.1;
|
2019-05-05 15:17:57 +00:00
|
|
|
|
2019-07-22 19:11:00 +00:00
|
|
|
// Possible TODO: Add more modes
|
2019-08-18 09:01:57 +00:00
|
|
|
#[derive(PartialEq, Clone, Copy, Eq, Hash)]
|
2019-07-22 19:11:00 +00:00
|
|
|
pub enum CameraMode {
|
2020-04-04 19:36:55 +00:00
|
|
|
FirstPerson = 0,
|
|
|
|
ThirdPerson = 1,
|
2020-07-09 17:12:11 +00:00
|
|
|
Freefly = 2,
|
2019-07-22 19:11:00 +00:00
|
|
|
}
|
|
|
|
|
2019-08-18 09:01:57 +00:00
|
|
|
impl Default for CameraMode {
|
2020-02-01 20:39:39 +00:00
|
|
|
fn default() -> Self { Self::ThirdPerson }
|
2019-08-18 09:01:57 +00:00
|
|
|
}
|
|
|
|
|
2020-02-29 03:59:11 +00:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct Dependents {
|
|
|
|
pub view_mat: Mat4<f32>,
|
|
|
|
pub proj_mat: Mat4<f32>,
|
|
|
|
pub cam_pos: Vec3<f32>,
|
|
|
|
}
|
|
|
|
|
2019-01-11 23:18:34 +00:00
|
|
|
pub struct Camera {
|
2019-05-05 15:17:57 +00:00
|
|
|
tgt_focus: Vec3<f32>,
|
2019-01-11 23:18:34 +00:00
|
|
|
focus: Vec3<f32>,
|
2020-02-17 15:28:17 +00:00
|
|
|
tgt_ori: Vec3<f32>,
|
2019-01-11 23:18:34 +00:00
|
|
|
ori: Vec3<f32>,
|
2019-07-25 10:26:25 +00:00
|
|
|
tgt_dist: f32,
|
2019-01-11 23:18:34 +00:00
|
|
|
dist: f32,
|
|
|
|
fov: f32,
|
|
|
|
aspect: f32,
|
2019-07-22 19:11:00 +00:00
|
|
|
mode: CameraMode,
|
2019-05-05 15:17:57 +00:00
|
|
|
|
|
|
|
last_time: Option<f64>,
|
2020-02-29 03:59:11 +00:00
|
|
|
|
|
|
|
dependents: Dependents,
|
2019-01-11 23:18:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Camera {
|
|
|
|
/// Create a new `Camera` with default parameters.
|
2019-07-22 19:11:00 +00:00
|
|
|
pub fn new(aspect: f32, mode: CameraMode) -> Self {
|
2019-01-11 23:18:34 +00:00
|
|
|
Self {
|
2019-05-05 15:17:57 +00:00
|
|
|
tgt_focus: Vec3::unit_z() * 10.0,
|
2019-01-13 20:53:55 +00:00
|
|
|
focus: Vec3::unit_z() * 10.0,
|
2020-02-17 15:28:17 +00:00
|
|
|
tgt_ori: Vec3::zero(),
|
2019-01-11 23:18:34 +00:00
|
|
|
ori: Vec3::zero(),
|
2019-05-05 15:17:57 +00:00
|
|
|
tgt_dist: 10.0,
|
2019-04-16 18:01:56 +00:00
|
|
|
dist: 10.0,
|
2019-08-05 16:47:07 +00:00
|
|
|
fov: 1.1,
|
2019-04-14 15:05:51 +00:00
|
|
|
aspect,
|
2019-07-22 19:11:00 +00:00
|
|
|
mode,
|
|
|
|
|
2019-05-05 15:17:57 +00:00
|
|
|
last_time: None,
|
2020-02-29 03:59:11 +00:00
|
|
|
|
|
|
|
dependents: Dependents {
|
|
|
|
view_mat: Mat4::identity(),
|
|
|
|
proj_mat: Mat4::identity(),
|
|
|
|
cam_pos: Vec3::zero(),
|
|
|
|
},
|
2019-01-11 23:18:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-01 20:39:39 +00:00
|
|
|
/// Compute the transformation matrices (view matrix and projection matrix)
|
|
|
|
/// and position of the camera.
|
2020-02-29 03:59:11 +00:00
|
|
|
pub fn compute_dependents(&mut self, terrain: &impl ReadVol) {
|
2019-05-05 15:17:57 +00:00
|
|
|
let dist = {
|
2020-07-09 17:12:11 +00:00
|
|
|
let (start, end) = (self.focus - self.forward() * self.dist, self.focus);
|
2019-05-05 15:17:57 +00:00
|
|
|
|
2020-02-29 03:59:11 +00:00
|
|
|
match terrain
|
2019-05-12 05:37:10 +00:00
|
|
|
.ray(start, end)
|
|
|
|
.ignore_error()
|
|
|
|
.max_iter(500)
|
2019-06-26 10:50:55 +00:00
|
|
|
.until(|b| b.is_empty())
|
2019-05-12 05:37:10 +00:00
|
|
|
.cast()
|
|
|
|
{
|
2019-06-26 10:50:55 +00:00
|
|
|
(d, Ok(Some(_))) => f32::min(self.dist - d - 0.03, self.dist),
|
2019-05-05 15:17:57 +00:00
|
|
|
(_, Ok(None)) => self.dist,
|
|
|
|
(_, Err(_)) => self.dist,
|
2019-05-05 15:54:08 +00:00
|
|
|
}
|
|
|
|
.max(0.0)
|
2019-05-05 15:17:57 +00:00
|
|
|
};
|
|
|
|
|
2020-02-29 03:59:11 +00:00
|
|
|
self.dependents.view_mat = Mat4::<f32>::identity()
|
2019-05-05 15:17:57 +00:00
|
|
|
* Mat4::translation_3d(-Vec3::unit_z() * dist)
|
2019-01-11 23:18:34 +00:00
|
|
|
* Mat4::rotation_z(self.ori.z)
|
|
|
|
* Mat4::rotation_x(self.ori.y)
|
|
|
|
* Mat4::rotation_y(self.ori.x)
|
|
|
|
* Mat4::rotation_3d(PI / 2.0, -Vec4::unit_x())
|
|
|
|
* Mat4::translation_3d(-self.focus);
|
|
|
|
|
2020-02-29 03:59:11 +00:00
|
|
|
self.dependents.proj_mat =
|
|
|
|
Mat4::perspective_rh_no(self.fov, self.aspect, NEAR_PLANE, FAR_PLANE);
|
2019-01-11 23:18:34 +00:00
|
|
|
|
2019-05-17 09:22:32 +00:00
|
|
|
// TODO: Make this more efficient.
|
2020-02-29 03:59:11 +00:00
|
|
|
self.dependents.cam_pos = Vec3::from(self.dependents.view_mat.inverted() * Vec4::unit_w());
|
2019-01-11 23:18:34 +00:00
|
|
|
}
|
2019-01-12 01:14:58 +00:00
|
|
|
|
2020-02-29 03:59:11 +00:00
|
|
|
pub fn frustum(&self) -> Frustum<f32> {
|
|
|
|
Frustum::from_modelview_projection(
|
|
|
|
(self.dependents.proj_mat * self.dependents.view_mat).into_col_arrays(),
|
|
|
|
)
|
2019-08-16 13:45:10 +00:00
|
|
|
}
|
|
|
|
|
2020-02-29 03:59:11 +00:00
|
|
|
pub fn dependents(&self) -> Dependents { self.dependents.clone() }
|
|
|
|
|
2020-02-01 20:39:39 +00:00
|
|
|
/// Rotate the camera about its focus by the given delta, limiting the input
|
|
|
|
/// accordingly.
|
2019-01-12 13:56:34 +00:00
|
|
|
pub fn rotate_by(&mut self, delta: Vec3<f32>) {
|
2019-03-02 19:43:51 +00:00
|
|
|
// Wrap camera yaw
|
2020-02-17 15:28:17 +00:00
|
|
|
self.tgt_ori.x = (self.tgt_ori.x + delta.x).rem_euclid(2.0 * PI);
|
2019-01-12 13:56:34 +00:00
|
|
|
// Clamp camera pitch to the vertical limits
|
2020-02-17 15:28:17 +00:00
|
|
|
self.tgt_ori.y = (self.tgt_ori.y + delta.y)
|
|
|
|
.min(PI / 2.0 - 0.0001)
|
|
|
|
.max(-PI / 2.0 + 0.0001);
|
2019-03-02 19:43:51 +00:00
|
|
|
// Wrap camera roll
|
2020-02-17 15:28:17 +00:00
|
|
|
self.tgt_ori.z = (self.tgt_ori.z + delta.z).rem_euclid(2.0 * PI);
|
2019-01-12 13:56:34 +00:00
|
|
|
}
|
|
|
|
|
2019-05-17 09:22:32 +00:00
|
|
|
/// Set the orientation of the camera about its focus.
|
2020-02-17 15:28:17 +00:00
|
|
|
pub fn set_orientation(&mut self, ori: Vec3<f32>) {
|
2019-05-12 13:02:47 +00:00
|
|
|
// Wrap camera yaw
|
2020-02-17 15:28:17 +00:00
|
|
|
self.tgt_ori.x = ori.x.rem_euclid(2.0 * PI);
|
2019-05-12 13:02:47 +00:00
|
|
|
// Clamp camera pitch to the vertical limits
|
2020-02-17 15:28:17 +00:00
|
|
|
self.tgt_ori.y = ori.y.min(PI / 2.0 - 0.0001).max(-PI / 2.0 + 0.0001);
|
2019-05-12 13:02:47 +00:00
|
|
|
// Wrap camera roll
|
2020-02-17 15:28:17 +00:00
|
|
|
self.tgt_ori.z = ori.z.rem_euclid(2.0 * PI);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set the orientation of the camera about its focus without lerping.
|
|
|
|
pub fn set_ori_instant(&mut self, ori: Vec3<f32>) {
|
|
|
|
// Wrap camera yaw
|
|
|
|
self.ori.x = ori.x.rem_euclid(2.0 * PI);
|
|
|
|
// Clamp camera pitch to the vertical limits
|
|
|
|
self.ori.y = ori.y.min(PI / 2.0 - 0.0001).max(-PI / 2.0 + 0.0001);
|
|
|
|
// Wrap camera roll
|
|
|
|
self.ori.z = ori.z.rem_euclid(2.0 * PI);
|
2019-05-12 13:02:47 +00:00
|
|
|
}
|
|
|
|
|
2019-01-30 12:11:34 +00:00
|
|
|
/// Zoom the camera by the given delta, limiting the input accordingly.
|
|
|
|
pub fn zoom_by(&mut self, delta: f32) {
|
2020-07-09 17:12:11 +00:00
|
|
|
if self.mode == CameraMode::ThirdPerson {
|
|
|
|
// Clamp camera dist to the 2 <= x <= infinity range
|
|
|
|
self.tgt_dist = (self.tgt_dist + delta).max(2.0);
|
|
|
|
}
|
2019-05-05 15:17:57 +00:00
|
|
|
}
|
|
|
|
|
2019-08-13 17:54:13 +00:00
|
|
|
/// Zoom with the ability to switch between first and third-person mode.
|
|
|
|
pub fn zoom_switch(&mut self, delta: f32) {
|
|
|
|
if delta > 0_f32 || self.mode != CameraMode::FirstPerson {
|
|
|
|
let t = self.tgt_dist + delta;
|
2020-05-18 22:40:28 +00:00
|
|
|
const MIN_THIRD_PERSON: f32 = 2.35;
|
2019-08-13 17:54:13 +00:00
|
|
|
match self.mode {
|
|
|
|
CameraMode::ThirdPerson => {
|
2020-05-18 22:40:28 +00:00
|
|
|
if t < MIN_THIRD_PERSON {
|
2019-08-13 17:54:13 +00:00
|
|
|
self.set_mode(CameraMode::FirstPerson);
|
|
|
|
} else {
|
|
|
|
self.tgt_dist = t;
|
|
|
|
}
|
2020-02-01 20:39:39 +00:00
|
|
|
},
|
2019-08-13 17:54:13 +00:00
|
|
|
CameraMode::FirstPerson => {
|
|
|
|
self.set_mode(CameraMode::ThirdPerson);
|
2020-05-18 22:40:28 +00:00
|
|
|
self.tgt_dist = MIN_THIRD_PERSON;
|
2020-02-01 20:39:39 +00:00
|
|
|
},
|
2020-07-09 17:12:11 +00:00
|
|
|
_ => {},
|
2019-08-13 17:54:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-18 23:35:58 +00:00
|
|
|
/// Get the distance of the camera from the focus
|
2020-05-18 22:40:28 +00:00
|
|
|
pub fn get_distance(&self) -> f32 { self.dist }
|
2019-06-10 10:50:48 +00:00
|
|
|
|
2020-05-18 23:35:58 +00:00
|
|
|
/// Set the distance of the camera from the focus (i.e., zoom).
|
2020-02-01 20:39:39 +00:00
|
|
|
pub fn set_distance(&mut self, dist: f32) { self.tgt_dist = dist; }
|
2019-05-12 13:02:47 +00:00
|
|
|
|
2020-04-23 22:59:34 +00:00
|
|
|
pub fn update(&mut self, time: f64, dt: f32, smoothing_enabled: bool) {
|
2019-05-05 15:17:57 +00:00
|
|
|
// This is horribly frame time dependent, but so is most of the game
|
|
|
|
let delta = self.last_time.replace(time).map_or(0.0, |t| time - t);
|
|
|
|
if (self.dist - self.tgt_dist).abs() > 0.01 {
|
2019-08-18 06:55:14 +00:00
|
|
|
self.dist = f32::lerp(
|
|
|
|
self.dist,
|
|
|
|
self.tgt_dist,
|
2020-05-18 22:40:28 +00:00
|
|
|
0.65 * (delta as f32) / self.interp_time(),
|
2019-08-18 06:55:14 +00:00
|
|
|
);
|
2019-05-05 15:17:57 +00:00
|
|
|
}
|
|
|
|
|
2020-05-18 22:40:28 +00:00
|
|
|
if (self.focus - self.tgt_focus).magnitude_squared() > 0.001 {
|
|
|
|
let lerped_focus = Lerp::lerp(
|
2019-08-18 06:55:14 +00:00
|
|
|
self.focus,
|
|
|
|
self.tgt_focus,
|
2020-05-18 23:35:58 +00:00
|
|
|
(delta as f32) / self.interp_time()
|
|
|
|
* if matches!(self.mode, CameraMode::FirstPerson) {
|
|
|
|
2.0
|
|
|
|
} else {
|
|
|
|
1.0
|
|
|
|
},
|
2019-08-18 06:55:14 +00:00
|
|
|
);
|
2020-05-18 22:40:28 +00:00
|
|
|
|
2020-06-01 20:33:20 +00:00
|
|
|
self.focus.x = lerped_focus.x;
|
|
|
|
self.focus.y = lerped_focus.y;
|
2020-05-18 22:40:28 +00:00
|
|
|
|
|
|
|
// Always lerp in z
|
|
|
|
self.focus.z = lerped_focus.z;
|
2019-08-18 06:55:14 +00:00
|
|
|
}
|
2020-02-17 15:28:17 +00:00
|
|
|
|
|
|
|
let lerp_angle = |a: f32, b: f32, rate: f32| {
|
|
|
|
let offs = [-2.0 * PI, 0.0, 2.0 * PI]
|
|
|
|
.iter()
|
|
|
|
.min_by_key(|offs: &&f32| ((a - (b + *offs)).abs() * 1000.0) as i32)
|
|
|
|
.unwrap();
|
|
|
|
Lerp::lerp(a, b + *offs, rate)
|
|
|
|
};
|
|
|
|
|
2020-04-23 22:59:34 +00:00
|
|
|
if smoothing_enabled {
|
|
|
|
self.set_ori_instant(Vec3::new(
|
|
|
|
lerp_angle(self.ori.x, self.tgt_ori.x, LERP_ORI_RATE * dt),
|
|
|
|
Lerp::lerp(self.ori.y, self.tgt_ori.y, LERP_ORI_RATE * dt),
|
|
|
|
lerp_angle(self.ori.z, self.tgt_ori.z, LERP_ORI_RATE * dt),
|
|
|
|
));
|
|
|
|
} else {
|
|
|
|
self.set_ori_instant(self.tgt_ori)
|
|
|
|
};
|
2019-08-18 06:55:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn interp_time(&self) -> f32 {
|
|
|
|
match self.mode {
|
|
|
|
CameraMode::FirstPerson => FIRST_PERSON_INTERP_TIME,
|
|
|
|
CameraMode::ThirdPerson => THIRD_PERSON_INTERP_TIME,
|
2020-07-09 17:12:11 +00:00
|
|
|
CameraMode::Freefly => FREEFLY_INTERP_TIME,
|
2019-05-05 15:17:57 +00:00
|
|
|
}
|
2019-01-30 12:11:34 +00:00
|
|
|
}
|
|
|
|
|
2019-01-12 01:14:58 +00:00
|
|
|
/// Get the focus position of the camera.
|
2020-02-06 17:34:32 +00:00
|
|
|
pub fn get_focus_pos(&self) -> Vec3<f32> { self.focus }
|
2020-02-01 20:39:39 +00:00
|
|
|
|
2019-01-12 01:14:58 +00:00
|
|
|
/// Set the focus position of the camera.
|
2020-02-01 20:39:39 +00:00
|
|
|
pub fn set_focus_pos(&mut self, focus: Vec3<f32>) { self.tgt_focus = focus; }
|
2019-01-23 22:39:31 +00:00
|
|
|
|
|
|
|
/// Get the aspect ratio of the camera.
|
2020-02-01 20:39:39 +00:00
|
|
|
pub fn get_aspect_ratio(&self) -> f32 { self.aspect }
|
|
|
|
|
2019-01-23 22:39:31 +00:00
|
|
|
/// Set the aspect ratio of the camera.
|
2019-04-29 20:37:19 +00:00
|
|
|
pub fn set_aspect_ratio(&mut self, aspect: f32) {
|
2019-05-17 05:13:14 +00:00
|
|
|
self.aspect = if aspect.is_normal() { aspect } else { 1.0 };
|
2019-04-29 20:37:19 +00:00
|
|
|
}
|
2019-03-02 03:48:30 +00:00
|
|
|
|
2019-05-17 09:22:32 +00:00
|
|
|
/// Get the orientation of the camera.
|
2020-02-01 20:39:39 +00:00
|
|
|
pub fn get_orientation(&self) -> Vec3<f32> { self.ori }
|
2019-05-20 06:09:20 +00:00
|
|
|
|
|
|
|
/// Get the field of view of the camera in radians.
|
2020-02-01 20:39:39 +00:00
|
|
|
pub fn get_fov(&self) -> f32 { self.fov }
|
2019-07-22 19:11:00 +00:00
|
|
|
|
2019-08-05 16:37:52 +00:00
|
|
|
/// Set the field of view of the camera in radians.
|
2020-02-01 20:39:39 +00:00
|
|
|
pub fn set_fov(&mut self, fov: f32) { self.fov = fov; }
|
2019-08-05 16:37:52 +00:00
|
|
|
|
|
|
|
/// Set the FOV in degrees
|
|
|
|
pub fn set_fov_deg(&mut self, fov: u16) {
|
2019-08-05 16:47:07 +00:00
|
|
|
//Magic value comes from pi/180; no use recalculating.
|
2019-08-05 16:37:52 +00:00
|
|
|
self.set_fov((fov as f32) * 0.01745329)
|
|
|
|
}
|
|
|
|
|
2019-07-22 19:11:00 +00:00
|
|
|
/// Set the mode of the camera.
|
|
|
|
pub fn set_mode(&mut self, mode: CameraMode) {
|
2019-07-25 10:26:25 +00:00
|
|
|
if self.mode != mode {
|
|
|
|
self.mode = mode;
|
|
|
|
match self.mode {
|
|
|
|
CameraMode::ThirdPerson => {
|
2019-07-23 19:40:56 +00:00
|
|
|
self.zoom_by(5.0);
|
2020-02-01 20:39:39 +00:00
|
|
|
},
|
2019-07-25 10:26:25 +00:00
|
|
|
CameraMode::FirstPerson => {
|
|
|
|
self.set_distance(MIN_ZOOM);
|
2020-02-01 20:39:39 +00:00
|
|
|
},
|
2020-07-09 17:12:11 +00:00
|
|
|
CameraMode::Freefly => {
|
|
|
|
self.zoom_by(0.0);
|
|
|
|
},
|
2019-07-24 14:27:13 +00:00
|
|
|
}
|
2019-07-22 19:11:00 +00:00
|
|
|
}
|
|
|
|
}
|
2019-07-25 10:26:25 +00:00
|
|
|
|
|
|
|
/// Get the mode of the camera
|
2020-05-18 22:40:28 +00:00
|
|
|
pub fn get_mode(&self) -> CameraMode {
|
2020-05-18 23:35:58 +00:00
|
|
|
// Perfom a bit of a trick... don't report first-person until the camera has
|
|
|
|
// lerped close enough to the player.
|
2020-05-18 22:40:28 +00:00
|
|
|
match self.mode {
|
|
|
|
CameraMode::FirstPerson if self.dist < 0.5 => CameraMode::FirstPerson,
|
|
|
|
CameraMode::FirstPerson => CameraMode::ThirdPerson,
|
|
|
|
mode => mode,
|
|
|
|
}
|
|
|
|
}
|
2020-07-09 17:12:11 +00:00
|
|
|
|
|
|
|
/// Cycle the camera to its next valid mode
|
|
|
|
pub fn next_mode(&mut self) {
|
|
|
|
self.set_mode(match self.mode {
|
|
|
|
CameraMode::ThirdPerson => CameraMode::FirstPerson,
|
|
|
|
CameraMode::FirstPerson => CameraMode::Freefly,
|
|
|
|
CameraMode::Freefly => CameraMode::ThirdPerson,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return a unit vector in the forward direction for the current camera
|
|
|
|
/// orientation
|
|
|
|
pub fn forward(&self) -> Vec3<f32> {
|
|
|
|
Vec3::new(
|
|
|
|
f32::sin(self.ori.x) * f32::cos(self.ori.y),
|
|
|
|
f32::cos(self.ori.x) * f32::cos(self.ori.y),
|
|
|
|
-f32::sin(self.ori.y),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return a unit vector in the right direction for the current camera
|
|
|
|
/// orientation
|
|
|
|
pub fn right(&self) -> Vec3<f32> {
|
|
|
|
const UP: Vec3<f32> = Vec3::new(0.0, 0.0, 1.0);
|
|
|
|
self.forward().cross(UP).normalized()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return a unit vector in the forward direction on the XY plane for
|
|
|
|
/// the current camera orientation
|
|
|
|
pub fn forward_xy(&self) -> Vec2<f32> { Vec2::new(f32::sin(self.ori.x), f32::cos(self.ori.x)) }
|
|
|
|
|
|
|
|
/// Return a unit vector in the right direction on the XY plane for
|
|
|
|
/// the current camera orientation
|
|
|
|
pub fn right_xy(&self) -> Vec2<f32> { Vec2::new(f32::cos(self.ori.x), -f32::sin(self.ori.x)) }
|
2019-01-11 23:18:34 +00:00
|
|
|
}
|