ranim_core/primitives/
camera_frame.rs

1// MARK: CameraFrame
2
3use glam::{DMat4, DVec3, dvec2};
4
5use crate::{
6    prelude::{Alignable, Interpolatable},
7    primitives::{Primitive, Primitives},
8};
9
10/// The data of a camera
11///
12/// The [`CameraFrame`] has a [`CameraFrame::perspective_blend`] property (default is `0.0`),
13/// which is used to blend between orthographic and perspective projection.
14#[derive(Clone, Debug, PartialEq)]
15pub struct CameraFrame {
16    /// The position
17    pub pos: DVec3,
18    /// The up unit vec
19    pub up: DVec3,
20    /// The facing unit vec
21    pub facing: DVec3,
22    /// The scaling factor, used in orthographic projection
23    pub scale: f64,
24    /// The field of view angle, used in perspective projection
25    pub fovy: f64,
26    // far > near
27    /// The near pane
28    pub near: f64,
29    /// The far pane
30    pub far: f64,
31    /// The perspective blend value in [0.0, 1.0]
32    pub perspective_blend: f64,
33}
34
35impl Primitive for CameraFrame {
36    fn build_primitives<T: IntoIterator<Item = Self>>(iter: T) -> super::Primitives {
37        Primitives::CameraFrame(iter.into_iter().collect())
38    }
39}
40
41impl Interpolatable for CameraFrame {
42    fn lerp(&self, target: &Self, t: f64) -> Self {
43        Self {
44            pos: self.pos.lerp(target.pos, t),
45            up: self.up.lerp(target.up, t),
46            facing: self.facing.lerp(target.facing, t),
47            scale: self.scale.lerp(&target.scale, t),
48            fovy: self.fovy.lerp(&target.fovy, t),
49            near: self.near.lerp(&target.near, t),
50            far: self.far.lerp(&target.far, t),
51            perspective_blend: self
52                .perspective_blend
53                .lerp(&target.perspective_blend, t)
54                .clamp(0.0, 1.0),
55        }
56    }
57}
58
59impl Alignable for CameraFrame {
60    fn is_aligned(&self, _other: &Self) -> bool {
61        true
62    }
63    fn align_with(&mut self, _other: &mut Self) {}
64}
65
66impl Default for CameraFrame {
67    fn default() -> Self {
68        Self {
69            pos: DVec3::ZERO,
70            up: DVec3::Y,
71            facing: DVec3::NEG_Z,
72
73            scale: 1.0,
74            fovy: std::f64::consts::PI / 2.0,
75
76            near: -1000.0,
77            far: 1000.0,
78            perspective_blend: 0.0,
79        }
80    }
81}
82
83impl CameraFrame {
84    /// Create a new CameraFrame at the origin facing to the negative z-axis and use Y as up vector with default projection settings.
85    pub fn new() -> Self {
86        Self::default()
87    }
88}
89
90impl CameraFrame {
91    /// The view matrix of the camera
92    pub fn view_matrix(&self) -> DMat4 {
93        DMat4::look_at_rh(self.pos, self.pos + self.facing, self.up)
94    }
95
96    /// Use the given frame size as `left`, `right`, `bottom`, `top` to construct an orthographic matrix
97    pub fn orthographic_mat(&self, frame_height: f64, aspect_ratio: f64) -> DMat4 {
98        let frame_size = dvec2(frame_height * aspect_ratio, frame_height);
99        let frame_size = frame_size * self.scale;
100        DMat4::orthographic_rh(
101            -frame_size.x / 2.0,
102            frame_size.x / 2.0,
103            -frame_size.y / 2.0,
104            frame_size.y / 2.0,
105            self.near,
106            self.far,
107        )
108    }
109
110    /// Use the given frame aspect ratio to construct a perspective matrix
111    pub fn perspective_mat(&self, aspect_ratio: f64) -> DMat4 {
112        let near = self.near.max(0.1);
113        let far = self.far.max(near);
114        DMat4::perspective_rh(self.fovy, aspect_ratio, near, far)
115    }
116
117    /// Use the given frame size to construct projection matrix
118    pub fn projection_matrix(&self, frame_height: f64, aspect_ratio: f64) -> DMat4 {
119        self.orthographic_mat(frame_height, aspect_ratio)
120            .lerp(&self.perspective_mat(aspect_ratio), self.perspective_blend)
121    }
122
123    /// Use the given frame size to construct view projection matrix
124    pub fn view_projection_matrix(&self, frame_height: f64, aspect_ratio: f64) -> DMat4 {
125        self.projection_matrix(frame_height, aspect_ratio) * self.view_matrix()
126    }
127}
128
129impl CameraFrame {
130    /// Center the canvas in the frame when [`CameraFrame::perspective_blend`] is `1.0`
131    pub fn center_canvas_in_frame(
132        &mut self,
133        center: DVec3,
134        width: f64,
135        height: f64,
136        up: DVec3,
137        normal: DVec3,
138        aspect_ratio: f64,
139    ) -> &mut Self {
140        let canvas_ratio = height / width;
141        let up = up.normalize();
142        let normal = normal.normalize();
143
144        let height = if aspect_ratio > canvas_ratio {
145            height
146        } else {
147            width / aspect_ratio
148        };
149
150        let distance = height * 0.5 / (0.5 * self.fovy).tan();
151
152        self.up = up;
153        self.pos = center + normal * distance;
154        self.facing = -normal;
155        self
156    }
157}