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

@@ -117,6 +117,19 @@ add_executable (vulkan_engine
scene/tangent_space.cpp
scene/camera.h
scene/camera.cpp
scene/camera/icamera_mode.h
scene/camera/camera_rig.h
scene/camera/camera_rig.cpp
scene/camera/mode_free.h
scene/camera/mode_free.cpp
scene/camera/mode_orbit.h
scene/camera/mode_orbit.cpp
scene/camera/mode_follow.h
scene/camera/mode_follow.cpp
scene/camera/mode_chase.h
scene/camera/mode_chase.cpp
scene/camera/mode_fixed.h
scene/camera/mode_fixed.cpp
# compute
compute/vk_compute.h
compute/vk_compute.cpp

View File

@@ -1671,7 +1671,7 @@ void VulkanEngine::run()
if (_sceneManager && _input)
{
_sceneManager->getMainCamera().process_input(*_input, ui_capture_keyboard, ui_capture_mouse);
_sceneManager->getCameraRig().process_input(*_input, ui_capture_keyboard, ui_capture_mouse);
}
if (freeze_rendering)

View File

@@ -39,8 +39,8 @@
#include "core/assets/ibl_manager.h"
#include "core/ui/imgui_system.h"
#include "core/picking/picking_system.h"
#include "core/input/input_system.h"
class InputSystem;
class DebugDrawSystem;
struct DebugDrawDeleter

View File

@@ -21,6 +21,7 @@
#include "render/passes/background.h"
#include "render/passes/particles.h"
#include <glm/gtx/euler_angles.hpp>
#include <glm/gtx/quaternion.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "render/graph/graph.h"
#include "core/pipeline/manager.h"
@@ -781,6 +782,213 @@ namespace
}
}
static void ui_camera(VulkanEngine *eng)
{
if (!eng || !eng->_sceneManager)
{
ImGui::TextUnformatted("SceneManager not available");
return;
}
SceneManager *sceneMgr = eng->_sceneManager.get();
CameraRig &rig = sceneMgr->getCameraRig();
Camera &cam = sceneMgr->getMainCamera();
// Mode switch
static const char *k_mode_names[] = {"Free", "Orbit", "Follow", "Chase", "Fixed"};
int mode = static_cast<int>(rig.mode());
if (ImGui::Combo("Mode", &mode, k_mode_names, IM_ARRAYSIZE(k_mode_names)))
{
rig.set_mode(static_cast<CameraMode>(mode), *sceneMgr, cam);
if (eng->_input)
{
eng->_input->set_cursor_mode(CursorMode::Normal);
}
}
ImGui::Text("Active mode: %s", rig.mode_name());
ImGui::Separator();
// Camera state (world)
double pos[3] = {cam.position_world.x, cam.position_world.y, cam.position_world.z};
if (ImGui::InputScalarN("Position (world)", ImGuiDataType_Double, pos, 3, nullptr, nullptr, "%.3f"))
{
cam.position_world = WorldVec3(pos[0], pos[1], pos[2]);
}
float fov = cam.fovDegrees;
if (ImGui::SliderFloat("FOV (deg)", &fov, 30.0f, 110.0f))
{
cam.fovDegrees = fov;
}
WorldVec3 origin = sceneMgr->get_world_origin();
glm::vec3 camLocal = sceneMgr->get_camera_local_position();
ImGui::Text("Origin (world): (%.3f, %.3f, %.3f)", origin.x, origin.y, origin.z);
ImGui::Text("Camera (local): (%.3f, %.3f, %.3f)", camLocal.x, camLocal.y, camLocal.z);
auto target_from_last_pick = [&](CameraTarget &target) -> bool {
PickingSystem *picking = eng->picking();
if (!picking) return false;
const auto &pick = picking->last_pick();
if (!pick.valid) return false;
if (pick.ownerType == RenderObject::OwnerType::MeshInstance)
{
target.type = CameraTargetType::MeshInstance;
target.name = pick.ownerName;
}
else if (pick.ownerType == RenderObject::OwnerType::GLTFInstance)
{
target.type = CameraTargetType::GLTFInstance;
target.name = pick.ownerName;
}
else
{
target.type = CameraTargetType::WorldPoint;
target.world_point = pick.worldPos;
target.name.clear();
}
return true;
};
auto draw_target = [&](const char *id, CameraTarget &target, char *name_buf, size_t name_buf_size) {
ImGui::PushID(id);
static const char *k_target_types[] = {"None", "WorldPoint", "MeshInstance", "GLTFInstance"};
int type = static_cast<int>(target.type);
if (ImGui::Combo("Target type", &type, k_target_types, IM_ARRAYSIZE(k_target_types)))
{
target.type = static_cast<CameraTargetType>(type);
if (target.type != CameraTargetType::MeshInstance && target.type != CameraTargetType::GLTFInstance)
{
target.name.clear();
if (name_buf_size > 0)
{
name_buf[0] = '\0';
}
}
}
if (target.type == CameraTargetType::WorldPoint)
{
double p[3] = {target.world_point.x, target.world_point.y, target.world_point.z};
if (ImGui::InputScalarN("World point", ImGuiDataType_Double, p, 3, nullptr, nullptr, "%.3f"))
{
target.world_point = WorldVec3(p[0], p[1], p[2]);
}
}
else if (target.type == CameraTargetType::MeshInstance || target.type == CameraTargetType::GLTFInstance)
{
if (std::strncmp(name_buf, target.name.c_str(), name_buf_size) != 0)
{
std::snprintf(name_buf, name_buf_size, "%s", target.name.c_str());
}
ImGui::InputText("Target name", name_buf, name_buf_size);
target.name = name_buf;
}
WorldVec3 tpos{};
glm::quat trot{};
bool ok = rig.resolve_target(*sceneMgr, target, tpos, trot);
ImGui::Text("Resolved: %s", ok ? "yes" : "no");
if (ok)
{
ImGui::Text("Target world: (%.3f, %.3f, %.3f)", tpos.x, tpos.y, tpos.z);
}
ImGui::PopID();
};
// Free
if (ImGui::CollapsingHeader("Free", ImGuiTreeNodeFlags_DefaultOpen))
{
auto &s = rig.free_settings();
ImGui::InputFloat("Move speed (u/s)", &s.move_speed);
s.move_speed = std::clamp(s.move_speed, 0.06f, 300.0f);
ImGui::InputFloat("Look sensitivity", &s.look_sensitivity);
ImGui::InputFloat("Roll speed (rad/s)", &s.roll_speed);
ImGui::TextUnformatted("Roll keys: Q/E");
}
// Orbit
if (ImGui::CollapsingHeader("Orbit"))
{
auto &s = rig.orbit_settings();
static char orbitName[128] = "";
draw_target("orbit_target", s.target, orbitName, IM_ARRAYSIZE(orbitName));
if (ImGui::Button("Orbit target = Last Pick"))
{
target_from_last_pick(s.target);
}
ImGui::InputDouble("Distance", &s.distance, 0.1, 1.0, "%.3f");
s.distance = std::clamp(s.distance, 0.2, 100000.0);
float yawDeg = glm::degrees(s.yaw);
float pitchDeg = glm::degrees(s.pitch);
if (ImGui::SliderFloat("Yaw (deg)", &yawDeg, -180.0f, 180.0f))
{
s.yaw = glm::radians(yawDeg);
}
if (ImGui::SliderFloat("Pitch (deg)", &pitchDeg, -89.0f, 89.0f))
{
s.pitch = glm::radians(pitchDeg);
}
ImGui::InputFloat("Look sensitivity##orbit", &s.look_sensitivity);
}
// Follow
if (ImGui::CollapsingHeader("Follow"))
{
auto &s = rig.follow_settings();
static char followName[128] = "";
draw_target("follow_target", s.target, followName, IM_ARRAYSIZE(followName));
if (ImGui::Button("Follow target = Last Pick"))
{
target_from_last_pick(s.target);
}
ImGui::InputFloat3("Position offset (local)", &s.position_offset_local.x);
glm::vec3 rotDeg = glm::degrees(glm::eulerAngles(s.rotation_offset));
float r[3] = {rotDeg.x, rotDeg.y, rotDeg.z};
if (ImGui::InputFloat3("Rotation offset (deg XYZ)", r))
{
glm::mat4 R = glm::eulerAngleXYZ(glm::radians(r[0]), glm::radians(r[1]), glm::radians(r[2]));
s.rotation_offset = glm::quat_cast(R);
}
if (ImGui::Button("Reset rotation offset"))
{
s.rotation_offset = glm::quat(1.0f, 0.0f, 0.0f, 0.0f);
}
}
// Chase
if (ImGui::CollapsingHeader("Chase"))
{
auto &s = rig.chase_settings();
static char chaseName[128] = "";
draw_target("chase_target", s.target, chaseName, IM_ARRAYSIZE(chaseName));
if (ImGui::Button("Chase target = Last Pick"))
{
target_from_last_pick(s.target);
}
ImGui::InputFloat3("Position offset (local)##chase", &s.position_offset_local.x);
glm::vec3 rotDeg = glm::degrees(glm::eulerAngles(s.rotation_offset));
float r[3] = {rotDeg.x, rotDeg.y, rotDeg.z};
if (ImGui::InputFloat3("Rotation offset (deg XYZ)##chase", r))
{
glm::mat4 R = glm::eulerAngleXYZ(glm::radians(r[0]), glm::radians(r[1]), glm::radians(r[2]));
s.rotation_offset = glm::quat_cast(R);
}
ImGui::SliderFloat("Position lag (1/s)", &s.position_lag, 0.0f, 30.0f);
ImGui::SliderFloat("Rotation lag (1/s)", &s.rotation_lag, 0.0f, 30.0f);
}
// Fixed
if (ImGui::CollapsingHeader("Fixed"))
{
ImGui::TextUnformatted("Fixed mode does not modify the camera automatically.");
}
}
// Texture streaming + budget UI
static const char *stateName(uint8_t s)
{
@@ -2051,6 +2259,7 @@ namespace
bool show_ibl{false};
bool show_postfx{false};
bool show_scene{false};
bool show_camera{false};
bool show_async_assets{false};
bool show_textures{false};
};
@@ -2072,6 +2281,7 @@ void vk_engine_draw_debug_ui(VulkanEngine *eng)
ImGui::MenuItem("Window", nullptr, &g_debug_windows.show_window);
ImGui::Separator();
ImGui::MenuItem("Scene", nullptr, &g_debug_windows.show_scene);
ImGui::MenuItem("Camera", nullptr, &g_debug_windows.show_camera);
ImGui::MenuItem("Render Graph", nullptr, &g_debug_windows.show_render_graph);
ImGui::MenuItem("Pipelines", nullptr, &g_debug_windows.show_pipelines);
ImGui::Separator();
@@ -2187,6 +2397,15 @@ void vk_engine_draw_debug_ui(VulkanEngine *eng)
ImGui::End();
}
if (g_debug_windows.show_camera)
{
if (ImGui::Begin("Camera", &g_debug_windows.show_camera))
{
ui_camera(eng);
}
ImGui::End();
}
if (g_debug_windows.show_async_assets)
{
if (ImGui::Begin("Async Assets", &g_debug_windows.show_async_assets))

View File

@@ -13,6 +13,7 @@
#include "core/picking/picking_system.h"
#include "scene/vk_scene.h"
#include "scene/camera.h"
#include "scene/camera/camera_rig.h"
#include <glm/gtx/matrix_decompose.hpp>
#include <glm/gtx/quaternion.hpp>
@@ -1579,6 +1580,268 @@ void Engine::camera_look_at(const glm::dvec3& target)
cam.orientation = glm::quat_cast(rot);
}
namespace
{
::CameraMode to_internal_camera_mode(GameAPI::CameraMode mode)
{
switch (mode)
{
case GameAPI::CameraMode::Free: return ::CameraMode::Free;
case GameAPI::CameraMode::Orbit: return ::CameraMode::Orbit;
case GameAPI::CameraMode::Follow: return ::CameraMode::Follow;
case GameAPI::CameraMode::Chase: return ::CameraMode::Chase;
case GameAPI::CameraMode::Fixed: return ::CameraMode::Fixed;
default: return ::CameraMode::Free;
}
}
GameAPI::CameraMode to_api_camera_mode(::CameraMode mode)
{
switch (mode)
{
case ::CameraMode::Free: return GameAPI::CameraMode::Free;
case ::CameraMode::Orbit: return GameAPI::CameraMode::Orbit;
case ::CameraMode::Follow: return GameAPI::CameraMode::Follow;
case ::CameraMode::Chase: return GameAPI::CameraMode::Chase;
case ::CameraMode::Fixed: return GameAPI::CameraMode::Fixed;
default: return GameAPI::CameraMode::Free;
}
}
::CameraTargetType to_internal_target_type(GameAPI::CameraTargetType type)
{
switch (type)
{
case GameAPI::CameraTargetType::None: return ::CameraTargetType::None;
case GameAPI::CameraTargetType::WorldPoint: return ::CameraTargetType::WorldPoint;
case GameAPI::CameraTargetType::MeshInstance: return ::CameraTargetType::MeshInstance;
case GameAPI::CameraTargetType::GLTFInstance: return ::CameraTargetType::GLTFInstance;
default: return ::CameraTargetType::None;
}
}
GameAPI::CameraTargetType to_api_target_type(::CameraTargetType type)
{
switch (type)
{
case ::CameraTargetType::None: return GameAPI::CameraTargetType::None;
case ::CameraTargetType::WorldPoint: return GameAPI::CameraTargetType::WorldPoint;
case ::CameraTargetType::MeshInstance: return GameAPI::CameraTargetType::MeshInstance;
case ::CameraTargetType::GLTFInstance: return GameAPI::CameraTargetType::GLTFInstance;
default: return GameAPI::CameraTargetType::None;
}
}
::CameraTarget to_internal_target(const GameAPI::CameraTarget &target)
{
::CameraTarget t;
t.type = to_internal_target_type(target.type);
t.name = target.name;
t.world_point = WorldVec3(target.worldPoint);
return t;
}
GameAPI::CameraTarget to_api_target(const ::CameraTarget &target)
{
GameAPI::CameraTarget t;
t.type = to_api_target_type(target.type);
t.name = target.name;
t.worldPoint = glm::dvec3(target.world_point);
return t;
}
} // namespace
void Engine::set_camera_mode(CameraMode mode)
{
if (!_engine || !_engine->_sceneManager)
{
return;
}
SceneManager *scene = _engine->_sceneManager.get();
Camera &cam = scene->getMainCamera();
CameraRig &rig = scene->getCameraRig();
if (_engine->_input)
{
_engine->_input->set_cursor_mode(CursorMode::Normal);
}
rig.set_mode(to_internal_camera_mode(mode), *scene, cam);
}
CameraMode Engine::get_camera_mode() const
{
if (!_engine || !_engine->_sceneManager)
{
return CameraMode::Free;
}
return to_api_camera_mode(_engine->_sceneManager->getCameraRig().mode());
}
void Engine::set_free_camera_settings(const FreeCameraSettings& settings)
{
if (!_engine || !_engine->_sceneManager)
{
return;
}
::FreeCameraSettings &s = _engine->_sceneManager->getCameraRig().free_settings();
s.move_speed = settings.moveSpeed;
s.look_sensitivity = settings.lookSensitivity;
s.roll_speed = settings.rollSpeed;
}
FreeCameraSettings Engine::get_free_camera_settings() const
{
FreeCameraSettings out{};
if (!_engine || !_engine->_sceneManager)
{
return out;
}
const ::FreeCameraSettings &s = _engine->_sceneManager->getCameraRig().free_settings();
out.moveSpeed = s.move_speed;
out.lookSensitivity = s.look_sensitivity;
out.rollSpeed = s.roll_speed;
return out;
}
void Engine::set_orbit_camera_settings(const OrbitCameraSettings& settings)
{
if (!_engine || !_engine->_sceneManager)
{
return;
}
::OrbitCameraSettings &s = _engine->_sceneManager->getCameraRig().orbit_settings();
s.target = to_internal_target(settings.target);
s.distance = settings.distance;
s.yaw = settings.yaw;
s.pitch = settings.pitch;
s.look_sensitivity = settings.lookSensitivity;
}
OrbitCameraSettings Engine::get_orbit_camera_settings() const
{
OrbitCameraSettings out{};
if (!_engine || !_engine->_sceneManager)
{
return out;
}
const ::OrbitCameraSettings &s = _engine->_sceneManager->getCameraRig().orbit_settings();
out.target = to_api_target(s.target);
out.distance = s.distance;
out.yaw = s.yaw;
out.pitch = s.pitch;
out.lookSensitivity = s.look_sensitivity;
return out;
}
void Engine::set_follow_camera_settings(const FollowCameraSettings& settings)
{
if (!_engine || !_engine->_sceneManager)
{
return;
}
::FollowCameraSettings &s = _engine->_sceneManager->getCameraRig().follow_settings();
s.target = to_internal_target(settings.target);
s.position_offset_local = settings.positionOffsetLocal;
s.rotation_offset = settings.rotationOffset;
}
FollowCameraSettings Engine::get_follow_camera_settings() const
{
FollowCameraSettings out{};
if (!_engine || !_engine->_sceneManager)
{
return out;
}
const ::FollowCameraSettings &s = _engine->_sceneManager->getCameraRig().follow_settings();
out.target = to_api_target(s.target);
out.positionOffsetLocal = s.position_offset_local;
out.rotationOffset = s.rotation_offset;
return out;
}
void Engine::set_chase_camera_settings(const ChaseCameraSettings& settings)
{
if (!_engine || !_engine->_sceneManager)
{
return;
}
::ChaseCameraSettings &s = _engine->_sceneManager->getCameraRig().chase_settings();
s.target = to_internal_target(settings.target);
s.position_offset_local = settings.positionOffsetLocal;
s.rotation_offset = settings.rotationOffset;
s.position_lag = settings.positionLag;
s.rotation_lag = settings.rotationLag;
}
ChaseCameraSettings Engine::get_chase_camera_settings() const
{
ChaseCameraSettings out{};
if (!_engine || !_engine->_sceneManager)
{
return out;
}
const ::ChaseCameraSettings &s = _engine->_sceneManager->getCameraRig().chase_settings();
out.target = to_api_target(s.target);
out.positionOffsetLocal = s.position_offset_local;
out.rotationOffset = s.rotation_offset;
out.positionLag = s.position_lag;
out.rotationLag = s.rotation_lag;
return out;
}
bool Engine::set_camera_target_from_last_pick()
{
if (!_engine || !_engine->_sceneManager)
{
return false;
}
const PickingSystem *picking = _engine->picking();
if (!picking)
{
return false;
}
const auto &pick = picking->last_pick();
if (!pick.valid)
{
return false;
}
::CameraTarget t;
if (pick.ownerType == RenderObject::OwnerType::MeshInstance)
{
t.type = ::CameraTargetType::MeshInstance;
t.name = pick.ownerName;
}
else if (pick.ownerType == RenderObject::OwnerType::GLTFInstance)
{
t.type = ::CameraTargetType::GLTFInstance;
t.name = pick.ownerName;
}
else
{
t.type = ::CameraTargetType::WorldPoint;
t.world_point = pick.worldPos;
}
CameraRig &rig = _engine->_sceneManager->getCameraRig();
rig.orbit_settings().target = t;
rig.follow_settings().target = t;
rig.chase_settings().target = t;
return true;
}
// ----------------------------------------------------------------------------
// Rendering
// ----------------------------------------------------------------------------

