Add camera system with FPS and Orbit support

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.
This commit is contained in:
Djalim Simaila 2025-10-28 13:09:06 +01:00
parent 7e36811c91
commit 579d5ec483
12 changed files with 396 additions and 74 deletions

93
include/Camera.h Normal file
View File

@ -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>(), 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

View File

@ -3,6 +3,7 @@
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include "Camera.h"
#include "ShaderProgram.h"
#include <map>
#include <memory>
@ -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

182
src/Camera.cpp Normal file
View File

@ -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<float>()),
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<float>() / 2.0f + 0.1f, glm::pi<float>() / 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<float>() / 2.0f + 0.1f, glm::pi<float>() / 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);
}

View File

@ -1,12 +1,10 @@
#include "Mesh3D.h"
#include <iostream>
#include <fstream>
#include <iterator>
#include <sstream>
#include <string>
#include <vector>
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) {
}
}
}

View File

@ -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<djalim::OpenGlEngine*>(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<djalim::OpenGlEngine*>(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);
}

View File

@ -1,5 +1,6 @@
#include "engine.h"
#include "Mesh3D.h"
#include <GLFW/glfw3.h>
#include <cstdlib>
#include <glm/ext/vector_float3.hpp>
#include <iostream>
@ -9,6 +10,8 @@
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
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));
}

View File

@ -1 +0,0 @@

View File

@ -1,32 +0,0 @@
#include "engine.h"
#include "loops.h"
#include "iostream"
#include <GL/gl.h>
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);
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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),

View File

@ -4,23 +4,25 @@
#include <iostream>
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());
}