ADD: Camera mode

This commit is contained in:
2025-12-26 18:09:11 +09:00
parent cead54c32e
commit 0ca3a5b8f1
24 changed files with 1466 additions and 133 deletions

View File

@@ -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
{

View File

@@ -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();
};

View 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);
}
}

View 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{};
};

View 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;
};

View 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;
}
}

View 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;
};

View 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;
}

View 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;
};

View 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);
}

View 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;
};

View 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);
}
}

View 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;
};

View 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;
}

View 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;
};

View File

@@ -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)
{

View File

@@ -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;