View File

@@ -304,6 +304,66 @@ struct Stats
int drawCallCount{0};
};
// ============================================================================
// Camera Rig Types
// ============================================================================
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{};
glm::dvec3 worldPoint{0.0, 0.0, 0.0};
};
struct FreeCameraSettings
{
float moveSpeed{1.8f}; // world units / second
float lookSensitivity{0.0020f};
float rollSpeed{1.0f}; // radians / second
};
struct OrbitCameraSettings
{
CameraTarget target{};
double distance{10.0};
float yaw{0.0f}; // radians
float pitch{0.0f}; // radians
float lookSensitivity{0.0020f};
};
struct FollowCameraSettings
{
CameraTarget target{};
glm::vec3 positionOffsetLocal{0.0f, 2.0f, 6.0f};
glm::quat rotationOffset{1.0f, 0.0f, 0.0f, 0.0f};
};
struct ChaseCameraSettings
{
CameraTarget target{};
glm::vec3 positionOffsetLocal{0.0f, 2.0f, 6.0f};
glm::quat rotationOffset{1.0f, 0.0f, 0.0f, 0.0f};
float positionLag{8.0f}; // smoothing rate (1/sec), higher = snappier
float rotationLag{10.0f}; // smoothing rate (1/sec)
};
// ============================================================================
// Main API Class
// ============================================================================
@@ -651,6 +711,25 @@ public:
void camera_look_at(const glm::vec3& target);
void camera_look_at(const glm::dvec3& target);
// Camera mode and per-mode settings
void set_camera_mode(CameraMode mode);
CameraMode get_camera_mode() const;
void set_free_camera_settings(const FreeCameraSettings& settings);
FreeCameraSettings get_free_camera_settings() const;
void set_orbit_camera_settings(const OrbitCameraSettings& settings);
OrbitCameraSettings get_orbit_camera_settings() const;
void set_follow_camera_settings(const FollowCameraSettings& settings);
FollowCameraSettings get_follow_camera_settings() const;
void set_chase_camera_settings(const ChaseCameraSettings& settings);
ChaseCameraSettings get_chase_camera_settings() const;
// Convenience: set Orbit/Follow/Chase target from the engine's last pick.
bool set_camera_target_from_last_pick();
// ------------------------------------------------------------------------
// Rendering
// ------------------------------------------------------------------------

View File

@@ -128,7 +128,7 @@ namespace GameRuntime
// --- Camera input (if not captured by UI) ---
if (_renderer->_sceneManager && input)
{
_renderer->_sceneManager->getMainCamera().process_input(*input, ui_capture_keyboard, ui_capture_mouse);
_renderer->_sceneManager->getCameraRig().process_input(*input, ui_capture_keyboard, ui_capture_mouse);
}
// --- Throttle when minimized ---

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;