From 579d5ec4839e4bce449251d2208194944cc734aa Mon Sep 17 00:00:00 2001 From: Djalim Simaila Date: Tue, 28 Oct 2025 13:09:06 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20camera=20system=20with=20FPS?= =?UTF-8?q?=20and=20Orbit=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce Camera.h/cpp defining abstract Camera, FPSCamera, OrbitCamera. Update engine.h to include Camera and window size fields. Implement cameraUpdate in engine.cpp handling mouse movement and key input. Add framebuffer size and scroll callbacks for viewport resizing and zoom. Remove legacy loop files (cube, rainbowWindow, rbgTriangle). Adjust main.cpp formatting. Update onCreate to load objects with textures and transforms. Modify onUpdate to use Camera's view/projection matrices instead of hardcoded values. --- include/Camera.h | 93 ++++++++++++++++++ include/engine.h | 18 +++- src/Camera.cpp | 182 ++++++++++++++++++++++++++++++++++++ src/core/Mesh3D.cpp | 6 -- src/core/callbacks.cpp | 32 +++++++ src/core/engine.cpp | 50 +++++++++- src/loops/cube.cpp | 1 - src/loops/rainbowWindow.cpp | 32 ------- src/loops/rbgTriangle.cpp | 12 --- src/main.cpp | 6 +- src/onCreate.cpp | 10 +- src/onUpdate.cpp | 28 +++--- 12 files changed, 396 insertions(+), 74 deletions(-) create mode 100644 include/Camera.h create mode 100644 src/Camera.cpp delete mode 100644 src/loops/cube.cpp delete mode 100644 src/loops/rainbowWindow.cpp delete mode 100644 src/loops/rbgTriangle.cpp diff --git a/include/Camera.h b/include/Camera.h new file mode 100644 index 0000000..087c4ce --- /dev/null +++ b/include/Camera.h @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------------- +// Basic camera class including derived orbit-style and first person +// shooter (FPS) style camera support +//----------------------------------------------------------------------------- +#ifndef CAMERA_H +#define CAMERA_H + +#include "glm/glm.hpp" +#include "glm/gtc/constants.hpp" + + +//-------------------------------------------------------------- +// Abstract Camera Class +//-------------------------------------------------------------- +class Camera +{ +public: + glm::mat4 getViewMatrix() const; + + virtual void setPosition(const glm::vec3& position) {} + virtual void rotate(float yaw, float pitch) {} // in degrees + virtual void move(const glm::vec3& offsetPos) {} + + const glm::vec3& getLook() const; + const glm::vec3& getRight() const; + const glm::vec3& getUp() const; + const glm::vec3& getPosition() const; + + float getFOV() const { return mFOV; } + void setFOV(float fov) { mFOV = fov; } // in degrees + +protected: + Camera(); + + virtual void updateCameraVectors() {} + + glm::vec3 mPosition; + glm::vec3 mTargetPos; + glm::vec3 mLook; + glm::vec3 mUp; + glm::vec3 mRight; + const glm::vec3 WORLD_UP; + + // Euler Angles (in radians) + float mYaw; + float mPitch; + + // Camera parameters + float mFOV; // degrees +}; + +//-------------------------------------------------------------- +// FPS Camera Class +//-------------------------------------------------------------- +class FPSCamera : public Camera +{ +public: + + FPSCamera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), float yaw = glm::pi(), float pitch = 0.0f); // (yaw) initial angle faces -Z + + virtual void setPosition(const glm::vec3& position); + virtual void rotate(float yaw, float pitch); // in degrees + virtual void move(const glm::vec3& offsetPos); + +private: + + void updateCameraVectors(); +}; + + +//-------------------------------------------------------------- +// Orbit Camera Class +//-------------------------------------------------------------- +class OrbitCamera : public Camera +{ +public: + + OrbitCamera(); + + virtual void rotate(float yaw, float pitch); // in degrees + + // Camera Controls + void setLookAt(const glm::vec3& target); + void setRadius(float radius); + +private: + + void updateCameraVectors(); + + // Camera parameters + float mRadius; +}; +#endif //CAMERA_H diff --git a/include/engine.h b/include/engine.h index e96017d..820aea3 100644 --- a/include/engine.h +++ b/include/engine.h @@ -3,6 +3,7 @@ #include #include +#include "Camera.h" #include "ShaderProgram.h" #include #include @@ -29,24 +30,35 @@ namespace djalim { public: void start(); + OpenGlEngine(); OpenGlEngine( const char* title, int width, int height); ~OpenGlEngine(); GLFWwindow* window; + + int windowWidth; + int windowHeight; + + const double ZOOM_SENSITIVITY = -3.0; + const float MOVE_SPEED = 5.0; // units per second + const float MOUSE_SENSITIVITY = 0.1f; + double previousSeconds = 0.0; double elapsedSeconds; int frameCount = 0; ShaderProgram shaderProgram; + FPSCamera Camera = FPSCamera(glm::vec3(0.0f, 2.0f, 10.0f)); Objects3D objects; - + unsigned int VBO; unsigned int VAO; - + bool gWireframe = false; void onUpdate(); void loadHints(); + void cameraUpdate(); void createObject(std::string name, std::string textures, std::string filepath); void createObject(std::string name, std::string textures, std::string filepath, glm::vec3 position, glm::vec3 rotation, glm::vec3 scale); void loadCallbacks(); @@ -57,4 +69,6 @@ namespace djalim { }; } + + #endif // ENGINE_H diff --git a/src/Camera.cpp b/src/Camera.cpp new file mode 100644 index 0000000..cd21e4d --- /dev/null +++ b/src/Camera.cpp @@ -0,0 +1,182 @@ +//----------------------------------------------------------------------------- +// Basic camera class including derived orbit-style and first person +// shooter (FPS) style camera support +//----------------------------------------------------------------------------- +#include "Camera.h" +#include "glm/gtc/matrix_transform.hpp" + +// Default camera values +const float DEF_FOV = 45.0f; // degrees + +//------------------------------------------------------------ +// Base Camera class constructor +//------------------------------------------------------------ +Camera::Camera() +: mPosition(glm::vec3(0.0f, 0.0f, 0.0f)), + mTargetPos(glm::vec3(0.0f, 0.0f, 0.0f)), + mUp(glm::vec3(0.0f, 1.0f, 0.0f)), + mRight(1.0f, 0.0f, 0.0f), + WORLD_UP(0.0f, 1.0f, 0.0f), + mYaw(glm::pi()), + mPitch(0.0f), + mFOV(DEF_FOV) +{ +} + +//------------------------------------------------------------ +// Base Camera - Returns view matrix +//------------------------------------------------------------ +glm::mat4 Camera::getViewMatrix()const +{ + return glm::lookAt(mPosition, mTargetPos, mUp); +} + +//------------------------------------------------------------ +// Base Camera - Returns camera's local look vector +//------------------------------------------------------------ +const glm::vec3& Camera::getLook() const +{ + return mLook; +} + +//------------------------------------------------------------ +// Base Camera - Returns camera's local right vector +//------------------------------------------------------------ +const glm::vec3& Camera::getRight() const +{ + return mRight; +} + +//------------------------------------------------------------ +// Base Camera - Returns camera's local up vector +//------------------------------------------------------------ +const glm::vec3& Camera::getUp() const +{ + return mUp; +} + +//------------------------------------------------------------ +// Base Camera - Returns camera's position +//------------------------------------------------------------ +const glm::vec3& Camera::getPosition() const +{ + return mPosition; +} + +//----------------------------------------------------------------------------- +// FPSCamera - Constructor +//----------------------------------------------------------------------------- +FPSCamera::FPSCamera(glm::vec3 position, float yaw, float pitch) +{ + mPosition = position; + mYaw = yaw; + mPitch = pitch; +} + +//----------------------------------------------------------------------------- +// FPSCamera - Sets the camera position in world space +//----------------------------------------------------------------------------- +void FPSCamera::setPosition(const glm::vec3& position) +{ + mPosition = position; +} + +//----------------------------------------------------------------------------- +// FPSCamera - Sets the incremental position of the camera in world space +//----------------------------------------------------------------------------- +void FPSCamera::move(const glm::vec3& offsetPos) +{ + mPosition += offsetPos; + updateCameraVectors(); +} + +//----------------------------------------------------------------------------- +// FPSCamera - Sets the incremental orientation of the camera +//----------------------------------------------------------------------------- +void FPSCamera::rotate(float yaw, float pitch) +{ + mYaw += glm::radians(yaw); + mPitch += glm::radians(pitch); + + // Constrain the pitch + mPitch = glm::clamp(mPitch, -glm::pi() / 2.0f + 0.1f, glm::pi() / 2.0f - 0.1f); + updateCameraVectors(); +} + +//----------------------------------------------------------------------------- +// FPSCamera - Calculates the front vector from the Camera's (updated) Euler Angles +//----------------------------------------------------------------------------- +void FPSCamera::updateCameraVectors() +{ + // Spherical to Cartesian coordinates + // https://en.wikipedia.org/wiki/Spherical_coordinate_system (NOTE: Our coordinate sys has Y up not Z) + + // Calculate the view direction vector based on yaw and pitch angles (roll not considered) + // radius is 1 for normalized length + glm::vec3 look; + look.x = cosf(mPitch) * sinf(mYaw); + look.y = sinf(mPitch); + look.z = cosf(mPitch) * cosf(mYaw); + + mLook = glm::normalize(look); + + // Re-calculate the Right and Up vector. For simplicity the Right vector will + // be assumed horizontal w.r.t. the world's Up vector. + mRight = glm::normalize(glm::cross(mLook, WORLD_UP)); + mUp = glm::normalize(glm::cross(mRight, mLook)); + + mTargetPos = mPosition + mLook; +} + + +//------------------------------------------------------------ +// OrbitCamera - constructor +//------------------------------------------------------------ +OrbitCamera::OrbitCamera() + : mRadius(10.0f) +{} + +//------------------------------------------------------------ +// OrbitCamera - Sets the target to look at +//------------------------------------------------------------ +void OrbitCamera::setLookAt(const glm::vec3& target) +{ + mTargetPos = target; +} + +//------------------------------------------------------------ +// OrbitCamera - Sets the radius of camera to target distance +//------------------------------------------------------------ +void OrbitCamera::setRadius(float radius) +{ + // Clamp the radius + mRadius = glm::clamp(radius, 2.0f, 80.0f); +} + +//------------------------------------------------------------ +// OrbitCamera - Rotates the camera around the target look +// at position given yaw and pitch in degrees. +//------------------------------------------------------------ +void OrbitCamera::rotate(float yaw, float pitch) +{ + mYaw = glm::radians(yaw); + mPitch = glm::radians(pitch); + + mPitch = glm::clamp(mPitch, -glm::pi() / 2.0f + 0.1f, glm::pi() / 2.0f - 0.1f); + + // Update Front, Right and Up Vectors using the updated Euler angles + updateCameraVectors(); +} + +//------------------------------------------------------------ +// OrbitCamera - Calculates the front vector from the Camera's +// (updated) Euler Angles +//------------------------------------------------------------ +void OrbitCamera::updateCameraVectors() +{ + // Spherical to Cartesian coordinates + // https://en.wikipedia.org/wiki/Spherical_coordinate_system (NOTE: Our coordinate sys has Y up not Z) + mPosition.x = mTargetPos.x + mRadius * cosf(mPitch) * sinf(mYaw); + mPosition.y = mTargetPos.y + mRadius * sinf(mPitch); + mPosition.z = mTargetPos.z + mRadius * cosf(mPitch) * cosf(mYaw); +} diff --git a/src/core/Mesh3D.cpp b/src/core/Mesh3D.cpp index 5248d34..821fe95 100644 --- a/src/core/Mesh3D.cpp +++ b/src/core/Mesh3D.cpp @@ -1,12 +1,10 @@ #include "Mesh3D.h" #include #include -#include #include #include #include - struct objVertex { float x, y, z; }; @@ -23,8 +21,6 @@ struct objFace { objFaceIndice vertices[3]; }; - - djalim::Mesh3D::Mesh3D(){} djalim::Mesh3D::Mesh3D(const std::string& filepath) { @@ -122,5 +118,3 @@ void djalim::Mesh3D::loadOBJFile(const std::string& filepath) { } } } - - diff --git a/src/core/callbacks.cpp b/src/core/callbacks.cpp index a83c5b2..97ea5a2 100644 --- a/src/core/callbacks.cpp +++ b/src/core/callbacks.cpp @@ -24,6 +24,38 @@ void mainCallback(GLFWwindow* window, int key, int scancode, int action, int mod } +void glfw_onFramebufferSize(GLFWwindow* window, int width, int height) +{ + auto* engine = static_cast(glfwGetWindowUserPointer(window)); + + engine->windowWidth = width; + engine->windowHeight = height; + + + // Define the viewport dimensions + int w, h; + glfwGetFramebufferSize( engine->window, &w, &h); // For retina display + glViewport(0, 0, w, h); + // glViewport(0, 0, gWindowWidth, gWindowHeight); + +} + +//----------------------------------------------------------------------------- +// Called by GLFW when the mouse wheel is rotated +//----------------------------------------------------------------------------- +void glfw_onMouseScroll(GLFWwindow* window, double deltaX, double deltaY) +{ + auto* engine = static_cast(glfwGetWindowUserPointer(window)); + double fov = engine->Camera.getFOV() + deltaY * engine->ZOOM_SENSITIVITY; + + fov = glm::clamp(fov, 1.0, 120.0); + + engine->Camera.setFOV((float)fov); +} + + void djalim::OpenGlEngine::loadCallbacks(){ glfwSetKeyCallback(window, mainCallback); + glfwSetFramebufferSizeCallback(window, glfw_onFramebufferSize); + glfwSetScrollCallback(window, glfw_onMouseScroll); } diff --git a/src/core/engine.cpp b/src/core/engine.cpp index d820bef..9425807 100644 --- a/src/core/engine.cpp +++ b/src/core/engine.cpp @@ -1,5 +1,6 @@ #include "engine.h" #include "Mesh3D.h" +#include #include #include #include @@ -9,6 +10,8 @@ #include #include +djalim::OpenGlEngine::OpenGlEngine(){} + djalim::OpenGlEngine::OpenGlEngine(const char* title, int width, int height) { // initialize GLFW if (!glfwInit()) { @@ -25,7 +28,12 @@ djalim::OpenGlEngine::OpenGlEngine(const char* title, int width, int height) { exit(1); } + windowWidth = width; + windowHeight = height; + glfwMakeContextCurrent(this->window); + glfwSetWindowUserPointer(this->window, this); + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); // initialize GLEW glewExperimental = GL_TRUE; @@ -53,8 +61,6 @@ void djalim::OpenGlEngine::createObject(std::string name, std::string textures, (objects[name])->scale = scale; } - - void djalim::OpenGlEngine::createObject(std::string name, std::string textures, std::string filepath){ // Load the mesh from the file @@ -62,7 +68,7 @@ void djalim::OpenGlEngine::createObject(std::string name, std::string textures, bool textureLoaded = objects[name]->texture.loadTexture(textures); - std::cout << &(objects[name]) << std::endl; + std::cout << &(objects[name]) << std::endl; if (!textureLoaded) { std::cerr << "Failed to load " << name << " texture!" << std::endl; @@ -114,6 +120,7 @@ void djalim::OpenGlEngine::start(){ //glClearColor(0, 0.5, 0.5, 1.0f); onUpdate(); + cameraUpdate(); glfwSwapBuffers(this->window); @@ -136,3 +143,40 @@ void djalim::OpenGlEngine::draw(djalim::Mesh3D* object){ glBindVertexArray(object->VAO); glDrawArrays(GL_TRIANGLES, 0, object->numFaces * 3); } + + +void djalim::OpenGlEngine::cameraUpdate(){ + + // Camera orientation + double mouseX, mouseY; + + // Get the current mouse cursor position delta + glfwGetCursorPos(window, &mouseX, &mouseY); + + // Rotate the camera the difference in mouse distance from the center screen. Multiply this delta by a speed scaler + Camera.rotate((float)(windowWidth / 2.0 - mouseX) * MOUSE_SENSITIVITY, (float)(windowHeight / 2.0 - mouseY) * MOUSE_SENSITIVITY); + + // Clamp mouse cursor to center of screen + glfwSetCursorPos(window, windowWidth / 2.0, windowHeight / 2.0); + + // Camera FPS movement + + // Forward/backward + if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) + Camera.move(MOVE_SPEED * (float)elapsedSeconds * Camera.getLook()); + else if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) + Camera.move(MOVE_SPEED * (float)elapsedSeconds * -Camera.getLook()); + + // Strafe left/right + if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) + Camera.move(MOVE_SPEED * (float)elapsedSeconds * -Camera.getRight()); + else if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) + Camera.move(MOVE_SPEED * (float)elapsedSeconds * Camera.getRight()); + + // Up/down + if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS) + Camera.move(MOVE_SPEED * (float)elapsedSeconds * glm::vec3(0.0f, 1.0f, 0.0f)); + else if (glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) + Camera.move(MOVE_SPEED * (float)elapsedSeconds * -glm::vec3(0.0f, 1.0f, 0.0f)); + +} diff --git a/src/loops/cube.cpp b/src/loops/cube.cpp deleted file mode 100644 index 8b13789..0000000 --- a/src/loops/cube.cpp +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/loops/rainbowWindow.cpp b/src/loops/rainbowWindow.cpp deleted file mode 100644 index 02e3e44..0000000 --- a/src/loops/rainbowWindow.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "engine.h" -#include "loops.h" -#include "iostream" -#include - - -void djalim::rainbowWindow(double elapsedSeconds){ - static double red = 0.0; - static double green= 0.0; - static double blue = 0.0; - static double thresh = 0.0; - - thresh += elapsedSeconds; - - if (thresh >0.1){ - thresh = 0.0; - red += 0.1; - if( red > 1.0 ) { - red = 0.0; - green += 0.1; - if( green > 1.0 ) { - green = 0.0; - blue += 0.1; - if( blue > 1.0 ) { - blue = 0.0; - } - } - } - } - std::cout << "red: " << red << ", green: " << green << ", blue: " << blue << " \r"; - glClearColor(red, green, blue, 1.0f); -} diff --git a/src/loops/rbgTriangle.cpp b/src/loops/rbgTriangle.cpp deleted file mode 100644 index 9c8a686..0000000 --- a/src/loops/rbgTriangle.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "engine.h" -#include "loops.h" - -void djalim::rgbTriangle(djalim::ShaderProgram shaderProgram, unsigned int VAO){ - // Dessiner le triangle - shaderProgram.use(); - glBindVertexArray(VAO); - glDrawArrays(GL_TRIANGLES, 0, 3); - - glClearColor(0, 0.5, 0.5, 1.0f); - -} diff --git a/src/main.cpp b/src/main.cpp index a1ec38f..9443b40 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,7 @@ #include "engine.h" int main() { - djalim::OpenGlEngine engine = djalim::OpenGlEngine("Mon app", 800, 600); - engine.start(); - return 0; + djalim::OpenGlEngine engine = djalim::OpenGlEngine("Mon app", 800, 600); + engine.start(); + return 0; } diff --git a/src/onCreate.cpp b/src/onCreate.cpp index 6bf639e..6d6ab79 100644 --- a/src/onCreate.cpp +++ b/src/onCreate.cpp @@ -2,12 +2,18 @@ #include "Texture2D.h" void djalim::OpenGlEngine::onCreate(){ - glEnable(GL_DEPTH_TEST); + glEnable(GL_DEPTH_TEST); stbi_set_flip_vertically_on_load(true); - createObject("cube", "../assets/textures/prof.png", "../assets/models/L.obj"); createObject("mimikyu", + "../assets/textures/crate.jpg", + "../assets/models/destiny island.obj", + glm::vec3(0.0,0.0,0.0), + glm::vec3(0.0,0.0,0.0), + glm::vec3(.1,.1,.1) + ); + createObject("cube", "../assets/textures/crate.jpg", "../assets/models/crate.obj", glm::vec3(1.0,1.0,1.0), diff --git a/src/onUpdate.cpp b/src/onUpdate.cpp index 199b497..cb3c0e7 100644 --- a/src/onUpdate.cpp +++ b/src/onUpdate.cpp @@ -4,23 +4,25 @@ #include void djalim::OpenGlEngine::onUpdate(){ - showFps(); + showFps(); - shaderProgram.use(); - shaderProgram.setUniform("ourTexture", 0); + shaderProgram.use(); + shaderProgram.setUniform("ourTexture", 0); - // Matrice de Projection - glm::mat4 projection = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f); - shaderProgram.setUniform("projection", projection); + glm::mat4 model(1.0), view(1.0), projection(1.0); - // Matrice de Vue - glm::mat4 view = glm::lookAt(glm::vec3(0.0f, 0.0f, 10.0f), // Position caméra - glm::vec3(0.0f, 0.0f, 0.0f), // Cible - glm::vec3(0.0f, 1.0f, 0.0f)); // Axe Haut - shaderProgram.setUniform("view", view); + // Create the View matrix + view = Camera.getViewMatrix(); - draw((objects["cube"]).get()); - draw((objects["mimikyu"]).get()); + // Create the projection matrix + projection = glm::perspective(glm::radians(Camera.getFOV()), (float)windowWidth / (float)windowHeight, 0.1f, 200.0f); + + // Matrice de Projection + shaderProgram.setUniform("projection", projection); + shaderProgram.setUniform("view", view); + + draw((objects["cube"]).get()); + draw((objects["mimikyu"]).get()); }