camera: UE Scene-view semantics — RMB fly, MMB focus-orbit, wheel dolly, F focus

Key conflict fix (UE semantics): while RMB is held the keyboard belongs
to the camera, so WASD no longer collides with the gizmo W/E/R tool
hotkeys. Editor mode gates fly movement behind RMB; game mode keeps
always-on WASD. Viewport tool hotkeys (W/E/R/T/F) are suppressed while
RMB is held.

Focus orbit (pivot): MMB drag rotates the camera around a pivot keeping
distance; first orbit picks a pivot 5m ahead on the view ray. Shift+MMB
pans in screen plane (camera and pivot move together). Mouse wheel
dollies toward/away from the pivot with exponential steps (no pivot:
move along view ray). F focuses the selected entity (frame-selection):
pivot set to its position, camera slides along the view ray; orbit
distance persists.

Window gains a polled scroll-delta channel (ConsumeScrollDelta) that
respects ImGui mouse capture; Camera::Update mutates orbit state (no
longer const).
This commit is contained in:
2026-10-03 13:02:55 +08:00
parent 9ff08651f0
commit ffe33e9967
6 changed files with 178 additions and 45 deletions
+5
View File
@@ -67,6 +67,10 @@ namespace Engine {
bool IsMouseButtonPressed(int button) const;
void GetMouseDelta(float& dx, float& dy) const;
// 轮询式滚轮增量(上行 = +1,一档 ≈1):读后清零。UI 捕获鼠标
//(面板悬停)时不累积——滚轮归 ImGui
float ConsumeScrollDelta();
// 光标捕获(自由视角):DISABLED 隐藏光标出无界增量,NORMAL 恢复。
// 重复设置同值无操作;切换后鼠标增量参考点重置(防跳变)
void SetCursorCaptured(bool captured);
@@ -92,6 +96,7 @@ namespace Engine {
mutable bool m_FirstMouse = true;
mutable double m_LastMouseX = 0.0;
mutable double m_LastMouseY = 0.0;
mutable float m_ScrollAccum = 0.0f; // 滚轮累积(ConsumeScrollDelta)
// GLFW 静态回调(通过 window user pointer 找回 this)
static void FramebufferResizeCallback(GLFWwindow* window, int width, int height);
+28 -8
View File
@@ -4,9 +4,18 @@
// Camera:独立的相机模块。
//
// 设计(Transform 单一事实源):相机的位置与朝向完全由实体上的
// TransformComponent 承载——Update 把输入直接写回 transform(末尾
// Compose),视图矩阵 = 实体世界矩阵的逆(本地 -Z 为视线方向)。
// 相机类自身不保存任何位姿状态,杜绝"位置两套来源"的脱节。
// TransformComponent 承载——Update 把输入直接写回 transform,视图
// 矩阵 = 实体世界矩阵的逆(本地 -Z 为视线方向)。相机类自身只保存
// 焦点环绕(pivot orbit)状态(pivot/距离),杜绝位姿两套来源。
//
// 交互(UE Scene view 语义):
// RMB 按住 自由视角:鼠标转向 + WASD/Space/Shift 飞行
// (编辑器模式下 RMB 按住期间键盘归相机,W/E/R 不切
// Gizmo——键位冲突的解法与 UE 一致)
// MMB 拖拽 焦点环绕:绕 pivot 转向,距离不变
// Shift+MMB 屏幕平面平移(相机与 pivot 同步移动)
// 滚轮 推拉:改环绕距离(有 pivot 时指数步进)
// F(面板侧) 焦点选中实体(ViewportPanel 调 FocusOn)
//
#ifndef HELLOVULKAN_CAMERA_H
@@ -22,9 +31,15 @@ namespace Engine {
class Camera {
public:
// 输入驱动:WASD/Space/Shift 移动 + 按住右键鼠标视角。
// 全部写回传入的 transform,有变更才 Compose。
void Update(Window& window, ECS::TransformComponent& transform, f32 deltaTime) const;
// 输入驱动。flyRequiresRMB=true(编辑器模式):移动键仅在 RMB
// 按住时生效——解决与 Gizmo W/E/R 工具热键的键位冲突(UE 语义:
// 飞行时键盘归相机)。全部写回传入的 transform
void Update(Window& window, ECS::TransformComponent& transform,
f32 deltaTime, bool flyRequiresRMB = false);
// 焦点选中(UE frame-selection):pivot 设为 point,相机沿当前
// 视线平移对准;环绕距离沿用当前值(初始 5m,滚轮可改)
void FocusOn(ECS::TransformComponent& transform, const glm::vec3& point);
// 相机视图 = 实体世界矩阵的逆(要求 transform 已 Compose)
glm::mat4 GetView(const ECS::TransformComponent& transform) const;
@@ -35,8 +50,7 @@ namespace Engine {
void SetMoveSpeed(f32 speed) { m_MoveSpeed = speed; }
void SetMouseSensitivity(f32 sens) { m_MouseSensitivity = sens; }
// 鼠标视角总开关(默认开)。交互 = 按住右键自由视角(光标捕获),
// 松开释放光标;右键落在 ImGui 面板上时归 UI 不触发相机
// 相机输入总开关(默认开)。关闭后转向/环绕/推拉/飞行全部静止
void SetMouseLookEnabled(const bool enabled) { m_MouseLookEnabled = enabled; }
bool IsMouseLookEnabled() const { return m_MouseLookEnabled; }
@@ -44,6 +58,12 @@ namespace Engine {
f32 m_MoveSpeed = 2.5f;
f32 m_MouseSensitivity = 0.1f;
bool m_MouseLookEnabled = true;
// 焦点环绕状态:pivot + 距离。首次环绕无 pivot 时自动取当前
// 视线上 m_OrbitDistance 处;FocusOn 显式设置
glm::vec3 m_OrbitPivot{0.0f};
f32 m_OrbitDistance = 5.0f;
bool m_HasPivot = false;
};
} // namespace Engine