As a hobby I am working on a game engine in order to learn C++ and graphics programming, now I have completed my first iteration of my camera system using OpengGL and GLM. Because I am mostly self taught I am searching for some feedback.
I am mostly searching for feedback regarding the following points:
- Is the API easy to understand/implement by another user
- Are there any obvious mistakes made regarding performance
- Are there any missing features you would suspect to be in a camera controller
- Is the API consistent regarding code style and practices
But of course any other feedback is also much appreciated!
Perspective Camera
Low level, responsible for the projection matrix
// PerspectiveCamera.h
#ifndef CHEETAH_ENGINE_RENDERER_PERSPECTIVECAMERA_H_
#define CHEETAH_ENGINE_RENDERER_PERSPECTIVECAMERA_H_
#include "Core/Core.h"
#include "Events/ApplicationEvents.h"
#include "Camera.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
namespace cheetah
{
struct PerspectiveCameraParams
{
const float radians = 45.0f;
const float zNear = -1.0f;
const float zFar = 1.0f;
const float aspectRatio;
const float zoom;
const glm::vec3 position = glm::vec3(0.0f);
const glm::vec3 rotationAxis = glm::vec3(1.0f);
const float rotationDegrees = 0.0f;
};
class CH_API PerspectiveCamera : public Camera
{
public:
PerspectiveCamera(const PerspectiveCameraParams& params);
inline glm::mat4 getViewProjectionMatrix() const override { return m_viewProjectionMatrix; };
inline glm::mat4 getProjectionMatrix() const override { return m_projectionMatrix; };
inline glm::mat4 getViewMatrix() const override { return m_viewMatrix; };
inline float getZoom() const override { return m_zoom; };
inline float getAspectRatio() const override { return m_aspectRatio; };
void setZoom(const float& zoom) override;
void setAspectRatio(const float& aspectRatio) override;
void setViewMatrix(const glm::mat4& viewMatrix) override;
void recalculateViewProjectionMatrix() override;
private:
float m_aspectRatio;
float m_zoom;
float m_zNear;
float m_zFar;
float m_radians;
glm::mat4 m_projectionMatrix;
glm::mat4 m_viewMatrix;
glm::mat4 m_viewProjectionMatrix;
};
}
#endif // !CHEETAH_ENGINE_RENDERER_PERSPECTIVECAMERA_H_
// PerspectiveCamera.cpp
#include "PerspectiveCamera.h"
namespace cheetah
{
PerspectiveCamera::PerspectiveCamera(const PerspectiveCameraParams& params)
:
m_projectionMatrix(glm::perspective(glm::radians(params.radians), params.aspectRatio, params.zNear, params.zFar)),
m_viewMatrix(glm::rotate(glm::translate(glm::mat4(1.0f), params.position), params.rotationDegrees, params.rotationAxis)),
m_viewProjectionMatrix(m_projectionMatrix* m_viewMatrix),
m_aspectRatio(params.aspectRatio),
m_zoom(params.zoom),
m_zNear(params.zNear),
m_zFar(params.zFar),
m_radians(params.radians)
{
}
void PerspectiveCamera::setViewMatrix(const glm::mat4& viewMatrix)
{
m_viewMatrix = viewMatrix;
recalculateViewProjectionMatrix();
}
void PerspectiveCamera::setZoom(const float& zoom)
{
m_zoom = zoom;
m_projectionMatrix = glm::perspective(glm::radians(m_radians += m_zoom), m_aspectRatio, m_zNear, m_zFar);
recalculateViewProjectionMatrix();
}
void PerspectiveCamera::setAspectRatio(const float& aspectRatio)
{
m_aspectRatio = aspectRatio;
m_projectionMatrix = glm::perspective(glm::radians(m_radians), aspectRatio, m_zNear, m_zFar);
recalculateViewProjectionMatrix();
}
void PerspectiveCamera::recalculateViewProjectionMatrix()
{
m_viewProjectionMatrix = m_projectionMatrix * m_viewMatrix;
}
}
CameraController Higher level, doesn't care about camera type(ortho or perspective)
// CameraController.h
#ifndef CHEETAH_ENGINE_RENDERER_CAMERACONTROLLER_H_
#define CHEETAH_ENGINE_RENDERER_CAMERACONTROLLER_H_
#include "Core/Core.h"
#include "Camera.h"
#include "OrthoGraphicCamera.h"
#include "PerspectiveCamera.h"
#include <glm/gtc/matrix_transform.hpp>
#include <glm/glm.hpp>
#include <memory>
namespace cheetah
{
class CH_API CameraController
{
public:
CameraController(const OrthoGraphicCameraParams& params);
CameraController(const PerspectiveCameraParams& params);
// affect ProjectionMatrix
void setZoom(const float& zoom);
void setAspectRatio(const float& width, const float& height);
// affect ViewMatrix
void setPosition(const glm::vec3& position);
void translate(const glm::vec3& position);
void rotate(const float& degrees, const glm::vec3& axis);
inline float getZoom() const { return m_camera->getZoom(); };
inline float getAspectRatio() const { return m_camera->getAspectRatio(); };
inline glm::vec3 getPosition() const { return m_position; };
inline glm::vec3 getRotationAxis() const { return m_rotationAxis; };
inline float getRotationDegrees() const { return m_rotationDegrees; };
inline Camera& getCamera() const { return *m_camera; };
private:
float m_rotationDegrees;
glm::vec3 m_rotationAxis;
glm::vec3 m_position;
std::unique_ptr<Camera> m_camera;
};
}
#endif // !CHEETAH_ENGINE_RENDERER_CAMERACONTROLLER_H_
// CameraController.cpp
#include "CameraController.h"
namespace cheetah
{
CameraController::CameraController(const OrthoGraphicCameraParams& params)
:
m_camera(std::make_unique<OrthoGraphicCamera>(params)),
m_position(params.position),
m_rotationAxis(params.rotationAxis),
m_rotationDegrees(params.rotationDegrees)
{
}
CameraController::CameraController(const PerspectiveCameraParams& params)
:
m_camera(std::make_unique<PerspectiveCamera>(params)),
m_position(params.position),
m_rotationAxis(params.rotationAxis),
m_rotationDegrees(params.rotationDegrees)
{
}
void CameraController::setZoom(const float& zoom)
{
m_camera->setZoom(zoom);
}
void CameraController::setAspectRatio(const float& width, const float& height)
{
m_camera->setAspectRatio(width / height);
}
void CameraController::setPosition(const glm::vec3& position)
{
m_position = position;
m_camera->setViewMatrix(glm::rotate(glm::translate(glm::mat4(1.0f), position), m_rotationDegrees, m_rotationAxis));
}
void CameraController::translate(const glm::vec3& position)
{
m_position = position;
m_camera->setViewMatrix(glm::translate(m_camera->getViewMatrix(), m_position));
}
void CameraController::rotate(const float& degrees, const glm::vec3& axis)
{
m_rotationDegrees = degrees;
m_rotationAxis = axis;
m_camera->setViewMatrix(glm::rotate(m_camera->getViewMatrix(), degrees, axis));
}
}
Implementation
Here a possible way of implementing the CameraController
// MainCamera.h
#ifndef GAME_MAINCAMERA_H_
#define GAME_MAINCAMERA_H_
#include "Cheetah.h"
class MainCamera : public cheetah::CameraController
{
public:
MainCamera(const cheetah::PerspectiveCameraParams& params);
void onUpdate(const float& deltaTime);
bool onWindowResize(const cheetah::WindowResizeEvent& event);
private:
void handleKeyInput(const float& deltaTime);
};
#endif // !GAME_MAINCAMERA_H_
// MainCamera.cpp
#include "MainCamera.h"
using namespace cheetah;
using namespace math;
using namespace input;
MainCamera::MainCamera(const cheetah::PerspectiveCameraParams& params)
: CameraController(params)
{
}
bool MainCamera::onWindowResize(const WindowResizeEvent& event)
{
setAspectRatio((float)event.m_width, (float)event.m_height);
return true;
}
void MainCamera::onUpdate(const float& deltaTime)
{
handleKeyInput(deltaTime);
}
void MainCamera::handleKeyInput(const float& deltaTime)
{
// reset
if (Input::isKeyPressed(keys::R))
{
setPosition(vec3(0.0f, 0.0f, 0.0));
}
// moving
if (Input::isKeyPressed(keys::W))
{
translate(vec3(0.0f, -(0.001f * deltaTime), 0.0f));
}
if (Input::isKeyPressed(keys::A))
{
translate(vec3(-(0.001f * deltaTime), 0.0f, 0.0f));
}
if (Input::isKeyPressed(keys::S))
{
translate(vec3(0.0f, 0.001f * deltaTime, 0.0f));
}
if (Input::isKeyPressed(keys::D))
{
translate(vec3(0.001f * deltaTime, 0.0f, 0.0f));
}
// rotating
if (Input::isKeyPressed(keys::Q))
{
rotate(-(0.001f * deltaTime), vec3(0, 1, 0));
}
if (Input::isKeyPressed(keys::E))
{
rotate(0.001f * deltaTime, vec3(0, 1, 0));
}
if (Input::isKeyPressed(keys::Z))
{
translate(vec3(0.0f, 0.0f, 0.001f * deltaTime));
}
if (Input::isKeyPressed(keys::X))
{
translate(vec3(0.0f, 0.0f, -(0.001f * deltaTime)));
}
}