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:
@@ -125,26 +125,38 @@ std::optional<entt::entity> ViewportPanel::Draw(const EditorContext &ctx,
|
||||
else size.y = avail.x / aspect;
|
||||
ImGui::Image((ImTextureID) (intptr_t) sceneTex, size);
|
||||
|
||||
// ---- W/E/R 切换 Gizmo 模式(UE 语义:W 平移 / E 旋转 /
|
||||
// R 缩放),T 切换 local/world 操纵空间。仅 Viewport 持焦时
|
||||
// 生效——输入框打字(WantTextInput)由 BlockingKeyboard 拦
|
||||
// 游戏侧,但 ImGui 自身仍收键,故必须限定持焦面板,避免在
|
||||
// Inspector 拖数值时误切 ----
|
||||
if (out.focused && !ImGui::GetIO().WantTextInput) {
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_W)) gizmo.op = ImGuizmo::TRANSLATE;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_E)) gizmo.op = ImGuizmo::ROTATE;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_R)) gizmo.op = ImGuizmo::SCALE;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_T)) gizmo.localSpace = !gizmo.localSpace;
|
||||
}
|
||||
|
||||
// ---- Gizmo 操纵杆(ImGui drawlist 叠加,无 GPU pass)----
|
||||
// 选中实体 + 有 Transform 才出杆;矩阵分解直接写回
|
||||
// TransformComponent(与 Inspector 同源,下一帧数值自动跟随)
|
||||
// ---- 工具热键(UE 语义):W/E/R 切 Gizmo 模式、T 切
|
||||
// local/world、F 焦点选中实体。仅 Viewport 持焦时生效——
|
||||
// 输入框打字(WantTextInput)由 BlockingKeyboard 拦游戏侧,
|
||||
// 但 ImGui 自身仍收键,故必须限定持焦面板。RMB 按住 = 相机
|
||||
// 飞行模式(键盘归相机),工具热键全部抑制——W 前进与
|
||||
// W 平移 Gizmo 的键位冲突就此解耦 ----
|
||||
auto ® = ctx.scene->GetRegistry();
|
||||
ECS::TransformComponent *tr =
|
||||
selected != entt::null
|
||||
? reg.try_get<ECS::TransformComponent>(selected)
|
||||
: nullptr;
|
||||
if (out.focused && !ImGui::GetIO().WantTextInput &&
|
||||
!ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_W)) gizmo.op = ImGuizmo::TRANSLATE;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_E)) gizmo.op = ImGuizmo::ROTATE;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_R)) gizmo.op = ImGuizmo::SCALE;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_T)) gizmo.localSpace = !gizmo.localSpace;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_F) && tr != nullptr) {
|
||||
// F = 焦点选中实体(frame-selection):环绕 pivot 设
|
||||
// 为选中实体位置,相机沿视线平移对准(环绕距离沿用
|
||||
// 当前值,滚轮可调)
|
||||
ECS::TransformComponent *camT = nullptr;
|
||||
if (auto *camComp = ctx.scene->GetPrimaryCamera(&camT);
|
||||
camComp != nullptr && camT != nullptr) {
|
||||
camComp->camera.FocusOn(*camT, tr->GetPosition());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Gizmo 操纵杆(ImGui drawlist 叠加,无 GPU pass)----
|
||||
// 选中实体 + 有 Transform 才出杆;矩阵分解直接写回
|
||||
// TransformComponent(与 Inspector 同源,下一帧数值自动跟随)
|
||||
if (tr != nullptr) {
|
||||
// 相机矩阵:primary 相机 view + Vulkan proj(转 GL 形式),
|
||||
// 参数与渲染侧一致(aspect = 渲染分辨率、near/far 0.1/100)
|
||||
|
||||
+6
-3
@@ -201,12 +201,15 @@ namespace
|
||||
|
||||
void OnUpdate(const FrameTime& frameTime) override
|
||||
{
|
||||
// 相机(WASD/Space/Shift 移动;位姿读写实体 Transform)。
|
||||
// 编辑器相机走真实轨:暂停(timeScale=0)时仍可飞行观察
|
||||
// 相机(RMB 飞行 / MMB 焦点环绕 / 滚轮推拉;位姿读写实体
|
||||
// Transform)。编辑器相机走真实轨:暂停(timeScale=0)时仍
|
||||
// 可飞行观察。编辑器模式移动键需 RMB 按住(UE 语义:飞行时
|
||||
// 键盘归相机,W/E/R 归 Gizmo)
|
||||
m_Camera.GetComponent<CameraComponent>()
|
||||
.camera.Update(GetWindow(),
|
||||
m_Camera.GetComponent<TransformComponent>(),
|
||||
frameTime.UnscaledDt());
|
||||
frameTime.UnscaledDt(),
|
||||
GetRenderer().GetDisplayMode() == kDisplayViewport);
|
||||
|
||||
// 旋转动画:编辑 Transform 分量后 Compose 重算 world。
|
||||
// 动画时钟 = Time::Elapsed()(scaled 累计,暂停自动冻结——
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,17 +102,15 @@ 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);
|
||||
|
||||
// ---- 飞行移动:编辑器模式仅 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); }
|
||||
@@ -64,6 +120,34 @@ namespace Engine {
|
||||
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
|
||||
{
|
||||
// 相机作为实体:视图 = 世界矩阵的逆(无重复位姿状态)
|
||||
|
||||
Reference in New Issue
Block a user