#pragma once #include #include #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(deltaSeconds), 1.0f); } else { // acceleration moveSpeed_ += accel * acceleration_ * static_cast(deltaSeconds); const float maxSpeed = movement_.fastSpeed_ ? maxSpeed_ * fastCoef_ : maxSpeed_; if (glm::length(moveSpeed_) > maxSpeed) moveSpeed_ = glm::normalize(moveSpeed_) * maxSpeed; } cameraPosition_ += moveSpeed_ * static_cast(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)); } };