// // Created by atdunbg on 2026/9/22. // #include #include #include #include #include #include "Engine/Core/Input.h" // 键查询走统一输入门面(不依赖 GLFW) namespace Engine { void Camera::Update(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 (!Input::IsCursorCaptured()) { Input::SetCursorCaptured(true); } f32 mouseDX = 0, mouseDY = 0; Input::GetMouseDelta(mouseDX, mouseDY); if (mouseDX != 0 || mouseDY != 0) { if (wantLook) { // ---- FPS 式视角(UE/Unity 编辑器视口语义): // yaw 绕世界 up 左乘、pitch 绕本地 X 右乘—— // 右轴恒保持水平,水平线永不 roll。此前版本 // yaw 也绕本地轴(6DOF 自由视角),俯视后再 // 平移鼠标会带 roll,up 向量歪斜。pitch 用 // 前向量的仰角钳 ±89°:过顶会倒置视图,编辑 // 器视口不要这种行为。符号约定不变:鼠标右移 // 向右看、鼠标下移低头 ---- const glm::quat dyaw = glm::angleAxis( glm::radians(-mouseDX * m_MouseSensitivity), glm::vec3(0, 1, 0)); const glm::vec3 fwd = transform.GetRotationQuat() * glm::vec3(0, 0, -1); const float pitch = std::asin(glm::clamp(fwd.y, -1.0f, 1.0f)); float dPitch = glm::radians(-mouseDY * m_MouseSensitivity); dPitch = glm::clamp(pitch + dPitch, glm::radians(-89.0f), glm::radians(89.0f)) - pitch; const glm::quat dpitch = glm::angleAxis(dPitch, glm::vec3(1, 0, 0)); transform.SetRotationQuat( dyaw * transform.GetRotationQuat() * dpitch); // 自由视角改写朝向后,旧环绕 pivot 已不在视线上—— // 若保留,下一次 MMB/滚轮会绕老焦点猛拽位置(Focus // 后飞远再 MMB 的"瞬移锁回":角增量 × 大半径 = // 米级甩动)。采 Blender/UE 语义:pivot 跟随当前 // 视线,失效待 MMB/滚轮按新视线自动重建 m_HasPivot = false; } else if (Input::IsKeyDown(Input::Key::LeftShift)) { // ---- 屏幕平面平移:内容跟手(拖右 → 相机左移、 // 拖下 → 相机上移),pivot 同步平移保持环绕点的 // 屏幕相对位置 ---- const glm::mat3 r(transform.GetRotationQuat()); 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 { // ---- 焦点环绕:世界系左乘增量(yaw 绕世界 Y、pitch // 绕相机右轴),roll 保持不变;位置绕 pivot 同 // 角度刚体旋转 → 到 pivot 距离恒定。首次环绕无 // pivot:取当前视线上 m_OrbitDistance 处 ---- if (!m_HasPivot) { const glm::mat3 r(transform.GetRotationQuat()); m_OrbitPivot = transform.GetPosition() + (r * glm::vec3(0, 0, -1)) * m_OrbitDistance; m_HasPivot = true; } const glm::vec3 offset = transform.GetPosition() - m_OrbitPivot; m_OrbitDistance = glm::length(offset); const glm::vec3 camRight = glm::mat3(transform.GetRotationQuat()) * glm::vec3(1, 0, 0); const glm::quat dq = glm::angleAxis( glm::radians(-mouseDX * m_MouseSensitivity), glm::vec3(0, 1, 0)) * glm::angleAxis( glm::radians(-mouseDY * m_MouseSensitivity), camRight); // 朝向与位置共用同一 dq:视线始终精确穿过 pivot transform.SetRotationQuat(dq * transform.GetRotationQuat()); transform.SetPosition(m_OrbitPivot + dq * offset); } } } else if (Input::IsCursorCaptured()) { Input::SetCursorCaptured(false); } // 基向量由(可能已更新的)实体旋转决定(本地 -Z 为视线方向) const glm::mat3 r(transform.GetRotationQuat()); 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; bool flew = false; if (Input::IsKeyDown(Input::Key::W)) { transform.SetPosition(transform.GetPosition() + forward * speed); flew = true; } if (Input::IsKeyDown(Input::Key::S)) { transform.SetPosition(transform.GetPosition() - forward * speed); flew = true; } if (Input::IsKeyDown(Input::Key::A)) { transform.SetPosition(transform.GetPosition() - right * speed); flew = true; } if (Input::IsKeyDown(Input::Key::D)) { transform.SetPosition(transform.GetPosition() + right * speed); flew = true; } if (Input::IsKeyDown(Input::Key::Space)) { transform.SetPosition(transform.GetPosition() + glm::vec3(0, speed, 0)); flew = true; } if (Input::IsKeyDown(Input::Key::LeftShift)) { transform.SetPosition(transform.GetPosition() - glm::vec3(0, speed, 0)); flew = true; } // 飞行位移同样令环绕 pivot 失效(同自由视角:位移后老 // pivot 与视线脱钩,保留会让 MMB/滚轮绕老焦点拽位置) if (flew) { m_HasPivot = false; } } // ---- 滚轮推拉:有 pivot 时改环绕距离(指数步进,近处精细/ // 远处大步);无 pivot 时沿视线平移 ---- const f32 wheel = Input::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 = glm::mat3(transform.GetRotationQuat()) * 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 { glm::mat4 proj; if (m_ProjectionMode == ProjectionMode::Perspective) { proj = glm::perspective(glm::radians(m_FovDeg), aspect, m_NearZ, m_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 系数 proj[2][2] = proj[2][2] * 0.5f - 0.5f; proj[3][2] *= 0.5f; } else { // 正交(Unity orthographic size 语义 = 半垂直高度):全宽由 // 视口 aspect 推导,长宽比跟随视口永不变形 const f32 halfH = m_OrthoSize; const f32 halfW = halfH * aspect; proj = glm::ortho(-halfW, halfW, -halfH, halfH, m_NearZ, m_FarZ); proj[1][1] *= -1.0f; // Vulkan Y 翻转(与透视同款) // ortho 的 w_clip = 1:偏移直接加进平移分量 //(与 ComputeCascades 光源正交投影同一公式) proj[2][2] *= 0.5f; proj[3][2] = proj[3][2] * 0.5f + 0.5f; } return proj; } } // namespace Engine