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