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
+9
View File
@@ -72,6 +72,8 @@ namespace Engine
if (!self->m_EventCallback) return;
if (self->m_MouseCapture && self->m_MouseCapture()) return;
self->m_ScrollAccum += static_cast<f32>(yoffset); // 轮询消费通道
Event e;
e.type = EventType::MouseScroll;
e.deltaX = static_cast<f32>(xoffset);
@@ -245,4 +247,11 @@ namespace Engine
dx = dy = 0.0f;
}
}
float Window::ConsumeScrollDelta()
{
const float d = m_ScrollAccum;
m_ScrollAccum = 0.0f;
return d;
}
} // namespace Engine
+103 -19
View File
@@ -8,16 +8,38 @@
#include <glm/gtc/matrix_transform.hpp>
#include <cmath>
#include "GLFW/glfw3.h"
namespace Engine {
void Camera::Update(Window& window, ECS::TransformComponent& transform, const f32 deltaTime) const {
// 鼠标视角(编辑器习惯):按住右键捕获光标进入自由视角,
// 松开释放光标(ImGui 面板可正常交互)。m_MouseLookEnabled 总开关
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 &&
window.IsMouseButtonPressed(GLFW_MOUSE_BUTTON_RIGHT);
if (wantLook)
const bool wantOrbit = m_MouseLookEnabled &&
window.IsMouseButtonPressed(GLFW_MOUSE_BUTTON_MIDDLE);
if (wantLook || wantOrbit)
{
if (!window.IsCursorCaptured())
{
@@ -37,6 +59,42 @@ namespace Engine {
rot.x = glm::clamp(
rot.x - mouseDY * m_MouseSensitivity, -89.0f, 89.0f);
transform.SetRotation(rot);
if (wantOrbit)
{
if (window.IsKeyPressed(GLFW_KEY_LEFT_SHIFT))
{
// ---- 屏幕平面平移:内容跟手(拖右 → 相机左移、
// 拖下 → 相机上移),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())
@@ -44,24 +102,50 @@ namespace Engine {
window.SetCursorCaptured(false);
}
// 基向量由实体旋转决定(本地 -Z 为视线方向;应用顺序 Y→X,
// 与 TransformComponent::Compose 一致;roll 不参与 FPS 相机)
const glm::vec3 rot = transform.GetRotation(); // 派生欧拉(度)
const glm::mat3 r = static_cast<glm::mat3>(
glm::rotate(glm::mat4(1.0f), glm::radians(rot.y),
glm::vec3(0, 1, 0)) *
glm::rotate(glm::mat4(1.0f), glm::radians(rot.x),
glm::vec3(1, 0, 0)));
// 基向量由(可能已更新的)实体旋转决定(本地 -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);
const f32 speed = m_MoveSpeed * deltaTime;
if (window.IsKeyPressed(GLFW_KEY_W)) { transform.SetPosition(transform.GetPosition() + forward * speed); }
if (window.IsKeyPressed(GLFW_KEY_S)) { transform.SetPosition(transform.GetPosition() - forward * speed); }
if (window.IsKeyPressed(GLFW_KEY_A)) { transform.SetPosition(transform.GetPosition() - right * speed); }
if (window.IsKeyPressed(GLFW_KEY_D)) { transform.SetPosition(transform.GetPosition() + right * speed); }
if (window.IsKeyPressed(GLFW_KEY_SPACE)) { transform.SetPosition(transform.GetPosition() + glm::vec3(0, speed, 0)); }
if (window.IsKeyPressed(GLFW_KEY_LEFT_SHIFT)) { transform.SetPosition(transform.GetPosition() - glm::vec3(0, speed, 0)); }
// ---- 飞行移动:编辑器模式仅 RMB(wantLook)时生效;游戏模式
// 常开(原行为)----
if (wantLook || !flyRequiresRMB)
{
const f32 speed = m_MoveSpeed * deltaTime;
if (window.IsKeyPressed(GLFW_KEY_W)) { transform.SetPosition(transform.GetPosition() + forward * speed); }
if (window.IsKeyPressed(GLFW_KEY_S)) { transform.SetPosition(transform.GetPosition() - forward * speed); }
if (window.IsKeyPressed(GLFW_KEY_A)) { transform.SetPosition(transform.GetPosition() - right * speed); }
if (window.IsKeyPressed(GLFW_KEY_D)) { transform.SetPosition(transform.GetPosition() + right * speed); }
if (window.IsKeyPressed(GLFW_KEY_SPACE)) { transform.SetPosition(transform.GetPosition() + glm::vec3(0, speed, 0)); }
if (window.IsKeyPressed(GLFW_KEY_LEFT_SHIFT)) { 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