Stabilize debug startup and modularize scene interactions

This commit is contained in:
ayuan9957 2026-03-26 09:58:09 +08:00
parent 79fe8fb906
commit 706d4553f8
12 changed files with 347 additions and 377 deletions

View File

@ -325,7 +325,6 @@ public:
BeginRename(editorContext, requestedRenameId);
}
DrawToolbar(editorContext);
ImGui::TextUnformatted("SampleScene");
ImGui::Separator();
@ -339,20 +338,6 @@ public:
}
private:
void DrawToolbar(MetaCoreEditorContext& editorContext) {
const bool keepWorld = editorContext.GetReparentTransformRule() == MetaCoreReparentTransformRule::KeepWorld;
if (ImGui::Button("保持世界", ImVec2(84.0F, 0.0F))) {
editorContext.SetReparentTransformRule(MetaCoreReparentTransformRule::KeepWorld);
}
ImGui::SameLine();
if (ImGui::Button("保持局部", ImVec2(84.0F, 0.0F))) {
editorContext.SetReparentTransformRule(MetaCoreReparentTransformRule::KeepLocal);
}
ImGui::SameLine();
ImGui::TextDisabled(keepWorld ? "当前: 世界" : "当前: 局部");
ImGui::Separator();
}
void DrawHierarchyNode(MetaCoreEditorContext& editorContext, MetaCoreId objectId) {
MetaCoreScene& scene = editorContext.GetScene();
MetaCoreGameObject* gameObject = scene.FindGameObject(objectId);

View File

@ -54,7 +54,7 @@ void MetaCoreTraceStartup(const char*) {
#endif
#if defined(_DEBUG)
constexpr bool GMetaCoreEnableImGuizmo = false;
constexpr bool GMetaCoreEnableImGuizmo = true;
#else
constexpr bool GMetaCoreEnableImGuizmo = true;
#endif
@ -79,6 +79,25 @@ void MetaCoreApplyEditorStyle() {
style.Colors[ImGuiCol_Tab] = ImVec4(0.12F, 0.14F, 0.16F, 1.0F);
style.Colors[ImGuiCol_TabSelected] = ImVec4(0.20F, 0.26F, 0.34F, 1.0F);
style.Colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.0F, 0.0F, 0.0F, 0.0F);
// Optimize ImGuizmo style for premium feel and remove hatching artifacts (black dashed lines).
ImGuizmo::Style& gizmoStyle = ImGuizmo::GetStyle();
gizmoStyle.TranslationLineThickness = 3.5F;
gizmoStyle.ScaleLineThickness = 3.5F;
gizmoStyle.RotationLineThickness = 3.0F;
gizmoStyle.RotationOuterLineThickness = 2.0F;
gizmoStyle.CenterCircleSize = 4.0F;
gizmoStyle.HatchedAxisLineThickness = 0.0F; // Disables the black dashed segments.
gizmoStyle.TranslationLineArrowSize = 6.0F;
// Unity-style Color Palette
gizmoStyle.Colors[ImGuizmo::DIRECTION_X] = ImVec4(0.85F, 0.25F, 0.25F, 1.0F); // Red
gizmoStyle.Colors[ImGuizmo::DIRECTION_Y] = ImVec4(0.55F, 0.80F, 0.10F, 1.0F); // Green
gizmoStyle.Colors[ImGuizmo::DIRECTION_Z] = ImVec4(0.25F, 0.50F, 0.95F, 1.0F); // Blue
gizmoStyle.Colors[ImGuizmo::PLANE_X] = ImVec4(0.85F, 0.25F, 0.25F, 0.38F);
gizmoStyle.Colors[ImGuizmo::PLANE_Y] = ImVec4(0.55F, 0.80F, 0.10F, 0.38F);
gizmoStyle.Colors[ImGuizmo::PLANE_Z] = ImVec4(0.25F, 0.50F, 0.95F, 0.38F);
gizmoStyle.Colors[ImGuizmo::SELECTION] = ImVec4(1.00F, 0.80F, 0.10F, 1.0F); // Orange-Yellow
}
void MetaCoreConfigureChineseFont() {
@ -122,89 +141,7 @@ void MetaCoreConfigureChineseFont() {
#endif
}
glm::mat4 MetaCoreBuildTransformMatrix(const MetaCoreTransformComponent& transform) {
const glm::mat4 translationMatrix = glm::translate(glm::mat4(1.0F), transform.Position);
const glm::mat4 rotationMatrix = glm::yawPitchRoll(
glm::radians(transform.RotationEulerDegrees.y),
glm::radians(transform.RotationEulerDegrees.x),
glm::radians(transform.RotationEulerDegrees.z)
);
const glm::mat4 scaleMatrix = glm::scale(glm::mat4(1.0F), transform.Scale);
return translationMatrix * rotationMatrix * scaleMatrix;
}
glm::mat4 MetaCoreBuildPandaBasisMatrix() {
glm::mat4 basis(1.0F);
basis[0] = glm::vec4(1.0F, 0.0F, 0.0F, 0.0F);
basis[1] = glm::vec4(0.0F, 0.0F, 1.0F, 0.0F);
basis[2] = glm::vec4(0.0F, -1.0F, 0.0F, 0.0F);
basis[3] = glm::vec4(0.0F, 0.0F, 0.0F, 1.0F);
return basis;
}
glm::vec3 MetaCoreToPandaSpacePoint(const glm::vec3& value) {
return glm::vec3(value.x, -value.z, value.y);
}
glm::vec3 MetaCoreToPandaSpaceVector(const glm::vec3& value) {
return glm::vec3(value.x, -value.z, value.y);
}
glm::vec3 MetaCoreFromPandaSpacePoint(const glm::vec3& value) {
return glm::vec3(value.x, value.z, -value.y);
}
glm::mat4 MetaCoreBuildPandaSpaceTransformMatrix(const MetaCoreTransformComponent& transform) {
const glm::mat4 basis = MetaCoreBuildPandaBasisMatrix();
const glm::mat4 inverseBasis = glm::inverse(basis);
return basis * MetaCoreBuildTransformMatrix(transform) * inverseBasis;
}
glm::mat4 MetaCoreConvertPandaSpaceMatrixToMetaCoreMatrix(const glm::mat4& pandaSpaceMatrix) {
const glm::mat4 basis = MetaCoreBuildPandaBasisMatrix();
const glm::mat4 inverseBasis = glm::inverse(basis);
return inverseBasis * pandaSpaceMatrix * basis;
}
void MetaCoreApplyMatrixToTransform(const glm::mat4& matrix, MetaCoreTransformComponent& transform) {
glm::vec3 skew{};
glm::vec4 perspective{};
glm::quat rotation{};
glm::decompose(matrix, transform.Scale, rotation, transform.Position, skew, perspective);
transform.RotationEulerDegrees = glm::degrees(glm::eulerAngles(rotation));
}
void MetaCoreApplyTranslationFromMatrix(const glm::mat4& matrix, MetaCoreTransformComponent& transform) {
transform.Position = glm::vec3(matrix[3]);
}
void MetaCoreApplyTranslationFromPandaSpaceMatrix(const glm::mat4& matrix, MetaCoreTransformComponent& transform) {
transform.Position = MetaCoreFromPandaSpacePoint(glm::vec3(matrix[3]));
}
void MetaCoreApplyTransformFromPandaSpaceMatrix(const glm::mat4& matrix, MetaCoreTransformComponent& transform) {
MetaCoreApplyMatrixToTransform(MetaCoreConvertPandaSpaceMatrixToMetaCoreMatrix(matrix), transform);
}
ImGuizmo::OPERATION MetaCoreToImGuizmoOperation(MetaCoreGizmoOperation operation) {
switch (operation) {
case MetaCoreGizmoOperation::Translate:
return ImGuizmo::TRANSLATE;
case MetaCoreGizmoOperation::Rotate:
return ImGuizmo::ROTATE;
case MetaCoreGizmoOperation::Scale:
return ImGuizmo::SCALE;
case MetaCoreGizmoOperation::None:
default:
return ImGuizmo::TRANSLATE;
}
}
ImGuizmo::MODE MetaCoreToImGuizmoMode(MetaCoreGizmoMode mode) {
return mode == MetaCoreGizmoMode::World ? ImGuizmo::WORLD : ImGuizmo::LOCAL;
}
} // namespace
} // namespace
MetaCoreEditorApp::~MetaCoreEditorApp() {
Shutdown();
@ -289,7 +226,6 @@ bool MetaCoreEditorApp::Initialize() {
}
int MetaCoreEditorApp::Run() {
MetaCoreTraceStartup("main: run loop enter");
while (!Window_.ShouldClose()) {
Window_.BeginFrame();
@ -370,6 +306,8 @@ void MetaCoreEditorApp::ShutdownImGui() {
}
void MetaCoreEditorApp::DrawEditorFrame() {
SceneInteractionService_.HandleShortcuts(*EditorContext_);
const ImGuiViewport* mainViewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(mainViewport->WorkPos);
ImGui::SetNextWindowSize(mainViewport->WorkSize);
@ -465,6 +403,7 @@ void MetaCoreEditorApp::DrawEditorFrame() {
mousePosition.x <= centralNode->Pos.x + centralNode->Size.x &&
mousePosition.y >= centralNode->Pos.y + sceneToolbarHeight &&
mousePosition.y <= centralNode->Pos.y + centralNode->Size.y;
// 1. Process Input and Viewport State
viewportState.Focused = viewportState.Hovered && !ImGui::GetIO().WantCaptureMouse;
if (viewportState.Focused && !ImGui::GetIO().WantCaptureKeyboard && EditorContext_->GetInput().WasKeyPressed(MetaCoreInputKey::Focus)) {
@ -474,18 +413,10 @@ void MetaCoreEditorApp::DrawEditorFrame() {
}
}
if (!ImGui::GetIO().WantCaptureKeyboard) {
if (ImGui::IsKeyPressed(ImGuiKey_Q)) {
EditorContext_->SetGizmoOperation(MetaCoreGizmoOperation::None);
} else if (ImGui::IsKeyPressed(ImGuiKey_W)) {
EditorContext_->SetGizmoOperation(MetaCoreGizmoOperation::Translate);
} else if (ImGui::IsKeyPressed(ImGuiKey_E)) {
EditorContext_->SetGizmoOperation(MetaCoreGizmoOperation::Rotate);
} else if (ImGui::IsKeyPressed(ImGuiKey_R)) {
EditorContext_->SetGizmoOperation(MetaCoreGizmoOperation::Scale);
}
}
// 2. Handle Gizmo Mode/Operation Changes (State Updates BEFORE Rendering)
// Handled by SceneInteractionService_.HandleShortcuts
// 3. Render Viewport Background
MetaCoreSceneView sceneView = EditorContext_->GetCameraController().BuildSceneView();
sceneView.SelectedObjectId = EditorContext_->GetSelectedObjectId();
@ -509,78 +440,44 @@ void MetaCoreEditorApp::DrawEditorFrame() {
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0F, 0.0F));
constexpr ImGuiWindowFlags gizmoCanvasFlags =
ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoDocking |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoNavFocus |
ImGuiWindowFlags_NoBackground |
ImGuiWindowFlags_NoInputs;
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoDocking |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus |
ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs;
bool gizmoCanvasOpen = true;
ImGui::Begin("MetaCoreSceneGizmoCanvas", &gizmoCanvasOpen, gizmoCanvasFlags);
if (ImGui::Begin("MetaCoreSceneGizmoCanvas", &gizmoCanvasOpen, gizmoCanvasFlags)) {
SceneInteractionService_.HandleGizmoManipulation(*EditorContext_);
gizmoUsing = ImGuizmo::IsUsing();
gizmoHovering = ImGuizmo::IsOver();
// View Cube (Unity-style perspective switching)
const float cameraDistance = glm::distance(sceneView.CameraPosition, sceneView.CameraTarget);
const float gizmoAspect = viewportState.Width / viewportState.Height;
const ImVec2 viewCubePos = ImVec2(
centralNode->Pos.x + viewportState.Width - 120.0F,
centralNode->Pos.y + sceneToolbarHeight + 20.0F
);
const ImVec2 viewCubeSize = ImVec2(100.0F, 100.0F);
MetaCoreGameObject* selectedObject = EditorContext_->GetSelectedGameObject();
if (selectedObject != nullptr &&
EditorContext_->GetGizmoOperation() != MetaCoreGizmoOperation::None &&
cameraDistance >= 0.001F &&
gizmoAspect >= 0.001F) {
glm::mat4 gizmoViewMatrix(1.0F);
glm::mat4 gizmoProjectionMatrix(1.0F);
if (!RenderDevice_.TryGetEditorCameraMatrices(gizmoViewMatrix, gizmoProjectionMatrix)) {
gizmoProjectionMatrix = glm::perspective(
glm::radians(sceneView.VerticalFieldOfViewDegrees),
gizmoAspect, 0.05F, 500.0F
);
}
glm::mat4 cubeViewMatrix = glm::lookAt(sceneView.CameraPosition, sceneView.CameraTarget, sceneView.CameraUp);
const glm::mat4 originalCubeViewMatrix = cubeViewMatrix;
glm::mat4 transformMatrix = MetaCoreBuildPandaSpaceTransformMatrix(selectedObject->Transform);
(void)ViewportRenderer_.TryGetObjectWorldMatrix(selectedObject->Id, transformMatrix);
const glm::mat4 originalTransformMatrix = transformMatrix;
ImGuizmo::ViewManipulate(
glm::value_ptr(cubeViewMatrix),
cameraDistance,
viewCubePos,
viewCubeSize,
0x00000000 // transparent background
);
ImGuizmo::SetOrthographic(false);
ImGuizmo::Enable(true);
ImGuizmo::AllowAxisFlip(true);
ImGuizmo::SetGizmoSizeClipSpace(0.30F);
ImGuizmo::SetDrawlist(ImGui::GetWindowDrawList());
ImGuizmo::SetRect(
ImGui::GetWindowPos().x,
ImGui::GetWindowPos().y,
viewportState.Width,
viewportState.Height
);
ImGuizmo::Manipulate(
glm::value_ptr(gizmoViewMatrix),
glm::value_ptr(gizmoProjectionMatrix),
MetaCoreToImGuizmoOperation(EditorContext_->GetGizmoOperation()),
MetaCoreToImGuizmoMode(EditorContext_->GetGizmoMode()),
glm::value_ptr(transformMatrix)
);
gizmoHovering = ImGuizmo::IsOver();
gizmoUsing = ImGuizmo::IsUsing();
SceneInteractionService_.HandleGizmoBeginUse(*EditorContext_, gizmoUsing);
if (gizmoUsing) {
switch (EditorContext_->GetGizmoOperation()) {
case MetaCoreGizmoOperation::Translate:
MetaCoreApplyTranslationFromPandaSpaceMatrix(transformMatrix, selectedObject->Transform);
break;
case MetaCoreGizmoOperation::Rotate:
case MetaCoreGizmoOperation::Scale:
MetaCoreApplyTransformFromPandaSpaceMatrix(transformMatrix, selectedObject->Transform);
break;
case MetaCoreGizmoOperation::None:
default:
transformMatrix = originalTransformMatrix;
break;
}
}
if (cubeViewMatrix != originalCubeViewMatrix) {
const glm::mat4 cubeCameraWorldMatrix = glm::inverse(cubeViewMatrix);
MetaCoreSceneView updatedView = sceneView;
updatedView.CameraPosition = glm::vec3(cubeCameraWorldMatrix[3]);
EditorContext_->GetCameraController().ApplySceneView(updatedView);
}
}
ImGui::End();
ImGui::PopStyleVar(3);
} else {
@ -601,117 +498,13 @@ void MetaCoreEditorApp::DrawEditorFrame() {
viewportState,
EditorContext_->GetInput().GetCursorPosition()
);
SceneInteractionService_.ApplyViewportSelection(*EditorContext_, Scene_, pickedObjectId);
SceneInteractionService_.ApplyViewportSelection(*EditorContext_, pickedObjectId);
sceneView.SelectedObjectId = EditorContext_->GetActiveObjectId();
}
SceneInteractionService_.HandleGizmoEndUse(*EditorContext_, gizmoUsing);
ImGui::SetNextWindowPos(centralNode->Pos);
ImGui::SetNextWindowSize(centralNode->Size);
ImGui::SetNextWindowBgAlpha(0.0F);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0F);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0F);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0F, 0.0F));
constexpr ImGuiWindowFlags sceneOverlayFlags =
ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoDocking |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoInputs |
ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoNavFocus |
ImGuiWindowFlags_NoBackground;
bool sceneOverlayOpen = true;
ImGui::Begin("MetaCoreSceneViewportOverlay", &sceneOverlayOpen, sceneOverlayFlags);
ImDrawList* drawList = ImGui::GetWindowDrawList();
const ImVec2 overlayOrigin = ImGui::GetWindowPos();
const ImVec2 overlaySize = ImGui::GetWindowSize();
drawList->AddRectFilled(
overlayOrigin,
ImVec2(overlayOrigin.x + overlaySize.x, overlayOrigin.y + sceneToolbarHeight),
IM_COL32(28, 31, 36, 220)
);
drawList->AddLine(
ImVec2(overlayOrigin.x, overlayOrigin.y + sceneToolbarHeight),
ImVec2(overlayOrigin.x + overlaySize.x, overlayOrigin.y + sceneToolbarHeight),
IM_COL32(72, 84, 98, 220),
1.0F
);
const char* helpText = "Alt+左键 环绕 | 中键 平移 | 滚轮 缩放 | F 聚焦";
drawList->AddText(
ImVec2(overlayOrigin.x + 14.0F, overlayOrigin.y + 9.0F),
IM_COL32(220, 224, 230, 220),
helpText
);
ImGui::End();
ImGui::PopStyleVar(3);
ImGui::SetNextWindowPos(centralNode->Pos);
ImGui::SetNextWindowSize(ImVec2(centralNode->Size.x, sceneToolbarHeight));
ImGui::SetNextWindowBgAlpha(0.0F);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0F);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0F);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0F, 4.0F));
constexpr ImGuiWindowFlags sceneToolbarFlags =
ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoDocking |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoNavFocus |
ImGuiWindowFlags_NoBackground;
bool sceneToolbarOpen = true;
ImGui::Begin("MetaCoreSceneToolbarOverlay", &sceneToolbarOpen, sceneToolbarFlags);
ImGui::SetCursorPos(ImVec2(390.0F, 4.0F));
const auto drawToolbarToggle = [](const char* label, bool selected) {
if (selected) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.20F, 0.36F, 0.62F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.25F, 0.42F, 0.70F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.18F, 0.31F, 0.54F, 1.0F));
}
const bool clicked = ImGui::Button(label, ImVec2(38.0F, 24.0F));
if (selected) {
ImGui::PopStyleColor(3);
}
return clicked;
};
if (drawToolbarToggle("Q", EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::None)) {
EditorContext_->SetGizmoOperation(MetaCoreGizmoOperation::None);
}
ImGui::SameLine();
if (drawToolbarToggle("W", EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::Translate)) {
EditorContext_->SetGizmoOperation(MetaCoreGizmoOperation::Translate);
}
ImGui::SameLine();
if (drawToolbarToggle("E", EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::Rotate)) {
EditorContext_->SetGizmoOperation(MetaCoreGizmoOperation::Rotate);
}
ImGui::SameLine();
if (drawToolbarToggle("R", EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::Scale)) {
EditorContext_->SetGizmoOperation(MetaCoreGizmoOperation::Scale);
}
ImGui::SameLine();
ImGui::Dummy(ImVec2(10.0F, 0.0F));
ImGui::SameLine();
if (drawToolbarToggle("Local", EditorContext_->GetGizmoMode() == MetaCoreGizmoMode::Local)) {
EditorContext_->SetGizmoMode(MetaCoreGizmoMode::Local);
}
ImGui::SameLine();
if (drawToolbarToggle("World", EditorContext_->GetGizmoMode() == MetaCoreGizmoMode::World)) {
EditorContext_->SetGizmoMode(MetaCoreGizmoMode::World);
}
ImGui::End();
ImGui::PopStyleVar(3);
SceneInteractionService_.DrawViewportToolbar(*EditorContext_);
} else {
SceneInteractionService_.ResetFrameState();
ViewportRenderer_.SetViewportRect(MetaCoreViewportRect{});

View File

@ -7,7 +7,6 @@
#include <glm/geometric.hpp>
#include <glm/trigonometric.hpp>
#include <algorithm>
#include <cmath>
@ -23,19 +22,36 @@ void MetaCoreEditorCameraController::Update(const MetaCoreSceneViewportState& vi
return;
}
if (viewportState.Hovered && std::abs(io.MouseWheel) > 0.001F) {
const bool altDown = input.IsKeyDown(MetaCoreInputKey::LeftAlt) || input.IsKeyDown(MetaCoreInputKey::RightAlt);
// Zoom: Alt + Right Drag or Mouse Wheel
if (viewportState.Hovered && altDown && ImGui::IsMouseDragging(ImGuiMouseButton_Right)) {
Distance_ *= (1.0F - io.MouseDelta.y * 0.01F);
Distance_ = std::clamp(Distance_, 1.5F, 50.0F);
} else if (viewportState.Hovered && std::abs(io.MouseWheel) > 0.001F) {
Distance_ *= (io.MouseWheel > 0.0F) ? 0.88F : 1.12F;
Distance_ = std::clamp(Distance_, 1.5F, 40.0F);
Distance_ = std::clamp(Distance_, 1.5F, 50.0F);
}
const bool altDown = input.IsKeyDown(MetaCoreInputKey::LeftAlt) || input.IsKeyDown(MetaCoreInputKey::RightAlt);
// Orbit: Alt + Left Drag
if (viewportState.Hovered && altDown && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
YawDegrees_ += io.MouseDelta.x * 0.35F;
PitchDegrees_ -= io.MouseDelta.y * 0.35F;
PitchDegrees_ = std::clamp(PitchDegrees_, -85.0F, 85.0F);
}
// Look Around: Right Drag (without Alt)
else if (viewportState.Hovered && !altDown && ImGui::IsMouseDragging(ImGuiMouseButton_Right)) {
const glm::vec3 oldPosition = GetCameraPosition();
YawDegrees_ += io.MouseDelta.x * 0.35F;
PitchDegrees_ -= io.MouseDelta.y * 0.35F;
PitchDegrees_ = std::clamp(PitchDegrees_, -85.0F, 85.0F);
// Offset target to keep camera position stable during look-around
Target_ = oldPosition + GetForwardDirection() * Distance_;
}
if (viewportState.Hovered && ImGui::IsMouseDragging(ImGuiMouseButton_Middle)) {
// Pan: Middle Drag (with or without Alt)
if (viewportState.Hovered && (ImGui::IsMouseDragging(ImGuiMouseButton_Middle) || (altDown && ImGui::IsMouseDragging(ImGuiMouseButton_Left) && false /* reserved for future */))) {
const glm::vec3 right = GetRightDirection();
const glm::vec3 up = GetUpDirection();
const float panScale = 0.01F * Distance_;

View File

@ -1,20 +1,28 @@
#include "MetaCoreEditor/MetaCoreSceneInteractionService.h"
#include "MetaCoreEditor/MetaCoreEditorContext.h"
#include "MetaCoreEditorCameraController.h"
#include "MetaCoreRender/MetaCoreEditorViewportRenderer.h"
#include "MetaCoreScene/MetaCoreScene.h"
#include "MetaCoreScene/MetaCoreGameObject.h"
#include "MetaCoreScene/MetaCoreComponents.h"
#include "MetaCorePlatform/MetaCoreInput.h"
#include <imgui.h>
#include <ImGuizmo.h>
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/matrix_transform.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/euler_angles.hpp>
#include <glm/ext/matrix_clip_space.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/gtx/euler_angles.hpp>
#include <algorithm>
#include <limits>
#include <unordered_map>
namespace MetaCore {
namespace {
glm::mat4 MetaCoreBuildTransformMatrix(const MetaCoreTransformComponent& transform) {
@ -28,6 +36,45 @@ glm::mat4 MetaCoreBuildTransformMatrix(const MetaCoreTransformComponent& transfo
return translationMatrix * rotationMatrix * scaleMatrix;
}
// Helper to convert Panda3D (Z-up) matrix to MetaCore (Y-up) space
glm::mat4 MetaCoreConvertPandaSpaceMatrixToMetaCoreMatrix(const glm::mat4& pandaMatrix) {
const glm::mat4 pandaToMetaCore(
1.0F, 0.0F, 0.0F, 0.0F,
0.0F, 0.0F, 1.0F, 0.0F,
0.0F, 1.0F, 0.0F, 0.0F,
0.0F, 0.0F, 0.0F, 1.0F
);
return pandaToMetaCore * pandaMatrix * pandaToMetaCore;
}
// Helper to apply MetaCore matrix back to Transform component
void MetaCoreApplyMatrixToTransform(const glm::mat4& matrix, MetaCoreTransformComponent& transform) {
transform.Position = glm::vec3(matrix[3]);
glm::vec3 scale;
scale.x = glm::length(glm::vec3(matrix[0]));
scale.y = glm::length(glm::vec3(matrix[1]));
scale.z = glm::length(glm::vec3(matrix[2]));
transform.Scale = scale;
const glm::mat3 rotationMatrix(
glm::vec3(matrix[0]) / scale.x,
glm::vec3(matrix[1]) / scale.y,
glm::vec3(matrix[2]) / scale.z
);
glm::vec3 euler;
glm::extractEulerAngleYXZ(glm::mat4(rotationMatrix), euler.y, euler.x, euler.z);
transform.RotationEulerDegrees = glm::degrees(euler);
}
ImGuizmo::OPERATION MetaCoreToImGuizmoOperation(MetaCoreGizmoOperation operation) {
switch (operation) {
case MetaCoreGizmoOperation::Translate: return ImGuizmo::TRANSLATE;
case MetaCoreGizmoOperation::Rotate: return ImGuizmo::ROTATE;
case MetaCoreGizmoOperation::Scale: return ImGuizmo::SCALE;
default: return ImGuizmo::TRANSLATE;
}
}
} // namespace
void MetaCoreSceneInteractionService::ResetFrameState() {
@ -165,26 +212,13 @@ MetaCoreId MetaCoreSceneInteractionService::PickGameObjectFromViewport(
return bestObjectId;
}
void MetaCoreSceneInteractionService::ApplyViewportSelection(
MetaCoreEditorContext& editorContext,
const MetaCoreScene& scene,
MetaCoreId pickedObjectId
) const {
const bool ctrlDown = ImGui::GetIO().KeyCtrl;
const bool shiftDown = ImGui::GetIO().KeyShift;
if (pickedObjectId != 0) {
if (shiftDown) {
editorContext.SelectRangeByOrderedIds(scene.BuildHierarchyPreorder(), pickedObjectId, ctrlDown);
} else if (ctrlDown) {
void MetaCoreSceneInteractionService::ApplyViewportSelection(MetaCoreEditorContext& editorContext, MetaCoreId pickedObjectId) const {
if (editorContext.GetInput().IsKeyDown(MetaCoreInputKey::Control)) {
if (pickedObjectId != 0) {
editorContext.ToggleSelection(pickedObjectId);
} else {
editorContext.SelectOnly(pickedObjectId);
}
if (const MetaCoreGameObject* selectedObject = editorContext.GetSelectedGameObject(); selectedObject != nullptr) {
editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Scene", "视口已选中对象: " + selectedObject->Name);
}
} else if (!ctrlDown && !shiftDown) {
editorContext.ClearSelection();
} else {
editorContext.SelectOnly(pickedObjectId);
}
}
@ -196,12 +230,208 @@ void MetaCoreSceneInteractionService::HandleGizmoBeginUse(MetaCoreEditorContext&
}
void MetaCoreSceneInteractionService::HandleGizmoEndUse(MetaCoreEditorContext& editorContext, bool gizmoUsing) {
if (!gizmoUsing && GizmoWasUsing_ && HasGizmoBeforeSnapshot_) {
const MetaCoreEditorStateSnapshot gizmoAfterSnapshot = editorContext.CaptureStateSnapshot();
(void)editorContext.CommitStateTransition("修改变换", GizmoBeforeSnapshot_, gizmoAfterSnapshot, true);
if (!gizmoUsing && GizmoWasUsing_) {
if (HasGizmoBeforeSnapshot_) {
const MetaCoreEditorStateSnapshot afterSnapshot = editorContext.CaptureStateSnapshot();
editorContext.CommitStateTransition("Gizmo Transform", GizmoBeforeSnapshot_, afterSnapshot, true);
}
GizmoWasUsing_ = false;
HasGizmoBeforeSnapshot_ = false;
}
GizmoWasUsing_ = gizmoUsing;
}
void MetaCoreSceneInteractionService::HandleGizmoManipulation(MetaCoreEditorContext& editorContext) {
if (editorContext.GetGizmoOperation() == MetaCoreGizmoOperation::None) {
return;
}
MetaCoreGameObject* selectedObject = editorContext.GetSelectedGameObject();
if (selectedObject == nullptr) {
return;
}
const MetaCoreSceneView sceneView = editorContext.GetCameraController().BuildSceneView();
const MetaCoreSceneViewportState& viewportState = editorContext.GetSceneViewportState();
MetaCoreEditorViewportRenderer& viewportRenderer = editorContext.GetViewportRenderer();
const float gizmoAspect = viewportState.Width / viewportState.Height;
const glm::mat4 gizmoViewMatrix = glm::lookAt(sceneView.CameraPosition, sceneView.CameraTarget, sceneView.CameraUp);
const glm::mat4 gizmoProjectionMatrix = glm::perspective(
glm::radians(sceneView.VerticalFieldOfViewDegrees),
gizmoAspect, 0.05F, 500.0F
);
// 1. Fetch World Matrix from ViewportRenderer (in Panda3D Z-up space).
glm::mat4 worldMatrixPanda(1.0F);
if (viewportRenderer.TryGetObjectWorldMatrix(selectedObject->Id, worldMatrixPanda)) {
// 2. Convert to MetaCore Y-up World Space.
glm::mat4 worldMatrixMetaCore = MetaCoreConvertPandaSpaceMatrixToMetaCoreMatrix(worldMatrixPanda);
const glm::mat4 originalWorldMatrixMetaCore = worldMatrixMetaCore;
// 3. Setup ImGuizmo state.
ImGuizmo::SetOrthographic(false);
ImGuizmo::Enable(true);
ImGuizmo::AllowAxisFlip(false);
ImGuizmo::SetGizmoSizeClipSpace(0.30F);
ImGuizmo::SetDrawlist(ImGui::GetWindowDrawList());
ImGuizmo::SetRect(
ImGui::GetWindowPos().x,
ImGui::GetWindowPos().y,
viewportState.Width,
viewportState.Height
);
// 4. Determine Gizmo Mode (force Local for Scale).
const MetaCoreGizmoOperation currentOp = editorContext.GetGizmoOperation();
const MetaCoreGizmoMode mcMode = editorContext.GetGizmoMode();
ImGuizmo::MODE effectiveMode = (mcMode == MetaCoreGizmoMode::Global) ? ImGuizmo::WORLD : ImGuizmo::LOCAL;
if (currentOp == MetaCoreGizmoOperation::Scale) {
effectiveMode = ImGuizmo::LOCAL;
}
// 5. Manipulate.
ImGuizmo::PushID(reinterpret_cast<void*>(selectedObject));
// WORLD mode handle alignment fix.
glm::mat4 gizmoMatrix = worldMatrixMetaCore;
if (effectiveMode == ImGuizmo::WORLD) {
gizmoMatrix = glm::translate(glm::mat4(1.0F), glm::vec3(worldMatrixMetaCore[3]));
}
ImGuizmo::Manipulate(
glm::value_ptr(gizmoViewMatrix),
glm::value_ptr(gizmoProjectionMatrix),
MetaCoreToImGuizmoOperation(currentOp),
effectiveMode,
glm::value_ptr(gizmoMatrix)
);
bool gizmoUsing = ImGuizmo::IsUsing();
ImGuizmo::PopID();
if (gizmoUsing && (gizmoMatrix != worldMatrixMetaCore || gizmoMatrix != originalWorldMatrixMetaCore)) {
if (effectiveMode == ImGuizmo::WORLD) {
if (currentOp == MetaCoreGizmoOperation::Translate) {
worldMatrixMetaCore[3] = gizmoMatrix[3];
} else if (currentOp == MetaCoreGizmoOperation::Rotate) {
worldMatrixMetaCore = gizmoMatrix;
}
} else {
worldMatrixMetaCore = gizmoMatrix;
}
// 6. Decompose back to Local Space.
glm::mat4 newLocalMatrix = worldMatrixMetaCore;
if (selectedObject->ParentId != 0) {
glm::mat4 parentWorldMatrixPanda(1.0F);
if (viewportRenderer.TryGetObjectWorldMatrix(selectedObject->ParentId, parentWorldMatrixPanda)) {
const glm::mat4 parentWorldMatrixMetaCore = MetaCoreConvertPandaSpaceMatrixToMetaCoreMatrix(parentWorldMatrixPanda);
newLocalMatrix = glm::inverse(parentWorldMatrixMetaCore) * worldMatrixMetaCore;
}
}
MetaCoreApplyMatrixToTransform(newLocalMatrix, selectedObject->Transform);
}
HandleGizmoBeginUse(editorContext, gizmoUsing);
HandleGizmoEndUse(editorContext, gizmoUsing);
}
}
void MetaCoreSceneInteractionService::DrawViewportToolbar(MetaCoreEditorContext& editorContext) {
const MetaCoreSceneViewportState& viewportState = editorContext.GetSceneViewportState();
// Position at top-left of the viewport window
ImGui::SetNextWindowPos(ImVec2(viewportState.Left + 8.0f, viewportState.Top + 8.0f));
ImGui::SetNextWindowBgAlpha(0.6f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0F);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0F); // Subtle border
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.3f, 0.3f, 0.3f, 0.5f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(4.0F, 4.0F));
constexpr ImGuiWindowFlags toolbarFlags =
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoDocking |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_AlwaysAutoResize;
if (ImGui::Begin("##ViewportToolbar", nullptr, toolbarFlags)) {
const auto drawToolbarToggle = [](const char* label, bool selected) {
if (selected) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.20F, 0.36F, 0.62F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.25F, 0.42F, 0.70F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.18F, 0.31F, 0.54F, 1.0F));
} else {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15F, 0.17F, 0.20F, 0.7F));
}
const bool clicked = ImGui::Button(label, ImVec2(32.0F, 24.0F));
if (selected) {
ImGui::PopStyleColor(3);
} else {
ImGui::PopStyleColor(1);
}
return clicked;
};
// Tools
if (drawToolbarToggle("Q", editorContext.GetGizmoOperation() == MetaCoreGizmoOperation::None)) {
editorContext.SetGizmoOperation(MetaCoreGizmoOperation::None);
}
ImGui::SameLine();
if (drawToolbarToggle("W", editorContext.GetGizmoOperation() == MetaCoreGizmoOperation::Translate)) {
editorContext.SetGizmoOperation(MetaCoreGizmoOperation::Translate);
}
ImGui::SameLine();
if (drawToolbarToggle("E", editorContext.GetGizmoOperation() == MetaCoreGizmoOperation::Rotate)) {
editorContext.SetGizmoOperation(MetaCoreGizmoOperation::Rotate);
}
ImGui::SameLine();
if (drawToolbarToggle("R", editorContext.GetGizmoOperation() == MetaCoreGizmoOperation::Scale)) {
editorContext.SetGizmoOperation(MetaCoreGizmoOperation::Scale);
}
ImGui::SameLine(0, 12);
// Mode Toggles (Unity uses "Global" instead of "World")
if (drawToolbarToggle("Local", editorContext.GetGizmoMode() == MetaCoreGizmoMode::Local)) {
editorContext.SetGizmoMode(MetaCoreGizmoMode::Local);
}
ImGui::SameLine(0, 2);
if (drawToolbarToggle("Global", editorContext.GetGizmoMode() == MetaCoreGizmoMode::Global)) {
editorContext.SetGizmoMode(MetaCoreGizmoMode::Global);
}
}
ImGui::End();
ImGui::PopStyleColor();
ImGui::PopStyleVar(3);
}
void MetaCoreSceneInteractionService::HandleShortcuts(MetaCoreEditorContext& editorContext) {
if (ImGui::GetIO().WantCaptureKeyboard) {
return;
}
// Q, W, E, R for Tool Selection
if (ImGui::IsKeyPressed(ImGuiKey_Q)) {
editorContext.SetGizmoOperation(MetaCoreGizmoOperation::None);
} else if (ImGui::IsKeyPressed(ImGuiKey_W)) {
editorContext.SetGizmoOperation(MetaCoreGizmoOperation::Translate);
} else if (ImGui::IsKeyPressed(ImGuiKey_E)) {
editorContext.SetGizmoOperation(MetaCoreGizmoOperation::Rotate);
} else if (ImGui::IsKeyPressed(ImGuiKey_R)) {
editorContext.SetGizmoOperation(MetaCoreGizmoOperation::Scale);
}
// Z for Coordinate Space Toggle (Local/Global)
if (ImGui::IsKeyPressed(ImGuiKey_Z)) {
if (editorContext.GetGizmoMode() == MetaCoreGizmoMode::Local) {
editorContext.SetGizmoMode(MetaCoreGizmoMode::Global);
} else {
editorContext.SetGizmoMode(MetaCoreGizmoMode::Local);
}
}
}
} // namespace MetaCore

View File

@ -29,7 +29,7 @@ enum class MetaCoreGizmoOperation {
enum class MetaCoreGizmoMode {
Local = 0,
World
Global
};
enum class MetaCoreReparentTransformRule {

View File

@ -21,9 +21,14 @@ public:
const glm::vec2& cursorPosition
) const;
void ApplyViewportSelection(MetaCoreEditorContext& editorContext, const MetaCoreScene& scene, MetaCoreId pickedObjectId) const;
void ApplyViewportSelection(MetaCoreEditorContext& editorContext, MetaCoreId pickedObjectId) const;
void HandleGizmoBeginUse(MetaCoreEditorContext& editorContext, bool gizmoUsing);
void HandleGizmoEndUse(MetaCoreEditorContext& editorContext, bool gizmoUsing);
// Unity-style modular interactive features
void HandleGizmoManipulation(MetaCoreEditorContext& editorContext);
void DrawViewportToolbar(MetaCoreEditorContext& editorContext);
void HandleShortcuts(MetaCoreEditorContext& editorContext);
private:
bool GizmoWasUsing_ = false;

View File

@ -262,6 +262,7 @@ void MetaCoreWindow::BeginFrame() {
Impl_->Input.SetKeyState(MetaCoreInputKey::LeftAlt, MetaCoreIsVirtualKeyDown(VK_LMENU));
Impl_->Input.SetKeyState(MetaCoreInputKey::RightAlt, MetaCoreIsVirtualKeyDown(VK_RMENU));
Impl_->Input.SetKeyState(MetaCoreInputKey::Control, MetaCoreIsVirtualKeyDown(VK_LCONTROL) || MetaCoreIsVirtualKeyDown(VK_RCONTROL));
Impl_->Input.SetKeyState(MetaCoreInputKey::Focus, MetaCoreIsVirtualKeyDown('F'));
Impl_->Input.SetMouseButtonState(MetaCoreMouseButton::Left, MetaCoreIsVirtualKeyDown(VK_LBUTTON));
Impl_->Input.SetMouseButtonState(MetaCoreMouseButton::Right, MetaCoreIsVirtualKeyDown(VK_RBUTTON));
@ -277,14 +278,7 @@ void MetaCoreWindow::EndFrame() {
}
bool MetaCoreWindow::ShouldClose() const {
if (!Impl_->Initialized) {
return true;
}
if (Impl_->CloseRequested) {
MetaCoreWindowTrace("window: should_close true (close requested)");
return true;
}
return false;
return !Impl_->Initialized || Impl_->CloseRequested;
}
void MetaCoreWindow::RequestClose() {

View File

@ -10,6 +10,7 @@ namespace MetaCore {
enum class MetaCoreInputKey : unsigned int {
LeftAlt = 0,
RightAlt,
Control,
Focus,
Count
};

View File

@ -171,52 +171,6 @@ NodePath MetaCoreCreateGridNode(const NodePath& parentNode) {
}
}
NodePath MetaCoreCreateAxisNode(const NodePath& parentNode) {
MetaCoreTrace("create_axis: begin");
try {
PT(GeomVertexData) vertexData = new GeomVertexData(
"MetaCoreAxis",
GeomVertexFormat::get_v3c4(),
Geom::UH_static
);
GeomVertexWriter vertexWriter(vertexData, "vertex");
GeomVertexWriter colorWriter(vertexData, "color");
// X Axis (Red)
vertexWriter.add_data3(MetaCoreToPandaPoint(glm::vec3(0.0F, 0.0F, 0.0F)));
colorWriter.add_data4(LColor(0.92F, 0.26F, 0.26F, 1.0F));
vertexWriter.add_data3(MetaCoreToPandaPoint(glm::vec3(2.0F, 0.0F, 0.0F)));
colorWriter.add_data4(LColor(0.92F, 0.26F, 0.26F, 1.0F));
// Y Axis (Green)
vertexWriter.add_data3(MetaCoreToPandaPoint(glm::vec3(0.0F, 0.0F, 0.0F)));
colorWriter.add_data4(LColor(0.28F, 0.86F, 0.36F, 1.0F));
vertexWriter.add_data3(MetaCoreToPandaPoint(glm::vec3(0.0F, 2.0F, 0.0F)));
colorWriter.add_data4(LColor(0.28F, 0.86F, 0.36F, 1.0F));
// Z Axis (Blue)
vertexWriter.add_data3(MetaCoreToPandaPoint(glm::vec3(0.0F, 0.0F, 0.0F)));
colorWriter.add_data4(LColor(0.32F, 0.56F, 0.96F, 1.0F));
vertexWriter.add_data3(MetaCoreToPandaPoint(glm::vec3(0.0F, 0.0F, 2.0F)));
colorWriter.add_data4(LColor(0.32F, 0.56F, 0.96F, 1.0F));
PT(GeomLines) lines = new GeomLines(Geom::UH_static);
lines->add_vertices(0, 1);
lines->add_vertices(2, 3);
lines->add_vertices(4, 5);
PT(Geom) geom = new Geom(vertexData);
geom->add_primitive(lines.p());
PT(GeomNode) geomNode = new GeomNode("MetaCoreAxisNode");
geomNode->add_geom(geom);
return parentNode.attach_new_node(geomNode);
} catch (...) {
return NodePath();
}
}
NodePath MetaCoreCreateUnitCubeNode(const NodePath& parentNode) {
PT(GeomVertexData) vertexData = new GeomVertexData(
@ -306,7 +260,6 @@ public:
MetaCoreRenderDevice* RenderDevice = nullptr;
NodePath GridNode{};
NodePath AxisNode{};
NodePath AmbientLightNode{};
MetaCoreId SelectedObjectId = 0;
std::unordered_map<MetaCoreId, MetaCorePandaObjectState> ObjectStates{};
@ -332,10 +285,7 @@ bool MetaCorePandaSceneBridge::Initialize(MetaCoreRenderDevice& renderDevice) {
Impl_->RenderDevice = &renderDevice;
Impl_->GridNode = MetaCoreCreateGridNode(*sceneRootHandle);
Impl_->AxisNode = MetaCoreCreateAxisNode(*sceneRootHandle);
Impl_->GridNode.set_light_off(1);
Impl_->AxisNode.set_light_off(1);
PT(AmbientLight) ambientLight = new AmbientLight("MetaCoreAmbientLight");
ambientLight->set_color(LColor(0.85F, 0.85F, 0.85F, 1.0F));
@ -363,10 +313,6 @@ void MetaCorePandaSceneBridge::Shutdown() {
Impl_->AmbientLightNode = NodePath();
}
if (!Impl_->AxisNode.is_empty()) {
Impl_->AxisNode.remove_node();
Impl_->AxisNode = NodePath();
}
if (!Impl_->GridNode.is_empty()) {
Impl_->GridNode.remove_node();

BIN
diff.txt Normal file

Binary file not shown.

BIN
diff_head.txt Normal file

Binary file not shown.

View File

@ -787,8 +787,8 @@ namespace IMGUIZMO_NAMESPACE
static const char* scaleInfoMask[] = { "X : %5.2f", "Y : %5.2f", "Z : %5.2f", "XYZ : %5.2f" };
static const char* rotationInfoMask[] = { "X : %5.2f deg %5.2f rad", "Y : %5.2f deg %5.2f rad", "Z : %5.2f deg %5.2f rad", "Screen : %5.2f deg %5.2f rad" };
static const int translationInfoIndex[] = { 0,0,0, 1,0,0, 2,0,0, 1,2,0, 0,2,0, 0,1,0, 0,1,2 };
static const float quadMin = 0.5f;
static const float quadMax = 0.8f;
static const float quadMin = 0.12f;
static const float quadMax = 0.38f;
static const float quadUV[8] = { quadMin, quadMin, quadMin, quadMax, quadMax, quadMax, quadMax, quadMin };
static const int halfCircleSegmentCount = 64;
static const float snapTension = 0.5f;