1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
// Module declaration
pub mod cone;

// External dependencies
use cgmath::{self, Deg, Matrix4, Point3, Vector3};


/// # General Information
///
/// Camera struct. Makes movement arround viewport possible. Always uses projection matrix and moves arround a sphere given a target.
///
/// # Fields
///
/// * `camera_position` - Camera position from original coordinate system (world coordinate system).
/// * `camera_target` - Normally set to (0,0,0) but can change. What camera points at.
/// * `view_matrix` - How camera ends up viewing object.
/// * `active_view_change` - Wether we can change view matrix. Normally used in callback functions inside loop.
/// * `projection_matrix` - Perspective matrix to see final results in screen.
/// * `up_vector` - Vector to create a coordinate system for camera relative to it's position (position ends up in (0,0,0) in default mode).
/// * `camera_sensitivity` -  How much should camera get close when zooming and moving arround objective.
/// * `theta` - y axis - position angle to move camera.
/// * `phi` - xz plane - position angle to move camera.
/// * `radius` - how far away camera is from object.
///
#[derive(Debug)]pub(crate) struct Camera {
    pub(crate) camera_position: Point3<f32>,
    camera_target: Point3<f32>,
    pub(crate) view_matrix: Matrix4<f32>,
    pub(crate) active_view_change: bool,
    pub(crate) projection_matrix: Matrix4<f32>,
    up_vector: Vector3<f32>,
    pub(crate) camera_sensitivity: f32,
    pub(crate) theta: f32,
    pub(crate) phi: f32,
    pub(crate) radius: f32,
}

/// # General Information
///
/// The camera builder. Gives some control to user, such as distance from target, initial position arround target, fov, speed, sensitivity and
/// object being pointed at.
///
/// # Fields
///
/// * `radius` - Distance to target.
/// * `theta` - One of two angles that dictates camera position arround target (in a sphere).
/// * `phi` - One of two angles that dictates camera position arround target (in a sphere).
/// * `fov` - Field of view of projection matrix.
/// * `camera_sensitivity` - Speed at which camera moves arround target (in a sphere).
/// * `camera_target` - Point at which camera is looking.
///
#[derive(Default, Debug)]
pub struct CameraBuilder {
    pub(crate) radius: Option<f32>,
    theta: Option<f32>,
    phi: Option<f32>,
    fov: Option<f32>,
    camera_sensitivity: Option<f32>,
    camera_target: Option<Point3<f32>>,
}

impl CameraBuilder {
    /// Creates default CameraBuilder
    fn new() -> Self {
        CameraBuilder {
            radius: None,
            theta: None,
            phi: None,
            fov: None,
            camera_sensitivity: None,
            camera_target: None,
        }
    }
    /// Changes distance (radius) to object centered
    pub fn change_distance_to_object(self, radius: f32) -> Self {
        CameraBuilder {
            radius: Some(radius),
            ..self
        }
    }
    /// Changes object being targeted
    pub fn with_target(self, x: f32, y: f32, z: f32) -> Self {
        CameraBuilder {
            camera_target: Some(Point3::new(x, y, z)),
            ..self
        }
    }
    /// Changes initial camera position in a sphere with center `camera_target`
    pub fn with_camera_position(self, theta: f32, phi: f32) -> Self {
        CameraBuilder {
            theta: Some(theta),
            phi: Some(phi),
            ..self
        }
    }
    /// Changes fov when using projection matrix
    pub fn with_fov(self, fov: f32) -> Self {
        CameraBuilder {
            fov: Some(fov),
            ..self
        }
    }
    /// Changes camera movement sensitivity arround object being targeted
    pub fn with_sensitivity(self, sensitivity: f32) -> Self {
        CameraBuilder {
            camera_sensitivity: Some(sensitivity),
            ..self
        }
    }
    /// # General Information
    ///
    /// Builds a Camera from parameters given.
    ///
    /// # Details
    ///
    /// Camera moves arround a sphere (theta, phi, radius) centered on a point of an object with a given radius.
    /// Object on camera is projected on viewport via a projection matrix with a certain fov. There's no plan to add orthogonal projection.
    /// Camera sensitivity helps move camera around sphere.
    ///
    /// # Parameters
    ///
    /// * `self` - Every parameter appearing in Camera struct but not here is derived from the ones that do appear
    /// * `mesh_length` - Length of mesh to display in order to properly set radius if not given
    /// * `height` - Winow height
    /// * `width` - Window width
    ///
    pub(crate) fn build(self, mesh_length: f32, height: u32, width: u32) -> Camera {
        // Normal fov is 45 degrees
        let fov = if let Some(fov) = self.fov { fov } else { 45.0 };
        // Obtain radius or get predetermined one (use of the predetermined radius is recommended)
        let radius = if let Some(radius) = self.radius {
            radius
        } else {
            mesh_length * 2.0
        };
        // y axis - position angle
        let theta = if let Some(theta) = self.theta {
            theta
        } else {
            90.0
        };
        // zx plane - position angle
        let phi = if let Some(phi) = self.phi { phi } else { 0.0 };
        // It also works for zoom
        let camera_sensitivity = if let Some(camera_sensitivity) = self.camera_sensitivity {
            camera_sensitivity
        } else {
            0.5
        };
        // Up vector is always (0,1,0) (y goes upwards)
        let up_vector = Vector3::new(0.0, 1.0, 0.0);
        // Camera target. Normally leaving 0,0,0 is best, since object's center is translated to such point.
        let camera_target = if let Some(camera_target) = self.camera_target {
            camera_target
        } else {
            Point3::new(0.0, 0.0, 0.0)
        };

        // After obtaining values from builder:
        // The easier values to obtain from mesh are near and far
        // near is obtained from max_length ( or radius )
        let mut near = radius - 50.0;
        if near <= 0.0 {
            near = 0.1;
        }
        // far is always 100 away from near
        let far = near + 100.0;
        // Aspect ratio is obtained from height and width of viewport
        let aspect_ratio: f32 = width as f32 / height as f32;
        // Camera position is given by theta and phi (since it's a sphere)
        let camera_position: Point3<f32> = Point3::new(
            theta.to_radians().sin() * phi.to_radians().sin(),
            theta.to_radians().cos(),
            theta.to_radians().sin() * phi.to_radians().cos(),
        ) * radius
            + Vector3::new(camera_target.x, camera_target.y, camera_target.z);
        // View and projection matrix
        // They are closely related, that's why they're both in the same structure.
        let view_matrix = Matrix4::look_at_rh(camera_position, camera_target, up_vector);
        let projection_matrix = cgmath::perspective(Deg(fov), aspect_ratio, near, far);
        // Change view matrix defaults to false.
        let active_view_change = false;

        Camera {
            camera_position,
            camera_target,
            up_vector,
            projection_matrix,
            theta,
            phi,
            radius,
            view_matrix,
            active_view_change,
            camera_sensitivity,
        }
    }
}

impl Camera {
    /// Create a camera builder.
    pub fn builder() -> CameraBuilder {
        CameraBuilder::new()
    }

    /// Change view matrix associated to camera.
    pub(crate) fn modify_view_matrix(&mut self) {
        self.view_matrix =
            Matrix4::look_at_rh(self.camera_position, self.camera_target, self.up_vector);
    }
}