From fb470cdefbb799b3e7025a8a94289b27fd820d6e Mon Sep 17 00:00:00 2001 From: Jonas Sorgenfrei Date: Sat, 12 Sep 2026 19:58:05 +0200 Subject: [PATCH 1/2] feat(camera): use quaternion orientation by default --- include/modules/cam2.h | 173 +--------------- include/modules/camera.h | 319 +++++++++++++++-------------- samples/Basics/Camera/src/main.cpp | 44 +++- 3 files changed, 210 insertions(+), 326 deletions(-) diff --git a/include/modules/cam2.h b/include/modules/cam2.h index f1aab29..ea8162d 100644 --- a/include/modules/cam2.h +++ b/include/modules/cam2.h @@ -1,171 +1,6 @@ #pragma once -#ifndef CAMERA_H -#define CAMERA_H -#include -#include -#include - -#include - -/** -* Defines several possible options for camera movement. -* Used as abstraction to stay away from window-system -* specific input methods -*/ -enum Camera_Movement { - FORWARD, - BACKWARD, - LEFT, - RIGHT, - UP, - DOWN -}; - -// Default camera values -const float YAW = -90.0f; -const float PITCH = 0.0f; -const float ROLL = 0.0f; -const float SENSITIVITY = 0.05f; -const float ZOOM = 45.0f; - -// An abstract camera class that process input and calculates the corresponding -// Euler angles, vectors, and matrices used by the OpenGL camera -class Camera -{ -public: - glm::vec3 Position; - glm::vec3 Front; - glm::vec3 Up; - glm::vec3 Right; - glm::vec3 WorldUp; - //Focus Point - float distance = 1.0f; - - float radius = 1.0f; - // Eular Angles - float Yaw; - float Pitch; - float Roll; - // camera options - float MouseSensitivity; - float Zoom; - - //Constructor with vectors - Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float yaw = YAW, float pitch = PITCH, float roll = ROLL) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MouseSensitivity(SENSITIVITY), Zoom(ZOOM) - { - Position = position; - WorldUp = up; - Yaw = yaw; - Pitch = pitch; - updateCameraVectors(); - } - - // constructor with scalar values - Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MouseSensitivity(SENSITIVITY), Zoom(ZOOM) - { - Position = glm::vec3(posX, posY, posZ); - WorldUp = glm::vec3(upX, upY, upZ); - Yaw = yaw; - Pitch = pitch; - updateCameraVectors(); - } - - // Returns the view matrix calculated using Eular Angles and the LookAt Matrix - glm::mat4 GetViewMatrix() - { - //FPS camera: RotationX(pitch) * RotationY(yaw) - glm::quat qPitch = glm::angleAxis(Pitch, glm::vec3(1, 0, 0)); - glm::quat qYaw = glm::angleAxis(Yaw, glm::vec3(0, 1, 0)); - glm::quat qRoll = glm::angleAxis(Roll, glm::vec3(0, 0, 1)); - - //For a FPS camera we can omit roll - glm::quat orientation = qPitch * qYaw; - orientation = glm::normalize(orientation); - glm::mat4 rotate = glm::mat4_cast(orientation); - - glm::mat4 translate = glm::mat4(1.0f); - translate = glm::translate(translate, -Position); - - return rotate * translate; - - } - - - // Process input received from any keyboard-like input system. - // Accepts input parameter in the form of camera defined ENUM - // (to abstract it from windowing systems) - void translate(Camera_Movement direction, float velocity) - { - if (direction == FORWARD) - Position += Front * velocity; - if (direction == BACKWARD) - Position -= Front * velocity; - if (direction == LEFT) - Position -= Right * velocity; - if (direction == RIGHT) - Position += Right * velocity; - if (direction == UP) - Position += Up * velocity; - if (direction == DOWN) - Position -= Up * velocity; - - updateCameraVectors(); - } - - // processes input received from a mouse input system. - // Expects the offset value in both the x and y direction. - void rotate(float xoffset, float yoffset, GLboolean constrainPitch = true) - { - - /* TODO: ARC-BALL CAM */ - - xoffset *= MouseSensitivity; - yoffset *= MouseSensitivity; - - Yaw += xoffset; - Pitch += yoffset; - - // Make sure that when pitch is out of bounds, screen doen't get flipped - if (constrainPitch) - { - if (Pitch > 89.0f) - Pitch = 89.0f; - if (Pitch < -89.0f) - Pitch = -89.0f; - } - - //Update Front, Right and Up Vectors using the updated Eular angles - updateCameraVectors(); - } - - //Processess input received from a mouse scroll-wheel event. - // Only requires input on the vertical wheel-axis - void ProcessMouseScroll(float yoffset) - { - if (Zoom >= 1.0f && Zoom <= 45.0f) - Zoom -= yoffset; - if (Zoom <= 1.0f) - Zoom = 1.0f; - if (Zoom >= 45.0f) - Zoom = 45.0f; - } - -private: - // Calculates the front vector from the Camera's (updated) Eular Angles - void updateCameraVectors() - { - /*glm::vec3 front;*/ - Position.x = cos(glm::radians(Yaw)) * cos(glm::radians(Pitch)); - Position.y = sin(glm::radians(Pitch)); - Position.z = sin(glm::radians(Yaw)) * cos(glm::radians(Pitch)); - - //Front = glm::normalize(focus - Position); - // Also re-calculate the Right and Up vector - Right = glm::normalize(glm::cross(Front, WorldUp)); - // Normalize the vecotrs, because their length gets closer to 0 the more - // you look up or down which results in slower movement. - Up = glm::normalize(glm::cross(Right, Front)); - } -}; -#endif \ No newline at end of file +// Backward-compatible include for projects that used the old experimental +// quaternion camera header. The production implementation now lives in +// camera.h and is the default Camera type. +#include "camera.h" diff --git a/include/modules/camera.h b/include/modules/camera.h index 6663849..45cacbf 100644 --- a/include/modules/camera.h +++ b/include/modules/camera.h @@ -2,217 +2,234 @@ #define CAMERA_H #include + #include #include +#include +#include -#include +#include +#include -/** - * Defines several possible options for camera movement. - * Used as abstraction to stay away from window-system - * specific input methods - */ -enum Camera_Movement { +enum Camera_Movement +{ FORWARD, - BACKWARD, + BACKWARD, LEFT, RIGHT, UP, DOWN }; -// Default camera values -const float YAW = -90.0f; -const float PITCH = 0.0f; -const float SPEED = 2.5f; -const float SENSITIVITY = 0.1f; -const float ZOOM = 45.0f; +constexpr float YAW = -90.0f; +constexpr float PITCH = 0.0f; +constexpr float SPEED = 2.5f; +constexpr float SENSITIVITY = 0.1f; +constexpr float ZOOM = 45.0f; -// An abstract camera class that process input and calculates the corresponding -// Euler angles, vectors, and matrices used by the OpenGL camera -class Camera +/** The original yaw/pitch camera, retained for teaching and comparison. */ +class EulerCamera { public: glm::vec3 Position; - glm::vec3 Front; - glm::vec3 Up; - glm::vec3 Right; + glm::vec3 Front{0.0f, 0.0f, -1.0f}; + glm::vec3 Up{0.0f, 1.0f, 0.0f}; + glm::vec3 Right{1.0f, 0.0f, 0.0f}; glm::vec3 WorldUp; - //Focus Point - glm::vec3 focus; - // Eular Angles + glm::vec3 focus{0.0f, 0.0f, -1.0f}; float Yaw; float Pitch; - // camera options float MovementSpeed; float MouseSensitivity; float Zoom; - //Constructor with vectors - Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float yaw = YAW, float pitch = PITCH) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM) + EulerCamera(glm::vec3 position = glm::vec3(0.0f), + glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), + float yaw = YAW, float pitch = PITCH) + : Position(position), WorldUp(up), Yaw(yaw), Pitch(pitch), + MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM) { - Position = position; - WorldUp = up; - Yaw = yaw; - Pitch = pitch; updateCameraVectors(); } - // constructor with scalar values - Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM) + EulerCamera(float posX, float posY, float posZ, + float upX, float upY, float upZ, float yaw, float pitch) + : EulerCamera(glm::vec3(posX, posY, posZ), glm::vec3(upX, upY, upZ), yaw, pitch) { - Position = glm::vec3(posX, posY, posZ); - WorldUp = glm::vec3(upX, upY, upZ); - Yaw = yaw; - Pitch = pitch; - updateCameraVectors(); } - // Returns the view matrix calculated using Eular Angles and the LookAt Matrix - glm::mat4 GetViewMatrix() + glm::mat4 GetViewMatrix() const { return glm::lookAt(Position, Position + Front, Up); } - /*glm::mat4 lookAt(glm::vec3 pos, glm::vec3 target, glm::vec3 up) - { - glm::mat4 transform = glm::mat4(1.0f); - glm::mat4 translate = glm::mat4(1.0f); - - glm::vec3 zaxis = glm::normalize(pos - target); - glm::vec3 xaxis = glm::normalize(glm::cross(glm::normalize(up), zaxis)); - glm::vec3 yaxis = glm::cross(zaxis, xaxis); - - translate[3][0] = -pos.x; - translate[3][1] = -pos.y; - translate[3][2] = -pos.z; - - transform[0][0] = xaxis.x; - transform[1][0] = xaxis.y; - transform[2][0] = xaxis.z; - - transform[0][1] = yaxis.x; - transform[1][1] = yaxis.y; - transform[2][1] = yaxis.z; - - transform[0][2] = zaxis.x; - transform[1][2] = zaxis.y; - transform[2][2] = zaxis.z; - - - return transform * translate; - }*/ - - // Process input received from any keyboard-like input system. - // Accepts input parameter in the form of camera defined ENUM - // (to abstract it from windowing systems) - void translate(Camera_Movement direction, float velocity) - { - if (direction == FORWARD) - Position += Front * velocity; - if (direction == BACKWARD) - Position -= Front * velocity; - if (direction == LEFT) - Position -= Right * velocity; - if (direction == RIGHT) - Position += Right * velocity; - if (direction == UP) - Position += Up * velocity; - if (direction == DOWN) - Position -= Up * velocity; - } - - // Process input received from any keyboard-like input system. - // Accepts input parameter in the form of camera defined ENUM - // (to abstract it from windowing systems) + void translate(Camera_Movement direction, float velocity) { move(direction, velocity); } + void ProcessKeyboard(Camera_Movement direction, float deltaTime) { - float velocity = MovementSpeed * deltaTime; - if (direction == FORWARD) - Position += Front * velocity; - if (direction == BACKWARD) - Position -= Front * velocity; - if (direction == LEFT) - Position -= Right * velocity; - if (direction == RIGHT) - Position += Right * velocity; + move(direction, MovementSpeed * deltaTime); } - // processes input received from a mouse input system. - // Expects the offset value in both the x and y direction. void ProcessMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true) { - xoffset *= MouseSensitivity; - yoffset *= MouseSensitivity; - - Yaw += xoffset; - Pitch += yoffset; + rotate(xoffset, yoffset, constrainPitch); + } - // Make sure that when pitch is out of bounds, screen doen't get flipped + void rotate(float xoffset, float yoffset, GLboolean constrainPitch = true) + { + Yaw += xoffset * MouseSensitivity; + Pitch += yoffset * MouseSensitivity; if (constrainPitch) - { - if (Pitch > 89.0f) - Pitch = 89.0f; - if (Pitch < -89.0f) - Pitch = -89.0f; - } - - //Update Front, Right and Up Vectors using the updated Eular angles + Pitch = std::clamp(Pitch, -89.0f, 89.0f); updateCameraVectors(); } - - // processes input received from a mouse input system. - // Expects the offset value in both the x and y direction. - void rotate(float xoffset, float yoffset, GLboolean constrainPitch = true) + + void ProcessMouseScroll(float yoffset) + { + Zoom = std::clamp(Zoom - yoffset, 1.0f, 45.0f); + } + +private: + void move(Camera_Movement direction, float velocity) + { + if (direction == FORWARD) Position += Front * velocity; + if (direction == BACKWARD) Position -= Front * velocity; + if (direction == LEFT) Position -= Right * velocity; + if (direction == RIGHT) Position += Right * velocity; + if (direction == UP) Position += Up * velocity; + if (direction == DOWN) Position -= Up * velocity; + } + + void updateCameraVectors() + { + glm::vec3 front; + front.x = std::cos(glm::radians(Yaw)) * std::cos(glm::radians(Pitch)); + front.y = std::sin(glm::radians(Pitch)); + front.z = std::sin(glm::radians(Yaw)) * std::cos(glm::radians(Pitch)); + Front = glm::normalize(front); + Right = glm::normalize(glm::cross(Front, WorldUp)); + Up = glm::normalize(glm::cross(Right, Front)); + focus = Front; + } +}; + +/** + * Quaternion-backed camera. Rotations are accumulated as normalized + * quaternion deltas; Yaw and Pitch remain available for compatibility and + * diagnostics in the existing samples. + */ +class QuaternionCamera +{ +public: + glm::vec3 Position; + glm::vec3 Front{0.0f, 0.0f, -1.0f}; + glm::vec3 Up{0.0f, 1.0f, 0.0f}; + glm::vec3 Right{1.0f, 0.0f, 0.0f}; + glm::vec3 WorldUp; + glm::vec3 focus{0.0f, 0.0f, -1.0f}; + float Yaw; + float Pitch; + float MovementSpeed; + float MouseSensitivity; + float Zoom; + + QuaternionCamera(glm::vec3 position = glm::vec3(0.0f), + glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), + float yaw = YAW, float pitch = PITCH) + : Position(position), WorldUp(glm::normalize(up)), Yaw(yaw), Pitch(pitch), + MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM) + { + setOrientationFromAngles(); + } + + QuaternionCamera(float posX, float posY, float posZ, + float upX, float upY, float upZ, float yaw, float pitch) + : QuaternionCamera(glm::vec3(posX, posY, posZ), glm::vec3(upX, upY, upZ), yaw, pitch) { + } - /* TODO: ARC-BALL CAM */ - - xoffset *= MouseSensitivity; - yoffset *= MouseSensitivity; + glm::mat4 GetViewMatrix() const + { + const glm::mat4 rotation = glm::mat4_cast(glm::conjugate(orientation_)); + const glm::mat4 translation = glm::translate(glm::mat4(1.0f), -Position); + return rotation * translation; + } - Yaw += xoffset; - Pitch += yoffset; + const glm::quat& Orientation() const { return orientation_; } + + void translate(Camera_Movement direction, float velocity) { move(direction, velocity); } + + void ProcessKeyboard(Camera_Movement direction, float deltaTime) + { + move(direction, MovementSpeed * deltaTime); + } + + void ProcessMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true) + { + rotate(xoffset, yoffset, constrainPitch); + } - // Make sure that when pitch is out of bounds, screen doen't get flipped + void rotate(float xoffset, float yoffset, GLboolean constrainPitch = true) + { + const float yawDelta = xoffset * MouseSensitivity; + float pitchDelta = yoffset * MouseSensitivity; if (constrainPitch) { - if (Pitch > 89.0f) - Pitch = 89.0f; - if (Pitch < -89.0f) - Pitch = -89.0f; + const float constrainedPitch = std::clamp(Pitch + pitchDelta, -89.0f, 89.0f); + pitchDelta = constrainedPitch - Pitch; + Pitch = constrainedPitch; } + else + { + Pitch += pitchDelta; + } + Yaw += yawDelta; - //Update Front, Right and Up Vectors using the updated Eular angles + const glm::quat yawRotation = glm::angleAxis(glm::radians(-yawDelta), WorldUp); + orientation_ = glm::normalize(yawRotation * orientation_); + updateCameraVectors(); + const glm::quat pitchRotation = glm::angleAxis(glm::radians(pitchDelta), Right); + orientation_ = glm::normalize(pitchRotation * orientation_); updateCameraVectors(); } - //Processess input received from a mouse scroll-wheel event. - // Only requires input on the vertical wheel-axis void ProcessMouseScroll(float yoffset) { - Zoom -= yoffset; - if (Zoom <= 1.0f) - Zoom = 1.0f; - if (Zoom >= 45.0f) - Zoom = 45.0f; + Zoom = std::clamp(Zoom - yoffset, 1.0f, 45.0f); } private: - // Calculates the front vector from the Camera's (updated) Eular Angles + glm::quat orientation_{1.0f, 0.0f, 0.0f, 0.0f}; + + void move(Camera_Movement direction, float velocity) + { + if (direction == FORWARD) Position += Front * velocity; + if (direction == BACKWARD) Position -= Front * velocity; + if (direction == LEFT) Position -= Right * velocity; + if (direction == RIGHT) Position += Right * velocity; + if (direction == UP) Position += Up * velocity; + if (direction == DOWN) Position -= Up * velocity; + } + + void setOrientationFromAngles() + { + const glm::quat yawRotation = glm::angleAxis(glm::radians(-(Yaw + 90.0f)), WorldUp); + const glm::vec3 initialRight = yawRotation * glm::vec3(1.0f, 0.0f, 0.0f); + const glm::quat pitchRotation = glm::angleAxis(glm::radians(Pitch), initialRight); + orientation_ = glm::normalize(pitchRotation * yawRotation); + updateCameraVectors(); + } + void updateCameraVectors() { - // calculate the new Front vector - glm::vec3 front; - front.x = cos(glm::radians(Yaw)) * cos(glm::radians(Pitch)); - front.y = sin(glm::radians(Pitch)); - front.z = sin(glm::radians(Yaw)) * cos(glm::radians(Pitch)); - Front = glm::normalize(front); - // Also re-calculate the Right and Up vector - Right = glm::normalize(glm::cross(Front, WorldUp)); - // Normalize the vecotrs, because their length gets closer to 0 the more - // you look up or down which results in slower movement. - Up = glm::normalize(glm::cross(Right, Front)); + Front = glm::normalize(orientation_ * glm::vec3(0.0f, 0.0f, -1.0f)); + Right = glm::normalize(orientation_ * glm::vec3(1.0f, 0.0f, 0.0f)); + Up = glm::normalize(orientation_ * glm::vec3(0.0f, 1.0f, 0.0f)); + focus = Front; } }; -#endif \ No newline at end of file + +// Regular samples use quaternion orientation without call-site changes. +using Camera = QuaternionCamera; + +#endif diff --git a/samples/Basics/Camera/src/main.cpp b/samples/Basics/Camera/src/main.cpp index 4f9cec0..b83209a 100644 --- a/samples/Basics/Camera/src/main.cpp +++ b/samples/Basics/Camera/src/main.cpp @@ -24,8 +24,12 @@ void processInput(GLFWwindow *window); const unsigned int SCR_WIDTH = 800; const unsigned int SCR_HEIGHT = 600; -// camera -Camera camera(glm::vec3(0.0f, 0.0f, 3.0f)); +// Keep both implementations synchronized so C can switch between them +// without changing the current view. +QuaternionCamera camera(glm::vec3(0.0f, 0.0f, 3.0f)); +EulerCamera eulerCamera(glm::vec3(0.0f, 0.0f, 3.0f)); +bool useQuaternionCamera = true; +bool cameraModeKeyPressed = false; bool firstMouse = true; float lastX = SCR_WIDTH / 2.0; float lastY = SCR_HEIGHT / 2.0; @@ -51,7 +55,7 @@ int main() // glfw window creation // -------------------- - GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL); + GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL - Quaternion Camera", NULL, NULL); //Vollbild if (window == NULL) @@ -271,12 +275,13 @@ int main() // pass projection matrix to shader (it can change every frame in this sample) glm::mat4 projection = glm::mat4(1.0f); - projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); + const float zoom = useQuaternionCamera ? camera.Zoom : eulerCamera.Zoom; + projection = glm::perspective(glm::radians(zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); ourShader.setMat4("projection", projection); // camera/view transformation glm::mat4 view = glm::mat4(1.0f); - view = camera.GetViewMatrix(); + view = useQuaternionCamera ? camera.GetViewMatrix() : eulerCamera.GetViewMatrix(); ourShader.setMat4("view", view); //render boxes @@ -321,16 +326,41 @@ void processInput(GLFWwindow *window) float cameraSpeed = 2.5f * deltaTime; // adjust accordingly if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) + { camera.ProcessKeyboard(FORWARD, deltaTime); + eulerCamera.ProcessKeyboard(FORWARD, deltaTime); + } if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) + { camera.ProcessKeyboard(BACKWARD, deltaTime); + eulerCamera.ProcessKeyboard(BACKWARD, deltaTime); + } if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) + { camera.ProcessKeyboard(LEFT, deltaTime); + eulerCamera.ProcessKeyboard(LEFT, deltaTime); + } if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) + { camera.ProcessKeyboard(RIGHT, deltaTime); + eulerCamera.ProcessKeyboard(RIGHT, deltaTime); + } + + if (glfwGetKey(window, GLFW_KEY_C) == GLFW_PRESS && !cameraModeKeyPressed) + { + useQuaternionCamera = !useQuaternionCamera; + cameraModeKeyPressed = true; + glfwSetWindowTitle(window, useQuaternionCamera + ? "LearnOpenGL - Quaternion Camera" + : "LearnOpenGL - Euler Camera"); + std::cout << "Camera mode: " + << (useQuaternionCamera ? "Quaternion" : "Euler") << std::endl; + } + if (glfwGetKey(window, GLFW_KEY_C) == GLFW_RELEASE) + cameraModeKeyPressed = false; if (glfwGetKey(window, GLFW_KEY_F2) == GLFW_PRESS) { @@ -358,6 +388,7 @@ void mouse_callback(GLFWwindow* window, double xpos, double ypos) lastY = ypos; camera.ProcessMouseMovement(xoffset, yoffset); + eulerCamera.ProcessMouseMovement(xoffset, yoffset); } // glfw: whenever the mouse scroll wheel scrolls, this callback is called @@ -365,6 +396,7 @@ void mouse_callback(GLFWwindow* window, double xpos, double ypos) void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { camera.ProcessMouseScroll(yoffset); + eulerCamera.ProcessMouseScroll(yoffset); } // glfw: whenever the window size changed (by OS or user resize) this callback function executes @@ -374,4 +406,4 @@ void framebuffer_size_callback(GLFWwindow* window, int width, int height) // make sure the viewport matches the new window dimensions; note that width and // height will be significantly larger than specified on retina displays. glViewport(0, 0, width, height); -} \ No newline at end of file +} From a3c6e17ae9d2fc910dcb93a7885a9d274d9e7456 Mon Sep 17 00:00:00 2001 From: Jonas Sorgenfrei Date: Sat, 12 Sep 2026 21:16:52 +0200 Subject: [PATCH 2/2] docs(camera): explain quaternion orientation model --- include/modules/camera.h | 34 +++++++++++++++++++++++++----- samples/Basics/Camera/src/main.cpp | 10 +++++++-- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/include/modules/camera.h b/include/modules/camera.h index 45cacbf..d675873 100644 --- a/include/modules/camera.h +++ b/include/modules/camera.h @@ -11,6 +11,11 @@ #include #include +/** + * Window-system-independent movement commands understood by both camera types. + * Callers supply either an explicit distance through translate() or a frame + * delta through ProcessKeyboard(). + */ enum Camera_Movement { FORWARD, @@ -21,13 +26,18 @@ enum Camera_Movement DOWN }; +// Defaults preserve the orientation and controls used by the existing samples. constexpr float YAW = -90.0f; constexpr float PITCH = 0.0f; constexpr float SPEED = 2.5f; constexpr float SENSITIVITY = 0.1f; constexpr float ZOOM = 45.0f; -/** The original yaw/pitch camera, retained for teaching and comparison. */ +/** + * Conventional yaw/pitch camera retained for teaching and direct comparison. + * Its public fields and method names match the historical Camera API so the + * Camera sample can switch implementations without changing render code. + */ class EulerCamera { public: @@ -60,6 +70,7 @@ class EulerCamera glm::mat4 GetViewMatrix() const { + // glm::lookAt builds the inverse camera transform from its local basis. return glm::lookAt(Position, Position + Front, Up); } @@ -102,6 +113,7 @@ class EulerCamera void updateCameraVectors() { + // Reconstruct an orthonormal basis after either Euler angle changes. glm::vec3 front; front.x = std::cos(glm::radians(Yaw)) * std::cos(glm::radians(Pitch)); front.y = std::sin(glm::radians(Pitch)); @@ -114,9 +126,11 @@ class EulerCamera }; /** - * Quaternion-backed camera. Rotations are accumulated as normalized - * quaternion deltas; Yaw and Pitch remain available for compatibility and - * diagnostics in the existing samples. + * Quaternion-backed camera used by default throughout the repository. + * + * Rotations accumulate as normalized quaternion deltas, avoiding an Euler + * matrix as the authoritative orientation. Yaw and Pitch remain available for + * API compatibility, diagnostics, and the optional vertical-look constraint. */ class QuaternionCamera { @@ -150,11 +164,14 @@ class QuaternionCamera glm::mat4 GetViewMatrix() const { + // A view matrix is the inverse camera transform: inverse rotation first, + // followed by translation of the world opposite the camera position. const glm::mat4 rotation = glm::mat4_cast(glm::conjugate(orientation_)); const glm::mat4 translation = glm::translate(glm::mat4(1.0f), -Position); return rotation * translation; } + /** Returns the normalized world-space camera orientation. */ const glm::quat& Orientation() const { return orientation_; } void translate(Camera_Movement direction, float velocity) { move(direction, velocity); } @@ -185,6 +202,8 @@ class QuaternionCamera } Yaw += yawDelta; + // Yaw is world-relative, while pitch is applied around the camera's newly + // rotated local right axis. Normalization limits accumulated drift. const glm::quat yawRotation = glm::angleAxis(glm::radians(-yawDelta), WorldUp); orientation_ = glm::normalize(yawRotation * orientation_); updateCameraVectors(); @@ -213,6 +232,8 @@ class QuaternionCamera void setOrientationFromAngles() { + // Convert legacy constructor angles once; subsequent updates are quaternion + // deltas. The +90 degree offset maps historical yaw=-90 to forward -Z. const glm::quat yawRotation = glm::angleAxis(glm::radians(-(Yaw + 90.0f)), WorldUp); const glm::vec3 initialRight = yawRotation * glm::vec3(1.0f, 0.0f, 0.0f); const glm::quat pitchRotation = glm::angleAxis(glm::radians(Pitch), initialRight); @@ -222,6 +243,8 @@ class QuaternionCamera void updateCameraVectors() { + // Derive the complete basis from one normalized orientation so the axes + // remain mutually orthogonal after long input sequences. Front = glm::normalize(orientation_ * glm::vec3(0.0f, 0.0f, -1.0f)); Right = glm::normalize(orientation_ * glm::vec3(1.0f, 0.0f, 0.0f)); Up = glm::normalize(orientation_ * glm::vec3(0.0f, 1.0f, 0.0f)); @@ -229,7 +252,8 @@ class QuaternionCamera } }; -// Regular samples use quaternion orientation without call-site changes. +// Preserve the repository's Camera name while changing its implementation. +// The explicit EulerCamera type remains available to the Camera sample. using Camera = QuaternionCamera; #endif diff --git a/samples/Basics/Camera/src/main.cpp b/samples/Basics/Camera/src/main.cpp index b83209a..84c3276 100644 --- a/samples/Basics/Camera/src/main.cpp +++ b/samples/Basics/Camera/src/main.cpp @@ -24,8 +24,8 @@ void processInput(GLFWwindow *window); const unsigned int SCR_WIDTH = 800; const unsigned int SCR_HEIGHT = 600; -// Keep both implementations synchronized so C can switch between them -// without changing the current view. +// Keep both implementations synchronized so C can compare their orientation +// math without introducing a position, zoom, or input-history discontinuity. QuaternionCamera camera(glm::vec3(0.0f, 0.0f, 3.0f)); EulerCamera eulerCamera(glm::vec3(0.0f, 0.0f, 3.0f)); bool useQuaternionCamera = true; @@ -42,6 +42,7 @@ int main() { std::cout << "Basics - Camera" << std::endl; std::cout << "ESC - Exit" << std::endl; + std::cout << "C - Compare quaternion / Euler camera" << std::endl; // glfw: initialize and configure // ------------------------------ glfwInit(); @@ -275,6 +276,8 @@ int main() // pass projection matrix to shader (it can change every frame in this sample) glm::mat4 projection = glm::mat4(1.0f); + // Only the selected implementation contributes matrices; both continue to + // receive input below so switching remains a meaningful A/B comparison. const float zoom = useQuaternionCamera ? camera.Zoom : eulerCamera.Zoom; projection = glm::perspective(glm::radians(zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); ourShader.setMat4("projection", projection); @@ -349,6 +352,7 @@ void processInput(GLFWwindow *window) eulerCamera.ProcessKeyboard(RIGHT, deltaTime); } + // Toggle on the press edge so holding C cannot flip modes every frame. if (glfwGetKey(window, GLFW_KEY_C) == GLFW_PRESS && !cameraModeKeyPressed) { useQuaternionCamera = !useQuaternionCamera; @@ -387,6 +391,7 @@ void mouse_callback(GLFWwindow* window, double xpos, double ypos) lastX = xpos; lastY = ypos; + // Feed identical deltas to both cameras to preserve comparison state. camera.ProcessMouseMovement(xoffset, yoffset); eulerCamera.ProcessMouseMovement(xoffset, yoffset); } @@ -395,6 +400,7 @@ void mouse_callback(GLFWwindow* window, double xpos, double ypos) // ---------------------------------------------------------------------- void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { + // Zoom is synchronized for the same reason as position and orientation. camera.ProcessMouseScroll(yoffset); eulerCamera.ProcessMouseScroll(yoffset); }