vk-book/shared/Camera.h
2025-05-23 21:13:53 -04:00

232 lines
6.6 KiB
C++

#pragma once
#include <assert.h>
#include <algorithm>
#include "shared/UtilsMath.h"
#include "shared/Trackball.h"
#include "glm/gtx/euler_angles.hpp"
class CameraPositionerInterface
{
public:
virtual ~CameraPositionerInterface() = default;
virtual glm::mat4 getViewMatrix() const = 0;
virtual glm::vec3 getPosition() const = 0;
};
class Camera final
{
public:
explicit Camera(CameraPositionerInterface& positioner)
: positioner_(&positioner)
{}
Camera(const Camera&) = default;
Camera& operator = (const Camera&) = default;
glm::mat4 getViewMatrix() const { return positioner_->getViewMatrix(); }
glm::vec3 getPosition() const { return positioner_->getPosition(); }
glm::mat4 getProjMatrix() const { return proj_; }
private:
const CameraPositionerInterface* positioner_;
glm::mat4 proj_;
};
class CameraPositioner_FirstPerson final: public CameraPositionerInterface
{
public:
CameraPositioner_FirstPerson() = default;
CameraPositioner_FirstPerson(const glm::vec3& pos, const glm::vec3& target, const glm::vec3& up)
: cameraPosition_(pos)
, cameraOrientation_(glm::lookAt(pos, target, up))
, up_(up)
{}
void update(double deltaSeconds, const glm::vec2& mousePos, bool mousePressed)
{
if (mousePressed)
{
const glm::vec2 delta = mousePos - mousePos_;
const glm::quat deltaQuat = glm::quat(glm::vec3(-mouseSpeed_ * delta.y, mouseSpeed_ * delta.x, 0.0f));
cameraOrientation_ = deltaQuat * cameraOrientation_;
cameraOrientation_ = glm::normalize(cameraOrientation_);
setUpVector(up_);
}
mousePos_ = mousePos;
const glm::mat4 v = glm::mat4_cast(cameraOrientation_);
const glm::vec3 forward = -glm::vec3(v[0][2], v[1][2], v[2][2]);
const glm::vec3 right = glm::vec3(v[0][0], v[1][0], v[2][0]);
const glm::vec3 up = glm::cross(right, forward);
glm::vec3 accel(0.0f);
if (movement_.forward_) accel += forward;
if (movement_.backward_) accel -= forward;
if (movement_.left_) accel -= right;
if (movement_.right_) accel += right;
if (movement_.up_) accel += up;
if (movement_.down_) accel -= up;
if (movement_.fastSpeed_) accel *= fastCoef_;
if (accel == glm::vec3(0))
{
// decelerate naturally according to the damping value
moveSpeed_ -= moveSpeed_ * std::min((1.0f / damping_) * static_cast<float>(deltaSeconds), 1.0f);
}
else
{
// acceleration
moveSpeed_ += accel * acceleration_ * static_cast<float>(deltaSeconds);
const float maxSpeed = movement_.fastSpeed_ ? maxSpeed_ * fastCoef_ : maxSpeed_;
if (glm::length(moveSpeed_) > maxSpeed) moveSpeed_ = glm::normalize(moveSpeed_) * maxSpeed;
}
cameraPosition_ += moveSpeed_ * static_cast<float>(deltaSeconds);
}
virtual glm::mat4 getViewMatrix() const override
{
const glm::mat4 t = glm::translate(glm::mat4(1.0f), -cameraPosition_);
const glm::mat4 r = glm::mat4_cast(cameraOrientation_);
return r * t;
}
virtual glm::vec3 getPosition() const override
{
return cameraPosition_;
}
void setPosition(const glm::vec3& pos)
{
cameraPosition_ = pos;
}
void setSpeed(const glm::vec3& speed) {
moveSpeed_ = speed;
}
void resetMousePosition(const glm::vec2& p) { mousePos_ = p; };
void setUpVector(const glm::vec3& up)
{
const glm::mat4 view = getViewMatrix();
const glm::vec3 dir = -glm::vec3(view[0][2], view[1][2], view[2][2]);
cameraOrientation_ = glm::lookAt(cameraPosition_, cameraPosition_ + dir, up);
}
inline void lookAt(const glm::vec3& pos, const glm::vec3& target, const glm::vec3& up) {
cameraPosition_ = pos;
cameraOrientation_ = glm::lookAt(pos, target, up);
}
public:
struct Movement
{
bool forward_ = false;
bool backward_ = false;
bool left_ = false;
bool right_ = false;
bool up_ = false;
bool down_ = false;
//
bool fastSpeed_ = false;
} movement_;
public:
float mouseSpeed_ = 4.0f;
float acceleration_ = 150.0f;
float damping_ = 0.2f;
float maxSpeed_ = 10.0f;
float fastCoef_ = 10.0f;
private:
glm::vec2 mousePos_ = glm::vec2(0);
glm::vec3 cameraPosition_ = glm::vec3(0.0f, 10.0f, 10.0f);
glm::quat cameraOrientation_ = glm::quat(glm::vec3(0));
glm::vec3 moveSpeed_ = glm::vec3(0.0f);
glm::vec3 up_ = glm::vec3(0.0f, 0.0f, 1.0f);
};
class CameraPositioner_MoveTo final : public CameraPositionerInterface
{
public:
CameraPositioner_MoveTo(const glm::vec3& pos, const glm::vec3& angles)
: positionCurrent_(pos)
, positionDesired_(pos)
, anglesCurrent_(angles)
, anglesDesired_(angles)
{}
void update(float deltaSeconds, const glm::vec2& mousePos, bool mousePressed)
{
positionCurrent_ += dampingLinear_ * deltaSeconds * (positionDesired_ - positionCurrent_);
// normalization is required to avoid "spinning" around the object 2pi times
anglesCurrent_ = clipAngles(anglesCurrent_);
anglesDesired_ = clipAngles(anglesDesired_);
// update angles
anglesCurrent_ -= angleDelta(anglesCurrent_, anglesDesired_) * dampingEulerAngles_ * deltaSeconds;
// normalize new angles
anglesCurrent_ = clipAngles(anglesCurrent_);
const glm::vec3 a = glm::radians(anglesCurrent_);
currentTransform_ = glm::translate(glm::yawPitchRoll(a.y, a.x, a.z), -positionCurrent_);
}
void setPosition(const glm::vec3& p) { positionCurrent_ = p; }
void setAngles(float pitch, float pan, float roll) { anglesCurrent_ = glm::vec3(pitch, pan, roll); }
void setAngles(const glm::vec3& angles) { anglesCurrent_ = angles; }
void setDesiredPosition(const glm::vec3& p) { positionDesired_ = p; }
void setDesiredAngles(float pitch, float pan, float roll) { anglesDesired_ = glm::vec3(pitch, pan, roll); }
void setDesiredAngles(const glm::vec3& angles) { anglesDesired_ = angles; }
virtual glm::vec3 getPosition() const override { return positionCurrent_; }
virtual glm::mat4 getViewMatrix() const override { return currentTransform_; }
public:
float dampingLinear_ = 10.0f;
glm::vec3 dampingEulerAngles_ = glm::vec3(5.0f, 5.0f, 5.0f);
private:
glm::vec3 positionCurrent_ = glm::vec3(0.0f);
glm::vec3 positionDesired_ = glm::vec3(0.0f);
/// pitch, pan, roll
glm::vec3 anglesCurrent_ = glm::vec3(0.0f);
glm::vec3 anglesDesired_ = glm::vec3(0.0f);
glm::mat4 currentTransform_ = glm::mat4(1.0f);
static inline float clipAngle(float d)
{
if (d < -180.0f) return d + 360.0f;
if (d > +180.0f) return d - 360.f;
return d;
}
static inline glm::vec3 clipAngles(const glm::vec3& angles)
{
return glm::vec3(
std::fmod(angles.x, 360.0f),
std::fmod(angles.y, 360.0f),
std::fmod(angles.z, 360.0f)
);
}
static inline glm::vec3 angleDelta(const glm::vec3& anglesCurrent, const glm::vec3& anglesDesired)
{
const glm::vec3 d = clipAngles(anglesCurrent) - clipAngles(anglesDesired);
return glm::vec3(clipAngle(d.x), clipAngle(d.y), clipAngle(d.z));
}
};