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, 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 fn position (&self) -> math::Point3 <f32> {
200 self.pose.position
201 }
202
203 pub fn yaw (&self) -> math::Rad <f32> {
204 self.pose.angles.yaw.angle()
205 }
206
207 pub fn pitch (&self) -> math::Rad <f32> {
208 self.pose.angles.pitch.angle()
209 }
210
211 pub fn roll (&self) -> math::Rad <f32> {
212 self.pose.angles.roll.angle()
213 }
214
215 pub fn orientation (&self) -> math::Rotation3 <f32> {
216 self.orientation
217 }
218
219 pub fn transform_mat_world_to_view (&self) -> math::Matrix4 <f32> {
220 self.transform_mat_world_to_view
221 }
222
223 pub 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 fn viewport_width (&self) -> u16 {
416 self.viewport_width
417 }
418 pub fn viewport_height (&self) -> u16 {
419 self.viewport_height
420 }
421
422 #[inline]
424 pub fn as_matrix (&self) -> &math::Matrix4 <f32> {
425 self.inner.as_matrix()
426 }
427
428 pub fn is_orthographic (&self) -> bool {
429 match self.inner {
430 Projection3dInner::Orthographic {..} => true,
431 Projection3dInner::Perspective {..} => false
432 }
433 }
434
435 pub 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 fn as_matrix (&self) -> &math::Matrix4 <f32> {
588 match *self {
589 Projection3dInner::Perspective { ref mat, .. } => mat,
590 Projection3dInner::Orthographic { ref 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 {
601 ref mut perspective_fov, ref mut mat
602 } => {
603 perspective_fov.aspect = aspect_ratio (viewport_width, viewport_height);
604 *mat = graphics::projection_mat_perspective (perspective_fov);
605 }
606 Projection3dInner::Orthographic {
607 ref zoom, ref mut ortho, ref mut mat
608 } => {
609 *ortho = compute_ortho (viewport_width, viewport_height, *zoom);
610 *mat = graphics::projection_mat_orthographic (ortho);
611 }
612 }
613 }
614
615 fn set_perspective_fovy (&mut self, new_fovy : math::Rad <f32>) {
616 use approx::AbsDiffEq;
617 let max_fovy = math::Rad (std::f32::consts::PI - f32::default_epsilon());
618 assert!(f32::default_epsilon() < new_fovy.0);
619 assert!(new_fovy <= max_fovy);
620 match *self {
621 Projection3dInner::Perspective {
622 ref mut perspective_fov, ref mut mat
623 } => {
624 if perspective_fov.fovy != new_fovy {
625 perspective_fov.fovy = new_fovy;
626 *mat = graphics::projection_mat_perspective (perspective_fov);
627 }
628 }
629 _ => unreachable!("expected perspective projection")
630 }
631 }
632
633 fn set_orthographic_zoom (&mut self,
634 viewport_width : u16, viewport_height : u16, new_zoom : f32
635 ) {
636 use approx::AbsDiffEq;
637 assert!(f32::default_epsilon() < new_zoom);
638 match *self {
639 Projection3dInner::Orthographic {
640 ref mut zoom, ref mut ortho, ref mut mat
641 } => {
642 if *zoom != new_zoom {
643 *zoom = new_zoom;
644 *ortho = compute_ortho (viewport_width, viewport_height, *zoom);
645 *mat = graphics::projection_mat_orthographic (ortho);
646 }
647 }
648 _ => unreachable!("expected orthographic projection")
649 }
650 }
651
652}
653
654#[cfg(test)]
655mod tests {
656 use super::*;
657 use math;
658 use approx;
659
660 #[test]
661 fn camera3d_look_at() {
662 use approx::AbsDiffEq;
663 let epsilon = 4.0 * f32::default_epsilon();
664 let mut camera = Camera3d::new (640, 480);
665 camera.look_at ([0.0, 1.0, 0.0].into());
666 approx::assert_relative_eq!(
667 *camera.orientation(),
668 *math::Rotation3::identity());
669 camera.look_at ([0.0, -1.0, 0.0].into());
670 approx::assert_relative_eq!(
671 *camera.orientation(),
672 *math::Rotation3::from_angle_z (math::Turn (0.5).into()),
673 epsilon=epsilon);
674 }
675}