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
375 = Projection3dInner::perspective (viewport_width, viewport_height, fovy);
376 Projection3d { viewport_width, viewport_height, inner }
377 }
378
379 pub fn orthographic (viewport_width : u16, viewport_height : u16, zoom : f32)
406 -> Self
407 {
408 let inner
409 = Projection3dInner::orthographic (viewport_width, viewport_height, zoom);
410 Projection3d { viewport_width, viewport_height, inner }
411 }
412
413 pub const fn viewport_width (&self) -> u16 {
414 self.viewport_width
415 }
416 pub const fn viewport_height (&self) -> u16 {
417 self.viewport_height
418 }
419
420 #[inline]
422 pub const fn as_matrix (&self) -> &math::Matrix4 <f32> {
423 self.inner.as_matrix()
424 }
425
426 pub const fn is_orthographic (&self) -> bool {
427 match self.inner {
428 Projection3dInner::Orthographic {..} => true,
429 Projection3dInner::Perspective {..} => false
430 }
431 }
432
433 pub const fn is_perspective (&self) -> bool {
434 match self.inner {
435 Projection3dInner::Orthographic {..} => false,
436 Projection3dInner::Perspective {..} => true
437 }
438 }
439
440 pub fn to_orthographic (&mut self, zoom : f32) {
444 match self.inner {
445 ref mut inner@Projection3dInner::Orthographic { .. } => {
446 inner.set_orthographic_zoom (
447 self.viewport_width, self.viewport_height, zoom);
448 }
449 ref mut inner@Projection3dInner::Perspective { .. } => {
450 *inner = Projection3dInner::orthographic (
451 self.viewport_width, self.viewport_height, 1.0);
452 }
453 }
454 }
455
456 pub fn to_perspective (&mut self, fovy : math::Rad <f32>) {
460 match self.inner {
461 ref mut inner@Projection3dInner::Orthographic { .. } => {
462 *inner = Projection3dInner::perspective (
463 self.viewport_width, self.viewport_height, fovy);
464 }
465 ref mut inner@Projection3dInner::Perspective { .. } => {
466 inner.set_perspective_fovy (fovy);
467 }
468 }
469 }
470
471 pub fn set_viewport_dimensions (&mut self,
488 viewport_width : u16, viewport_height : u16
489 ) {
490 self.viewport_width = viewport_width;
491 self.viewport_height = viewport_height;
492 self.inner.update_viewport_dimensions (viewport_width, viewport_height);
493 }
494
495 pub fn scale_fovy_or_zoom (&mut self, scale : f32) {
516 assert!(0.0 < scale);
517 if scale != 1.0 {
518 use approx::AbsDiffEq;
519 match self.inner {
520 Projection3dInner::Perspective {
521 ref mut perspective_fov, ref mut mat
522 } => {
523 let max_fovy = math::Rad (
524 std::f32::consts::PI - f32::default_epsilon()
525 );
526 let min_fovy = math::Rad (f32::default_epsilon());
527 perspective_fov.fovy *= scale;
528 debug_assert!(math::Rad (0.0) <= perspective_fov.fovy);
529 if max_fovy < perspective_fov.fovy {
530 perspective_fov.fovy = max_fovy;
531 } else if perspective_fov.fovy < min_fovy {
532 perspective_fov.fovy = min_fovy;
533 }
534 *mat = graphics::projection_mat_perspective (perspective_fov);
535 }
536 Projection3dInner::Orthographic {
537 ref mut zoom, ref mut ortho, ref mut mat
538 } => {
539 *zoom *= scale;
540 debug_assert!(0.0 <= *zoom);
541 if *zoom < f32::default_epsilon() {
542 *zoom = f32::default_epsilon();
543 }
544 *ortho = compute_ortho (
545 self.viewport_width, self.viewport_height, *zoom);
546 *mat = graphics::projection_mat_orthographic (ortho);
547 }
548 }
549 }
550 }
551
552}
553
554impl Projection3dInner {
555 fn perspective (
556 viewport_width : u16, viewport_height : u16, fovy : math::Rad <f32>
557 ) -> Self {
558 use approx::AbsDiffEq;
559 assert!(0 < viewport_width);
560 assert!(0 < viewport_height);
561 assert!(0.0 < fovy.0);
562 assert!(fovy.0 <= std::f32::consts::PI - f32::default_epsilon());
563 let perspective_fov = PerspectiveFov {
564 fovy,
565 aspect: aspect_ratio (viewport_width, viewport_height),
566 near: PERSPECTIVE_NEAR_PLANE,
567 far: PERSPECTIVE_FAR_PLANE
568 };
569 let mat = graphics::projection_mat_perspective (&perspective_fov);
570 Projection3dInner::Perspective { perspective_fov, mat }
571 }
572
573 fn orthographic (viewport_width : u16, viewport_height : u16, zoom : f32)
574 -> Self
575 {
576 assert!(0 < viewport_width);
577 assert!(0 < viewport_height);
578 assert!(0.0 < zoom);
579 let ortho = compute_ortho (viewport_width, viewport_height, zoom);
580 let mat = graphics::projection_mat_orthographic (&ortho);
581 Projection3dInner::Orthographic { zoom, ortho, mat }
582 }
583
584 const fn as_matrix (&self) -> &math::Matrix4 <f32> {
586 match self {
587 Projection3dInner::Perspective { mat, .. } |
588 Projection3dInner::Orthographic { mat, .. } => mat
589 }
590 }
591
592 fn update_viewport_dimensions (&mut self,
593 viewport_width : u16, viewport_height : u16
594 ) {
595 assert!(0 < viewport_width);
596 assert!(0 < viewport_height);
597 match self {
598 Projection3dInner::Perspective { perspective_fov, mat } => {
599 perspective_fov.aspect = aspect_ratio (viewport_width, viewport_height);
600 *mat = graphics::projection_mat_perspective (perspective_fov);
601 }
602 Projection3dInner::Orthographic { zoom, ortho, mat } => {
603 *ortho = compute_ortho (viewport_width, viewport_height, *zoom);
604 *mat = graphics::projection_mat_orthographic (ortho);
605 }
606 }
607 }
608
609 fn set_perspective_fovy (&mut self, new_fovy : math::Rad <f32>) {
610 use approx::AbsDiffEq;
611 let max_fovy = math::Rad (std::f32::consts::PI - f32::default_epsilon());
612 assert!(f32::default_epsilon() < new_fovy.0);
613 assert!(new_fovy <= max_fovy);
614 match *self {
615 Projection3dInner::Perspective {
616 ref mut perspective_fov, ref mut mat
617 } => {
618 if perspective_fov.fovy != new_fovy {
619 perspective_fov.fovy = new_fovy;
620 *mat = graphics::projection_mat_perspective (perspective_fov);
621 }
622 }
623 Projection3dInner::Orthographic {..} =>
624 unreachable!("expected perspective projection")
625 }
626 }
627
628 fn set_orthographic_zoom (&mut self,
629 viewport_width : u16, viewport_height : u16, new_zoom : f32
630 ) {
631 use approx::AbsDiffEq;
632 assert!(f32::default_epsilon() < new_zoom);
633 match *self {
634 Projection3dInner::Orthographic {
635 ref mut zoom, ref mut ortho, ref mut mat
636 } => {
637 if *zoom != new_zoom {
638 *zoom = new_zoom;
639 *ortho = compute_ortho (viewport_width, viewport_height, *zoom);
640 *mat = graphics::projection_mat_orthographic (ortho);
641 }
642 }
643 Projection3dInner::Perspective {..} =>
644 unreachable!("expected orthographic projection")
645 }
646 }
647
648}
649
650#[cfg(test)]
651mod tests {
652 use super::*;
653 use math;
654 use approx;
655
656 #[test]
657 fn camera3d_look_at() {
658 use approx::AbsDiffEq;
659 let epsilon = 4.0 * f32::default_epsilon();
660 let mut camera = Camera3d::new (640, 480);
661 camera.look_at ([0.0, 1.0, 0.0].into());
662 approx::assert_relative_eq!(
663 camera.orientation().mat(),
664 math::Rotation3::identity().mat());
665 camera.look_at ([0.0, -1.0, 0.0].into());
666 approx::assert_relative_eq!(
667 camera.orientation().mat(),
668 math::Rotation3::from_angle_z (math::Turn (0.5).into()).mat(),
669 epsilon=epsilon);
670 }
671}