Files
HelloVulkan/Engine/src/Renderer/Camera.cpp
T
atdunbg 641b255cb2 Unify input behind Engine::Input facade; fix Ctrl+P and Pause button
New Engine/Core/Input.h/.cpp: static input facade with a Key/MouseButton
enum (values map to GLFW key codes internally) so scripts and game logic
no longer touch GLFW directly - window backend becomes swappable and the
facade is the surface to expose when embedding scripting later. Wired up
in Application's constructor; Camera (editor camera WASD/RMB/MMB), the
WASDMove script and SandboxApp key handling now query Input only.

Ctrl+P hotkey fix: the !m_Playing guard made the hotkey dead exactly
when it was needed - after pressing Play. Now toggles both ways.

Toolbar Pause button now reads "Resume" while paused (tooltip follows).
2026-10-05 11:34:27 +08:00

179 lines
7.9 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// Created by atdunbg on 2026/9/22.
//
#include <Engine/Renderer/Camera.h>
#include <Engine/Core/Window.h>
#include <Engine/ECS/Components.h>
#include <glm/gtc/matrix_transform.hpp>
#include <cmath>
#include "Engine/Core/Input.h" // 键查询走统一输入门面(不依赖 GLFW)
namespace Engine {
namespace {
// 欧拉(度)→ 旋转矩阵:应用序 Y→X,与 TransformComponent::
// Compose 一致(roll 不参与 FPS/编辑器相机)
glm::mat3 EulerRotMat(const glm::vec3& rotDeg)
{
return static_cast<glm::mat3>(
glm::rotate(glm::mat4(1.0f), glm::radians(rotDeg.y),
glm::vec3(0, 1, 0)) *
glm::rotate(glm::mat4(1.0f), glm::radians(rotDeg.x),
glm::vec3(1, 0, 0)));
}
}
void Camera::Update(Window& window, ECS::TransformComponent& transform,
const f32 deltaTime, const bool flyRequiresRMB)
{
// ---- 输入模式(UE Scene view 语义):RMB=自由视角(鼠标转向
// + 飞行),MMB=焦点环绕(Shift+MMB=平移),滚轮=推拉。三种
// 光标型交互互斥共用捕获;编辑器模式 flyRequiresRMB=true 时
// 移动键仅 RMB 按住生效(否则与 Gizmo W/E/R 热键冲突)----
const bool wantLook = m_MouseLookEnabled &&
Input::IsMouseButtonDown(Input::MouseButton::Right);
const bool wantOrbit = m_MouseLookEnabled &&
Input::IsMouseButtonDown(Input::MouseButton::Middle);
if (wantLook || wantOrbit)
{
if (!window.IsCursorCaptured())
{
window.SetCursorCaptured(true);
}
f32 mouseDX = 0, mouseDY = 0;
window.GetMouseDelta(mouseDX, mouseDY);
if (mouseDX != 0 || mouseDY != 0)
{
glm::vec3 rot = transform.GetRotation();
// GetRotation 是 0-360 显示域(UE Details 约定);pitch 的
// clamp 需要连续域,wrap 回 [-180,180)(yaw 同步处理,
// 跨 ±180 继续累减不打折返)
for (int i = 0; i < 2; ++i) {
rot[i] = std::fmod(rot[i], 360.0f);
if (rot[i] >= 180.0f) rot[i] -= 360.0f;
if (rot[i] < -180.0f) rot[i] += 360.0f;
}
// 右手系绕 Y 正角度 = 向左转,鼠标右移(DX>0)应看向右
// → yaw 取负。pitch 正角度 = 抬头,鼠标下移(DY>0)低头
// → rot.x 直接用负号,两者符号已核对一致
rot.y -= mouseDX * m_MouseSensitivity;
rot.x = glm::clamp(
rot.x - mouseDY * m_MouseSensitivity, -89.0f, 89.0f);
transform.SetRotation(rot);
if (wantOrbit)
{
if (Input::IsKeyDown(Input::Key::LeftShift))
{
// ---- 屏幕平面平移:内容跟手(拖右 → 相机左移、
// 拖下 → 相机上移),pivot 同步平移保持环绕点的
// 屏幕相对位置 ----
const glm::mat3 r = EulerRotMat(rot);
const glm::vec3 delta =
(-r * glm::vec3(1, 0, 0)) * mouseDX +
(r * glm::vec3(0, 1, 0)) * mouseDY;
const f32 k = m_OrbitDistance * 0.002f; // m/px
transform.SetPosition(transform.GetPosition() + delta * k);
if (m_HasPivot) m_OrbitPivot += delta * k;
}
else
{
// ---- 焦点环绕:转向后保持到 pivot 的距离。首次
// 环绕无 pivot:取当前视线上 m_OrbitDistance 处
if (!m_HasPivot)
{
m_OrbitPivot = transform.GetPosition() +
EulerRotMat(transform.GetRotation()) *
glm::vec3(0, 0, -1) *
m_OrbitDistance;
m_HasPivot = true;
}
m_OrbitDistance = glm::length(
transform.GetPosition() - m_OrbitPivot);
const glm::vec3 orbitFwd =
EulerRotMat(rot) * glm::vec3(0, 0, -1);
transform.SetPosition(m_OrbitPivot -
orbitFwd * m_OrbitDistance);
}
}
}
}
else if (window.IsCursorCaptured())
{
window.SetCursorCaptured(false);
}
// 基向量由(可能已更新的)实体旋转决定(本地 -Z 为视线方向)
const glm::mat3 r = EulerRotMat(transform.GetRotation());
const glm::vec3 forward = r * glm::vec3(0, 0, -1);
const glm::vec3 right = r * glm::vec3(1, 0, 0);
// ---- 飞行移动:编辑器模式仅 RMB(wantLook)时生效;游戏模式
// 常开(原行为)----
if (wantLook || !flyRequiresRMB)
{
const f32 speed = m_MoveSpeed * deltaTime;
if (Input::IsKeyDown(Input::Key::W)) { transform.SetPosition(transform.GetPosition() + forward * speed); }
if (Input::IsKeyDown(Input::Key::S)) { transform.SetPosition(transform.GetPosition() - forward * speed); }
if (Input::IsKeyDown(Input::Key::A)) { transform.SetPosition(transform.GetPosition() - right * speed); }
if (Input::IsKeyDown(Input::Key::D)) { transform.SetPosition(transform.GetPosition() + right * speed); }
if (Input::IsKeyDown(Input::Key::Space)) { transform.SetPosition(transform.GetPosition() + glm::vec3(0, speed, 0)); }
if (Input::IsKeyDown(Input::Key::LeftShift)) { transform.SetPosition(transform.GetPosition() - glm::vec3(0, speed, 0)); }
}
// ---- 滚轮推拉:有 pivot 时改环绕距离(指数步进,近处精细/
// 远处大步);无 pivot 时沿视线平移 ----
const f32 wheel = window.ConsumeScrollDelta();
if (m_MouseLookEnabled && wheel != 0.0f)
{
if (m_HasPivot)
{
m_OrbitDistance = glm::clamp(
m_OrbitDistance * std::pow(0.9f, wheel), 0.5f, 500.0f);
transform.SetPosition(m_OrbitPivot - forward * m_OrbitDistance);
}
else
{
transform.SetPosition(transform.GetPosition() +
forward * wheel * m_MoveSpeed * deltaTime);
}
}
}
void Camera::FocusOn(ECS::TransformComponent& transform, const glm::vec3& point)
{
const glm::vec3 fwd =
EulerRotMat(transform.GetRotation()) * glm::vec3(0, 0, -1);
m_OrbitPivot = point;
m_HasPivot = true;
transform.SetPosition(point - fwd * m_OrbitDistance);
}
glm::mat4 Camera::GetView(const ECS::TransformComponent& t) const
{
// 相机作为实体:视图 = 世界矩阵的逆(无重复位姿状态)
return glm::inverse(t.World());
}
glm::mat4 Camera::GetProjection(const f32 aspect, const f32 nearZ, const f32 farZ) const
{
auto proj = glm::perspective(glm::radians(45.0f), aspect, nearZ, farZ);
proj[1][1] *= -1.0f; // Vulkan Y 翻转
// Vulkan clip 深度 [0,1]:z' = 0.5*z_clip + 0.5*w_clip。
// perspective 的 w_clip = -z_eye,偏移折算进 z 系数
//(注意:与 ortho 的公式不同,ortho 的 w=1 才能直接加到 [3][2])
proj[2][2] = proj[2][2] * 0.5f - 0.5f;
proj[3][2] *= 0.5f;
return proj;
}
} // namespace Engine