procedural-3d-engine/base/camera.hpp
Claude Code 09ba229353
Some checks failed
Build Project / Build Ubuntu (push) Has been cancelled
Build Project / Build Windows (push) Has been cancelled
Build Project / Build macOS (push) Has been cancelled
Initial procedural 3D engine setup
- Updated README.md with modern project structure and features
- Cleaned up Android build files (not needed for desktop engine)
- Restructured as procedural 3D engine with ImGui integration
- Based on Sascha Willems Vulkan framework with dynamic rendering
- Added comprehensive build instructions and camera system docs

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-17 18:56:17 +02:00

323 lines
No EOL
7.9 KiB
C++

/*
* Basic camera class providing a look-at and first-person camera
*
* Copyright (C) 2016-2024 by Sascha Willems - www.saschawillems.de
*
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
*/
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtc/matrix_transform.hpp>
class Camera
{
private:
float fov;
float znear, zfar;
void updateViewMatrix()
{
glm::mat4 currentMatrix = matrices.view;
if (useOrbitMode && type == CameraType::lookat) {
// Orbit mode: position camera relative to orbit center using spherical coordinates
updateOrbitPosition();
// Use lookAt matrix for orbit mode
matrices.view = glm::lookAt(position, orbitCenter, glm::vec3(0.0f, 1.0f, 0.0f));
viewPos = glm::vec4(position, 0.0f) * glm::vec4(-1.0f, 1.0f, -1.0f, 1.0f);
} else {
// Standard Sascha camera behavior
glm::mat4 rotM = glm::mat4(1.0f);
glm::mat4 transM;
rotM = glm::rotate(rotM, glm::radians(rotation.x * (flipY ? -1.0f : 1.0f)), glm::vec3(1.0f, 0.0f, 0.0f));
rotM = glm::rotate(rotM, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
rotM = glm::rotate(rotM, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
glm::vec3 translation = position;
if (flipY) {
translation.y *= -1.0f;
}
transM = glm::translate(glm::mat4(1.0f), translation);
if (type == CameraType::firstperson)
{
matrices.view = rotM * transM;
}
else
{
matrices.view = transM * rotM;
}
viewPos = glm::vec4(position, 0.0f) * glm::vec4(-1.0f, 1.0f, -1.0f, 1.0f);
}
if (matrices.view != currentMatrix) {
updated = true;
}
};
public:
enum CameraType { lookat, firstperson };
CameraType type = CameraType::lookat;
glm::vec3 rotation = glm::vec3();
glm::vec3 position = glm::vec3();
glm::vec4 viewPos = glm::vec4();
// Orbit camera support
glm::vec3 orbitCenter = glm::vec3(0.0f);
float orbitDistance = 8.0f;
bool useOrbitMode = false;
float rotationSpeed = 1.0f;
float movementSpeed = 1.0f;
bool updated = true;
bool flipY = false;
struct
{
glm::mat4 perspective;
glm::mat4 view;
} matrices;
struct
{
bool left = false;
bool right = false;
bool up = false;
bool down = false;
} keys;
bool moving() const
{
return keys.left || keys.right || keys.up || keys.down;
}
float getNearClip() const {
return znear;
}
float getFarClip() const {
return zfar;
}
void setPerspective(float fov, float aspect, float znear, float zfar)
{
glm::mat4 currentMatrix = matrices.perspective;
this->fov = fov;
this->znear = znear;
this->zfar = zfar;
matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar);
if (flipY) {
matrices.perspective[1][1] *= -1.0f;
}
if (matrices.view != currentMatrix) {
updated = true;
}
};
void updateAspectRatio(float aspect)
{
glm::mat4 currentMatrix = matrices.perspective;
matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar);
if (flipY) {
matrices.perspective[1][1] *= -1.0f;
}
if (matrices.view != currentMatrix) {
updated = true;
}
}
void setPosition(glm::vec3 position)
{
this->position = position;
updateViewMatrix();
}
void setRotation(glm::vec3 rotation)
{
this->rotation = rotation;
updateViewMatrix();
}
void rotate(glm::vec3 delta)
{
this->rotation += delta;
updateViewMatrix();
}
void setTranslation(glm::vec3 translation)
{
this->position = translation;
updateViewMatrix();
};
void translate(glm::vec3 delta)
{
this->position += delta;
updateViewMatrix();
}
void setRotationSpeed(float rotationSpeed)
{
this->rotationSpeed = rotationSpeed;
}
void setMovementSpeed(float movementSpeed)
{
this->movementSpeed = movementSpeed;
}
// Update camera position based on orbit center and current rotation
void updateOrbitPosition()
{
if (!useOrbitMode) return;
// Convert Euler angles to spherical coordinates around orbit center
float pitch = glm::radians(rotation.x);
float yaw = glm::radians(rotation.y);
// Calculate position relative to orbit center using spherical coordinates
float x = orbitDistance * cos(pitch) * cos(yaw);
float y = orbitDistance * sin(pitch);
float z = orbitDistance * cos(pitch) * sin(yaw);
position = orbitCenter + glm::vec3(x, y, z);
}
// Enable orbit mode and set orbit center
void setOrbitMode(glm::vec3 center, float distance)
{
orbitCenter = center;
orbitDistance = distance;
useOrbitMode = true;
updateViewMatrix();
}
// Disable orbit mode and return to standard camera
void disableOrbitMode()
{
useOrbitMode = false;
updateViewMatrix();
}
// Focus camera on an object using orbit mode
void focusOnObject(glm::vec3 objectCenter, float objectRadius)
{
// Calculate optimal viewing distance based on object radius and FOV
float halfFovRadians = glm::radians(fov * 0.5f);
float distance = objectRadius / glm::tan(halfFovRadians) * 2.5f;
distance = glm::max(distance, objectRadius * 3.0f);
// Set orbit center to the object center
orbitCenter = objectCenter;
orbitDistance = distance;
useOrbitMode = true;
// Set camera to a good viewing angle (slightly elevated and angled)
rotation.x = 15.0f; // Slight elevation
rotation.y = 30.0f; // Angled view
rotation.z = 0.0f;
// Update position and view matrix
updateViewMatrix();
}
void update(float deltaTime)
{
updated = false;
if (type == CameraType::firstperson)
{
if (moving())
{
glm::vec3 camFront;
camFront.x = -cos(glm::radians(rotation.x)) * sin(glm::radians(rotation.y));
camFront.y = sin(glm::radians(rotation.x));
camFront.z = cos(glm::radians(rotation.x)) * cos(glm::radians(rotation.y));
camFront = glm::normalize(camFront);
float moveSpeed = deltaTime * movementSpeed;
if (keys.up)
position += camFront * moveSpeed;
if (keys.down)
position -= camFront * moveSpeed;
if (keys.left)
position -= glm::normalize(glm::cross(camFront, glm::vec3(0.0f, 1.0f, 0.0f))) * moveSpeed;
if (keys.right)
position += glm::normalize(glm::cross(camFront, glm::vec3(0.0f, 1.0f, 0.0f))) * moveSpeed;
}
}
updateViewMatrix();
};
// Update camera passing separate axis data (gamepad)
// Returns true if view or position has been changed
bool updatePad(glm::vec2 axisLeft, glm::vec2 axisRight, float deltaTime)
{
bool retVal = false;
if (type == CameraType::firstperson)
{
// Use the common console thumbstick layout
// Left = view, right = move
const float deadZone = 0.0015f;
const float range = 1.0f - deadZone;
glm::vec3 camFront;
camFront.x = -cos(glm::radians(rotation.x)) * sin(glm::radians(rotation.y));
camFront.y = sin(glm::radians(rotation.x));
camFront.z = cos(glm::radians(rotation.x)) * cos(glm::radians(rotation.y));
camFront = glm::normalize(camFront);
float moveSpeed = deltaTime * movementSpeed * 2.0f;
float rotSpeed = deltaTime * rotationSpeed * 50.0f;
// Move
if (fabsf(axisLeft.y) > deadZone)
{
float pos = (fabsf(axisLeft.y) - deadZone) / range;
position -= camFront * pos * ((axisLeft.y < 0.0f) ? -1.0f : 1.0f) * moveSpeed;
retVal = true;
}
if (fabsf(axisLeft.x) > deadZone)
{
float pos = (fabsf(axisLeft.x) - deadZone) / range;
position += glm::normalize(glm::cross(camFront, glm::vec3(0.0f, 1.0f, 0.0f))) * pos * ((axisLeft.x < 0.0f) ? -1.0f : 1.0f) * moveSpeed;
retVal = true;
}
// Rotate
if (fabsf(axisRight.x) > deadZone)
{
float pos = (fabsf(axisRight.x) - deadZone) / range;
rotation.y += pos * ((axisRight.x < 0.0f) ? -1.0f : 1.0f) * rotSpeed;
retVal = true;
}
if (fabsf(axisRight.y) > deadZone)
{
float pos = (fabsf(axisRight.y) - deadZone) / range;
rotation.x -= pos * ((axisRight.y < 0.0f) ? -1.0f : 1.0f) * rotSpeed;
retVal = true;
}
}
else
{
// todo: move code from example base class for look-at
}
if (retVal)
{
updateViewMatrix();
}
return retVal;
}
};