ADD: Camera mode
This commit is contained in:
@@ -117,6 +117,19 @@ add_executable (vulkan_engine
|
|||||||
scene/tangent_space.cpp
|
scene/tangent_space.cpp
|
||||||
scene/camera.h
|
scene/camera.h
|
||||||
scene/camera.cpp
|
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
|
||||||
compute/vk_compute.h
|
compute/vk_compute.h
|
||||||
compute/vk_compute.cpp
|
compute/vk_compute.cpp
|
||||||
|
|||||||
@@ -1671,7 +1671,7 @@ void VulkanEngine::run()
|
|||||||
|
|
||||||
if (_sceneManager && _input)
|
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)
|
if (freeze_rendering)
|
||||||
|
|||||||
@@ -39,8 +39,8 @@
|
|||||||
#include "core/assets/ibl_manager.h"
|
#include "core/assets/ibl_manager.h"
|
||||||
#include "core/ui/imgui_system.h"
|
#include "core/ui/imgui_system.h"
|
||||||
#include "core/picking/picking_system.h"
|
#include "core/picking/picking_system.h"
|
||||||
|
#include "core/input/input_system.h"
|
||||||
|
|
||||||
class InputSystem;
|
|
||||||
class DebugDrawSystem;
|
class DebugDrawSystem;
|
||||||
|
|
||||||
struct DebugDrawDeleter
|
struct DebugDrawDeleter
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#include "render/passes/background.h"
|
#include "render/passes/background.h"
|
||||||
#include "render/passes/particles.h"
|
#include "render/passes/particles.h"
|
||||||
#include <glm/gtx/euler_angles.hpp>
|
#include <glm/gtx/euler_angles.hpp>
|
||||||
|
#include <glm/gtx/quaternion.hpp>
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
#include "render/graph/graph.h"
|
#include "render/graph/graph.h"
|
||||||
#include "core/pipeline/manager.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
|
// Texture streaming + budget UI
|
||||||
static const char *stateName(uint8_t s)
|
static const char *stateName(uint8_t s)
|
||||||
{
|
{
|
||||||
@@ -2051,6 +2259,7 @@ namespace
|
|||||||
bool show_ibl{false};
|
bool show_ibl{false};
|
||||||
bool show_postfx{false};
|
bool show_postfx{false};
|
||||||
bool show_scene{false};
|
bool show_scene{false};
|
||||||
|
bool show_camera{false};
|
||||||
bool show_async_assets{false};
|
bool show_async_assets{false};
|
||||||
bool show_textures{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::MenuItem("Window", nullptr, &g_debug_windows.show_window);
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::MenuItem("Scene", nullptr, &g_debug_windows.show_scene);
|
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("Render Graph", nullptr, &g_debug_windows.show_render_graph);
|
||||||
ImGui::MenuItem("Pipelines", nullptr, &g_debug_windows.show_pipelines);
|
ImGui::MenuItem("Pipelines", nullptr, &g_debug_windows.show_pipelines);
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
@@ -2187,6 +2397,15 @@ void vk_engine_draw_debug_ui(VulkanEngine *eng)
|
|||||||
ImGui::End();
|
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 (g_debug_windows.show_async_assets)
|
||||||
{
|
{
|
||||||
if (ImGui::Begin("Async Assets", &g_debug_windows.show_async_assets))
|
if (ImGui::Begin("Async Assets", &g_debug_windows.show_async_assets))
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "core/picking/picking_system.h"
|
#include "core/picking/picking_system.h"
|
||||||
#include "scene/vk_scene.h"
|
#include "scene/vk_scene.h"
|
||||||
#include "scene/camera.h"
|
#include "scene/camera.h"
|
||||||
|
#include "scene/camera/camera_rig.h"
|
||||||
|
|
||||||
#include <glm/gtx/matrix_decompose.hpp>
|
#include <glm/gtx/matrix_decompose.hpp>
|
||||||
#include <glm/gtx/quaternion.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);
|
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
|
// Rendering
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -304,6 +304,66 @@ struct Stats
|
|||||||
int drawCallCount{0};
|
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
|
// Main API Class
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -651,6 +711,25 @@ public:
|
|||||||
void camera_look_at(const glm::vec3& target);
|
void camera_look_at(const glm::vec3& target);
|
||||||
void camera_look_at(const glm::dvec3& 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
|
// Rendering
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ namespace GameRuntime
|
|||||||
// --- Camera input (if not captured by UI) ---
|
// --- Camera input (if not captured by UI) ---
|
||||||
if (_renderer->_sceneManager && input)
|
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 ---
|
// --- Throttle when minimized ---
|
||||||
|
|||||||
@@ -1,102 +1,6 @@
|
|||||||
#include "camera.h"
|
#include "camera.h"
|
||||||
#include <glm/gtx/transform.hpp>
|
#include <glm/gtx/transform.hpp>
|
||||||
#include <glm/gtx/quaternion.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
|
glm::mat4 Camera::getViewMatrix(const glm::vec3 &position_local) const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,29 +2,15 @@
|
|||||||
|
|
||||||
#include <core/types.h>
|
#include <core/types.h>
|
||||||
|
|
||||||
#include <core/input/input_system.h>
|
|
||||||
|
|
||||||
#include "glm/vec3.hpp"
|
|
||||||
|
|
||||||
class Camera {
|
class Camera {
|
||||||
public:
|
public:
|
||||||
glm::vec3 velocity{0.0f, 0.0f, 0.0f};
|
|
||||||
glm::dvec3 position_world{0.0, 0.0, 0.0};
|
glm::dvec3 position_world{0.0, 0.0, 0.0};
|
||||||
// Orientation stored as a quaternion (local -> world).
|
// Orientation stored as a quaternion (local -> world).
|
||||||
glm::quat orientation { 1.0f, 0.0f, 0.0f, 0.0f };
|
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
|
// Field of view in degrees for projection
|
||||||
float fovDegrees { 50.f };
|
float fovDegrees { 50.f };
|
||||||
|
|
||||||
glm::mat4 getViewMatrix(const glm::vec3 &position_local) const;
|
glm::mat4 getViewMatrix(const glm::vec3 &position_local) const;
|
||||||
glm::mat4 getRotationMatrix() 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;
|
_context = context;
|
||||||
|
|
||||||
mainCamera.velocity = glm::vec3(0.f);
|
|
||||||
mainCamera.position_world = WorldVec3(30.0, 0.0, 85.0);
|
mainCamera.position_world = WorldVec3(30.0, 0.0, 85.0);
|
||||||
mainCamera.orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f);
|
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.sunlightDirection = glm::vec4(-0.2f, -1.0f, -0.3f, 1.0f);
|
||||||
sceneData.sunlightColor = glm::vec4(1.0f, 1.0f, 1.0f, 3.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);
|
_camera_position_local = world_to_local(mainCamera.position_world, _origin_world);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +152,25 @@ void SceneManager::update_scene()
|
|||||||
mainDrawContext.nextID = 1;
|
mainDrawContext.nextID = 1;
|
||||||
mainDrawContext.gltfNodeLocalOverrides = nullptr;
|
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
|
// Floating origin: keep render-local coordinates near (0,0,0) by shifting the origin
|
||||||
// when the camera drifts too far in world space.
|
// 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);
|
_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,
|
auto tagOwner = [&](RenderObject::OwnerType type, const std::string &name,
|
||||||
size_t opaqueBegin, size_t transpBegin)
|
size_t opaqueBegin, size_t transpBegin)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include <core/types.h>
|
#include <core/types.h>
|
||||||
#include <core/world.h>
|
#include <core/world.h>
|
||||||
#include <scene/camera.h>
|
#include <scene/camera.h>
|
||||||
|
#include <scene/camera/camera_rig.h>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
@@ -68,6 +69,8 @@ public:
|
|||||||
void update_scene();
|
void update_scene();
|
||||||
|
|
||||||
Camera &getMainCamera() { return mainCamera; }
|
Camera &getMainCamera() { return mainCamera; }
|
||||||
|
CameraRig &getCameraRig() { return cameraRig; }
|
||||||
|
const CameraRig &getCameraRig() const { return cameraRig; }
|
||||||
|
|
||||||
WorldVec3 get_world_origin() const { return _origin_world; }
|
WorldVec3 get_world_origin() const { return _origin_world; }
|
||||||
glm::vec3 get_camera_local_position() const { return _camera_position_local; }
|
glm::vec3 get_camera_local_position() const { return _camera_position_local; }
|
||||||
@@ -227,6 +230,7 @@ private:
|
|||||||
EngineContext *_context = nullptr;
|
EngineContext *_context = nullptr;
|
||||||
|
|
||||||
Camera mainCamera = {};
|
Camera mainCamera = {};
|
||||||
|
CameraRig cameraRig{};
|
||||||
GPUSceneData sceneData = {};
|
GPUSceneData sceneData = {};
|
||||||
DrawContext mainDrawContext;
|
DrawContext mainDrawContext;
|
||||||
std::vector<PointLight> pointLights;
|
std::vector<PointLight> pointLights;
|
||||||
|
|||||||
Reference in New Issue
Block a user