ADD: Camera mode
This commit is contained in:
@@ -1,102 +1,6 @@
|
||||
#include "camera.h"
|
||||
#include <glm/gtx/transform.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
void Camera::update()
|
||||
{
|
||||
glm::mat4 cameraRotation = getRotationMatrix();
|
||||
glm::vec3 delta = glm::vec3(cameraRotation * glm::vec4(velocity * moveSpeed, 0.f));
|
||||
position_world += glm::dvec3(delta);
|
||||
}
|
||||
|
||||
void Camera::process_input(InputSystem &input, bool ui_capture_keyboard, bool ui_capture_mouse)
|
||||
{
|
||||
const InputState &st = input.state();
|
||||
|
||||
// Movement is state-based so simultaneous keys work naturally.
|
||||
if (ui_capture_keyboard)
|
||||
{
|
||||
velocity = glm::vec3(0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
glm::vec3 v(0.0f);
|
||||
if (st.key_down(Key::W)) { v.z -= 1.0f; }
|
||||
if (st.key_down(Key::S)) { v.z += 1.0f; }
|
||||
if (st.key_down(Key::A)) { v.x -= 1.0f; }
|
||||
if (st.key_down(Key::D)) { v.x += 1.0f; }
|
||||
velocity = v;
|
||||
}
|
||||
|
||||
// Event-based mouse handling so we don't apply motion that happened before RMB was pressed in the same frame.
|
||||
for (const InputEvent &e : input.events())
|
||||
{
|
||||
if (ui_capture_mouse)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e.type == InputEvent::Type::MouseButtonDown && e.mouse_button == MouseButton::Right)
|
||||
{
|
||||
rmbDown = true;
|
||||
input.set_cursor_mode(CursorMode::Relative);
|
||||
}
|
||||
else if (e.type == InputEvent::Type::MouseButtonUp && e.mouse_button == MouseButton::Right)
|
||||
{
|
||||
rmbDown = false;
|
||||
input.set_cursor_mode(CursorMode::Normal);
|
||||
}
|
||||
else if (e.type == InputEvent::Type::MouseMove && rmbDown)
|
||||
{
|
||||
// Convert mouse motion to incremental yaw/pitch angles.
|
||||
float dx = e.mouse_delta.x * lookSensitivity;
|
||||
float dy = e.mouse_delta.y * lookSensitivity;
|
||||
|
||||
// Mouse right (xrel > 0) turns view right with -Z-forward: yaw around +Y.
|
||||
glm::quat yawRotation = glm::angleAxis(dx, glm::vec3 { 0.f, 1.f, 0.f });
|
||||
|
||||
// Mouse up (yrel < 0) looks up with -Z-forward: negative dy.
|
||||
float pitchDelta = -dy;
|
||||
// Pitch around the camera's local X (right) axis in world space.
|
||||
glm::vec3 right = glm::rotate(orientation, glm::vec3 { 1.f, 0.f, 0.f });
|
||||
glm::quat pitchRotation = glm::angleAxis(pitchDelta, glm::vec3(right));
|
||||
|
||||
// Apply yaw, then pitch, to the current orientation.
|
||||
orientation = glm::normalize(pitchRotation * yawRotation * orientation);
|
||||
}
|
||||
else if (e.type == InputEvent::Type::MouseWheel)
|
||||
{
|
||||
const float steps = e.wheel_delta.y; // positive = wheel up
|
||||
if (std::abs(steps) < 0.001f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ctrl modifies FOV, otherwise adjust move speed
|
||||
if (e.mods.ctrl)
|
||||
{
|
||||
// Wheel up -> zoom in (smaller FOV)
|
||||
fovDegrees -= steps * 2.0f;
|
||||
fovDegrees = std::clamp(fovDegrees, 30.0f, 110.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Exponential scale for pleasant feel
|
||||
float factor = std::pow(1.15f, steps);
|
||||
moveSpeed = std::clamp(moveSpeed * factor, 0.001f, 5.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Safety: if mouse state shows RMB is no longer down, release relative mode.
|
||||
if (rmbDown && !st.mouse_down(MouseButton::Right))
|
||||
{
|
||||
rmbDown = false;
|
||||
input.set_cursor_mode(CursorMode::Normal);
|
||||
}
|
||||
}
|
||||
|
||||
glm::mat4 Camera::getViewMatrix(const glm::vec3 &position_local) const
|
||||
{
|
||||
|
||||
@@ -2,29 +2,15 @@
|
||||
|
||||
#include <core/types.h>
|
||||
|
||||
#include <core/input/input_system.h>
|
||||
|
||||
#include "glm/vec3.hpp"
|
||||
|
||||
class Camera {
|
||||
public:
|
||||
glm::vec3 velocity{0.0f, 0.0f, 0.0f};
|
||||
glm::dvec3 position_world{0.0, 0.0, 0.0};
|
||||
// Orientation stored as a quaternion (local -> world).
|
||||
glm::quat orientation { 1.0f, 0.0f, 0.0f, 0.0f };
|
||||
|
||||
// Movement/look tuning
|
||||
float moveSpeed { 0.03f };
|
||||
float lookSensitivity { 0.0020f };
|
||||
bool rmbDown { false };
|
||||
|
||||
// Field of view in degrees for projection
|
||||
float fovDegrees { 50.f };
|
||||
|
||||
glm::mat4 getViewMatrix(const glm::vec3 &position_local) const;
|
||||
glm::mat4 getRotationMatrix() const;
|
||||
|
||||
void process_input(InputSystem &input, bool ui_capture_keyboard, bool ui_capture_mouse);
|
||||
|
||||
void update();
|
||||
};
|
||||
|
||||
129
src/scene/camera/camera_rig.cpp
Normal file
129
src/scene/camera/camera_rig.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#include "camera_rig.h"
|
||||
|
||||
#include <scene/camera/mode_chase.h>
|
||||
#include <scene/camera/mode_fixed.h>
|
||||
#include <scene/camera/mode_follow.h>
|
||||
#include <scene/camera/mode_free.h>
|
||||
#include <scene/camera/mode_orbit.h>
|
||||
#include <scene/camera.h>
|
||||
#include <scene/vk_scene.h>
|
||||
|
||||
#include <core/input/input_system.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::unique_ptr<ICameraMode> make_mode(CameraMode mode,
|
||||
FreeCameraSettings &free_settings,
|
||||
OrbitCameraSettings &orbit_settings,
|
||||
FollowCameraSettings &follow_settings,
|
||||
ChaseCameraSettings &chase_settings,
|
||||
FixedCameraSettings &fixed_settings)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case CameraMode::Free: return std::make_unique<FreeCameraMode>(free_settings);
|
||||
case CameraMode::Orbit: return std::make_unique<OrbitCameraMode>(orbit_settings);
|
||||
case CameraMode::Follow: return std::make_unique<FollowCameraMode>(follow_settings);
|
||||
case CameraMode::Chase: return std::make_unique<ChaseCameraMode>(chase_settings);
|
||||
case CameraMode::Fixed: return std::make_unique<FixedCameraMode>(fixed_settings);
|
||||
default: return std::make_unique<FreeCameraMode>(free_settings);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
CameraRig::CameraRig() = default;
|
||||
CameraRig::~CameraRig() = default;
|
||||
|
||||
void CameraRig::init(SceneManager &scene, Camera &camera)
|
||||
{
|
||||
_scene = &scene;
|
||||
_camera = &camera;
|
||||
recreate_mode(scene, camera);
|
||||
}
|
||||
|
||||
void CameraRig::set_mode(CameraMode mode, SceneManager &scene, Camera &camera)
|
||||
{
|
||||
if (_mode == mode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_mode = mode;
|
||||
recreate_mode(scene, camera);
|
||||
}
|
||||
|
||||
const char *CameraRig::mode_name() const
|
||||
{
|
||||
if (_mode_impl)
|
||||
{
|
||||
return _mode_impl->name();
|
||||
}
|
||||
return "None";
|
||||
}
|
||||
|
||||
void CameraRig::process_input(InputSystem &input, bool ui_capture_keyboard, bool ui_capture_mouse)
|
||||
{
|
||||
if (_mode_impl && _scene && _camera)
|
||||
{
|
||||
_mode_impl->process_input(*_scene, *_camera, input, ui_capture_keyboard, ui_capture_mouse);
|
||||
}
|
||||
}
|
||||
|
||||
void CameraRig::update(SceneManager &scene, Camera &camera, float dt)
|
||||
{
|
||||
if (_mode_impl)
|
||||
{
|
||||
_mode_impl->update(scene, camera, dt);
|
||||
}
|
||||
}
|
||||
|
||||
bool CameraRig::resolve_target(SceneManager &scene,
|
||||
const CameraTarget &target,
|
||||
WorldVec3 &out_position_world,
|
||||
glm::quat &out_rotation) const
|
||||
{
|
||||
out_rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f);
|
||||
|
||||
switch (target.type)
|
||||
{
|
||||
case CameraTargetType::WorldPoint:
|
||||
out_position_world = target.world_point;
|
||||
return true;
|
||||
case CameraTargetType::MeshInstance:
|
||||
{
|
||||
WorldVec3 t{};
|
||||
glm::quat r{};
|
||||
glm::vec3 s{};
|
||||
if (!scene.getMeshInstanceTRSWorld(target.name, t, r, s))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
out_position_world = t;
|
||||
out_rotation = r;
|
||||
return true;
|
||||
}
|
||||
case CameraTargetType::GLTFInstance:
|
||||
{
|
||||
WorldVec3 t{};
|
||||
glm::quat r{};
|
||||
glm::vec3 s{};
|
||||
if (!scene.getGLTFInstanceTRSWorld(target.name, t, r, s))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
out_position_world = t;
|
||||
out_rotation = r;
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void CameraRig::recreate_mode(SceneManager &scene, Camera &camera)
|
||||
{
|
||||
_mode_impl = make_mode(_mode, _free, _orbit, _follow, _chase, _fixed);
|
||||
if (_mode_impl)
|
||||
{
|
||||
_mode_impl->on_activate(scene, camera);
|
||||
}
|
||||
}
|
||||
124
src/scene/camera/camera_rig.h
Normal file
124
src/scene/camera/camera_rig.h
Normal file
@@ -0,0 +1,124 @@
|
||||
#pragma once
|
||||
|
||||
#include <core/world.h>
|
||||
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class Camera;
|
||||
class InputSystem;
|
||||
class SceneManager;
|
||||
|
||||
enum class CameraMode : uint8_t
|
||||
{
|
||||
Free = 0,
|
||||
Orbit = 1,
|
||||
Follow = 2,
|
||||
Chase = 3,
|
||||
Fixed = 4
|
||||
};
|
||||
|
||||
enum class CameraTargetType : uint8_t
|
||||
{
|
||||
None = 0,
|
||||
WorldPoint = 1,
|
||||
MeshInstance = 2,
|
||||
GLTFInstance = 3
|
||||
};
|
||||
|
||||
struct CameraTarget
|
||||
{
|
||||
CameraTargetType type{CameraTargetType::None};
|
||||
std::string name{};
|
||||
WorldVec3 world_point{0.0, 0.0, 0.0};
|
||||
};
|
||||
|
||||
struct FreeCameraSettings
|
||||
{
|
||||
float move_speed{1.8f}; // world units / second
|
||||
float look_sensitivity{0.0020f};
|
||||
float roll_speed{1.0f}; // radians / second
|
||||
};
|
||||
|
||||
struct OrbitCameraSettings
|
||||
{
|
||||
CameraTarget target{};
|
||||
double distance{10.0};
|
||||
float yaw{0.0f}; // radians
|
||||
float pitch{0.0f}; // radians
|
||||
float look_sensitivity{0.0020f};
|
||||
};
|
||||
|
||||
struct FollowCameraSettings
|
||||
{
|
||||
CameraTarget target{};
|
||||
glm::vec3 position_offset_local{0.0f, 2.0f, 6.0f};
|
||||
glm::quat rotation_offset{1.0f, 0.0f, 0.0f, 0.0f};
|
||||
};
|
||||
|
||||
struct ChaseCameraSettings
|
||||
{
|
||||
CameraTarget target{};
|
||||
glm::vec3 position_offset_local{0.0f, 2.0f, 6.0f};
|
||||
glm::quat rotation_offset{1.0f, 0.0f, 0.0f, 0.0f};
|
||||
float position_lag{8.0f}; // smoothing rate (1/sec), higher = snappier
|
||||
float rotation_lag{10.0f}; // smoothing rate (1/sec)
|
||||
};
|
||||
|
||||
struct FixedCameraSettings
|
||||
{
|
||||
};
|
||||
|
||||
class CameraRig
|
||||
{
|
||||
public:
|
||||
CameraRig();
|
||||
~CameraRig();
|
||||
|
||||
CameraRig(const CameraRig &) = delete;
|
||||
CameraRig &operator=(const CameraRig &) = delete;
|
||||
|
||||
void init(SceneManager &scene, Camera &camera);
|
||||
|
||||
CameraMode mode() const { return _mode; }
|
||||
void set_mode(CameraMode mode, SceneManager &scene, Camera &camera);
|
||||
|
||||
const char *mode_name() const;
|
||||
|
||||
FreeCameraSettings &free_settings() { return _free; }
|
||||
OrbitCameraSettings &orbit_settings() { return _orbit; }
|
||||
FollowCameraSettings &follow_settings() { return _follow; }
|
||||
ChaseCameraSettings &chase_settings() { return _chase; }
|
||||
FixedCameraSettings &fixed_settings() { return _fixed; }
|
||||
|
||||
const FreeCameraSettings &free_settings() const { return _free; }
|
||||
const OrbitCameraSettings &orbit_settings() const { return _orbit; }
|
||||
const FollowCameraSettings &follow_settings() const { return _follow; }
|
||||
const ChaseCameraSettings &chase_settings() const { return _chase; }
|
||||
const FixedCameraSettings &fixed_settings() const { return _fixed; }
|
||||
|
||||
void process_input(InputSystem &input, bool ui_capture_keyboard, bool ui_capture_mouse);
|
||||
void update(SceneManager &scene, Camera &camera, float dt);
|
||||
|
||||
bool resolve_target(SceneManager &scene,
|
||||
const CameraTarget &target,
|
||||
WorldVec3 &out_position_world,
|
||||
glm::quat &out_rotation) const;
|
||||
|
||||
private:
|
||||
void recreate_mode(SceneManager &scene, Camera &camera);
|
||||
|
||||
CameraMode _mode{CameraMode::Free};
|
||||
std::unique_ptr<class ICameraMode> _mode_impl;
|
||||
SceneManager *_scene = nullptr;
|
||||
Camera *_camera = nullptr;
|
||||
|
||||
FreeCameraSettings _free{};
|
||||
OrbitCameraSettings _orbit{};
|
||||
FollowCameraSettings _follow{};
|
||||
ChaseCameraSettings _chase{};
|
||||
FixedCameraSettings _fixed{};
|
||||
};
|
||||
20
src/scene/camera/icamera_mode.h
Normal file
20
src/scene/camera/icamera_mode.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
class Camera;
|
||||
class InputSystem;
|
||||
class SceneManager;
|
||||
|
||||
class ICameraMode
|
||||
{
|
||||
public:
|
||||
virtual ~ICameraMode() = default;
|
||||
|
||||
virtual const char *name() const = 0;
|
||||
virtual void on_activate(SceneManager &scene, Camera &camera) = 0;
|
||||
virtual void process_input(SceneManager &scene,
|
||||
Camera &camera,
|
||||
InputSystem &input,
|
||||
bool ui_capture_keyboard,
|
||||
bool ui_capture_mouse) = 0;
|
||||
virtual void update(SceneManager &scene, Camera &camera, float dt) = 0;
|
||||
};
|
||||
74
src/scene/camera/mode_chase.cpp
Normal file
74
src/scene/camera/mode_chase.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include "mode_chase.h"
|
||||
|
||||
#include <scene/camera/camera_rig.h>
|
||||
#include <scene/camera.h>
|
||||
#include <scene/vk_scene.h>
|
||||
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
ChaseCameraMode::ChaseCameraMode(ChaseCameraSettings &settings)
|
||||
: _settings(settings)
|
||||
{
|
||||
}
|
||||
|
||||
void ChaseCameraMode::on_activate(SceneManager &scene, Camera &camera)
|
||||
{
|
||||
// If no target set, chase a point in front of the camera.
|
||||
if (_settings.target.type == CameraTargetType::None)
|
||||
{
|
||||
glm::vec3 forward = glm::rotate(camera.orientation, glm::vec3(0.0f, 0.0f, -1.0f));
|
||||
_settings.target.type = CameraTargetType::WorldPoint;
|
||||
_settings.target.world_point = camera.position_world + WorldVec3(forward) * 10.0;
|
||||
}
|
||||
|
||||
// Preserve current relative transform to the target when possible.
|
||||
WorldVec3 target_pos{};
|
||||
glm::quat target_rot{};
|
||||
if (!scene.getCameraRig().resolve_target(scene, _settings.target, target_pos, target_rot))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
glm::quat inv_target = glm::inverse(target_rot);
|
||||
glm::vec3 rel_pos = glm::vec3(camera.position_world - target_pos);
|
||||
_settings.position_offset_local = glm::rotate(inv_target, rel_pos);
|
||||
_settings.rotation_offset = glm::normalize(inv_target * camera.orientation);
|
||||
}
|
||||
|
||||
void ChaseCameraMode::process_input(SceneManager & /*scene*/,
|
||||
Camera & /*camera*/,
|
||||
InputSystem & /*input*/,
|
||||
bool /*ui_capture_keyboard*/,
|
||||
bool /*ui_capture_mouse*/)
|
||||
{
|
||||
}
|
||||
|
||||
void ChaseCameraMode::update(SceneManager &scene, Camera &camera, float dt)
|
||||
{
|
||||
WorldVec3 target_pos{};
|
||||
glm::quat target_rot{};
|
||||
if (!scene.getCameraRig().resolve_target(scene, _settings.target, target_pos, target_rot))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
glm::vec3 offset_world = glm::rotate(target_rot, _settings.position_offset_local);
|
||||
WorldVec3 desired_pos = target_pos + WorldVec3(offset_world);
|
||||
glm::quat desired_rot = glm::normalize(target_rot * _settings.rotation_offset);
|
||||
|
||||
if (dt > 0.0f)
|
||||
{
|
||||
float pos_alpha = 1.0f - std::exp(-_settings.position_lag * dt);
|
||||
float rot_alpha = 1.0f - std::exp(-_settings.rotation_lag * dt);
|
||||
|
||||
camera.position_world += (desired_pos - camera.position_world) * static_cast<double>(pos_alpha);
|
||||
camera.orientation = glm::normalize(glm::slerp(camera.orientation, desired_rot, rot_alpha));
|
||||
}
|
||||
else
|
||||
{
|
||||
camera.position_world = desired_pos;
|
||||
camera.orientation = desired_rot;
|
||||
}
|
||||
}
|
||||
24
src/scene/camera/mode_chase.h
Normal file
24
src/scene/camera/mode_chase.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <scene/camera/icamera_mode.h>
|
||||
|
||||
struct ChaseCameraSettings;
|
||||
|
||||
class ChaseCameraMode : public ICameraMode
|
||||
{
|
||||
public:
|
||||
explicit ChaseCameraMode(ChaseCameraSettings &settings);
|
||||
~ChaseCameraMode() override = default;
|
||||
|
||||
const char *name() const override { return "Chase"; }
|
||||
void on_activate(SceneManager &scene, Camera &camera) override;
|
||||
void process_input(SceneManager &scene,
|
||||
Camera &camera,
|
||||
InputSystem &input,
|
||||
bool ui_capture_keyboard,
|
||||
bool ui_capture_mouse) override;
|
||||
void update(SceneManager &scene, Camera &camera, float dt) override;
|
||||
|
||||
private:
|
||||
ChaseCameraSettings &_settings;
|
||||
};
|
||||
25
src/scene/camera/mode_fixed.cpp
Normal file
25
src/scene/camera/mode_fixed.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include "mode_fixed.h"
|
||||
|
||||
#include <scene/camera/camera_rig.h>
|
||||
|
||||
FixedCameraMode::FixedCameraMode(FixedCameraSettings &settings)
|
||||
: _settings(settings)
|
||||
{
|
||||
}
|
||||
|
||||
void FixedCameraMode::on_activate(SceneManager & /*scene*/, Camera & /*camera*/)
|
||||
{
|
||||
}
|
||||
|
||||
void FixedCameraMode::process_input(SceneManager & /*scene*/,
|
||||
Camera & /*camera*/,
|
||||
InputSystem & /*input*/,
|
||||
bool /*ui_capture_keyboard*/,
|
||||
bool /*ui_capture_mouse*/)
|
||||
{
|
||||
}
|
||||
|
||||
void FixedCameraMode::update(SceneManager & /*scene*/, Camera & /*camera*/, float /*dt*/)
|
||||
{
|
||||
(void)_settings;
|
||||
}
|
||||
24
src/scene/camera/mode_fixed.h
Normal file
24
src/scene/camera/mode_fixed.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <scene/camera/icamera_mode.h>
|
||||
|
||||
struct FixedCameraSettings;
|
||||
|
||||
class FixedCameraMode : public ICameraMode
|
||||
{
|
||||
public:
|
||||
explicit FixedCameraMode(FixedCameraSettings &settings);
|
||||
~FixedCameraMode() override = default;
|
||||
|
||||
const char *name() const override { return "Fixed"; }
|
||||
void on_activate(SceneManager &scene, Camera &camera) override;
|
||||
void process_input(SceneManager &scene,
|
||||
Camera &camera,
|
||||
InputSystem &input,
|
||||
bool ui_capture_keyboard,
|
||||
bool ui_capture_mouse) override;
|
||||
void update(SceneManager &scene, Camera &camera, float dt) override;
|
||||
|
||||
private:
|
||||
FixedCameraSettings &_settings;
|
||||
};
|
||||
58
src/scene/camera/mode_follow.cpp
Normal file
58
src/scene/camera/mode_follow.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#include "mode_follow.h"
|
||||
|
||||
#include <scene/camera/camera_rig.h>
|
||||
#include <scene/camera.h>
|
||||
#include <scene/vk_scene.h>
|
||||
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
FollowCameraMode::FollowCameraMode(FollowCameraSettings &settings)
|
||||
: _settings(settings)
|
||||
{
|
||||
}
|
||||
|
||||
void FollowCameraMode::on_activate(SceneManager &scene, Camera &camera)
|
||||
{
|
||||
// If no target set, follow a point in front of the camera.
|
||||
if (_settings.target.type == CameraTargetType::None)
|
||||
{
|
||||
glm::vec3 forward = glm::rotate(camera.orientation, glm::vec3(0.0f, 0.0f, -1.0f));
|
||||
_settings.target.type = CameraTargetType::WorldPoint;
|
||||
_settings.target.world_point = camera.position_world + WorldVec3(forward) * 10.0;
|
||||
}
|
||||
|
||||
// Preserve current relative transform to the target when possible.
|
||||
WorldVec3 target_pos{};
|
||||
glm::quat target_rot{};
|
||||
if (!scene.getCameraRig().resolve_target(scene, _settings.target, target_pos, target_rot))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
glm::quat inv_target = glm::inverse(target_rot);
|
||||
glm::vec3 rel_pos = glm::vec3(camera.position_world - target_pos);
|
||||
_settings.position_offset_local = glm::rotate(inv_target, rel_pos);
|
||||
_settings.rotation_offset = glm::normalize(inv_target * camera.orientation);
|
||||
}
|
||||
|
||||
void FollowCameraMode::process_input(SceneManager & /*scene*/,
|
||||
Camera & /*camera*/,
|
||||
InputSystem & /*input*/,
|
||||
bool /*ui_capture_keyboard*/,
|
||||
bool /*ui_capture_mouse*/)
|
||||
{
|
||||
}
|
||||
|
||||
void FollowCameraMode::update(SceneManager &scene, Camera &camera, float /*dt*/)
|
||||
{
|
||||
WorldVec3 target_pos{};
|
||||
glm::quat target_rot{};
|
||||
if (!scene.getCameraRig().resolve_target(scene, _settings.target, target_pos, target_rot))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
glm::vec3 offset_world = glm::rotate(target_rot, _settings.position_offset_local);
|
||||
camera.position_world = target_pos + WorldVec3(offset_world);
|
||||
camera.orientation = glm::normalize(target_rot * _settings.rotation_offset);
|
||||
}
|
||||
24
src/scene/camera/mode_follow.h
Normal file
24
src/scene/camera/mode_follow.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <scene/camera/icamera_mode.h>
|
||||
|
||||
struct FollowCameraSettings;
|
||||
|
||||
class FollowCameraMode : public ICameraMode
|
||||
{
|
||||
public:
|
||||
explicit FollowCameraMode(FollowCameraSettings &settings);
|
||||
~FollowCameraMode() override = default;
|
||||
|
||||
const char *name() const override { return "Follow"; }
|
||||
void on_activate(SceneManager &scene, Camera &camera) override;
|
||||
void process_input(SceneManager &scene,
|
||||
Camera &camera,
|
||||
InputSystem &input,
|
||||
bool ui_capture_keyboard,
|
||||
bool ui_capture_mouse) override;
|
||||
void update(SceneManager &scene, Camera &camera, float dt) override;
|
||||
|
||||
private:
|
||||
FollowCameraSettings &_settings;
|
||||
};
|
||||
147
src/scene/camera/mode_free.cpp
Normal file
147
src/scene/camera/mode_free.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
#include "mode_free.h"
|
||||
|
||||
#include <scene/camera/camera_rig.h>
|
||||
#include <scene/camera.h>
|
||||
|
||||
#include <core/input/input_system.h>
|
||||
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
FreeCameraMode::FreeCameraMode(FreeCameraSettings &settings)
|
||||
: _settings(settings)
|
||||
{
|
||||
}
|
||||
|
||||
void FreeCameraMode::on_activate(SceneManager & /*scene*/, Camera & /*camera*/)
|
||||
{
|
||||
_velocity = glm::vec3(0.0f);
|
||||
_roll_dir = 0.0f;
|
||||
_rmb_down = false;
|
||||
}
|
||||
|
||||
void FreeCameraMode::process_input(SceneManager & /*scene*/,
|
||||
Camera &camera,
|
||||
InputSystem &input,
|
||||
bool ui_capture_keyboard,
|
||||
bool ui_capture_mouse)
|
||||
{
|
||||
const InputState &st = input.state();
|
||||
|
||||
// Movement is state-based so simultaneous keys work naturally.
|
||||
if (ui_capture_keyboard)
|
||||
{
|
||||
_velocity = glm::vec3(0.0f);
|
||||
_roll_dir = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
glm::vec3 v(0.0f);
|
||||
if (st.key_down(Key::W)) { v.z -= 1.0f; }
|
||||
if (st.key_down(Key::S)) { v.z += 1.0f; }
|
||||
if (st.key_down(Key::A)) { v.x -= 1.0f; }
|
||||
if (st.key_down(Key::D)) { v.x += 1.0f; }
|
||||
_velocity = v;
|
||||
|
||||
float roll = 0.0f;
|
||||
if (st.key_down(Key::Q)) { roll -= 1.0f; }
|
||||
if (st.key_down(Key::E)) { roll += 1.0f; }
|
||||
_roll_dir = roll;
|
||||
}
|
||||
|
||||
// Event-based mouse handling so we don't apply motion that happened
|
||||
// before RMB was pressed in the same frame.
|
||||
for (const InputEvent &e : input.events())
|
||||
{
|
||||
if (ui_capture_mouse)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e.type == InputEvent::Type::MouseButtonDown && e.mouse_button == MouseButton::Right)
|
||||
{
|
||||
_rmb_down = true;
|
||||
input.set_cursor_mode(CursorMode::Relative);
|
||||
}
|
||||
else if (e.type == InputEvent::Type::MouseButtonUp && e.mouse_button == MouseButton::Right)
|
||||
{
|
||||
_rmb_down = false;
|
||||
input.set_cursor_mode(CursorMode::Normal);
|
||||
}
|
||||
else if (e.type == InputEvent::Type::MouseMove && _rmb_down)
|
||||
{
|
||||
// Convert mouse motion to incremental yaw/pitch angles.
|
||||
float dx = e.mouse_delta.x * _settings.look_sensitivity;
|
||||
float dy = e.mouse_delta.y * _settings.look_sensitivity;
|
||||
|
||||
// Mouse right (xrel > 0) turns view right with -Z-forward: yaw around +Y.
|
||||
glm::quat yaw_rotation = glm::angleAxis(dx, glm::vec3{0.f, 1.f, 0.f});
|
||||
|
||||
// Mouse up (yrel < 0) looks up with -Z-forward: negative dy.
|
||||
float pitch_delta = -dy;
|
||||
// Pitch around the camera's local X (right) axis in world space.
|
||||
glm::vec3 right = glm::rotate(camera.orientation, glm::vec3{1.f, 0.f, 0.f});
|
||||
glm::quat pitch_rotation = glm::angleAxis(pitch_delta, glm::vec3(right));
|
||||
|
||||
// Apply yaw, then pitch, to the current orientation.
|
||||
camera.orientation = glm::normalize(pitch_rotation * yaw_rotation * camera.orientation);
|
||||
}
|
||||
else if (e.type == InputEvent::Type::MouseWheel)
|
||||
{
|
||||
const float steps = e.wheel_delta.y; // positive = wheel up
|
||||
if (std::abs(steps) < 0.001f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ctrl modifies FOV, otherwise adjust move speed.
|
||||
if (e.mods.ctrl)
|
||||
{
|
||||
// Wheel up -> zoom in (smaller FOV)
|
||||
camera.fovDegrees -= steps * 2.0f;
|
||||
camera.fovDegrees = std::clamp(camera.fovDegrees, 30.0f, 110.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Exponential scale for pleasant feel
|
||||
float factor = std::pow(1.15f, steps);
|
||||
_settings.move_speed = std::clamp(_settings.move_speed * factor, 0.06f, 300.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Safety: if mouse state shows RMB is no longer down, release relative mode.
|
||||
if (_rmb_down && !st.mouse_down(MouseButton::Right))
|
||||
{
|
||||
_rmb_down = false;
|
||||
input.set_cursor_mode(CursorMode::Normal);
|
||||
}
|
||||
}
|
||||
|
||||
void FreeCameraMode::update(SceneManager & /*scene*/, Camera &camera, float dt)
|
||||
{
|
||||
if (dt <= 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Roll around the camera's forward axis (world-space axis).
|
||||
if (_roll_dir != 0.0f && _settings.roll_speed > 0.0f)
|
||||
{
|
||||
glm::vec3 forward = glm::rotate(camera.orientation, glm::vec3{0.0f, 0.0f, -1.0f});
|
||||
float angle = _roll_dir * _settings.roll_speed * dt;
|
||||
glm::quat roll_rotation = glm::angleAxis(angle, glm::normalize(forward));
|
||||
camera.orientation = glm::normalize(roll_rotation * camera.orientation);
|
||||
}
|
||||
|
||||
// Move in camera-local space.
|
||||
if (_velocity.x != 0.0f || _velocity.y != 0.0f || _velocity.z != 0.0f)
|
||||
{
|
||||
glm::vec3 local_delta = _velocity * (_settings.move_speed * dt);
|
||||
glm::mat4 camera_rotation = camera.getRotationMatrix();
|
||||
glm::vec3 world_delta = glm::vec3(camera_rotation * glm::vec4(local_delta, 0.0f));
|
||||
camera.position_world += glm::dvec3(world_delta);
|
||||
}
|
||||
}
|
||||
27
src/scene/camera/mode_free.h
Normal file
27
src/scene/camera/mode_free.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <scene/camera/icamera_mode.h>
|
||||
|
||||
struct FreeCameraSettings;
|
||||
|
||||
class FreeCameraMode : public ICameraMode
|
||||
{
|
||||
public:
|
||||
explicit FreeCameraMode(FreeCameraSettings &settings);
|
||||
~FreeCameraMode() override = default;
|
||||
|
||||
const char *name() const override { return "Free"; }
|
||||
void on_activate(SceneManager &scene, Camera &camera) override;
|
||||
void process_input(SceneManager &scene,
|
||||
Camera &camera,
|
||||
InputSystem &input,
|
||||
bool ui_capture_keyboard,
|
||||
bool ui_capture_mouse) override;
|
||||
void update(SceneManager &scene, Camera &camera, float dt) override;
|
||||
|
||||
private:
|
||||
FreeCameraSettings &_settings;
|
||||
glm::vec3 _velocity{0.0f, 0.0f, 0.0f};
|
||||
float _roll_dir = 0.0f;
|
||||
bool _rmb_down = false;
|
||||
};
|
||||
163
src/scene/camera/mode_orbit.cpp
Normal file
163
src/scene/camera/mode_orbit.cpp
Normal file
@@ -0,0 +1,163 @@
|
||||
#include "mode_orbit.h"
|
||||
|
||||
#include <scene/camera/camera_rig.h>
|
||||
#include <scene/camera.h>
|
||||
#include <scene/vk_scene.h>
|
||||
|
||||
#include <core/input/input_system.h>
|
||||
|
||||
#include <glm/gtc/constants.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
OrbitCameraMode::OrbitCameraMode(OrbitCameraSettings &settings)
|
||||
: _settings(settings)
|
||||
{
|
||||
}
|
||||
|
||||
void OrbitCameraMode::on_activate(SceneManager &scene, Camera &camera)
|
||||
{
|
||||
_rmb_down = false;
|
||||
|
||||
// If no target set, orbit around a point in front of the camera.
|
||||
if (_settings.target.type == CameraTargetType::None)
|
||||
{
|
||||
glm::vec3 forward = glm::rotate(camera.orientation, glm::vec3(0.0f, 0.0f, -1.0f));
|
||||
_settings.target.type = CameraTargetType::WorldPoint;
|
||||
_settings.target.world_point = camera.position_world + WorldVec3(forward) * _settings.distance;
|
||||
}
|
||||
|
||||
// Derive yaw/pitch/distance from current camera pose to avoid snapping.
|
||||
WorldVec3 target_pos{};
|
||||
glm::quat target_rot{};
|
||||
if (!scene.getCameraRig().resolve_target(scene, _settings.target, target_pos, target_rot))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WorldVec3 to_cam = camera.position_world - target_pos;
|
||||
double dist = glm::length(to_cam);
|
||||
if (!std::isfinite(dist) || dist < 0.001)
|
||||
{
|
||||
dist = _settings.distance;
|
||||
}
|
||||
_settings.distance = dist;
|
||||
|
||||
glm::vec3 dir = glm::normalize(glm::vec3(to_cam / dist)); // target -> camera
|
||||
_settings.yaw = std::atan2(dir.x, dir.z);
|
||||
_settings.pitch = std::asin(std::clamp(-dir.y, -1.0f, 1.0f));
|
||||
}
|
||||
|
||||
void OrbitCameraMode::process_input(SceneManager & /*scene*/,
|
||||
Camera &camera,
|
||||
InputSystem &input,
|
||||
bool /*ui_capture_keyboard*/,
|
||||
bool ui_capture_mouse)
|
||||
{
|
||||
const InputState &st = input.state();
|
||||
|
||||
for (const InputEvent &e : input.events())
|
||||
{
|
||||
if (ui_capture_mouse)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e.type == InputEvent::Type::MouseButtonDown && e.mouse_button == MouseButton::Right)
|
||||
{
|
||||
_rmb_down = true;
|
||||
input.set_cursor_mode(CursorMode::Relative);
|
||||
}
|
||||
else if (e.type == InputEvent::Type::MouseButtonUp && e.mouse_button == MouseButton::Right)
|
||||
{
|
||||
_rmb_down = false;
|
||||
input.set_cursor_mode(CursorMode::Normal);
|
||||
}
|
||||
else if (e.type == InputEvent::Type::MouseMove && _rmb_down)
|
||||
{
|
||||
float dx = e.mouse_delta.x * _settings.look_sensitivity;
|
||||
float dy = e.mouse_delta.y * _settings.look_sensitivity;
|
||||
|
||||
_settings.yaw += dx;
|
||||
_settings.pitch += dy;
|
||||
|
||||
const float limit = glm::half_pi<float>() - 0.01f;
|
||||
_settings.pitch = std::clamp(_settings.pitch, -limit, limit);
|
||||
}
|
||||
else if (e.type == InputEvent::Type::MouseWheel)
|
||||
{
|
||||
const float steps = e.wheel_delta.y; // positive = wheel up
|
||||
if (std::abs(steps) < 0.001f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e.mods.ctrl)
|
||||
{
|
||||
camera.fovDegrees -= steps * 2.0f;
|
||||
camera.fovDegrees = std::clamp(camera.fovDegrees, 30.0f, 110.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
const double factor = std::pow(1.15, -static_cast<double>(steps));
|
||||
_settings.distance = std::clamp(_settings.distance * factor, 0.2, 100000.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_rmb_down && !st.mouse_down(MouseButton::Right))
|
||||
{
|
||||
_rmb_down = false;
|
||||
input.set_cursor_mode(CursorMode::Normal);
|
||||
}
|
||||
}
|
||||
|
||||
void OrbitCameraMode::update(SceneManager &scene, Camera &camera, float /*dt*/)
|
||||
{
|
||||
WorldVec3 target_pos{};
|
||||
glm::quat target_rot{};
|
||||
if (!scene.getCameraRig().resolve_target(scene, _settings.target, target_pos, target_rot))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto wrap_pi = [](float a) -> float {
|
||||
// Wrap angle to [-pi, pi] to avoid precision issues over long play sessions.
|
||||
constexpr float two_pi = glm::two_pi<float>();
|
||||
a = std::fmod(a + glm::pi<float>(), two_pi);
|
||||
if (a < 0.0f) a += two_pi;
|
||||
return a - glm::pi<float>();
|
||||
};
|
||||
|
||||
float yaw = wrap_pi(_settings.yaw);
|
||||
float pitch = std::clamp(_settings.pitch,
|
||||
-glm::half_pi<float>() + 0.01f,
|
||||
glm::half_pi<float>() - 0.01f);
|
||||
_settings.yaw = yaw;
|
||||
_settings.pitch = pitch;
|
||||
double dist = std::max(0.2, _settings.distance);
|
||||
|
||||
glm::quat yaw_q = glm::angleAxis(yaw, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
glm::vec3 right = glm::rotate(yaw_q, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
glm::quat pitch_q = glm::angleAxis(pitch, right);
|
||||
glm::quat orbit_q = glm::normalize(pitch_q * yaw_q);
|
||||
|
||||
// Place the camera on its local +Z axis relative to the target so the camera's
|
||||
// -Z forward axis points toward the target.
|
||||
const double yaw_d = static_cast<double>(yaw);
|
||||
const double pitch_d = static_cast<double>(pitch);
|
||||
const double cos_pitch = std::cos(pitch_d);
|
||||
const double sin_pitch = std::sin(pitch_d);
|
||||
const double sin_yaw = std::sin(yaw_d);
|
||||
const double cos_yaw = std::cos(yaw_d);
|
||||
|
||||
const glm::dvec3 dir_target_to_camera(
|
||||
sin_yaw * cos_pitch,
|
||||
-sin_pitch,
|
||||
cos_yaw * cos_pitch);
|
||||
|
||||
camera.position_world = target_pos + dir_target_to_camera * dist;
|
||||
camera.orientation = orbit_q;
|
||||
}
|
||||
25
src/scene/camera/mode_orbit.h
Normal file
25
src/scene/camera/mode_orbit.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <scene/camera/icamera_mode.h>
|
||||
|
||||
struct OrbitCameraSettings;
|
||||
|
||||
class OrbitCameraMode : public ICameraMode
|
||||
{
|
||||
public:
|
||||
explicit OrbitCameraMode(OrbitCameraSettings &settings);
|
||||
~OrbitCameraMode() override = default;
|
||||
|
||||
const char *name() const override { return "Orbit"; }
|
||||
void on_activate(SceneManager &scene, Camera &camera) override;
|
||||
void process_input(SceneManager &scene,
|
||||
Camera &camera,
|
||||
InputSystem &input,
|
||||
bool ui_capture_keyboard,
|
||||
bool ui_capture_mouse) override;
|
||||
void update(SceneManager &scene, Camera &camera, float dt) override;
|
||||
|
||||
private:
|
||||
OrbitCameraSettings &_settings;
|
||||
bool _rmb_down = false;
|
||||
};
|
||||
@@ -111,7 +111,6 @@ void SceneManager::init(EngineContext *context)
|
||||
{
|
||||
_context = context;
|
||||
|
||||
mainCamera.velocity = glm::vec3(0.f);
|
||||
mainCamera.position_world = WorldVec3(30.0, 0.0, 85.0);
|
||||
mainCamera.orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f);
|
||||
|
||||
@@ -119,6 +118,8 @@ void SceneManager::init(EngineContext *context)
|
||||
sceneData.sunlightDirection = glm::vec4(-0.2f, -1.0f, -0.3f, 1.0f);
|
||||
sceneData.sunlightColor = glm::vec4(1.0f, 1.0f, 1.0f, 3.0f);
|
||||
|
||||
cameraRig.init(*this, mainCamera);
|
||||
|
||||
_camera_position_local = world_to_local(mainCamera.position_world, _origin_world);
|
||||
}
|
||||
|
||||
@@ -151,7 +152,25 @@ void SceneManager::update_scene()
|
||||
mainDrawContext.nextID = 1;
|
||||
mainDrawContext.gltfNodeLocalOverrides = nullptr;
|
||||
|
||||
mainCamera.update();
|
||||
// Simple per-frame dt (seconds) for animations and camera behavior.
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if (_lastFrameTime.time_since_epoch().count() == 0)
|
||||
{
|
||||
_lastFrameTime = now;
|
||||
}
|
||||
float dt = std::chrono::duration<float>(now - _lastFrameTime).count();
|
||||
_lastFrameTime = now;
|
||||
if (dt < 0.f)
|
||||
{
|
||||
dt = 0.f;
|
||||
}
|
||||
if (dt > 0.1f)
|
||||
{
|
||||
dt = 0.1f;
|
||||
}
|
||||
_deltaTime = dt;
|
||||
|
||||
cameraRig.update(*this, mainCamera, dt);
|
||||
|
||||
// Floating origin: keep render-local coordinates near (0,0,0) by shifting the origin
|
||||
// when the camera drifts too far in world space.
|
||||
@@ -171,24 +190,6 @@ void SceneManager::update_scene()
|
||||
|
||||
_camera_position_local = world_to_local(mainCamera.position_world, _origin_world);
|
||||
|
||||
// Simple per-frame dt (seconds) for animations
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if (_lastFrameTime.time_since_epoch().count() == 0)
|
||||
{
|
||||
_lastFrameTime = now;
|
||||
}
|
||||
float dt = std::chrono::duration<float>(now - _lastFrameTime).count();
|
||||
_lastFrameTime = now;
|
||||
if (dt < 0.f)
|
||||
{
|
||||
dt = 0.f;
|
||||
}
|
||||
if (dt > 0.1f)
|
||||
{
|
||||
dt = 0.1f;
|
||||
}
|
||||
_deltaTime = dt;
|
||||
|
||||
auto tagOwner = [&](RenderObject::OwnerType type, const std::string &name,
|
||||
size_t opaqueBegin, size_t transpBegin)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <core/types.h>
|
||||
#include <core/world.h>
|
||||
#include <scene/camera.h>
|
||||
#include <scene/camera/camera_rig.h>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@@ -68,6 +69,8 @@ public:
|
||||
void update_scene();
|
||||
|
||||
Camera &getMainCamera() { return mainCamera; }
|
||||
CameraRig &getCameraRig() { return cameraRig; }
|
||||
const CameraRig &getCameraRig() const { return cameraRig; }
|
||||
|
||||
WorldVec3 get_world_origin() const { return _origin_world; }
|
||||
glm::vec3 get_camera_local_position() const { return _camera_position_local; }
|
||||
@@ -227,6 +230,7 @@ private:
|
||||
EngineContext *_context = nullptr;
|
||||
|
||||
Camera mainCamera = {};
|
||||
CameraRig cameraRig{};
|
||||
GPUSceneData sceneData = {};
|
||||
DrawContext mainDrawContext;
|
||||
std::vector<PointLight> pointLights;
|
||||
|
||||
Reference in New Issue
Block a user