6#include <glm/gtc/matrix_transform.hpp>
7#include <glm/gtx/component_wise.hpp>
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;
32 worldUp = glm::vec3(0.0f, 1.0f, 0.0f);
33 front = glm::vec3(0.0f, 0.0f, -1.0f);
43 return glm::lookAt(glm::vec3(0.0f),
front,
up);
47 const float tanHalfFov = std::tan(glm::radians(
fov) * 0.5f);
48 const float safeAspect = std::max(aspectRatio, kMinAspectRatio);
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;
60 farClip = std::max(farPlane, nearClip + kMinFarClipSeparation);
63void Camera::setView(
const glm::vec3& newPosition,
double newYaw,
double newPitch) {
64 targetObject =
nullptr;
67 pitch = std::clamp(newPitch, -kPitchClampDeg, kPitchClampDeg);
71 updateCameraVectors();
75 setView(newPosition, kDefaultYawDeg, kDefaultPitchDeg);
82 followOffset =
position - followPivot;
83 if (glm::length2(followOffset) <= kMinFollowOffsetLengthSq) {
84 followOffset = -
front * kDefaultFollowOffsetDistance;
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);
99 followOffset = glm::normalize(kFocusDirection) * distance;
102 followPivot = targetPos;
103 position = targetPos + followOffset;
108 pitch = glm::degrees(std::asin(std::clamp(
front.y, -1.0f, 1.0f)));
109 targetObject =
nullptr;
113 targetObject =
nullptr;
120 glm::vec3 targetPos = renderTargetPosition ? *renderTargetPosition : targetObject->
getPosition();
121 if (!std::isfinite(targetPos.x) || !std::isfinite(targetPos.y) || !std::isfinite(targetPos.z)) {
125 followPivot = targetPos;
126 position = targetPos + followOffset;
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));
143 orbitPitch = std::clamp(orbitPitch, glm::radians(-kTargetPitchClampDeg), glm::radians(kTargetPitchClampDeg));
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;
154 pitch = glm::degrees(std::asin(std::clamp(
front.y, -1.0f, 1.0f)));
161 if (
pitch > kPitchClampDeg)
162 pitch = kPitchClampDeg;
163 if (
pitch < -kPitchClampDeg)
164 pitch = -kPitchClampDeg;
166 updateCameraVectors();
170 if (wheelSteps == 0.0f)
return;
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);
180 glm::vec3 direction = glm::length2(followOffset) > kMinFollowOffsetLengthSq ? glm::normalize(followOffset) : -
front;
181 followOffset = direction * newDistance;
182 position = followPivot + followOffset;
216void Camera::updateCameraVectors() {
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);
Camera(glm::vec3 initPosition)
glm::mat4 getProjMatrix() const
void setView(const glm::vec3 &newPosition, double newYaw, double newPitch)
void update(const glm::vec3 *renderTargetPosition=nullptr)
glm::mat4 getViewMatrix() const
void setClipRange(float nearPlane, float farPlane)
void processKeyboard(Movement direction, float deltaTime)
void processScroll(float wheelSteps)
glm::mat4 getRenderViewMatrix() const
void resetView(const glm::vec3 &newPosition=glm::vec3(0.0f, 10.0f, 30.0f))
void processMouseMovement(float xoffset, float yoffset)
static constexpr float kDefaultNearClip
static constexpr float kDefaultFarClip
void focusOn(SceneObject *obj)
void setTarget(SceneObject *obj)
glm::vec3 getPosition() const
glm::vec3 getScale() const