Physics Simulation & Visualization Tool 0.1
A C++ physics simulation engine with real-time 3D visualization
Loading...
Searching...
No Matches
Camera.cpp
Go to the documentation of this file.
1#include "Camera.h"
2
3#include "SceneObject.h"
4#include <algorithm>
5#include <cmath>
6#include <glm/gtc/matrix_transform.hpp>
7#include <glm/gtx/component_wise.hpp>
8
9namespace {
10constexpr float kMinAspectRatio = 0.0001f;
11constexpr float kMinFollowOffsetLengthSq = 0.000001f;
12constexpr float kMinFollowOrbitRadius = 0.000001f;
13constexpr float kMinAspectRatioForFocus = 0.01f;
14constexpr float kMinFocusFramingAngleDeg = 1.0f;
15constexpr float kFocusPadding = 1.25f;
16constexpr float kMinFocusDistance = 0.35f;
17constexpr glm::vec3 kFocusDirection = glm::vec3(1.0f, 0.55f, 1.0f);
18constexpr float kMinFarClipSeparation = 1.0f;
19constexpr double kPitchClampDeg = 90.0;
20constexpr float kTargetPitchClampDeg = 89.0f;
21constexpr float kMinScrollTargetRadius = 0.01f;
22constexpr float kMinScrollDistanceMultiplier = 1.05f;
23constexpr float kMaxScrollDistanceMultiplier = 1000000.0f;
24constexpr float kTargetZoomFactorPerWheelStep = 0.85f;
25constexpr double kDefaultYawDeg = -90.0;
26constexpr double kDefaultPitchDeg = 0.0;
27constexpr float kDefaultFollowOffsetDistance = 1.0f;
28}
29
30Camera::Camera(glm::vec3 initPosition) {
31 position = initPosition;
32 worldUp = glm::vec3(0.0f, 1.0f, 0.0f);
33 front = glm::vec3(0.0f, 0.0f, -1.0f);
34 right = glm::normalize(glm::cross(front, worldUp));
35 up = glm::cross(right, front);
36}
37
38glm::mat4 Camera::getViewMatrix() const {
39 return glm::lookAt(position, position + front, up);
40}
41
42glm::mat4 Camera::getRenderViewMatrix() const {
43 return glm::lookAt(glm::vec3(0.0f), front, up);
44}
45
46glm::mat4 Camera::getProjMatrix() const {
47 const float tanHalfFov = std::tan(glm::radians(fov) * 0.5f);
48 const float safeAspect = std::max(aspectRatio, kMinAspectRatio);
49
50 glm::mat4 projection(0.0f);
51 projection[0][0] = 1.0f / (safeAspect * tanHalfFov);
52 projection[1][1] = 1.0f / tanHalfFov;
53 projection[2][3] = -1.0f;
54 projection[3][2] = nearClip;
55 return projection;
56}
57
58void Camera::setClipRange(float nearPlane, float farPlane) {
59 nearClip = std::max(nearPlane, kDefaultNearClip);
60 farClip = std::max(farPlane, nearClip + kMinFarClipSeparation);
61}
62
63void Camera::setView(const glm::vec3& newPosition, double newYaw, double newPitch) {
64 targetObject = nullptr;
65 position = newPosition;
66 yaw = newYaw;
67 pitch = std::clamp(newPitch, -kPitchClampDeg, kPitchClampDeg);
68 nearClip = kDefaultNearClip;
69 farClip = kDefaultFarClip;
70 resetMouse();
71 updateCameraVectors();
72}
73
74void Camera::resetView(const glm::vec3& newPosition) {
75 setView(newPosition, kDefaultYawDeg, kDefaultPitchDeg);
76}
77
79 targetObject = obj;
80 if (targetObject) {
81 followPivot = targetObject->getPosition();
82 followOffset = position - followPivot;
83 if (glm::length2(followOffset) <= kMinFollowOffsetLengthSq) {
84 followOffset = -front * kDefaultFollowOffsetDistance;
85 }
86 }
87}
88
90 if (!obj) return;
91
92 const float visualRadius = glm::compMax(glm::abs(obj->getScale())) * 0.5f;
93 const float verticalHalfFov = glm::radians(fov) * 0.5f;
94 const float horizontalHalfFov = std::atan(std::tan(verticalHalfFov) * std::max(aspectRatio, kMinAspectRatioForFocus));
95 const float framingHalfFov = std::max(std::min(verticalHalfFov, horizontalHalfFov), glm::radians(kMinFocusFramingAngleDeg));
96 const float distance = std::max((visualRadius * kFocusPadding) / std::sin(framingHalfFov), kMinFocusDistance);
97 nearClip = std::max(distance - visualRadius * 2.0f, kDefaultNearClip);
98 farClip = std::max(distance + visualRadius * 4.0f, kDefaultFarClip);
99 followOffset = glm::normalize(kFocusDirection) * distance;
100
101 const glm::vec3 targetPos = obj->getPosition();
102 followPivot = targetPos;
103 position = targetPos + followOffset;
104 front = glm::normalize(targetPos - position);
105 right = glm::normalize(glm::cross(front, worldUp));
106 up = glm::normalize(glm::cross(right, front));
107 yaw = glm::degrees(std::atan2(front.z, front.x));
108 pitch = glm::degrees(std::asin(std::clamp(front.y, -1.0f, 1.0f)));
109 targetObject = nullptr;
110}
111
113 targetObject = nullptr;
114 nearClip = kDefaultNearClip;
115 farClip = kDefaultFarClip;
116}
117
118void Camera::update(const glm::vec3* renderTargetPosition) {
119 if (targetObject) {
120 glm::vec3 targetPos = renderTargetPosition ? *renderTargetPosition : targetObject->getPosition();
121 if (!std::isfinite(targetPos.x) || !std::isfinite(targetPos.y) || !std::isfinite(targetPos.z)) {
122 return;
123 }
124
125 followPivot = targetPos;
126 position = targetPos + followOffset;
127
128 front = glm::normalize(targetPos - position);
129 right = glm::normalize(glm::cross(front, worldUp));
130 up = glm::normalize(glm::cross(right, front));
131 }
132}
133
134void Camera::processMouseMovement(float xoffset, float yoffset) {
135 if (targetObject) {
136 glm::vec3 toCamera = followOffset;
137 const float radius = std::max(glm::length(toCamera), kMinFollowOrbitRadius);
138 glm::vec3 direction = glm::normalize(toCamera);
139 float orbitYaw = std::atan2(direction.z, direction.x);
140 float orbitPitch = std::asin(std::clamp(direction.y, -1.0f, 1.0f));
141 orbitYaw += glm::radians(xoffset * mouseSensitivity);
142 orbitPitch -= glm::radians(yoffset * mouseSensitivity);
143 orbitPitch = std::clamp(orbitPitch, glm::radians(-kTargetPitchClampDeg), glm::radians(kTargetPitchClampDeg));
144
145 direction.x = std::cos(orbitYaw) * std::cos(orbitPitch);
146 direction.y = std::sin(orbitPitch);
147 direction.z = std::sin(orbitYaw) * std::cos(orbitPitch);
148 followOffset = glm::normalize(direction) * radius;
149 position = followPivot + followOffset;
150 front = glm::normalize(followPivot - position);
151 right = glm::normalize(glm::cross(front, worldUp));
152 up = glm::normalize(glm::cross(right, front));
153 yaw = glm::degrees(std::atan2(front.z, front.x));
154 pitch = glm::degrees(std::asin(std::clamp(front.y, -1.0f, 1.0f)));
155 return;
156 }
157
158 yaw += xoffset * this->mouseSensitivity;
159 pitch += yoffset * this->mouseSensitivity;
160
161 if (pitch > kPitchClampDeg)
162 pitch = kPitchClampDeg;
163 if (pitch < -kPitchClampDeg)
164 pitch = -kPitchClampDeg;
165
166 updateCameraVectors();
167}
168
169void Camera::processScroll(float wheelSteps) {
170 if (wheelSteps == 0.0f) return;
171
172 if (targetObject) {
173 const float targetRadius = std::max(glm::compMax(glm::abs(targetObject->getScale())) * 0.5f, kMinScrollTargetRadius);
174 const float minDistance = targetRadius * kMinScrollDistanceMultiplier;
175 const float maxDistance = std::max(targetRadius * kMaxScrollDistanceMultiplier, minDistance + kMinFarClipSeparation);
176 const float currentDistance = std::max(glm::length(followOffset), minDistance);
177 const float zoomFactor = std::pow(kTargetZoomFactorPerWheelStep, wheelSteps);
178 const float newDistance = std::clamp(currentDistance * zoomFactor, minDistance, maxDistance);
179
180 glm::vec3 direction = glm::length2(followOffset) > kMinFollowOffsetLengthSq ? glm::normalize(followOffset) : -front;
181 followOffset = direction * newDistance;
182 position = followPivot + followOffset;
183 front = glm::normalize(followPivot - position);
184 right = glm::normalize(glm::cross(front, worldUp));
185 up = glm::normalize(glm::cross(right, front));
186 return;
187 }
188
189 position += front * movementSpeed * wheelSteps;
190}
191
192void Camera::processKeyboard(Movement direction, float deltaTime) {
193 float velocity = this->movementSpeed * deltaTime;
194 if (targetObject) {
195 (void)direction;
196 (void)velocity;
197 return;
198 }
199
200 switch (direction) {
202 position += front * velocity;
203 break;
205 position -= front * velocity;
206 break;
207 case Movement::LEFT:
208 position -= right * velocity;
209 break;
210 case Movement::RIGHT:
211 position += right * velocity;
212 break;
213 }
214}
215
216void Camera::updateCameraVectors() {
217 glm::vec3 newDir;
218 newDir.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
219 newDir.y = sin(glm::radians(pitch));
220 newDir.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
221 front = glm::normalize(newDir);
222 right = glm::normalize(glm::cross(front, worldUp));
223 up = glm::normalize(glm::cross(right, front));
224}
225
227 firstMouse = true;
228}
Movement
Definition Camera.h:4
float movementSpeed
Definition Camera.h:30
glm::vec3 position
Definition Camera.h:15
Camera(glm::vec3 initPosition)
Definition Camera.cpp:30
glm::mat4 getProjMatrix() const
Definition Camera.cpp:46
void setView(const glm::vec3 &newPosition, double newYaw, double newPitch)
Definition Camera.cpp:63
void clearTarget()
Definition Camera.cpp:112
void update(const glm::vec3 *renderTargetPosition=nullptr)
Definition Camera.cpp:118
glm::mat4 getViewMatrix() const
Definition Camera.cpp:38
void setClipRange(float nearPlane, float farPlane)
Definition Camera.cpp:58
void processKeyboard(Movement direction, float deltaTime)
Definition Camera.cpp:192
float mouseSensitivity
Definition Camera.h:31
void processScroll(float wheelSteps)
Definition Camera.cpp:169
glm::vec3 up
Definition Camera.h:17
double pitch
Definition Camera.h:22
double yaw
Definition Camera.h:21
glm::mat4 getRenderViewMatrix() const
Definition Camera.cpp:42
glm::vec3 front
Definition Camera.h:16
void resetView(const glm::vec3 &newPosition=glm::vec3(0.0f, 10.0f, 30.0f))
Definition Camera.cpp:74
void processMouseMovement(float xoffset, float yoffset)
Definition Camera.cpp:134
glm::vec3 worldUp
Definition Camera.h:19
static constexpr float kDefaultNearClip
Definition Camera.h:27
static constexpr float kDefaultFarClip
Definition Camera.h:28
void focusOn(SceneObject *obj)
Definition Camera.cpp:89
void setTarget(SceneObject *obj)
Definition Camera.cpp:78
bool firstMouse
Definition Camera.h:58
glm::vec3 right
Definition Camera.h:18
void resetMouse()
Definition Camera.cpp:226
float fov
Definition Camera.h:32
glm::vec3 getPosition() const
glm::vec3 getScale() const