1use std;
4use math_utils as math;
5use math_utils::{approx, num, vek};
6
7use crate::graphics;
8
9const PERSPECTIVE_INITIAL_FOVY : math::Deg <f32> = math::Deg (90.0);
10const PERSPECTIVE_NEAR_PLANE : f32 = 0.1;
11const PERSPECTIVE_FAR_PLANE : f32 = 1000.0;
12const ORTHOGRAPHIC_PIXEL_SCALE : f32 = 64.0;
14const ORTHOGRAPHIC_NEAR_PLANE : f32 = 0.0;
15const ORTHOGRAPHIC_FAR_PLANE : f32 = 1000.0;
16
17#[derive(Clone, Debug, PartialEq)]
20pub struct Camera3d {
21 pose : math::Pose3 <f32>,
24 orientation : math::Rotation3 <f32>,
26 transform_mat_world_to_view : math::Matrix4 <f32>,
34
35 projection3d : Projection3d
37}
38
39#[derive(Clone, Debug, PartialEq)]
41pub struct Projection3d {
42 viewport_width : u16,
43 viewport_height : u16,
44 inner : Projection3dInner
45}
46
47#[derive(Clone, Debug, PartialEq)]
49pub enum Projection3dInner {
50 Perspective {
51 perspective_fov : PerspectiveFov <f32>,
53 mat : math::Matrix4 <f32>
57 },
58 Orthographic {
59 zoom : f32,
64 ortho : vek::FrustumPlanes <f32>,
66 mat : math::Matrix4 <f32>
70 }
71}
72
73#[derive(Clone, Debug, Eq, PartialEq)]
76pub struct PerspectiveFov <S> {
77 pub fovy : math::Rad <S>,
78 pub aspect : S,
79 pub near : S,
80 pub far : S
81}
82
83#[inline]
86pub fn aspect_ratio (viewport_width : u16, viewport_height : u16) -> f32 {
87 viewport_width as f32 / viewport_height as f32
88}
89
90pub fn transform_mat_world_to_view (
137 view_position : &math::Point3 <f32>,
138 view_orientation : &math::Rotation3 <f32>
139) -> math::Matrix4 <f32> {
140 let eye = view_position;
141 let target = *view_position + view_orientation.cols.y;
142 let up = view_orientation.cols.z;
143 math::Matrix4::<f32>::look_at_rh (eye.0, target.0, up)
144}
145
146fn compute_ortho (viewport_width : u16, viewport_height : u16, zoom : f32)
148 -> vek::FrustumPlanes <f32>
149{
150 let half_scaled_width = (0.5 * (viewport_width as f32 / zoom))
151 / ORTHOGRAPHIC_PIXEL_SCALE;
152 let half_scaled_height = (0.5 * (viewport_height as f32 / zoom))
153 / ORTHOGRAPHIC_PIXEL_SCALE;
154 vek::FrustumPlanes {
155 left: -half_scaled_width,
156 right: half_scaled_width,
157 bottom: -half_scaled_height,
158 top: half_scaled_height,
159 near: ORTHOGRAPHIC_NEAR_PLANE,
160 far: ORTHOGRAPHIC_FAR_PLANE
161 }
162}
163
164impl Camera3d {
165 #[inline]
168 pub fn new (viewport_width : u16, viewport_height : u16) -> Self {
169 Self::with_pose (
170 viewport_width, viewport_height, Default::default())
171 }
172
173 pub fn with_pose (
176 viewport_width : u16,
177 viewport_height : u16,
178 pose : math::Pose3 <f32>
179 ) -> Self {
180 let orientation = pose.angles.into();
182 let transform_mat_world_to_view =
183 transform_mat_world_to_view (&pose.position, &orientation);
184
185 let projection3d = Projection3d::perspective (
187 viewport_width, viewport_height, PERSPECTIVE_INITIAL_FOVY.into());
188
189 Camera3d {
190 pose,
191 orientation,
192 transform_mat_world_to_view,
193 projection3d
194 }
195 }
196
197 pub const fn position (&self) -> math::Point3 <f32> {
198 self.pose.position
199 }
200
201 pub const fn yaw (&self) -> math::Rad <f32> {
202 self.pose.angles.yaw.angle()
203 }
204
205 pub const fn pitch (&self) -> math::Rad <f32> {
206 self.pose.angles.pitch.angle()
207 }
208
209 pub const fn roll (&self) -> math::Rad <f32> {
210 self.pose.angles.roll.angle()
211 }
212
213 pub const fn orientation (&self) -> math::Rotation3 <f32> {
214 self.orientation
215 }
216
217 pub const fn transform_mat_world_to_view (&self) -> math::Matrix4 <f32> {
218 self.transform_mat_world_to_view
219 }
220
221 pub const fn projection (&self) -> &Projection3d {
222 &self.projection3d
223 }
224
225 #[inline]
227 pub fn set_viewport_dimensions (&mut self,
228 viewport_width : u16, viewport_height : u16
229 ) {
230 self.projection3d.set_viewport_dimensions (
231 viewport_width, viewport_height);
232 }
233
234 pub fn set_position (&mut self, position : math::Point3 <f32>) {
235 if self.pose.position != position {
236 self.pose.position = position;
237 self.compute_transform();
238 }
239 }
240
241 pub fn set_orientation (&mut self, orientation : math::Rotation3 <f32>) {
242 if self.orientation != orientation {
243 self.orientation = orientation;
244 self.compute_angles();
245 }
246 }
247
248 pub fn scale_fovy_or_zoom (&mut self, scale : f32) {
249 self.projection3d.scale_fovy_or_zoom (scale)
250 }
251
252 pub fn rotate (&mut self,
253 dyaw : math::Rad <f32>, dpitch : math::Rad <f32>, droll : math::Rad <f32>
254 ) {
255 use num::Zero;
256 self.pose.angles.yaw += dyaw;
257 self.pose.angles.pitch += dpitch;
258 self.pose.angles.roll += droll;
259 if !dyaw.is_zero() || !dpitch.is_zero() || !droll.is_zero() {
260 self.compute_orientation();
261 }
262 }
263
264 pub fn move_local_xy (&mut self, dx : f32, dy : f32, dz : f32) {
269 if dx != 0.0 || dy != 0.0 || dz != 0.0 {
270 let xy_basis = math::Matrix3::rotation_z (self.pose.angles.yaw.angle().0);
271 self.pose.position +=
272 (dx * xy_basis.cols.x) + (dy * xy_basis.cols.y) + (dz * xy_basis.cols.z);
273 self.compute_transform();
274 }
275 }
276
277 pub fn look_at (&mut self, target : math::Point3 <f32>) {
282 let orientation =
283 math::Rotation3::look_at ((target - self.pose.position).into());
284 self.set_orientation (orientation);
285 }
286
287 #[inline]
290 pub fn view_mats (&self) -> ([[f32; 4]; 4], [[f32; 4]; 4]) {
291 ( self.transform_mat_world_to_view.into_col_arrays(),
292 self.projection3d.as_matrix().into_col_arrays()
293 )
294 }
295
296 pub fn to_orthographic (&mut self, zoom : f32) {
298 self.projection3d.to_orthographic (zoom)
299 }
300
301 pub fn to_perspective (&mut self, fovy : math::Rad <f32>) {
303 self.projection3d.to_perspective (fovy)
304 }
305
306 #[inline]
307 fn compute_transform (&mut self) {
308 self.transform_mat_world_to_view =
309 transform_mat_world_to_view (&self.pose.position, &self.orientation);
310 }
311
312 #[inline]
315 fn compute_orientation (&mut self) {
316 self.orientation = self.pose.angles.into();
317 self.compute_transform();
318 }
319
320 #[inline]
323 fn compute_angles (&mut self) {
324 self.pose.angles = self.orientation.into();
325 self.compute_transform();
326 }
327}
328
329impl Projection3d {
330
331 pub fn perspective (
372 viewport_width : u16, viewport_height : u16, fovy : math::Rad <f32>
373 ) -> Self {
374 let inner = Projection3dInner::perspective (viewport_width, viewport_height, fovy);
375 Projection3d { viewport_width, viewport_height, inner }
376 }
377
378 pub fn orthographic (viewport_width : u16, viewport_height : u16, zoom : f32) -> Self {
405 let inner = Projection3dInner::orthographic (viewport_width, viewport_height, zoom);
406 Projection3d { viewport_width, viewport_height, inner }
407 }
408
409 pub const fn viewport_width (&self) -> u16 {
410 self.viewport_width
411 }
412 pub const fn viewport_height (&self) -> u16 {
413 self.viewport_height
414 }
415
416 #[inline]
418 pub const fn as_matrix (&self) -> &math::Matrix4 <f32> {
419 self.inner.as_matrix()
420 }
421
422 pub const fn is_orthographic (&self) -> bool {
423 match self.inner {
424 Projection3dInner::Orthographic {..} => true,
425 Projection3dInner::Perspective {..} => false
426 }
427 }
428
429 pub const fn is_perspective (&self) -> bool {
430 match self.inner {
431 Projection3dInner::Orthographic {..} => false,
432 Projection3dInner::Perspective {..} => true
433 }
434 }
435
436 pub fn to_orthographic (&mut self, zoom : f32) {
440 match self.inner {
441 ref mut inner@Projection3dInner::Orthographic { .. } => {
442 inner.set_orthographic_zoom (
443 self.viewport_width, self.viewport_height, zoom);
444 }
445 ref mut inner@Projection3dInner::Perspective { .. } => {
446 *inner = Projection3dInner::orthographic (
447 self.viewport_width, self.viewport_height, 1.0);
448 }
449 }
450 }
451
452 pub fn to_perspective (&mut self, fovy : math::Rad <f32>) {
456 match self.inner {
457 ref mut inner@Projection3dInner::Orthographic { .. } => {
458 *inner = Projection3dInner::perspective (
459 self.viewport_width, self.viewport_height, fovy);
460 }
461 ref mut inner@Projection3dInner::Perspective { .. } => {
462 inner.set_perspective_fovy (fovy);
463 }
464 }
465 }
466
467 pub fn set_viewport_dimensions (&mut self,
484 viewport_width : u16, viewport_height : u16
485 ) {
486 self.viewport_width = viewport_width;
487 self.viewport_height = viewport_height;
488 self.inner.update_viewport_dimensions (viewport_width, viewport_height);
489 }
490
491 pub fn scale_fovy_or_zoom (&mut self, scale : f32) {
512 assert!(0.0 < scale);
513 if scale != 1.0 {
514 use approx::AbsDiffEq;
515 match self.inner {
516 Projection3dInner::Perspective {
517 ref mut perspective_fov, ref mut mat
518 } => {
519 let max_fovy = math::Rad (
520 std::f32::consts::PI - f32::default_epsilon()
521 );
522 let min_fovy = math::Rad (f32::default_epsilon());
523 perspective_fov.fovy *= scale;
524 debug_assert!(math::Rad (0.0) <= perspective_fov.fovy);
525 if max_fovy < perspective_fov.fovy {
526 perspective_fov.fovy = max_fovy;
527 } else if perspective_fov.fovy < min_fovy {
528 perspective_fov.fovy = min_fovy;
529 }
530 *mat = graphics::projection_mat_perspective (perspective_fov);
531 }
532 Projection3dInner::Orthographic {
533 ref mut zoom, ref mut ortho, ref mut mat
534 } => {
535 *zoom *= scale;
536 debug_assert!(0.0 <= *zoom);
537 if *zoom < f32::default_epsilon() {
538 *zoom = f32::default_epsilon();
539 }
540 *ortho = compute_ortho (
541 self.viewport_width, self.viewport_height, *zoom);
542 *mat = graphics::projection_mat_orthographic (ortho);
543 }
544 }
545 }
546 }
547}
548
549impl Projection3dInner {
550 fn perspective (
551 viewport_width : u16, viewport_height : u16, fovy : math::Rad <f32>
552 ) -> Self {
553 use approx::AbsDiffEq;
554 assert!(0 < viewport_width);
555 assert!(0 < viewport_height);
556 assert!(0.0 < fovy.0);
557 assert!(fovy.0 <= std::f32::consts::PI - f32::default_epsilon());
558 let perspective_fov = PerspectiveFov {
559 fovy,
560 aspect: aspect_ratio (viewport_width, viewport_height),
561 near: PERSPECTIVE_NEAR_PLANE,
562 far: PERSPECTIVE_FAR_PLANE
563 };
564 let mat = graphics::projection_mat_perspective (&perspective_fov);
565 Projection3dInner::Perspective { perspective_fov, mat }
566 }
567
568 fn orthographic (viewport_width : u16, viewport_height : u16, zoom : f32)
569 -> Self
570 {
571 assert!(0 < viewport_width);
572 assert!(0 < viewport_height);
573 assert!(0.0 < zoom);
574 let ortho = compute_ortho (viewport_width, viewport_height, zoom);
575 let mat = graphics::projection_mat_orthographic (&ortho);
576 Projection3dInner::Orthographic { zoom, ortho, mat }
577 }
578
579 const fn as_matrix (&self) -> &math::Matrix4 <f32> {
581 match self {
582 Projection3dInner::Perspective { mat, .. } |
583 Projection3dInner::Orthographic { mat, .. } => mat
584 }
585 }
586
587 fn update_viewport_dimensions (&mut self,
588 viewport_width : u16, viewport_height : u16
589 ) {
590 assert!(0 < viewport_width);
591 assert!(0 < viewport_height);
592 match self {
593 Projection3dInner::Perspective { perspective_fov, mat } => {
594 perspective_fov.aspect = aspect_ratio (viewport_width, viewport_height);
595 *mat = graphics::projection_mat_perspective (perspective_fov);
596 }
597 Projection3dInner::Orthographic { zoom, ortho, mat } => {
598 *ortho = compute_ortho (viewport_width, viewport_height, *zoom);
599 *mat = graphics::projection_mat_orthographic (ortho);
600 }
601 }
602 }
603
604 fn set_perspective_fovy (&mut self, new_fovy : math::Rad <f32>) {
605 use approx::AbsDiffEq;
606 let max_fovy = math::Rad (std::f32::consts::PI - f32::default_epsilon());
607 assert!(f32::default_epsilon() < new_fovy.0);
608 assert!(new_fovy <= max_fovy);
609 match *self {
610 Projection3dInner::Perspective {
611 ref mut perspective_fov, ref mut mat
612 } => {
613 if perspective_fov.fovy != new_fovy {
614 perspective_fov.fovy = new_fovy;
615 *mat = graphics::projection_mat_perspective (perspective_fov);
616 }
617 }
618 Projection3dInner::Orthographic {..} =>
619 unreachable!("expected perspective projection")
620 }
621 }
622
623 fn set_orthographic_zoom (&mut self,
624 viewport_width : u16, viewport_height : u16, new_zoom : f32
625 ) {
626 use approx::AbsDiffEq;
627 assert!(f32::default_epsilon() < new_zoom);
628 match *self {
629 Projection3dInner::Orthographic {
630 ref mut zoom, ref mut ortho, ref mut mat
631 } => {
632 if *zoom != new_zoom {
633 *zoom = new_zoom;
634 *ortho = compute_ortho (viewport_width, viewport_height, *zoom);
635 *mat = graphics::projection_mat_orthographic (ortho);
636 }
637 }
638 Projection3dInner::Perspective {..} =>
639 unreachable!("expected orthographic projection")
640 }
641 }
642
643}
644
645#[cfg(test)]
646mod tests {
647 use super::*;
648 use math;
649 use approx;
650
651 #[test]
652 fn camera3d_look_at() {
653 use approx::AbsDiffEq;
654 let epsilon = 4.0 * f32::default_epsilon();
655 let mut camera = Camera3d::new (640, 480);
656 camera.look_at ([0.0, 1.0, 0.0].into());
657 approx::assert_relative_eq!(
658 camera.orientation().mat(),
659 math::Rotation3::identity().mat());
660 camera.look_at ([0.0, -1.0, 0.0].into());
661 approx::assert_relative_eq!(
662 camera.orientation().mat(),
663 math::Rotation3::from_angle_z (math::Turn (0.5).into()).mat(),
664 epsilon=epsilon);
665 }
666}