CPP-TOOLBOX
Loading...
Searching...
No Matches
fps_camera.hpp
Go to the documentation of this file.
1#ifndef FPS_CAMERA_HPP
2#define FPS_CAMERA_HPP
3
5
6#include <functional>
7#include <glm/ext/matrix_transform.hpp>
8
9// NOTE: we define a frustum by the area enclosed by 6 planes
10struct Frustum {
11 std::array<linalg_utils::Plane, 6> planes;
12
13 // TODO: need to figure out why this function works at all...
14 // this is mainly used for frustum culling
15 template <typename Container> bool intersects_points(const Container &points) const {
16 for (const auto &plane : planes) {
17 bool all_outside = true;
18
19 for (const auto &p : points) {
20 auto side = plane.classify_point(p);
21 bool inside = side == linalg_utils::Plane::Side::NormalSide;
22 all_outside &= !inside;
23 }
24
25 if (all_outside) {
26 return false;
27 }
28 }
29 return true;
30 }
31};
32
33// NOTE: if we ever need to redo this then I want to redo it by using connect n-gon from vertex geom.
34// draw_info::IndexedVertexPositions generate_frustum_ivp(bool center_at_origin = false) {
35// // Camera params
36// float aspect = static_cast<float>(screen_width_px) / screen_height_px;
37// float fov_y = glm::radians(camera.fov.get()); // vertical FOV
38// float near_z = camera.near_plane;
39// float far_z = camera.far_plane;
40// far_z = 2;
41//
42// // Half-sizes of near/far planes
43// float tan_half_fov_y = tanf(fov_y * 0.5f);
44// float near_height = 2.0f * near_z * tan_half_fov_y;
45// float near_width = near_height * aspect;
46// float far_height = 2.0f * far_z * tan_half_fov_y;
47// float far_width = far_height * aspect;
48//
49// // Define frustum corners in camera space
50// std::array<glm::vec3, 8> cam_corners = {
51// // Near plane
52// glm::vec3(-near_width / 2, -near_height / 2, -near_z), glm::vec3(near_width / 2, -near_height / 2,
53// -near_z), glm::vec3(-near_width / 2, near_height / 2, -near_z), glm::vec3(near_width / 2, near_height /
54// 2, -near_z),
55// // Far plane
56// glm::vec3(-far_width / 2, -far_height / 2, -far_z), glm::vec3(far_width / 2, -far_height / 2, -far_z),
57// glm::vec3(-far_width / 2, far_height / 2, -far_z), glm::vec3(far_width / 2, far_height / 2, -far_z)};
58//
59// std::vector<glm::vec3> world_corners;
60// world_corners.reserve(8);
61//
62// // --- Manually construct TRS matrix from Transform ---
63// glm::vec3 t = camera.transform.get_translation();
64// glm::vec3 s = camera.transform.get_scale();
65// glm::vec3 r = camera.transform.get_rotation(); // in turns (0..1)
66// glm::vec3 r_rad = r * glm::two_pi<float>(); // convert turns to radians
67//
68// // Translation matrix
69// glm::mat4 T = glm::translate(glm::mat4(1.0f), t);
70//
71// // Rotation matrices (Euler order: pitch=X, yaw=Y, roll=Z)
72// glm::mat4 Rx = glm::rotate(glm::mat4(1.0f), r_rad.x, glm::vec3(1, 0, 0));
73// glm::mat4 Ry = glm::rotate(glm::mat4(1.0f), -r_rad.y - glm::two_pi<float>() / 4, glm::vec3(0, 1, 0));
74// glm::mat4 Rz = glm::rotate(glm::mat4(1.0f), r_rad.z, glm::vec3(0, 0, 1));
75// glm::mat4 R = Ry * Rx * Rz; // assuming YXZ order (common for FPS camera)
76//
77// // Scale matrix
78// glm::mat4 S = glm::scale(glm::mat4(1.0f), s);
79//
80// // Combine to world matrix
81// glm::mat4 world_matrix = T * R * S; // TRS order
82//
83// for (auto &c : cam_corners) {
84// glm::vec4 world = world_matrix * glm::vec4(c, 1.0f);
85// world_corners.push_back(glm::vec3(world));
86// }
87//
88// if (center_at_origin) {
89// for (auto &p : world_corners) {
90// p -= t; // shift apex to origin
91// }
92// }
93//
94// // Triangle indices
95// std::vector<unsigned int> indices = {
96// 0, 1, 2, 1, 3, 2, // near
97// 4, 6, 5, 5, 6, 7, // far
98// 0, 2, 4, 2, 6, 4, // left
99// 1, 5, 3, 3, 5, 7, // right
100// 2, 3, 6, 3, 7, 6, // top
101// 0, 4, 1, 1, 4, 5 // bottom
102// };
103//
104// return draw_info::IndexedVertexPositions(indices, world_corners);
105// }
106
107inline std::array<glm::vec3, 8> get_aabb_corners_world(const vertex_geometry::AxisAlignedBoundingBox &box,
108 Transform &transform) {
109 glm::mat4 model = transform.get_transform_matrix();
110 std::array<glm::vec3, 8> corners = box.get_corners();
111 for (auto &c : corners) {
112 glm::vec4 world = model * glm::vec4(c, 1.0f);
113 c = glm::vec3(world);
114 }
115 return corners;
116}
117
118struct ICamera {
119 virtual ~ICamera() = default;
120 virtual glm::mat4 get_view_matrix() const = 0;
121 virtual glm::mat4 get_projection_matrix() const = 0;
122 // TODO: remove the below one
124 virtual bool is_visible(const std::vector<glm::vec3> &xyz_positions, Transform &transform) = 0;
127};
128
129class FPSCamera : public ICamera {
130 public:
132
133 // this is how many units per second you will move at
134 float move_speed = 2;
137 glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f);
138 glm::mat4 projection;
139
140 bool camera_frozen = false;
141 void toggle_mouse_freeze();
142 void freeze_camera();
143 void unfreeze_camera();
144
147
149
152
153 unsigned int &screen_width_px;
154 unsigned int &screen_height_px;
155
156 FPSCamera(unsigned int &screen_width_px, unsigned int &screen_height_px,
157 glm::vec3 start_position = glm::vec3(0.0f, 0.0f, 0.0f), double user_sensitivity = 1.0,
158 float fov = 90.0f, // Field of view in degrees
159 float zoom_fov = 30.0f, // Field of view in degrees
160 float near_plane = 0.01f, // Near clipping plane
161 float far_plane = 300.0f); // Far clipping plane
162
163 void toggle_zoom();
164
165 void change_active_sensitivity(double new_sens);
166 void zoom_in();
167 void zoom_out();
168
170
171 // Camera params
172 float aspect = static_cast<float>(screen_width_px) / screen_height_px;
173 float fov_y = glm::radians(fov.get());
174 float near_z = near_plane;
175 float far_z = far_plane;
176
177 float tan_half_fov_y = tanf(fov_y * 0.5f);
178 float near_height = 2.0f * near_z * tan_half_fov_y;
179 float near_width = near_height * aspect;
180 float far_height = 2.0f * far_z * tan_half_fov_y;
181 float far_width = far_height * aspect;
182
183 // camera-space corners
184 std::array<glm::vec3, 8> cam_corners = {
185 glm::vec3(-near_width / 2, -near_height / 2, -near_z), glm::vec3(near_width / 2, -near_height / 2, -near_z),
186 glm::vec3(-near_width / 2, near_height / 2, -near_z), glm::vec3(near_width / 2, near_height / 2, -near_z),
187 glm::vec3(-far_width / 2, -far_height / 2, -far_z), glm::vec3(far_width / 2, -far_height / 2, -far_z),
188 glm::vec3(-far_width / 2, far_height / 2, -far_z), glm::vec3(far_width / 2, far_height / 2, -far_z)};
189
190 // TRS matrices from camera
191 glm::vec3 t = transform.get_translation();
192 glm::vec3 s = transform.get_scale();
193 glm::vec3 r = transform.get_rotation();
194 glm::vec3 r_rad = r * glm::two_pi<float>();
195
196 glm::mat4 T = glm::translate(glm::mat4(1.0f), t);
197 glm::mat4 Rx = glm::rotate(glm::mat4(1.0f), r_rad.x, glm::vec3(1, 0, 0));
198 // TODO: once again I have no idea why we keep having to fix this
199 glm::mat4 Ry = glm::rotate(glm::mat4(1.0f), -r_rad.y - glm::two_pi<float>() / 4, glm::vec3(0, 1, 0));
200 glm::mat4 Rz = glm::rotate(glm::mat4(1.0f), r_rad.z, glm::vec3(0, 0, 1));
201 glm::mat4 R = Ry * Rx * Rz;
202 glm::mat4 S = glm::scale(glm::mat4(1.0f), s);
203 glm::mat4 world_matrix = T * R * S;
204
205 std::array<glm::vec3, 8> world_corners;
206 int i = 0;
207 for (auto &c : cam_corners) {
208 glm::vec4 w = world_matrix * glm::vec4(c, 1.0f);
209 world_corners[i] = glm::vec3(w);
210 i++;
211 }
212
213 glm::vec3 nbl = world_corners[0], nbr = world_corners[1], ntl = world_corners[2], ntr = world_corners[3];
214 glm::vec3 fbl = world_corners[4], fbr = world_corners[5], ftl = world_corners[6], ftr = world_corners[7];
215
216 /*
217 * ftl*--------------------------*ftr
218 * | . . |
219 * | . . |
220 * | . . |
221 | ntl*------*ntr |
222 * | | | |
223 * | | | |
224 | nbl*------*nbr |
225 * | . . |
226 * | . . |
227 * | . . |
228 * fbl*--------------------------*fbr
229 *
230 */
231 // NOTE: we use the above rule to make sure that normals are facing inside of the frustum
232 auto left = linalg_utils::Plane(nbl, fbl, ftl); // left
233 auto right = linalg_utils::Plane(nbr, ntr, ftr); // right
234 auto bottom = linalg_utils::Plane(nbl, nbr, fbr); // bottom
235 auto top = linalg_utils::Plane(ntl, ftl, ftr); // top
236 // NOTE: on windows I get a compliation error because apparently near and far is a keyword so I'll append an
237 // underscore to fix this.
238 auto near_ = linalg_utils::Plane(ntl, ntr, nbr); // near
239 auto far_ = linalg_utils::Plane(ftr, ftl, fbl); // far
240
241 return {left, right, bottom, top, near_, far_};
242 }
243
244 bool is_visible(const std::vector<glm::vec3> &xyz_positions, Transform &transform) override {
245 LogSection _(global_logger, "is_visible");
246 auto local_aabb = vertex_geometry::AxisAlignedBoundingBox(xyz_positions);
247 return is_visible(local_aabb, transform);
248 }
249
251 LogSection _(global_logger, "is_visible");
252 auto frustum = get_visible_frustum_world_space();
253 auto corners = get_aabb_corners_world(aabb, transform);
254 return frustum.intersects_points(corners);
255 }
256
257 void process_input(bool slow_move_pressed, bool fast_move_pressed, bool forward_pressed, bool left_pressed,
258 bool backward_pressed, bool right_pressed, bool up_pressed, bool down_pressed, float delta_time);
259
260 void mouse_callback(double xpos, double ypos, double sensitivity_override = -1);
261
265 glm::mat4 get_view_matrix() const override;
266 glm::mat4 get_third_person_view_matrix() const;
267 glm::mat4 get_view_matrix_at(glm::vec3 position) const;
271 glm::mat4 get_projection_matrix() const override;
272};
273
275 float zoom = 1.0f;
276
277 glm::vec2 offset = glm::vec2(0);
278
279 double last_mouse_pos_x = 0.0f;
280 double last_mouse_pos_y = 0.0f;
281
282 bool is_dragging = false;
283
285
288
290 float aspect = 1.0f; // caller can set actual aspect
291 float view_width = zoom * aspect;
292 float view_height = zoom;
293
294 // corners of the ortho box
295 glm::vec3 nbl(-view_width + offset.x, -view_height + offset.y, -1.0f);
296 glm::vec3 nbr(view_width + offset.x, -view_height + offset.y, -1.0f);
297 glm::vec3 ntl(-view_width + offset.x, view_height + offset.y, -1.0f);
298 glm::vec3 ntr(view_width + offset.x, view_height + offset.y, -1.0f);
299
300 glm::vec3 fbl(-view_width + offset.x, -view_height + offset.y, 1.0f);
301 glm::vec3 fbr(view_width + offset.x, -view_height + offset.y, 1.0f);
302 glm::vec3 ftl(-view_width + offset.x, view_height + offset.y, 1.0f);
303 glm::vec3 ftr(view_width + offset.x, view_height + offset.y, 1.0f);
304
305 // build planes just like in 3D
306 auto left = linalg_utils::Plane(nbl, fbl, ftl);
307 auto right = linalg_utils::Plane(nbr, ntr, ftr);
308 auto bottom = linalg_utils::Plane(nbl, nbr, fbr);
309 auto top = linalg_utils::Plane(ntl, ftl, ftr);
310 // NOTE: on windows I get a compliation error because apparently near and far is a keyword so I'll append an
311 // underscore to fix this.
312 auto near_ = linalg_utils::Plane(ntl, ntr, nbr);
313 auto far_ = linalg_utils::Plane(ftr, ftl, fbl);
314
315 return {left, right, bottom, top, near_, far_};
316 }
317
318 glm::mat4 get_view_matrix() const override {
319 // because we bake position info into projection which is weird fix later
320 return glm::mat4(1);
321 }
322
323 bool is_visible(const std::vector<glm::vec3> &xyz_positions, Transform &transform) override {
324 LogSection _(global_logger, "is_visible");
325
326 // compute object aabb in local space
327 auto local_aabb = vertex_geometry::AxisAlignedBoundingBox(xyz_positions);
328
329 return is_visible(local_aabb, transform);
330 }
331
333 LogSection _(global_logger, "is_visible");
334
335 auto corners = aabb.get_corners();
336
337 // Transform corners into world space
338 glm::mat4 model = transform.get_transform_matrix();
339 glm::vec3 min_world(std::numeric_limits<float>::max());
340 glm::vec3 max_world(-std::numeric_limits<float>::max());
341 for (auto &c : corners) {
342 glm::vec4 world_c = model * glm::vec4(c, 1.0f);
343 min_world = glm::min(min_world, glm::vec3(world_c));
344 max_world = glm::max(max_world, glm::vec3(world_c));
345 }
346
347 // Now we have a world-space AABB
348 glm::vec2 aabb_min(min_world.x, min_world.y);
349 glm::vec2 aabb_max(max_world.x, max_world.y);
350
351 // Compute camera view rectangle
352 float aspect = static_cast<float>(screen_width_px) / screen_height_px;
353 float view_width = zoom * aspect;
354 float view_height = zoom;
355
356 glm::vec2 cam_min(-view_width + offset.x, -view_height + offset.y);
357 glm::vec2 cam_max(view_width + offset.x, view_height + offset.y);
358
359 // Check overlap (2D AABB vs AABB)
360 return !(aabb_max.x < cam_min.x || aabb_min.x > cam_max.x || aabb_max.y < cam_min.y || aabb_min.y > cam_max.y);
361 }
362
363 // NOTE: yeah this is kinda dumb, fix it up when it matters
364 glm::mat4 get_projection_matrix() const override { return get_transform_matrix(); }
365
366 glm::mat4 get_transform_matrix() const {
367 float aspect = static_cast<float>(screen_width_px) / screen_height_px;
368 float view_width = zoom * aspect;
369 float view_height = zoom;
370
371 glm::mat4 projection = glm::ortho(-view_width + offset.x, view_width + offset.x, -view_height + offset.y,
372 view_height + offset.y, -1.0f, 1.0f);
373 return projection;
374 }
375
376 void on_scroll(double x_offset, double y_offset) {
377 if (y_offset > 0) {
378 zoom /= 1.1;
379 } else {
380 zoom *= 1.1;
381 }
382 }
383
384 // TODO: one day I want to implement that "momentum style of dragging"
385 void update(double mouse_delta_x, double mouse_delta_y, unsigned int width, unsigned int height, bool is_dragging) {
386 if (is_dragging) {
387 float aspect = static_cast<float>(width) / height;
388 float sensitivity = 10;
389 offset.x -= sensitivity * static_cast<float>(mouse_delta_x) / width * zoom * aspect * 2.0f;
390 offset.y += sensitivity * static_cast<float>(mouse_delta_y) / height * zoom * 2.0f;
391 }
392 }
393};
394
395#endif // FPS_CAMERA_HPP
void zoom_out()
Definition fps_camera.cpp:50
float slow_move_speed
Definition fps_camera.hpp:136
bool is_visible(const std::vector< glm::vec3 > &xyz_positions, Transform &transform) override
Definition fps_camera.hpp:244
void process_input(bool slow_move_pressed, bool fast_move_pressed, bool forward_pressed, bool left_pressed, bool backward_pressed, bool right_pressed, bool up_pressed, bool down_pressed, float delta_time)
Definition fps_camera.cpp:56
double unscoped_sensitivity
Definition fps_camera.hpp:150
void toggle_mouse_freeze()
Definition fps_camera.cpp:98
bool is_visible(const vertex_geometry::AxisAlignedBoundingBox &aabb, Transform &transform) override
Definition fps_camera.hpp:250
float original_fov
Definition fps_camera.hpp:146
float move_speed
Definition fps_camera.hpp:134
float far_plane
Definition fps_camera.hpp:146
double active_sensitivity
Definition fps_camera.hpp:151
glm::mat4 get_projection_matrix() const override
the projection matrix is the one that takes the world and applies the perspective view on the world
Definition fps_camera.cpp:141
Observable< float > fov
Definition fps_camera.hpp:148
void unfreeze_camera()
Definition fps_camera.cpp:100
void freeze_camera()
Definition fps_camera.cpp:99
void mouse_callback(double xpos, double ypos, double sensitivity_override=-1)
Definition fps_camera.cpp:102
glm::mat4 projection
Definition fps_camera.hpp:138
glm::mat4 get_third_person_view_matrix() const
Definition fps_camera.cpp:122
void change_active_sensitivity(double new_sens)
Definition fps_camera.cpp:40
glm::mat4 get_view_matrix_at(glm::vec3 position) const
Definition fps_camera.cpp:134
float zoom_fov
Definition fps_camera.hpp:146
FPSCamera(unsigned int &screen_width_px, unsigned int &screen_height_px, glm::vec3 start_position=glm::vec3(0.0f, 0.0f, 0.0f), double user_sensitivity=1.0, float fov=90.0f, float zoom_fov=30.0f, float near_plane=0.01f, float far_plane=300.0f)
Definition fps_camera.cpp:8
Mouse mouse
Definition fps_camera.hpp:131
void toggle_zoom()
Definition fps_camera.cpp:32
bool zoomed_in
Definition fps_camera.hpp:145
glm::vec3 up
Definition fps_camera.hpp:137
float near_plane
Definition fps_camera.hpp:146
unsigned int & screen_height_px
Definition fps_camera.hpp:154
glm::mat4 get_view_matrix() const override
this is the matrix that puts the camera in the right place and looking in the right place
Definition fps_camera.cpp:117
bool camera_frozen
Definition fps_camera.hpp:140
unsigned int & screen_width_px
Definition fps_camera.hpp:153
Frustum get_visible_frustum_world_space() override
Definition fps_camera.hpp:169
void zoom_in()
Definition fps_camera.cpp:45
float fast_move_speed
Definition fps_camera.hpp:135
Definition logger.hpp:182
Definition mouse.hpp:7
Definition observable.hpp:7
Definition transform.hpp:16
glm::mat4 get_transform_matrix()
Definition transform.cpp:52
Definition linalg_utils.hpp:42
@ NormalSide
Definition linalg_utils.hpp:57
Definition vertex_geometry.hpp:70
std::array< glm::vec3, 8 > get_corners() const
Definition vertex_geometry.hpp:86
std::array< glm::vec3, 8 > get_aabb_corners_world(const vertex_geometry::AxisAlignedBoundingBox &box, Transform &transform)
Definition fps_camera.hpp:107
@ s
Definition input_state.hpp:45
@ c
Definition input_state.hpp:29
@ r
Definition input_state.hpp:44
@ S
Definition input_state.hpp:72
@ p
Definition input_state.hpp:42
@ i
Definition input_state.hpp:35
@ T
Definition input_state.hpp:73
@ R
Definition input_state.hpp:71
@ t
Definition input_state.hpp:46
@ w
Definition input_state.hpp:49
Logger global_logger
Definition logger.cpp:3
glm::mat4 get_transform_matrix() const
Definition fps_camera.hpp:366
bool is_dragging
Definition fps_camera.hpp:282
bool is_visible(const vertex_geometry::AxisAlignedBoundingBox &aabb, Transform &transform) override
Definition fps_camera.hpp:332
double last_mouse_pos_x
Definition fps_camera.hpp:279
unsigned int & screen_height_px
Definition fps_camera.hpp:284
Camera2D(unsigned int &screen_width_px, unsigned int &screen_height_px)
Definition fps_camera.hpp:286
glm::mat4 get_view_matrix() const override
Definition fps_camera.hpp:318
void on_scroll(double x_offset, double y_offset)
Definition fps_camera.hpp:376
void update(double mouse_delta_x, double mouse_delta_y, unsigned int width, unsigned int height, bool is_dragging)
Definition fps_camera.hpp:385
glm::mat4 get_projection_matrix() const override
Definition fps_camera.hpp:364
bool is_visible(const std::vector< glm::vec3 > &xyz_positions, Transform &transform) override
Definition fps_camera.hpp:323
Frustum get_visible_frustum_world_space() override
Definition fps_camera.hpp:289
float zoom
Definition fps_camera.hpp:275
double last_mouse_pos_y
Definition fps_camera.hpp:280
glm::vec2 offset
Definition fps_camera.hpp:277
unsigned int & screen_width_px
Definition fps_camera.hpp:284
Definition fps_camera.hpp:10
std::array< linalg_utils::Plane, 6 > planes
Definition fps_camera.hpp:11
bool intersects_points(const Container &points) const
Definition fps_camera.hpp:15
Definition fps_camera.hpp:118
Transform transform
Definition fps_camera.hpp:126
virtual bool is_visible(const vertex_geometry::AxisAlignedBoundingBox &aabb, Transform &transform)=0
virtual glm::mat4 get_projection_matrix() const =0
virtual Frustum get_visible_frustum_world_space()=0
virtual ~ICamera()=default
virtual glm::mat4 get_view_matrix() const =0
virtual bool is_visible(const std::vector< glm::vec3 > &xyz_positions, Transform &transform)=0