utility 2026.1.9
A comprehensive C++ utilities library tailored for the development of modern desktop and extended reality (XR) applications.
Loading...
Searching...
No Matches
view.hpp
1/*
2 Copyright (c) 2026 ETIB Corporation
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy of
5 this software and associated documentation files (the "Software"), to deal in
6 the Software without restriction, including without limitation the rights to
7 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8 of the Software, and to permit persons to whom the Software is furnished to do
9 so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in all
12 copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 SOFTWARE.
21 */
22
23#pragma once
24
25#include <cmath>
26#include <numbers>
27#include <ostream>
28#include <stdexcept>
29#include <type_traits>
30#include <utility>
31#include <ostream>
32
33#include "utility/graphic/field_of_view.hpp"
34#include "utility/graphic/pose.hpp"
36
37namespace utility::graphic
38{
39
44 template<typename ViewComponentType>
48
54 template<CanBeViewComponent ViewComponentType> class View
55 {
56 private:
57 Pose<ViewComponentType> _pose; //< View position and orientation
58
60 _fieldOfView;
61
63 _viewportSize;
64
65 ViewComponentType _nearPlane;
66 ViewComponentType _farPlane;
67
68 bool _flipY = true;
69
77 static void validatePerspective(ViewComponentType verticalFovRadians,
78 ViewComponentType aspectRatio)
79 {
80 if (verticalFovRadians <= ViewComponentType {}
81 || verticalFovRadians
82 >= std::numbers::pi_v<ViewComponentType>) {
83 throw std::invalid_argument(
84 "View vertical FOV must be in range (0, pi) radians");
85 }
86
87 if (aspectRatio <= ViewComponentType {}) {
88 throw std::invalid_argument(
89 "View aspect ratio must be positive");
90 }
91 }
92
101 makeSymmetricFieldOfView(ViewComponentType verticalFovRadians,
102 ViewComponentType aspectRatio)
103 {
104 const ViewComponentType halfVertical =
105 verticalFovRadians / ViewComponentType { 2 };
106
107 const ViewComponentType halfHorizontal =
108 std::atan(std::tan(halfVertical) * aspectRatio);
109
111 halfVertical, -halfVertical, -halfHorizontal, halfHorizontal);
112 }
113
120 static void validateOrientation(
123 {
124 if (math::dot(forward, forward) == ViewComponentType {}) {
125 throw std::invalid_argument(
126 "View forward vector must be non-zero");
127 }
128 if (math::dot(up, up) == ViewComponentType {}) {
129 throw std::invalid_argument("View up vector must be non-zero");
130 }
131 if (math::dot(math::cross(forward, up), math::cross(forward, up))
132 == ViewComponentType {}) {
133 throw std::invalid_argument(
134 "View forward and up vectors must not be collinear");
135 }
136 }
137
143 math::Vector<ViewComponentType, 4> normalizedRotationComponents() const
144 {
145 const auto normalized = _pose.getOrientation().normalized();
147 static_cast<ViewComponentType>(normalized.x),
148 static_cast<ViewComponentType>(normalized.y),
149 static_cast<ViewComponentType>(normalized.z),
150 static_cast<ViewComponentType>(normalized.w)
151 };
152 }
153
159 math::Vector<ViewComponentType, 3> rotateVectorByRotation(
160 const math::Vector<ViewComponentType, 3> &vector) const
161 {
162 const auto q = normalizedRotationComponents();
163 const math::Vector<ViewComponentType, 3> u { q[0], q[1], q[2] };
164 const ViewComponentType s = q[3];
165
166 return u * (ViewComponentType { 2 } * math::dot(u, vector))
167 + vector.operator*(s * s - math::dot(u, u))
168 + math::cross(u, vector) * (ViewComponentType { 2 } * s);
169 }
170
171 public:
175 View(void)
176 : _pose()
177 , _fieldOfView()
178 , _viewportSize()
179 , _nearPlane(ViewComponentType { 1.0 })
180 , _farPlane(ViewComponentType { 1000.0 })
181 {
182 }
183
197 ViewComponentType nearPlane, ViewComponentType farPlane)
198 : _pose(std::move(pose))
199 , _fieldOfView(std::move(fov))
200 , _viewportSize(std::move(viewportSize))
201 , _nearPlane(nearPlane)
202 , _farPlane(farPlane)
203 {
204 }
205
210 View(const View &other) = default;
211
216 View(View &&other) noexcept = default;
217
223 View &operator=(const View &other) = default;
224
230 View &operator=(View &&other) noexcept = default;
231
235 ~View(void) = default;
236
242 {
243 _pose = pose;
244 }
245
251 {
252 return _pose;
253 }
254
260 {
261 return math::normalize(
262 rotateVectorByRotation(math::Vector<ViewComponentType, 3> {
263 ViewComponentType {}, ViewComponentType {},
264 ViewComponentType { -1 } }));
265 }
266
272 {
273 return math::normalize(rotateVectorByRotation(
274 math::Vector<ViewComponentType, 3> { ViewComponentType {},
275 ViewComponentType { 1 },
276 ViewComponentType {} }));
277 }
278
286 void setPerspective(ViewComponentType verticalFovRadians,
287 ViewComponentType aspectRatio)
288 {
289 validatePerspective(verticalFovRadians, aspectRatio);
290 _fieldOfView =
291 makeSymmetricFieldOfView(verticalFovRadians, aspectRatio);
292 }
293
298 ViewComponentType getAspectRatio() const
299 {
300 const ViewComponentType vertical = std::tan(_fieldOfView.getUp())
301 - std::tan(_fieldOfView.getDown());
302 const ViewComponentType horizontal =
303 std::tan(_fieldOfView.getRight())
304 - std::tan(_fieldOfView.getLeft());
305 if (vertical == ViewComponentType {}) {
306 return ViewComponentType {};
307 }
308 return horizontal / vertical;
309 }
310
316 {
317 _fieldOfView = fieldOfView;
318 }
319
325 {
326 return _fieldOfView;
327 }
328
334 {
335 return math::normalize(rotateVectorByRotation(
336 math::Vector<ViewComponentType, 3> { ViewComponentType { 1 },
337 ViewComponentType {},
338 ViewComponentType {} }));
339 }
340
347 void setClippingPlanes(ViewComponentType nearDistance,
348 ViewComponentType farDistance)
349 {
350 if (nearDistance <= ViewComponentType { 0 }) {
351 throw std::invalid_argument("Near plane must be > 0");
352 }
353 if (farDistance <= nearDistance) {
354 throw std::invalid_argument("Far plane must be > near plane");
355 }
356
357 _nearPlane = nearDistance;
358 _farPlane = farDistance;
359 }
360
365 ViewComponentType getNearPlane() const noexcept
366 {
367 return _nearPlane;
368 }
369
374 ViewComponentType getFarPlane() const noexcept
375 {
376 return _farPlane;
377 }
378
384 {
386 }
387
394 {
395 View result(*this);
396 result.move(offset);
397 return result;
398 }
399
406 Ray<ViewComponentType> viewRay(ViewComponentType ndcX,
407 ViewComponentType ndcY) const
408 {
409 const auto tx0 = std::tan(_fieldOfView.getLeft());
410 const auto tx1 = std::tan(_fieldOfView.getRight());
411 const auto ty0 = std::tan(_fieldOfView.getDown());
412 const auto ty1 = std::tan(_fieldOfView.getUp());
413
414 const ViewComponentType horizontalOffset =
415 ((ndcX + ViewComponentType { 1 }) * ViewComponentType { 0.5 })
416 * (tx1 - tx0)
417 + tx0;
418
419 const ViewComponentType verticalOffset =
420 ((ndcY + ViewComponentType { 1 }) * ViewComponentType { 0.5 })
421 * (ty1 - ty0)
422 + ty0;
423
424 const auto rayDirection = getForward()
425 + getRight() * horizontalOffset + getUp() * verticalOffset;
426
427 return Ray<ViewComponentType>(_pose.getPosition(),
428 math::normalize(rayDirection));
429 }
430
438 const math::Vector<ViewComponentType, 2> &point) const
439 {
440 if (point.x < 0 || point.y < 0 || point.x >= _viewportSize.x
441 || point.y >= _viewportSize.y) {
442 throw std::out_of_range(
443 "View point is outside of viewport bounds");
444 }
445
446 const ViewComponentType ndcX =
447 (static_cast<ViewComponentType>(point.x) / _viewportSize.x)
448 * ViewComponentType { 2 }
449 - ViewComponentType { 1 };
450
451 const ViewComponentType ndcY = ViewComponentType { 1 }
452 - (static_cast<ViewComponentType>(point.y) / _viewportSize.y)
453 * ViewComponentType { 2 };
454
455 return viewRay(ndcX, ndcY);
456 }
457
463 ViewComponentType getVerticalFovRadians() const noexcept
464 {
465 return _fieldOfView.getUp() - _fieldOfView.getDown();
466 }
467
472 ViewComponentType getHorizontalFovRadians() const noexcept
473 {
474 return _fieldOfView.getRight() - _fieldOfView.getLeft();
475 }
482 ViewComponentType epsilon = ViewComponentType {
483 1e-6 }) const noexcept
484 {
485 return _fieldOfView.isSymmetric(epsilon);
486 }
487
493 {
494 return viewRay(0, 0);
495 }
496
502 bool operator==(const View &other) const noexcept
503 {
504 return _pose == other._pose
505 && _fieldOfView.getUp() == other._fieldOfView.getUp()
506 && _fieldOfView.getDown() == other._fieldOfView.getDown()
507 && _fieldOfView.getLeft() == other._fieldOfView.getLeft()
508 && _fieldOfView.getRight() == other._fieldOfView.getRight();
509 }
510
516 bool operator!=(const View &other) const noexcept
517 {
518 return !(*this == other);
519 }
520
526 const math::Vector<ViewComponentType, 2> &viewportSize)
527 {
528 _viewportSize = viewportSize;
529 }
530
536 {
537 return _viewportSize;
538 }
539
545 {
546 const auto orientation = _pose.getOrientation();
547 const auto position = _pose.getPosition();
548
550 glm::translate(glm::identity<glm::mat4>(),
551 glm::vec3(position.x, position.y, position.z))
552 * glm::mat4_cast(glm::quat(orientation.w, orientation.x,
553 orientation.y, orientation.z))) };
554
555 // GLM is column-major: view[col][row]
556 // view[0][0] = static_cast<ViewComponentType>(right[0]);
557 // view[0][1] = static_cast<ViewComponentType>(up[0]);
558 // view[0][2] = static_cast<ViewComponentType>(-forward[0]);
559 // view[0][3] = static_cast<ViewComponentType>(0);
560
561 // view[1][0] = static_cast<ViewComponentType>(right[1]);
562 // view[1][1] = static_cast<ViewComponentType>(up[1]);
563 // view[1][2] = static_cast<ViewComponentType>(-forward[1]);
564 // view[1][3] = static_cast<ViewComponentType>(0);
565
566 // view[2][0] = static_cast<ViewComponentType>(right[2]);
567 // view[2][1] = static_cast<ViewComponentType>(up[2]);
568 // view[2][2] = static_cast<ViewComponentType>(-forward[2]);
569 // view[2][3] = static_cast<ViewComponentType>(0);
570
571 // view[3][0] =
572 // -static_cast<ViewComponentType>(math::dot(right, position));
573 // view[3][1] =
574 // -static_cast<ViewComponentType>(math::dot(up, position));
575 // view[3][2] =
576 // static_cast<ViewComponentType>(math::dot(forward, position));
577 // view[3][3] = static_cast<ViewComponentType>(1);
578
579 return view;
580 }
581
591 {
592 const ViewComponentType tanLeft = std::tan(_fieldOfView.getLeft());
593 const ViewComponentType tanRight =
594 std::tan(_fieldOfView.getRight());
595 const ViewComponentType tanUp = std::tan(_fieldOfView.getUp());
596 const ViewComponentType tanDown = std::tan(_fieldOfView.getDown());
597
598 const ViewComponentType width = tanRight - tanLeft;
599 const ViewComponentType height = tanUp - tanDown;
600
601 if (width <= ViewComponentType { 0 }
602 || height <= ViewComponentType { 0 }) {
603 throw std::invalid_argument(
604 "View frustum width and height must be positive");
605 }
606
607 if (_farPlane <= _nearPlane) {
608 throw std::invalid_argument(
609 "View far plane must be greater than near plane");
610 }
611
613
614 projection[0][0] = static_cast<ViewComponentType>(2) / width;
615 projection[1][1] = static_cast<ViewComponentType>(2) / height;
616 projection[2][0] = (tanRight + tanLeft) / width;
617 projection[2][1] = (tanUp + tanDown) / height;
618 projection[2][2] = -_farPlane / (_farPlane - _nearPlane);
619 projection[2][3] = -static_cast<ViewComponentType>(1);
620 projection[3][2] =
621 -(_farPlane * _nearPlane) / (_farPlane - _nearPlane);
622 projection[3][3] = static_cast<ViewComponentType>(0);
623
624 if (_flipY) {
625 // Invert Y for Vulkan-style NDC (Y pointing down).
626 projection[1][1] *= static_cast<ViewComponentType>(-1);
627 projection[2][1] *= static_cast<ViewComponentType>(-1);
628 }
629
630 return projection;
631 }
632
640 void setFlipY(bool flip) noexcept
641 {
642 _flipY = flip;
643 }
644
649 bool isFlipY(void) const noexcept
650 {
651 return _flipY;
652 }
653 };
654
658 using ViewF = View<float>;
659
663 using ViewD = View<double>;
664
671 std::ostream &operator<<(std::ostream &stream, const ViewF &view);
672
679 std::ostream &operator<<(std::ostream &stream, const ViewD &view);
680
681} // namespace utility::graphic
Field of view representation in radians for up, down, left, and right directions.
Type getLeft(void) const noexcept
Get left field-of-view in radians.
Type getUp(void) const noexcept
Get up field-of-view in radians.
Type getDown(void) const noexcept
Get down field-of-view in radians.
Type getRight(void) const noexcept
Get right field-of-view in radians.
bool isSymmetric(Type epsilon=Type { 1e-6 }) const noexcept
Check if this FOV is symmetric around vertical and horizontal axes.
Orientation normalized(void) const noexcept
Return a normalized orientation.
Template class representing a pose (Position + Orientation).
Definition pose.hpp:51
const Orientation< PoseComponentType > & getOrientation(void) const noexcept
Get the orientation component.
Definition pose.hpp:175
const Position< PoseComponentType > & getPosition(void) const noexcept
Get the position component.
Definition pose.hpp:141
Pose & translate(const Position< PoseComponentType > &offset) noexcept
Translate pose position by an offset.
Definition pose.hpp:185
Position in 3D space represented as a vector of three PositionComponentType components (x,...
Definition position.hpp:53
Geometric ray with arithmetic component type and fixed dimension.
Definition ray.hpp:61
3D perspective view with floating-point components.
Definition view.hpp:55
Ray< ViewComponentType > viewPointToRay(const math::Vector< ViewComponentType, 2 > &point) const
Create a world-space ray from viewport pixel coordinates.
Definition view.hpp:437
utility::math::Matrix< ViewComponentType, 4, 4 > toViewMatrix(void) const
Convert view to a GLM-compatible 4x4 view matrix.
Definition view.hpp:544
void setClippingPlanes(ViewComponentType nearDistance, ViewComponentType farDistance)
Set near and far clipping plane distances.
Definition view.hpp:347
bool isFieldOfViewSymmetric(ViewComponentType epsilon=ViewComponentType { 1e-6 }) const noexcept
Check whether current field-of-view is symmetric.
Definition view.hpp:481
void move(const math::Vector< ViewComponentType, 3 > &offset)
Translate view position by an offset.
Definition view.hpp:383
~View(void)=default
Destructor.
View & operator=(View &&other) noexcept=default
Move assignment operator.
utility::math::Matrix< ViewComponentType, 4, 4 > getProjectionMatrix() const
Build a perspective projection matrix from per-side field of view angles.
Definition view.hpp:590
View(const View &other)=default
Copy constructor.
math::Vector< ViewComponentType, 3 > getUp(void) const
Get normalized up direction.
Definition view.hpp:271
void setFlipY(bool flip) noexcept
Set whether the projection matrix inverts the Y axis.
Definition view.hpp:640
bool isFlipY(void) const noexcept
Get whether the projection matrix inverts the Y axis.
Definition view.hpp:649
View & operator=(const View &other)=default
Copy assignment operator.
ViewComponentType getNearPlane() const noexcept
Get near clipping plane distance.
Definition view.hpp:365
ViewComponentType getVerticalFovRadians() const noexcept
Get total vertical field-of-view in radians.
Definition view.hpp:463
View(void)
Default constructor with common perspective defaults.
Definition view.hpp:175
ViewComponentType getHorizontalFovRadians() const noexcept
Get total horizontal field-of-view in radians.
Definition view.hpp:472
void setPose(const Pose< ViewComponentType > &pose)
Set view world-space pose.
Definition view.hpp:241
Ray< ViewComponentType > viewRay(ViewComponentType ndcX, ViewComponentType ndcY) const
Create a world-space ray from normalized device coordinates.
Definition view.hpp:406
ViewComponentType getFarPlane() const noexcept
Get far clipping plane distance.
Definition view.hpp:374
View(Pose< ViewComponentType > pose, utility::graphic::FieldOfView< ViewComponentType > fov, math::Vector< ViewComponentType, 2 > viewportSize, ViewComponentType nearPlane, ViewComponentType farPlane)
Construct view from explicit quaternion orientation.
Definition view.hpp:194
void setViewportSize(const math::Vector< ViewComponentType, 2 > &viewportSize)
Set viewport size in pixels.
Definition view.hpp:525
Ray< ViewComponentType > centerRay(void) const
Create a ray going through the center of the viewport.
Definition view.hpp:492
View(View &&other) noexcept=default
Move constructor.
Pose< ViewComponentType > getPose(void) const
Get view world-space pose.
Definition view.hpp:250
ViewComponentType getAspectRatio() const
Get aspect ratio (width/height).
Definition view.hpp:298
void setPerspective(ViewComponentType verticalFovRadians, ViewComponentType aspectRatio)
Set perspective projection parameters.
Definition view.hpp:286
View moved(const math::Vector< ViewComponentType, 3 > &offset) const
Return translated view copy.
Definition view.hpp:393
math::Vector< ViewComponentType, 3 > getRight(void) const
Compute view right direction from orientation basis.
Definition view.hpp:333
bool operator==(const View &other) const noexcept
Equality comparison.
Definition view.hpp:502
math::Vector< ViewComponentType, 2 > getViewportSize(void) const
Get viewport size in pixels.
Definition view.hpp:535
bool operator!=(const View &other) const noexcept
Inequality comparison.
Definition view.hpp:516
math::Vector< ViewComponentType, 3 > getForward(void) const
Get normalized forward direction.
Definition view.hpp:259
void setFieldOfView(const FieldOfView< ViewComponentType > &fieldOfView)
Set per-direction field-of-view values.
Definition view.hpp:315
FieldOfView< ViewComponentType > getFieldOfView(void) const
Get field-of-view values.
Definition view.hpp:324
M x N matrix class inheriting from glm::mat<Cols, Rows, Type>.
Definition matrix.hpp:92
3D vector class inheriting from glm::vec3.
Definition vector.hpp:83
Concept to constrain field-of-view component type.
Concept to constrain pose component type.
Definition pose.hpp:38
Concept to ensure the type can be used as a ray component.
Definition ray.hpp:53
Concept to ensure the type can be used as a view component.
Definition view.hpp:45
Geometric ray template declaration.