Refine editor viewport and interaction workflow

This commit is contained in:
2026-03-29 15:12:38 +08:00
parent b0427b7091
commit 2651bad080
42 changed files with 3888 additions and 570 deletions

View File

@@ -1,233 +1,16 @@
#include "Actions/ActionRouting.h"
#include "Core/IEditorContext.h"
#include "Core/ISelectionManager.h"
#include "SceneViewPanel.h"
#include "Viewport/SceneViewportOverlayRenderer.h"
#include "ViewportPanelContent.h"
#include "UI/UI.h"
#include <XCEngine/Core/Math/Matrix4.h>
#include <XCEngine/Core/Math/Vector4.h>
#include <algorithm>
#include <cmath>
#include <imgui.h>
namespace XCEngine {
namespace Editor {
namespace {
float ComputeGridSpacing(float orbitDistance) {
const float target = (std::max)(orbitDistance * 0.35f, 0.1f);
const float exponent = std::floor(std::log10(target));
const float base = std::pow(10.0f, exponent);
const float normalized = target / base;
if (normalized < 2.0f) {
return base;
}
if (normalized < 5.0f) {
return 2.0f * base;
}
return 5.0f * base;
}
Math::Matrix4x4 BuildOverlayViewMatrix(const SceneViewportOverlayData& overlay) {
return Math::Matrix4x4::LookAt(
overlay.cameraPosition,
overlay.cameraPosition + overlay.cameraForward,
overlay.cameraUp);
}
Math::Matrix4x4 BuildOverlayProjectionMatrix(
const SceneViewportOverlayData& overlay,
const ImVec2& viewportSize) {
const float aspect = viewportSize.y > 0.0f
? viewportSize.x / viewportSize.y
: 1.0f;
return Math::Matrix4x4::Perspective(
overlay.verticalFovDegrees * Math::DEG_TO_RAD,
aspect,
overlay.nearClipPlane,
overlay.farClipPlane);
}
bool ProjectWorldPoint(
const Math::Matrix4x4& viewProjection,
const ImVec2& viewportMin,
const ImVec2& viewportSize,
const Math::Vector3& worldPoint,
ImVec2& screenPoint,
float& depth) {
const Math::Vector4 clip = viewProjection * Math::Vector4(worldPoint, 1.0f);
if (clip.w <= 0.001f) {
return false;
}
const float invW = 1.0f / clip.w;
const float ndcX = clip.x * invW;
const float ndcY = clip.y * invW;
const float ndcZ = clip.z * invW;
if (ndcZ < -0.2f || ndcZ > 1.2f) {
return false;
}
screenPoint.x = viewportMin.x + (ndcX * 0.5f + 0.5f) * viewportSize.x;
screenPoint.y = viewportMin.y + (-ndcY * 0.5f + 0.5f) * viewportSize.y;
depth = ndcZ;
return true;
}
void DrawSceneAxisWidget(
ImDrawList* drawList,
const SceneViewportOverlayData& overlay,
const ImVec2& viewportMin,
const ImVec2& viewportMax) {
if (drawList == nullptr || !overlay.valid) {
return;
}
const Math::Matrix4x4 view = BuildOverlayViewMatrix(overlay);
const ImVec2 center(viewportMin.x + 52.0f, viewportMax.y - 52.0f);
const float radius = 26.0f;
drawList->AddCircleFilled(center, radius + 10.0f, IM_COL32(18, 20, 24, 170), 24);
drawList->AddCircle(center, radius + 10.0f, IM_COL32(255, 255, 255, 28), 24, 1.0f);
struct AxisLine {
Math::Vector3 axis;
ImU32 color;
};
const AxisLine axes[] = {
{ Math::Vector3::Right(), IM_COL32(239, 83, 80, 255) },
{ Math::Vector3::Up(), IM_COL32(102, 187, 106, 255) },
{ Math::Vector3::Forward(), IM_COL32(66, 165, 245, 255) }
};
for (const AxisLine& axis : axes) {
const Math::Vector3 viewAxis = view.MultiplyVector(axis.axis);
const ImVec2 end(
center.x + viewAxis.x * radius,
center.y - viewAxis.y * radius);
drawList->AddLine(center, end, axis.color, 2.2f);
drawList->AddCircleFilled(end, 4.0f, axis.color, 12);
}
}
void DrawSceneGridOverlay(
ImDrawList* drawList,
const SceneViewportOverlayData& overlay,
const ImVec2& viewportMin,
const ImVec2& viewportMax,
const ImVec2& viewportSize) {
if (drawList == nullptr || !overlay.valid || viewportSize.x <= 1.0f || viewportSize.y <= 1.0f) {
return;
}
const Math::Matrix4x4 viewProjection =
BuildOverlayProjectionMatrix(overlay, viewportSize) * BuildOverlayViewMatrix(overlay);
const float spacing = ComputeGridSpacing(overlay.orbitDistance);
const int halfLineCount = 14;
const float extent = spacing * static_cast<float>(halfLineCount);
drawList->PushClipRect(viewportMin, viewportMax, true);
for (int lineIndex = -halfLineCount; lineIndex <= halfLineCount; ++lineIndex) {
const float offset = static_cast<float>(lineIndex) * spacing;
const bool majorLine = (lineIndex % 5) == 0;
const ImU32 lineColor = majorLine
? IM_COL32(255, 255, 255, 58)
: IM_COL32(255, 255, 255, 24);
ImVec2 a = {};
ImVec2 b = {};
ImVec2 c = {};
ImVec2 d = {};
float depthA = 0.0f;
float depthB = 0.0f;
float depthC = 0.0f;
float depthD = 0.0f;
if (ProjectWorldPoint(
viewProjection,
viewportMin,
viewportSize,
Math::Vector3(offset, 0.0f, -extent),
a,
depthA) &&
ProjectWorldPoint(
viewProjection,
viewportMin,
viewportSize,
Math::Vector3(offset, 0.0f, extent),
b,
depthB)) {
drawList->AddLine(a, b, lineColor, majorLine ? 1.35f : 1.0f);
}
if (ProjectWorldPoint(
viewProjection,
viewportMin,
viewportSize,
Math::Vector3(-extent, 0.0f, offset),
c,
depthC) &&
ProjectWorldPoint(
viewProjection,
viewportMin,
viewportSize,
Math::Vector3(extent, 0.0f, offset),
d,
depthD)) {
drawList->AddLine(c, d, lineColor, majorLine ? 1.35f : 1.0f);
}
}
ImVec2 origin = {};
ImVec2 xAxisEnd = {};
ImVec2 yAxisEnd = {};
ImVec2 zAxisEnd = {};
float originDepth = 0.0f;
float xDepth = 0.0f;
float yDepth = 0.0f;
float zDepth = 0.0f;
const float axisLength = spacing * 3.0f;
if (ProjectWorldPoint(viewProjection, viewportMin, viewportSize, Math::Vector3::Zero(), origin, originDepth)) {
if (ProjectWorldPoint(
viewProjection,
viewportMin,
viewportSize,
Math::Vector3(axisLength, 0.0f, 0.0f),
xAxisEnd,
xDepth)) {
drawList->AddLine(origin, xAxisEnd, IM_COL32(239, 83, 80, 220), 1.8f);
}
if (ProjectWorldPoint(
viewProjection,
viewportMin,
viewportSize,
Math::Vector3(0.0f, axisLength, 0.0f),
yAxisEnd,
yDepth)) {
drawList->AddLine(origin, yAxisEnd, IM_COL32(102, 187, 106, 220), 1.8f);
}
if (ProjectWorldPoint(
viewProjection,
viewportMin,
viewportSize,
Math::Vector3(0.0f, 0.0f, axisLength),
zAxisEnd,
zDepth)) {
drawList->AddLine(origin, zAxisEnd, IM_COL32(66, 165, 245, 220), 1.8f);
}
}
DrawSceneAxisWidget(drawList, overlay, viewportMin, viewportMax);
drawList->PopClipRect();
}
} // namespace
SceneViewPanel::SceneViewPanel() : Panel("Scene") {}
void SceneViewPanel::Render() {
@@ -239,35 +22,76 @@ void SceneViewPanel::Render() {
const ViewportPanelContentResult content = RenderViewportPanelContent(*m_context, EditorViewportKind::Scene);
if (IViewportHostService* viewportHostService = m_context->GetViewportHostService()) {
const ImGuiIO& io = ImGui::GetIO();
const bool selectClick =
content.hovered &&
content.frame.hasTexture &&
ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
!m_lookDragging &&
!m_panDragging;
const bool beginLookDrag =
content.hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Right);
const bool beginPanDrag =
content.hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Middle);
if (!m_lookDragging && content.hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
m_lookDragging = true;
if (selectClick || beginLookDrag || beginPanDrag) {
ImGui::SetWindowFocus();
}
if (!m_panDragging && content.hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Middle)) {
if (selectClick) {
const ImVec2 localMousePosition(
io.MousePos.x - content.itemMin.x,
io.MousePos.y - content.itemMin.y);
const uint64_t selectedEntity = viewportHostService->PickSceneViewEntity(
*m_context,
content.availableSize,
localMousePosition);
if (selectedEntity != 0) {
m_context->GetSelectionManager().SetSelectedEntity(selectedEntity);
} else {
m_context->GetSelectionManager().ClearSelection();
}
}
if (beginLookDrag) {
m_lookDragging = true;
m_lastLookDragDelta = ImVec2(0.0f, 0.0f);
}
if (beginPanDrag) {
m_panDragging = true;
m_lastPanDragDelta = ImVec2(0.0f, 0.0f);
}
if (m_lookDragging && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
m_lookDragging = false;
m_lastLookDragDelta = ImVec2(0.0f, 0.0f);
}
if (m_panDragging && !ImGui::IsMouseDown(ImGuiMouseButton_Middle)) {
m_panDragging = false;
m_lastPanDragDelta = ImVec2(0.0f, 0.0f);
}
if (m_lookDragging || m_panDragging) {
ImGui::SetNextFrameWantCaptureMouse(true);
}
if (m_lookDragging) {
ImGui::SetNextFrameWantCaptureKeyboard(true);
}
SceneViewportInput input = {};
input.viewportSize = content.availableSize;
input.deltaTime = io.DeltaTime;
input.hovered = content.hovered;
input.focused = content.focused;
input.mouseWheel = content.hovered ? -io.MouseWheel : 0.0f;
input.focused = content.focused || m_lookDragging || m_panDragging;
input.mouseWheel = (content.hovered && !m_lookDragging) ? io.MouseWheel : 0.0f;
input.flySpeedDelta = (content.hovered && m_lookDragging) ? io.MouseWheel : 0.0f;
input.looking = m_lookDragging;
input.orbiting = false;
input.panning = m_panDragging;
input.fastMove = io.KeyShift;
input.focusSelectionRequested =
content.focused && !io.WantTextInput && ImGui::IsKeyPressed(ImGuiKey_F, false);
input.focused && !io.WantTextInput && ImGui::IsKeyPressed(ImGuiKey_F, false);
if (m_lookDragging && content.focused && !io.WantTextInput) {
if (m_lookDragging && !io.WantTextInput) {
input.moveForward =
(ImGui::IsKeyDown(ImGuiKey_W) ? 1.0f : 0.0f) -
(ImGui::IsKeyDown(ImGuiKey_S) ? 1.0f : 0.0f);
@@ -280,14 +104,30 @@ void SceneViewPanel::Render() {
}
if (m_lookDragging || m_panDragging) {
input.mouseDelta = io.MouseDelta;
if (m_lookDragging) {
const ImVec2 lookDragDelta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Right, 0.0f);
input.mouseDelta.x += lookDragDelta.x - m_lastLookDragDelta.x;
input.mouseDelta.y += lookDragDelta.y - m_lastLookDragDelta.y;
m_lastLookDragDelta = lookDragDelta;
} else {
m_lastLookDragDelta = ImVec2(0.0f, 0.0f);
}
if (m_panDragging) {
const ImVec2 panDragDelta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Middle, 0.0f);
input.mouseDelta.x += panDragDelta.x - m_lastPanDragDelta.x;
input.mouseDelta.y += panDragDelta.y - m_lastPanDragDelta.y;
m_lastPanDragDelta = panDragDelta;
} else {
m_lastPanDragDelta = ImVec2(0.0f, 0.0f);
}
}
viewportHostService->UpdateSceneViewInput(*m_context, input);
if (content.hasViewportArea && content.frame.hasTexture) {
const SceneViewportOverlayData overlay = viewportHostService->GetSceneViewOverlayData();
DrawSceneGridOverlay(
DrawSceneViewportOverlay(
ImGui::GetWindowDrawList(),
overlay,
content.itemMin,