Improve editor viewport gizmo and Panda3D camera integration

This commit is contained in:
ayuan9957 2026-03-25 09:31:40 +08:00
parent 633156e3be
commit ea6291fba3
21 changed files with 4255 additions and 108 deletions

View File

@ -0,0 +1,11 @@
# THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY
version = 1
name = "MetaCore"
[setup]
script = ""
[[actions]]
name = "运行"
icon = "run"
command = "D:\\MetaCore\\build\\vs2022-release\\Release\\./MetaCoreEditorApp.exe"

View File

@ -160,6 +160,7 @@ set(METACORE_EDITOR_HEADERS
set(METACORE_EDITOR_PRIVATE_HEADERS
Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.h
Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.h
third_party/ImGuizmo/ImGuizmo.h
)
set(METACORE_EDITOR_SOURCES
@ -168,6 +169,7 @@ set(METACORE_EDITOR_SOURCES
Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.cpp
Source/MetaCoreEditor/Private/MetaCoreEditorContext.cpp
Source/MetaCoreEditor/Private/MetaCoreEditorModule.cpp
third_party/ImGuizmo/ImGuizmo.cpp
)
add_library(MetaCoreEditor STATIC
@ -181,6 +183,7 @@ target_include_directories(MetaCoreEditor
Source/MetaCoreEditor/Public
PRIVATE
Source/MetaCoreEditor/Private
third_party/ImGuizmo
)
target_link_libraries(MetaCoreEditor

View File

@ -62,3 +62,9 @@ cmake --build --preset build-release
- `Inspector` 中编辑 `Transform`
- `Alt+左键 / 中键 / 滚轮 / F` 相机交互
- 模块注册接口与内置编辑器模块
## 技术支持连接
https://github.com/CedricGuillemet/ImGuizmo
https://docs.panda3d.org/1.9/cpp/programming/using-cpp/index

View File

@ -136,62 +136,7 @@ public:
std::string GetPanelTitle() const override { return "场景"; }
bool IsOpenByDefault() const override { return true; }
void DrawPanel(MetaCoreEditorContext& editorContext) override {
MetaCoreSceneViewportState& viewportState = editorContext.GetSceneViewportState();
ImVec2 viewportSize = ImGui::GetContentRegionAvail();
if (viewportSize.x < 1.0F) {
viewportSize.x = 1.0F;
}
if (viewportSize.y < 1.0F) {
viewportSize.y = 1.0F;
}
const ImVec2 viewportScreenPosition = ImGui::GetCursorScreenPos();
ImGui::InvisibleButton("MetaCoreSceneViewport", viewportSize, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonMiddle);
const ImVec2 mainViewportPosition = ImGui::GetMainViewport()->Pos;
viewportState.Left = viewportScreenPosition.x - mainViewportPosition.x;
viewportState.Top = viewportScreenPosition.y - mainViewportPosition.y;
viewportState.Width = viewportSize.x;
viewportState.Height = viewportSize.y;
viewportState.Hovered = ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
viewportState.Focused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows);
if (viewportState.Focused && !ImGui::GetIO().WantCaptureKeyboard && editorContext.GetInput().WasKeyPressed(MetaCoreInputKey::Focus)) {
if (MetaCoreGameObject* selectedObject = editorContext.GetSelectedGameObject(); selectedObject != nullptr) {
editorContext.GetCameraController().FocusGameObject(*selectedObject);
editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Scene", "相机已聚焦到当前对象");
}
}
editorContext.GetCameraController().Update(viewportState, editorContext.GetInput());
MetaCoreSceneView sceneView = editorContext.GetCameraController().BuildSceneView();
sceneView.SelectedObjectId = editorContext.GetSelectedObjectId();
editorContext.GetViewportRenderer().SetViewportRect(MetaCoreViewportRect{
viewportState.Left,
viewportState.Top,
viewportState.Width,
viewportState.Height
});
editorContext.GetViewportRenderer().RenderSceneToViewport(editorContext.GetScene(), sceneView);
ImDrawList* drawList = ImGui::GetWindowDrawList();
drawList->AddRect(
viewportScreenPosition,
ImVec2(viewportScreenPosition.x + viewportSize.x, viewportScreenPosition.y + viewportSize.y),
IM_COL32(96, 108, 122, 220),
0.0F,
0,
1.0F
);
const char* helpText = "Alt+左键 环绕 | 中键 平移 | 滚轮 缩放 | F 聚焦";
drawList->AddText(
ImVec2(viewportScreenPosition.x + 12.0F, viewportScreenPosition.y + 10.0F),
IM_COL32(220, 224, 230, 220),
helpText
);
void DrawPanel(MetaCoreEditorContext&) override {
}
};

View File

@ -7,16 +7,27 @@
#include <imgui_internal.h>
#include <imgui_impl_opengl3.h>
#include <imgui_impl_win32.h>
#include "ImGuizmo.h"
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#define GLM_ENABLE_EXPERIMENTAL
#include <cstdlib>
#include <filesystem>
#include <glm/ext/matrix_clip_space.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/geometric.hpp>
#include <glm/gtx/euler_angles.hpp>
#include <glm/gtx/matrix_decompose.hpp>
#include <glm/vec4.hpp>
#include <memory>
#include <limits>
#include <iostream>
#include <vector>
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
@ -80,6 +91,194 @@ 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;
}
MetaCoreId MetaCorePickGameObjectFromViewport(
const MetaCoreScene& scene,
const MetaCoreSceneView& sceneView,
const MetaCoreSceneViewportState& viewportState,
const glm::vec2& cursorPosition
) {
if (viewportState.Width <= 1.0F || viewportState.Height <= 1.0F) {
return 0;
}
const glm::vec2 localCursor(
cursorPosition.x - viewportState.Left,
cursorPosition.y - viewportState.Top
);
if (localCursor.x < 0.0F || localCursor.y < 0.0F || localCursor.x > viewportState.Width || localCursor.y > viewportState.Height) {
return 0;
}
const glm::mat4 viewMatrix = glm::lookAt(
sceneView.CameraPosition,
sceneView.CameraTarget,
sceneView.CameraUp
);
const glm::mat4 projectionMatrix = glm::perspective(
glm::radians(sceneView.VerticalFieldOfViewDegrees),
viewportState.Width / viewportState.Height,
0.05F,
500.0F
);
const float normalizedX = (localCursor.x / viewportState.Width) * 2.0F - 1.0F;
const float normalizedY = 1.0F - (localCursor.y / viewportState.Height) * 2.0F;
const glm::vec4 rayStartClip(normalizedX, normalizedY, -1.0F, 1.0F);
const glm::vec4 rayEndClip(normalizedX, normalizedY, 1.0F, 1.0F);
const glm::mat4 inverseViewProjection = glm::inverse(projectionMatrix * viewMatrix);
glm::vec4 rayStartWorld = inverseViewProjection * rayStartClip;
glm::vec4 rayEndWorld = inverseViewProjection * rayEndClip;
if (std::abs(rayStartWorld.w) < 0.0001F || std::abs(rayEndWorld.w) < 0.0001F) {
return 0;
}
rayStartWorld /= rayStartWorld.w;
rayEndWorld /= rayEndWorld.w;
const glm::vec3 rayOrigin(rayStartWorld);
const glm::vec3 rayDirection = glm::normalize(glm::vec3(rayEndWorld - rayStartWorld));
MetaCoreId bestObjectId = 0;
float bestHitDistance = std::numeric_limits<float>::max();
for (const MetaCoreGameObject& gameObject : scene.GetGameObjects()) {
if (!gameObject.MeshRenderer.has_value() || !gameObject.MeshRenderer->Visible) {
continue;
}
const glm::mat4 worldMatrix = MetaCoreBuildTransformMatrix(gameObject.Transform);
const glm::mat4 inverseWorldMatrix = glm::inverse(worldMatrix);
const glm::vec3 localRayOrigin = glm::vec3(inverseWorldMatrix * glm::vec4(rayOrigin, 1.0F));
const glm::vec3 localRayDirection = glm::normalize(glm::vec3(inverseWorldMatrix * glm::vec4(rayDirection, 0.0F)));
constexpr glm::vec3 localBoundsMin(-0.5F, -0.5F, -0.5F);
constexpr glm::vec3 localBoundsMax(0.5F, 0.5F, 0.5F);
float nearDistance = 0.0F;
float farDistance = bestHitDistance;
bool hit = true;
for (int axisIndex = 0; axisIndex < 3; ++axisIndex) {
const float rayAxisOrigin = localRayOrigin[axisIndex];
const float rayAxisDirection = localRayDirection[axisIndex];
if (std::abs(rayAxisDirection) < 0.0001F) {
if (rayAxisOrigin < localBoundsMin[axisIndex] || rayAxisOrigin > localBoundsMax[axisIndex]) {
hit = false;
break;
}
continue;
}
float axisNearDistance = (localBoundsMin[axisIndex] - rayAxisOrigin) / rayAxisDirection;
float axisFarDistance = (localBoundsMax[axisIndex] - rayAxisOrigin) / rayAxisDirection;
if (axisNearDistance > axisFarDistance) {
std::swap(axisNearDistance, axisFarDistance);
}
nearDistance = std::max(nearDistance, axisNearDistance);
farDistance = std::min(farDistance, axisFarDistance);
if (nearDistance > farDistance) {
hit = false;
break;
}
}
if (hit && farDistance >= 0.0F) {
const float hitDistance = nearDistance >= 0.0F ? nearDistance : farDistance;
if (hitDistance >= 0.0F && hitDistance < bestHitDistance) {
bestHitDistance = hitDistance;
bestObjectId = gameObject.Id;
}
}
}
return bestObjectId;
}
} // namespace
MetaCoreEditorApp::~MetaCoreEditorApp() {
@ -96,12 +295,12 @@ bool MetaCoreEditorApp::Initialize() {
}
Window_.SetNativeWindowMessageHandler([](void* nativeWindowHandle, unsigned int message, std::uintptr_t wparam, std::intptr_t lparam) {
ImGui_ImplWin32_WndProcHandler(
return ImGui_ImplWin32_WndProcHandler(
static_cast<HWND>(nativeWindowHandle),
message,
static_cast<WPARAM>(wparam),
static_cast<LPARAM>(lparam)
);
) != 0;
});
if (!RenderDevice_.Initialize(Window_)) {
@ -153,6 +352,7 @@ int MetaCoreEditorApp::Run() {
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
ImGuizmo::BeginFrame();
DrawEditorFrame();
@ -243,10 +443,8 @@ void MetaCoreEditorApp::DrawEditorFrame() {
EnsureDefaultDockLayout(dockSpaceId);
ImGui::End();
MetaCoreIEditorPanelProvider* scenePanelProvider = nullptr;
for (const auto& panelProvider : ModuleRegistry_.GetPanelProviders()) {
if (panelProvider->GetPanelId() == "Scene") {
scenePanelProvider = panelProvider.get();
continue;
}
@ -284,23 +482,250 @@ void MetaCoreEditorApp::DrawEditorFrame() {
}
}
if (scenePanelProvider != nullptr && ModuleRegistry_.IsPanelOpen("Scene")) {
if (ModuleRegistry_.IsPanelOpen("Scene")) {
ImGuiDockNode* centralNode = ImGui::DockBuilderGetCentralNode(dockSpaceId);
if (centralNode != nullptr) {
const ImVec2 overlayPosition = centralNode->Pos;
const ImVec2 overlaySize = centralNode->Size;
constexpr float sceneToolbarHeight = 34.0F;
MetaCoreSceneViewportState& viewportState = EditorContext_->GetSceneViewportState();
const ImVec2 mainViewportPosition = ImGui::GetMainViewport()->Pos;
viewportState.Left = centralNode->Pos.x - mainViewportPosition.x;
viewportState.Top = centralNode->Pos.y - mainViewportPosition.y + sceneToolbarHeight;
viewportState.Width = centralNode->Size.x;
if (viewportState.Width < 1.0F) {
viewportState.Width = 1.0F;
}
viewportState.Height = centralNode->Size.y - sceneToolbarHeight;
if (viewportState.Height < 1.0F) {
viewportState.Height = 1.0F;
}
ImGui::SetNextWindowPos(overlayPosition);
ImGui::SetNextWindowSize(overlaySize);
const ImVec2 mousePosition = ImGui::GetMousePos();
viewportState.Hovered =
mousePosition.x >= centralNode->Pos.x &&
mousePosition.x <= centralNode->Pos.x + centralNode->Size.x &&
mousePosition.y >= centralNode->Pos.y + sceneToolbarHeight &&
mousePosition.y <= centralNode->Pos.y + centralNode->Size.y;
viewportState.Focused = viewportState.Hovered && !ImGui::GetIO().WantCaptureMouse;
if (viewportState.Focused && !ImGui::GetIO().WantCaptureKeyboard && EditorContext_->GetInput().WasKeyPressed(MetaCoreInputKey::Focus)) {
if (MetaCoreGameObject* selectedObject = EditorContext_->GetSelectedGameObject(); selectedObject != nullptr) {
EditorContext_->GetCameraController().FocusGameObject(*selectedObject);
EditorContext_->AddConsoleMessage(MetaCoreLogLevel::Info, "Scene", "相机已聚焦到当前对象");
}
}
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);
}
}
MetaCoreSceneView sceneView = EditorContext_->GetCameraController().BuildSceneView();
sceneView.SelectedObjectId = EditorContext_->GetSelectedObjectId();
ViewportRenderer_.SetViewportRect(MetaCoreViewportRect{
viewportState.Left,
viewportState.Top,
viewportState.Width,
viewportState.Height
});
ViewportRenderer_.RenderSceneToViewport(Scene_, sceneView);
bool gizmoHovering = false;
bool gizmoUsing = false;
ImGui::SetNextWindowPos(ImVec2(centralNode->Pos.x, centralNode->Pos.y + sceneToolbarHeight));
ImGui::SetNextWindowSize(ImVec2(viewportState.Width, viewportState.Height));
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 gizmoCanvasFlags =
ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoDocking |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoNavFocus |
ImGuiWindowFlags_NoBackground |
ImGuiWindowFlags_NoInputs;
bool gizmoCanvasOpen = true;
ImGui::Begin("MetaCoreSceneGizmoCanvas", &gizmoCanvasOpen, gizmoCanvasFlags);
// Guard against degenerate camera and aspect ratio.
const float cameraDistance = glm::distance(sceneView.CameraPosition, sceneView.CameraTarget);
const float gizmoAspect = viewportState.Width / viewportState.Height;
MetaCoreGameObject* selectedObject = EditorContext_->GetSelectedGameObject();
if (selectedObject != nullptr &&
EditorContext_->GetGizmoOperation() != MetaCoreGizmoOperation::None &&
cameraDistance >= 0.001F &&
gizmoAspect >= 0.001F) {
// Per reference guide: use Panda matrices directly.
// MetaCoreConvertPandaMatrixToGlm correctly handles Panda row-major→GLM column-major.
// TryGetEditorCameraMatrices returns:
// viewMatrix = inverse(EditorCamera.get_mat(SceneRoot))
// projectionMatrix = lens->get_projection_mat()
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
);
}
// Model matrix: Panda world matrix used directly.
glm::mat4 transformMatrix = MetaCoreBuildPandaSpaceTransformMatrix(selectedObject->Transform);
(void)ViewportRenderer_.TryGetObjectWorldMatrix(selectedObject->Id, transformMatrix);
const glm::mat4 originalTransformMatrix = transformMatrix;
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();
if (gizmoUsing) {
// transformMatrix is in Panda space — apply directly.
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;
}
}
constexpr float viewManipulateSize = 112.0F;
const ImVec2 canvasPosition = ImGui::GetWindowPos();
ImGuizmo::ViewManipulate(
glm::value_ptr(gizmoViewMatrix),
cameraDistance,
ImVec2(canvasPosition.x + viewportState.Width - viewManipulateSize - 16.0F, canvasPosition.y + 16.0F),
ImVec2(viewManipulateSize, viewManipulateSize),
IM_COL32(26, 30, 36, 180)
);
if (ImGuizmo::IsUsingViewManipulate()) {
// gizmoViewMatrix modified → camera moved. Extract Panda-space camera pos.
const glm::mat4 newCameraWorld = glm::inverse(gizmoViewMatrix);
const glm::vec3 newPandaEye = glm::vec3(newCameraWorld[3]);
MetaCoreSceneView manipulatedSceneView = sceneView;
manipulatedSceneView.CameraPosition = MetaCoreFromPandaSpacePoint(newPandaEye);
manipulatedSceneView.CameraTarget = selectedObject->Transform.Position;
EditorContext_->GetCameraController().ApplySceneView(manipulatedSceneView);
}
}
ImGui::End();
ImGui::PopStyleVar(3);
if (!gizmoUsing) {
EditorContext_->GetCameraController().Update(viewportState, EditorContext_->GetInput());
sceneView = EditorContext_->GetCameraController().BuildSceneView();
sceneView.SelectedObjectId = EditorContext_->GetSelectedObjectId();
}
if (viewportState.Hovered && !gizmoHovering && !gizmoUsing && !ImGui::GetIO().WantCaptureMouse && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
const MetaCoreId pickedObjectId = MetaCorePickGameObjectFromViewport(
Scene_,
sceneView,
viewportState,
EditorContext_->GetInput().GetCursorPosition()
);
if (pickedObjectId != 0 && pickedObjectId != EditorContext_->GetSelectedObjectId()) {
EditorContext_->SetSelectedObjectId(pickedObjectId);
if (const MetaCoreGameObject* selectedObject = EditorContext_->GetSelectedGameObject(); selectedObject != nullptr) {
EditorContext_->AddConsoleMessage(MetaCoreLogLevel::Info, "Scene", "视口已选中对象: " + selectedObject->Name);
}
sceneView.SelectedObjectId = pickedObjectId;
}
}
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));
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0F, 0.0F, 0.0F, 0.0F));
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.0F, 0.0F, 0.0F, 0.0F));
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.0F, 0.0F, 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 |
@ -309,14 +734,55 @@ void MetaCoreEditorApp::DrawEditorFrame() {
ImGuiWindowFlags_NoNavFocus |
ImGuiWindowFlags_NoBackground;
bool sceneOpen = true;
ImGui::Begin("MetaCoreSceneOverlay", &sceneOpen, sceneOverlayFlags);
scenePanelProvider->DrawPanel(*EditorContext_);
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::PopStyleColor(3);
ImGui::PopStyleVar(3);
} else {
ViewportRenderer_.SetViewportRect(MetaCoreViewportRect{});
}
} else {
ViewportRenderer_.SetViewportRect(MetaCoreViewportRect{});
}
}

View File

@ -50,6 +50,20 @@ void MetaCoreEditorCameraController::FocusGameObject(const MetaCoreGameObject& g
Distance_ = std::max(3.5F, scaleRadius * 4.0F + 2.5F);
}
void MetaCoreEditorCameraController::ApplySceneView(const MetaCoreSceneView& sceneView) {
Target_ = sceneView.CameraTarget;
FieldOfViewDegrees_ = sceneView.VerticalFieldOfViewDegrees;
const glm::vec3 offset = sceneView.CameraPosition - sceneView.CameraTarget;
Distance_ = std::max(0.1F, glm::length(offset));
if (Distance_ <= 0.1001F) {
return;
}
const glm::vec3 forward = glm::normalize(sceneView.CameraTarget - sceneView.CameraPosition);
PitchDegrees_ = glm::degrees(std::asin(glm::clamp(forward.y, -1.0F, 1.0F)));
YawDegrees_ = glm::degrees(std::atan2(forward.x, forward.z));
}
MetaCoreSceneView MetaCoreEditorCameraController::BuildSceneView() const {
MetaCoreSceneView sceneView;
sceneView.CameraPosition = GetCameraPosition();

View File

@ -16,16 +16,17 @@ public:
void FocusGameObject(const MetaCoreGameObject& gameObject);
[[nodiscard]] MetaCoreSceneView BuildSceneView() const;
[[nodiscard]] glm::vec3 GetCameraPosition() const;
void ApplySceneView(const MetaCoreSceneView& sceneView);
private:
[[nodiscard]] glm::vec3 GetForwardDirection() const;
[[nodiscard]] glm::vec3 GetRightDirection() const;
[[nodiscard]] glm::vec3 GetUpDirection() const;
glm::vec3 Target_{0.0F, 0.7F, 0.0F};
float Distance_ = 7.5F;
float YawDegrees_ = 38.0F;
float PitchDegrees_ = -28.0F;
glm::vec3 Target_{0.0F, 0.5F, 0.0F};
float Distance_ = 8.5F;
float YawDegrees_ = 45.0F;
float PitchDegrees_ = -32.0F;
float FieldOfViewDegrees_ = 60.0F;
};

View File

@ -46,6 +46,10 @@ MetaCoreId MetaCoreEditorContext::GetSelectedObjectId() const { return SelectedO
void MetaCoreEditorContext::SetSelectedObjectId(MetaCoreId objectId) { SelectedObjectId_ = objectId; }
MetaCoreGameObject* MetaCoreEditorContext::GetSelectedGameObject() { return Scene_.FindGameObject(SelectedObjectId_); }
const MetaCoreGameObject* MetaCoreEditorContext::GetSelectedGameObject() const { return Scene_.FindGameObject(SelectedObjectId_); }
MetaCoreGizmoOperation MetaCoreEditorContext::GetGizmoOperation() const { return GizmoOperation_; }
void MetaCoreEditorContext::SetGizmoOperation(MetaCoreGizmoOperation operation) { GizmoOperation_ = operation; }
MetaCoreGizmoMode MetaCoreEditorContext::GetGizmoMode() const { return GizmoMode_; }
void MetaCoreEditorContext::SetGizmoMode(MetaCoreGizmoMode mode) { GizmoMode_ = mode; }
void MetaCoreEditorContext::AddConsoleMessage(MetaCoreLogLevel level, const std::string& category, const std::string& message) {
LogService_.AddEntry(level, category, message);

View File

@ -17,6 +17,18 @@ struct MetaCoreGameObject;
class MetaCoreEditorModuleRegistry;
class MetaCoreEditorCameraController;
enum class MetaCoreGizmoOperation {
None = 0,
Translate,
Rotate,
Scale
};
enum class MetaCoreGizmoMode {
Local = 0,
World
};
struct MetaCoreSceneViewportState {
float Left = 0.0F;
float Top = 0.0F;
@ -57,6 +69,10 @@ public:
void SetSelectedObjectId(MetaCoreId objectId);
[[nodiscard]] MetaCoreGameObject* GetSelectedGameObject();
[[nodiscard]] const MetaCoreGameObject* GetSelectedGameObject() const;
[[nodiscard]] MetaCoreGizmoOperation GetGizmoOperation() const;
void SetGizmoOperation(MetaCoreGizmoOperation operation);
[[nodiscard]] MetaCoreGizmoMode GetGizmoMode() const;
void SetGizmoMode(MetaCoreGizmoMode mode);
void AddConsoleMessage(MetaCoreLogLevel level, const std::string& category, const std::string& message);
void SetDockLayoutBuilt(bool built);
[[nodiscard]] bool HasDockLayoutBuilt() const;
@ -71,6 +87,8 @@ private:
std::unique_ptr<MetaCoreEditorCameraController> CameraController_;
MetaCoreSceneViewportState SceneViewportState_{};
MetaCoreId SelectedObjectId_ = 0;
MetaCoreGizmoOperation GizmoOperation_ = MetaCoreGizmoOperation::Translate;
MetaCoreGizmoMode GizmoMode_ = MetaCoreGizmoMode::Local;
bool DockLayoutBuilt_ = false;
};

View File

@ -40,6 +40,11 @@ bool MetaCoreInput::IsMouseButtonDown(MetaCoreMouseButton button) const {
return CurrentMouseStates_[static_cast<std::size_t>(button)];
}
bool MetaCoreInput::WasMouseButtonPressed(MetaCoreMouseButton button) const {
const std::size_t buttonIndex = static_cast<std::size_t>(button);
return CurrentMouseStates_[buttonIndex] && !PreviousMouseStates_[buttonIndex];
}
float MetaCoreInput::GetMouseWheelDelta() const {
return MouseWheelDelta_;
}

View File

@ -1,7 +1,6 @@
#include "MetaCorePlatform/MetaCoreWindow.h"
#include "graphicsWindow.h"
#include "graphicsWindowProc.h"
#include "load_prc_file.h"
#include "nativeWindowHandle.h"
#include "pandaFramework.h"
@ -13,6 +12,7 @@
#include <windows.h>
#include <chrono>
#include <unordered_map>
#include <memory>
#include <utility>
@ -55,6 +55,11 @@ bool MetaCoreIsVirtualKeyDown(int virtualKey) {
return (GetAsyncKeyState(virtualKey) & 0x8000) != 0;
}
std::unordered_map<HWND, class MetaCoreWindow::MetaCoreWindowImpl*>& MetaCoreGetWindowMap() {
static std::unordered_map<HWND, class MetaCoreWindow::MetaCoreWindowImpl*> windowMap;
return windowMap;
}
} // namespace
class MetaCoreWindow::MetaCoreWindowImpl {
@ -65,37 +70,41 @@ public:
HWND NativeHandle = nullptr;
MetaCoreInput Input{};
MetaCoreNativeWindowMessageHandler MessageHandler{};
std::unique_ptr<class MetaCoreWindowMessageProc> MessageProc{};
WNDPROC OriginalWindowProc = nullptr;
std::chrono::steady_clock::time_point LastFrameTime = std::chrono::steady_clock::now();
float DeltaSeconds = 1.0F / 60.0F;
bool Initialized = false;
bool CloseRequested = false;
};
class MetaCoreWindowMessageProc final : public GraphicsWindowProc {
public:
explicit MetaCoreWindowMessageProc(MetaCoreWindow::MetaCoreWindowImpl& owner)
: Owner_(owner) {
LRESULT CALLBACK MetaCoreWindowSubclassProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
auto& windowMap = MetaCoreGetWindowMap();
const auto iterator = windowMap.find(hwnd);
if (iterator == windowMap.end() || iterator->second == nullptr) {
return DefWindowProc(hwnd, msg, wparam, lparam);
}
LONG wnd_proc(GraphicsWindow*, HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) override;
private:
MetaCoreWindow::MetaCoreWindowImpl& Owner_;
};
LONG MetaCoreWindowMessageProc::wnd_proc(GraphicsWindow*, HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
if (Owner_.MessageHandler) {
Owner_.MessageHandler(hwnd, msg, static_cast<std::uintptr_t>(wparam), static_cast<std::intptr_t>(lparam));
MetaCoreWindow::MetaCoreWindowImpl& owner = *iterator->second;
bool handledByEditor = false;
if (owner.MessageHandler) {
handledByEditor = owner.MessageHandler(hwnd, msg, static_cast<std::uintptr_t>(wparam), static_cast<std::intptr_t>(lparam));
}
if (msg == WM_MOUSEWHEEL) {
Owner_.Input.AddMouseWheelDelta(static_cast<float>(GET_WHEEL_DELTA_WPARAM(wparam)) / static_cast<float>(WHEEL_DELTA));
owner.Input.AddMouseWheelDelta(static_cast<float>(GET_WHEEL_DELTA_WPARAM(wparam)) / static_cast<float>(WHEEL_DELTA));
} else if (msg == WM_CLOSE) {
Owner_.CloseRequested = true;
owner.CloseRequested = true;
}
return 0;
if (handledByEditor) {
return 1;
}
if (owner.OriginalWindowProc != nullptr) {
return CallWindowProc(owner.OriginalWindowProc, hwnd, msg, wparam, lparam);
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
MetaCoreWindow::MetaCoreWindow()
@ -133,10 +142,10 @@ bool MetaCoreWindow::Initialize(int width, int height, const std::string& title)
return false;
}
Impl_->MessageProc = std::make_unique<MetaCoreWindowMessageProc>(*Impl_);
if (Impl_->GraphicsWindowHandle->supports_window_procs()) {
Impl_->GraphicsWindowHandle->add_window_proc(Impl_->MessageProc.get());
}
MetaCoreGetWindowMap()[Impl_->NativeHandle] = Impl_.get();
Impl_->OriginalWindowProc = reinterpret_cast<WNDPROC>(
SetWindowLongPtr(Impl_->NativeHandle, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&MetaCoreWindowSubclassProc))
);
Impl_->CloseRequested = false;
Impl_->LastFrameTime = std::chrono::steady_clock::now();
@ -149,12 +158,13 @@ void MetaCoreWindow::Shutdown() {
return;
}
if (Impl_->GraphicsWindowHandle != nullptr && Impl_->MessageProc != nullptr && Impl_->GraphicsWindowHandle->supports_window_procs()) {
Impl_->GraphicsWindowHandle->remove_window_proc(Impl_->MessageProc.get());
if (Impl_->NativeHandle != nullptr) {
MetaCoreGetWindowMap().erase(Impl_->NativeHandle);
if (Impl_->OriginalWindowProc != nullptr) {
SetWindowLongPtr(Impl_->NativeHandle, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(Impl_->OriginalWindowProc));
}
}
Impl_->MessageProc.reset();
if (Impl_->WindowFrameworkHandle != nullptr) {
Impl_->Framework.close_window(Impl_->WindowFrameworkHandle);
Impl_->WindowFrameworkHandle = nullptr;
@ -162,6 +172,7 @@ void MetaCoreWindow::Shutdown() {
Impl_->GraphicsWindowHandle = nullptr;
Impl_->NativeHandle = nullptr;
Impl_->OriginalWindowProc = nullptr;
Impl_->Framework.close_framework();
Impl_->Initialized = false;
}

View File

@ -49,6 +49,9 @@ public:
// 判断鼠标按键当前是否按下。
[[nodiscard]] bool IsMouseButtonDown(MetaCoreMouseButton button) const;
// 判断鼠标按键是否在本帧刚刚按下。
[[nodiscard]] bool WasMouseButtonPressed(MetaCoreMouseButton button) const;
// 返回当前帧累计的滚轮值。
[[nodiscard]] float GetMouseWheelDelta() const;

View File

@ -13,7 +13,7 @@ namespace MetaCore {
class MetaCoreRenderDevice;
// 定义宿主窗口层向上暴露的原生消息回调,用于把 Win32 消息转交给 ImGui。
using MetaCoreNativeWindowMessageHandler = std::function<void(void*, unsigned int, std::uintptr_t, std::intptr_t)>;
using MetaCoreNativeWindowMessageHandler = std::function<bool(void*, unsigned int, std::uintptr_t, std::intptr_t)>;
// 封装 Panda3D 窗口、时间步进与基础输入采集。
class MetaCoreWindow {

View File

@ -40,4 +40,8 @@ void MetaCoreEditorViewportRenderer::RenderSceneToViewport(const MetaCoreScene&
SceneBridge_.SyncScene(scene);
}
bool MetaCoreEditorViewportRenderer::TryGetObjectWorldMatrix(MetaCoreId objectId, glm::mat4& worldMatrix) const {
return SceneBridge_.TryGetObjectWorldMatrix(objectId, worldMatrix);
}
} // namespace MetaCore

View File

@ -8,6 +8,12 @@
#include "ambientLight.h"
#include "camera.h"
#include "directionalLight.h"
#include "geom.h"
#include "geomNode.h"
#include "geomTriangles.h"
#include "geomVertexData.h"
#include "geomVertexFormat.h"
#include "geomVertexWriter.h"
#include "lineSegs.h"
#include "loader.h"
#include "nodePath.h"
@ -18,6 +24,7 @@
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/ext/matrix_transform.hpp>
#include <glm/gtx/euler_angles.hpp>
#include <glm/mat4x4.hpp>
#include <memory>
#include <unordered_map>
@ -59,11 +66,28 @@ LMatrix4f MetaCoreConvertTransformToPanda(const MetaCoreTransformComponent& tran
}
LPoint3f MetaCoreToPandaPoint(const glm::vec3& value) {
return LPoint3f(value.x, value.z, value.y);
// MetaCore 使用右手系的 X 右 / Y 上 / Z 前。
// Panda3D 使用右手系的 X 右 / Y 前 / Z 上。
// 仅交换 Y/Z 会引入镜像,因此这里同时对前向轴取反,保持手性一致。
return LPoint3f(value.x, -value.z, value.y);
}
LVector3f MetaCoreToPandaVector(const glm::vec3& value) {
return LVector3f(value.x, value.z, value.y);
return LVector3f(value.x, -value.z, value.y);
}
LVecBase3f MetaCoreToPandaScale(const glm::vec3& value) {
return LVecBase3f(value.x, value.z, value.y);
}
void MetaCoreApplyTransformToPandaNode(const MetaCoreTransformComponent& transform, NodePath& nodePath) {
nodePath.set_pos(MetaCoreToPandaPoint(transform.Position));
nodePath.set_hpr(
transform.RotationEulerDegrees.y,
transform.RotationEulerDegrees.x,
transform.RotationEulerDegrees.z
);
nodePath.set_scale(MetaCoreToPandaScale(transform.Scale));
}
NodePath MetaCoreCreateGridNode(const NodePath& parentNode) {
@ -102,6 +126,83 @@ NodePath MetaCoreCreateAxisNode(const NodePath& parentNode) {
return parentNode.attach_new_node(axisBuilder.create());
}
NodePath MetaCoreCreateUnitCubeNode(const NodePath& parentNode) {
PT(GeomVertexData) vertexData = new GeomVertexData(
"MetaCoreUnitCube",
GeomVertexFormat::get_v3n3(),
Geom::UH_static
);
GeomVertexWriter vertexWriter(vertexData, "vertex");
GeomVertexWriter normalWriter(vertexData, "normal");
const auto addFace = [&](const glm::vec3& normal, const glm::vec3& a, const glm::vec3& b, const glm::vec3& c, const glm::vec3& d) {
const glm::vec3 corners[4] = {a, b, c, d};
for (const glm::vec3& corner : corners) {
const LPoint3f pandaPoint = MetaCoreToPandaPoint(corner);
const LVector3f pandaNormal = MetaCoreToPandaVector(normal);
vertexWriter.add_data3(pandaPoint);
normalWriter.add_data3(pandaNormal);
}
};
addFace(glm::vec3(0.0F, 0.0F, 1.0F),
glm::vec3(-0.5F, -0.5F, 0.5F),
glm::vec3(0.5F, -0.5F, 0.5F),
glm::vec3(0.5F, 0.5F, 0.5F),
glm::vec3(-0.5F, 0.5F, 0.5F));
addFace(glm::vec3(0.0F, 0.0F, -1.0F),
glm::vec3(0.5F, -0.5F, -0.5F),
glm::vec3(-0.5F, -0.5F, -0.5F),
glm::vec3(-0.5F, 0.5F, -0.5F),
glm::vec3(0.5F, 0.5F, -0.5F));
addFace(glm::vec3(-1.0F, 0.0F, 0.0F),
glm::vec3(-0.5F, -0.5F, -0.5F),
glm::vec3(-0.5F, -0.5F, 0.5F),
glm::vec3(-0.5F, 0.5F, 0.5F),
glm::vec3(-0.5F, 0.5F, -0.5F));
addFace(glm::vec3(1.0F, 0.0F, 0.0F),
glm::vec3(0.5F, -0.5F, 0.5F),
glm::vec3(0.5F, -0.5F, -0.5F),
glm::vec3(0.5F, 0.5F, -0.5F),
glm::vec3(0.5F, 0.5F, 0.5F));
addFace(glm::vec3(0.0F, 1.0F, 0.0F),
glm::vec3(-0.5F, 0.5F, 0.5F),
glm::vec3(0.5F, 0.5F, 0.5F),
glm::vec3(0.5F, 0.5F, -0.5F),
glm::vec3(-0.5F, 0.5F, -0.5F));
addFace(glm::vec3(0.0F, -1.0F, 0.0F),
glm::vec3(-0.5F, -0.5F, -0.5F),
glm::vec3(0.5F, -0.5F, -0.5F),
glm::vec3(0.5F, -0.5F, 0.5F),
glm::vec3(-0.5F, -0.5F, 0.5F));
PT(GeomTriangles) triangles = new GeomTriangles(Geom::UH_static);
for (int faceIndex = 0; faceIndex < 6; ++faceIndex) {
const int baseVertex = faceIndex * 4;
triangles->add_vertices(baseVertex + 0, baseVertex + 1, baseVertex + 2);
triangles->add_vertices(baseVertex + 0, baseVertex + 2, baseVertex + 3);
}
PT(Geom) geom = new Geom(vertexData);
geom->add_primitive(triangles);
PT(GeomNode) geomNode = new GeomNode("MetaCoreUnitCubeNode");
geomNode->add_geom(geom);
return parentNode.attach_new_node(geomNode);
}
glm::mat4 MetaCoreConvertPandaMatrixToGlm(const LMatrix4f& matrix) {
glm::mat4 result(1.0F);
for (int col = 0; col < 4; ++col) {
for (int row = 0; row < 4; ++row) {
// Transpose mathematical matrix to switch from Panda's v*M to GLM's M*v.
result[col][row] = matrix.get_cell(col, row);
}
}
return result;
}
} // namespace
class MetaCorePandaSceneBridge::MetaCorePandaSceneBridgeImpl {
@ -207,7 +308,7 @@ void MetaCorePandaSceneBridge::SyncScene(const MetaCoreScene& scene) {
}
objectState.RootNode.set_name(gameObject.Name);
objectState.RootNode.set_mat(MetaCoreConvertTransformToPanda(gameObject.Transform));
MetaCoreApplyTransformToPandaNode(gameObject.Transform, objectState.RootNode);
if (gameObject.ParentId != 0) {
const auto parentIterator = Impl_->ObjectStates.find(gameObject.ParentId);
@ -222,7 +323,7 @@ void MetaCorePandaSceneBridge::SyncScene(const MetaCoreScene& scene) {
if (gameObject.MeshRenderer.has_value()) {
if (objectState.MeshNode.is_empty()) {
objectState.MeshNode = windowFrameworkHandle->load_model(objectState.RootNode, Filename("models/box"));
objectState.MeshNode = MetaCoreCreateUnitCubeNode(objectState.RootNode);
}
if (!objectState.MeshNode.is_empty()) {
@ -309,9 +410,31 @@ void MetaCorePandaSceneBridge::ApplySceneView(const MetaCoreSceneView& sceneView
if (cameraNode != nullptr) {
auto* perspectiveLens = DCAST(PerspectiveLens, cameraNode->get_lens());
if (perspectiveLens != nullptr) {
perspectiveLens->set_fov(sceneView.VerticalFieldOfViewDegrees);
const float aspect = std::max(0.001F, perspectiveLens->get_aspect_ratio());
const float vFovRad = glm::radians(sceneView.VerticalFieldOfViewDegrees);
const float hFovDeg = glm::degrees(2.0F * std::atan(aspect * std::tan(vFovRad * 0.5F)));
perspectiveLens->set_fov(LVecBase2f(std::max(0.001F, hFovDeg), std::max(0.001F, sceneView.VerticalFieldOfViewDegrees)));
}
}
}
bool MetaCorePandaSceneBridge::TryGetObjectWorldMatrix(MetaCoreId objectId, glm::mat4& worldMatrix) const {
if (Impl_ == nullptr || Impl_->RenderDevice == nullptr) {
return false;
}
auto* sceneRootHandle = static_cast<NodePath*>(Impl_->RenderDevice->GetNativeSceneRootHandle());
if (sceneRootHandle == nullptr || sceneRootHandle->is_empty()) {
return false;
}
const auto objectIterator = Impl_->ObjectStates.find(objectId);
if (objectIterator == Impl_->ObjectStates.end() || objectIterator->second.RootNode.is_empty()) {
return false;
}
worldMatrix = MetaCoreConvertPandaMatrixToGlm(objectIterator->second.RootNode.get_mat(*sceneRootHandle));
return true;
}
} // namespace MetaCore

View File

@ -11,11 +11,29 @@
#include "perspectiveLens.h"
#include "windowFramework.h"
#include <glm/mat4x4.hpp>
#include <algorithm>
#include <memory>
namespace MetaCore {
namespace {
glm::mat4 MetaCoreConvertPandaMatrixToGlm(const LMatrix4f& matrix) {
glm::mat4 result(1.0F);
for (int col = 0; col < 4; ++col) {
for (int row = 0; row < 4; ++row) {
// Transpose mathematical matrix: result[col][row] is GLM(row, col).
// We want GLM(row, col) = Panda(col, row) to switch v*M to M*v.
result[col][row] = matrix.get_cell(col, row);
}
}
return result;
}
} // namespace
class MetaCoreRenderDevice::MetaCoreRenderDeviceImpl {
public:
MetaCoreWindow* Window = nullptr;
@ -153,6 +171,12 @@ void MetaCoreRenderDevice::SetSceneViewportRect(const MetaCoreViewportRect& view
1.0F - clampedBottom,
1.0F - clampedTop
);
if (auto* cameraNode = DCAST(Camera, Impl_->EditorCamera.node()); cameraNode != nullptr) {
if (auto* perspectiveLens = DCAST(PerspectiveLens, cameraNode->get_lens()); perspectiveLens != nullptr) {
perspectiveLens->set_aspect_ratio(viewportRect.Width / viewportRect.Height);
}
}
}
void* MetaCoreRenderDevice::GetNativeWindowFrameworkHandle() const {
@ -167,4 +191,28 @@ void* MetaCoreRenderDevice::GetNativeEditorCameraHandle() const {
return Impl_->EditorCamera.is_empty() ? nullptr : static_cast<void*>(&Impl_->EditorCamera);
}
bool MetaCoreRenderDevice::TryGetEditorCameraMatrices(glm::mat4& viewMatrix, glm::mat4& projectionMatrix) const {
if (!Impl_->Initialized || Impl_->EditorCamera.is_empty()) {
return false;
}
auto* cameraNode = DCAST(Camera, Impl_->EditorCamera.node());
if (cameraNode == nullptr) {
return false;
}
auto* lens = cameraNode->get_lens();
if (lens == nullptr) {
return false;
}
// view = inverse of camera world matrix (Panda world → Panda camera space).
const glm::mat4 cameraWorldMatrix = MetaCoreConvertPandaMatrixToGlm(Impl_->EditorCamera.get_mat(Impl_->SceneRoot));
viewMatrix = glm::inverse(cameraWorldMatrix);
// Panda's native projection matrix works correctly once mathematically transposed.
projectionMatrix = MetaCoreConvertPandaMatrixToGlm(lens->get_projection_mat());
return true;
}
} // namespace MetaCore

View File

@ -23,6 +23,9 @@ public:
// 将场景状态同步到 Panda3D并更新编辑器相机。
void RenderSceneToViewport(const MetaCoreScene& scene, const MetaCoreSceneView& sceneView);
// 返回 Panda3D 中对象当前真实使用的世界矩阵。
[[nodiscard]] bool TryGetObjectWorldMatrix(MetaCoreId objectId, glm::mat4& worldMatrix) const;
private:
MetaCoreRenderDevice* RenderDevice_ = nullptr;
MetaCorePandaSceneBridge SceneBridge_{};

View File

@ -1,5 +1,9 @@
#pragma once
#include "MetaCoreFoundation/MetaCoreId.h"
#include <glm/mat4x4.hpp>
#include <memory>
namespace MetaCore {
@ -26,6 +30,9 @@ public:
// 更新编辑器相机与当前选中对象高亮状态。
void ApplySceneView(const MetaCoreSceneView& sceneView);
// 返回 Panda3D 场景中对象当前真实使用的世界矩阵。
[[nodiscard]] bool TryGetObjectWorldMatrix(MetaCoreId objectId, glm::mat4& worldMatrix) const;
private:
class MetaCorePandaSceneBridgeImpl;
std::unique_ptr<MetaCorePandaSceneBridgeImpl> Impl_{};

View File

@ -2,6 +2,8 @@
#include "MetaCoreRender/MetaCoreRenderTypes.h"
#include <glm/mat4x4.hpp>
#include <memory>
namespace MetaCore {
@ -40,6 +42,9 @@ public:
// 返回编辑器相机节点原生对象指针,仅供 Panda3D 桥接层使用。
[[nodiscard]] void* GetNativeEditorCameraHandle() const;
// 返回 Panda3D 当前真实使用的编辑器相机视图矩阵和投影矩阵。
[[nodiscard]] bool TryGetEditorCameraMatrices(glm::mat4& viewMatrix, glm::mat4& projectionMatrix) const;
private:
class MetaCoreRenderDeviceImpl;

3164
third_party/ImGuizmo/ImGuizmo.cpp vendored Normal file

File diff suppressed because it is too large Load Diff

306
third_party/ImGuizmo/ImGuizmo.h vendored Normal file
View File

@ -0,0 +1,306 @@
// https://github.com/CedricGuillemet/ImGuizmo
// v1.92.5 WIP
//
// The MIT License(MIT)
//
// Copyright(c) 2016-2021 Cedric Guillemet
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files(the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions :
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// -------------------------------------------------------------------------------------------
// History :
// 2019/11/03 View gizmo
// 2016/09/11 Behind camera culling. Scaling Delta matrix not multiplied by source matrix scales. local/world rotation and translation fixed. Display message is incorrect (X: ... Y:...) in local mode.
// 2016/09/09 Hatched negative axis. Snapping. Documentation update.
// 2016/09/04 Axis switch and translation plan autohiding. Scale transform stability improved
// 2016/09/01 Mogwai changed to Manipulate. Draw debug cube. Fixed inverted scale. Mixing scale and translation/rotation gives bad results.
// 2016/08/31 First version
//
// -------------------------------------------------------------------------------------------
// Future (no order):
//
// - Multi view
// - display rotation/translation/scale infos in local/world space and not only local
// - finish local/world matrix application
// - OPERATION as bitmask
//
// -------------------------------------------------------------------------------------------
// Example
#if 0
void EditTransform(const Camera& camera, matrix_t& matrix)
{
static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::ROTATE);
static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::WORLD);
if (ImGui::IsKeyPressed(90))
mCurrentGizmoOperation = ImGuizmo::TRANSLATE;
if (ImGui::IsKeyPressed(69))
mCurrentGizmoOperation = ImGuizmo::ROTATE;
if (ImGui::IsKeyPressed(82)) // r Key
mCurrentGizmoOperation = ImGuizmo::SCALE;
if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE))
mCurrentGizmoOperation = ImGuizmo::TRANSLATE;
ImGui::SameLine();
if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE))
mCurrentGizmoOperation = ImGuizmo::ROTATE;
ImGui::SameLine();
if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE))
mCurrentGizmoOperation = ImGuizmo::SCALE;
float matrixTranslation[3], matrixRotation[3], matrixScale[3];
ImGuizmo::DecomposeMatrixToComponents(matrix.m16, matrixTranslation, matrixRotation, matrixScale);
ImGui::InputFloat3("Tr", matrixTranslation, 3);
ImGui::InputFloat3("Rt", matrixRotation, 3);
ImGui::InputFloat3("Sc", matrixScale, 3);
ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix.m16);
if (mCurrentGizmoOperation != ImGuizmo::SCALE)
{
if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL))
mCurrentGizmoMode = ImGuizmo::LOCAL;
ImGui::SameLine();
if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD))
mCurrentGizmoMode = ImGuizmo::WORLD;
}
static bool useSnap(false);
if (ImGui::IsKeyPressed(83))
useSnap = !useSnap;
ImGui::Checkbox("", &useSnap);
ImGui::SameLine();
vec_t snap;
switch (mCurrentGizmoOperation)
{
case ImGuizmo::TRANSLATE:
snap = config.mSnapTranslation;
ImGui::InputFloat3("Snap", &snap.x);
break;
case ImGuizmo::ROTATE:
snap = config.mSnapRotation;
ImGui::InputFloat("Angle Snap", &snap.x);
break;
case ImGuizmo::SCALE:
snap = config.mSnapScale;
ImGui::InputFloat("Scale Snap", &snap.x);
break;
}
ImGuiIO& io = ImGui::GetIO();
ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y);
ImGuizmo::Manipulate(camera.mView.m16, camera.mProjection.m16, mCurrentGizmoOperation, mCurrentGizmoMode, matrix.m16, NULL, useSnap ? &snap.x : NULL);
}
#endif
#pragma once
#ifdef USE_IMGUI_API
#include "imconfig.h"
#endif
#ifndef IMGUI_API
#define IMGUI_API
#endif
#ifndef IMGUIZMO_NAMESPACE
#define IMGUIZMO_NAMESPACE ImGuizmo
#endif
struct ImGuiWindow;
namespace IMGUIZMO_NAMESPACE
{
// call inside your own window and before Manipulate() in order to draw gizmo to that window.
// Or pass a specific ImDrawList to draw to (e.g. ImGui::GetForegroundDrawList()).
IMGUI_API void SetDrawlist(ImDrawList* drawlist = nullptr);
// call BeginFrame right after ImGui_XXXX_NewFrame();
IMGUI_API void BeginFrame();
// this is necessary because when imguizmo is compiled into a dll, and imgui into another
// globals are not shared between them.
// More details at https://stackoverflow.com/questions/19373061/what-happens-to-global-and-static-variables-in-a-shared-library-when-it-is-dynam
// expose method to set imgui context
IMGUI_API void SetImGuiContext(ImGuiContext* ctx);
// return true if mouse cursor is over any gizmo control (axis, plan or screen component)
IMGUI_API bool IsOver();
// return true if mouse IsOver or if the gizmo is in moving state
IMGUI_API bool IsUsing();
// return true if the view gizmo is in moving state
IMGUI_API bool IsUsingViewManipulate();
// only check if your mouse is over the view manipulator - no matter whether it's active or not
IMGUI_API bool IsViewManipulateHovered();
// return true if any gizmo is in moving state
IMGUI_API bool IsUsingAny();
// enable/disable the gizmo. Stay in the state until next call to Enable.
// gizmo is rendered with gray half transparent color when disabled
IMGUI_API void Enable(bool enable);
// helper functions for manualy editing translation/rotation/scale with an input float
// translation, rotation and scale float points to 3 floats each
// Angles are in degrees (more suitable for human editing)
// example:
// float matrixTranslation[3], matrixRotation[3], matrixScale[3];
// ImGuizmo::DecomposeMatrixToComponents(gizmoMatrix.m16, matrixTranslation, matrixRotation, matrixScale);
// ImGui::InputFloat3("Tr", matrixTranslation, 3);
// ImGui::InputFloat3("Rt", matrixRotation, 3);
// ImGui::InputFloat3("Sc", matrixScale, 3);
// ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, gizmoMatrix.m16);
//
// These functions have some numerical stability issues for now. Use with caution.
IMGUI_API void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale);
IMGUI_API void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix);
IMGUI_API void SetRect(float x, float y, float width, float height);
// default is false
IMGUI_API void SetOrthographic(bool isOrthographic);
// Render a cube with face color corresponding to face normal. Usefull for debug/tests
IMGUI_API void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount);
IMGUI_API void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize);
// call it when you want a gizmo
// Needs view and projection matrices.
// matrix parameter is the source matrix (where will be gizmo be drawn) and might be transformed by the function. Return deltaMatrix is optional
// translation is applied in world space
enum OPERATION
{
TRANSLATE_X = (1u << 0),
TRANSLATE_Y = (1u << 1),
TRANSLATE_Z = (1u << 2),
ROTATE_X = (1u << 3),
ROTATE_Y = (1u << 4),
ROTATE_Z = (1u << 5),
ROTATE_SCREEN = (1u << 6),
SCALE_X = (1u << 7),
SCALE_Y = (1u << 8),
SCALE_Z = (1u << 9),
BOUNDS = (1u << 10),
SCALE_XU = (1u << 11),
SCALE_YU = (1u << 12),
SCALE_ZU = (1u << 13),
TRANSLATE = TRANSLATE_X | TRANSLATE_Y | TRANSLATE_Z,
ROTATE = ROTATE_X | ROTATE_Y | ROTATE_Z | ROTATE_SCREEN,
SCALE = SCALE_X | SCALE_Y | SCALE_Z,
SCALEU = SCALE_XU | SCALE_YU | SCALE_ZU, // universal
UNIVERSAL = TRANSLATE | ROTATE | SCALEU
};
inline OPERATION operator|(OPERATION lhs, OPERATION rhs)
{
return static_cast<OPERATION>(static_cast<int>(lhs) | static_cast<int>(rhs));
}
enum MODE
{
LOCAL,
WORLD
};
IMGUI_API bool Manipulate(const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix = NULL, const float* snap = NULL, const float* localBounds = NULL, const float* boundsSnap = NULL);
//
// Please note that this cubeview is patented by Autodesk : https://patents.google.com/patent/US7782319B2/en
// It seems to be a defensive patent in the US. I don't think it will bring troubles using it as
// other software are using the same mechanics. But just in case, you are now warned!
//
IMGUI_API void ViewManipulate(float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor);
// use this version if you did not call Manipulate before and you are just using ViewManipulate
IMGUI_API void ViewManipulate(float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor);
IMGUI_API void SetAlternativeWindow(ImGuiWindow* window);
[[deprecated("Use PushID/PopID instead.")]]
IMGUI_API void SetID(int id);
// ID stack/scopes
// Read the FAQ (docs/FAQ.md or http://dearimgui.org/faq) for more details about how ID are handled in dear imgui.
// - Those questions are answered and impacted by understanding of the ID stack system:
// - "Q: Why is my widget not reacting when I click on it?"
// - "Q: How can I have widgets with an empty label?"
// - "Q: How can I have multiple widgets with the same label?"
// - Short version: ID are hashes of the entire ID stack. If you are creating widgets in a loop you most likely
// want to push a unique identifier (e.g. object pointer, loop index) to uniquely differentiate them.
// - You can also use the "Label##foobar" syntax within widget label to distinguish them from each others.
// - In this header file we use the "label"/"name" terminology to denote a string that will be displayed + used as an ID,
// whereas "str_id" denote a string that is only used as an ID and not normally displayed.
IMGUI_API void PushID(const char* str_id); // push string into the ID stack (will hash string).
IMGUI_API void PushID(const char* str_id_begin, const char* str_id_end); // push string into the ID stack (will hash string).
IMGUI_API void PushID(const void* ptr_id); // push pointer into the ID stack (will hash pointer).
IMGUI_API void PushID(int int_id); // push integer into the ID stack (will hash integer).
IMGUI_API void PopID(); // pop from the ID stack.
IMGUI_API ImGuiID GetID(const char* str_id); // calculate unique ID (hash of whole ID stack + given parameter). e.g. if you want to query into ImGuiStorage yourself
IMGUI_API ImGuiID GetID(const char* str_id_begin, const char* str_id_end);
IMGUI_API ImGuiID GetID(const void* ptr_id);
// return true if the cursor is over the operation's gizmo
IMGUI_API bool IsOver(OPERATION op);
IMGUI_API void SetGizmoSizeClipSpace(float value);
// Allow axis to flip
// When true (default), the guizmo axis flip for better visibility
// When false, they always stay along the positive world/local axis
IMGUI_API void AllowAxisFlip(bool value);
// Configure the limit where axis are hidden
IMGUI_API void SetAxisLimit(float value);
// Set an axis mask to permanently hide a given axis (true -> hidden, false -> shown)
IMGUI_API void SetAxisMask(bool x, bool y, bool z);
// Configure the limit where planes are hiden
IMGUI_API void SetPlaneLimit(float value);
// from a x,y,z point in space and using Manipulation view/projection matrix, check if mouse is in pixel radius distance of that projected point
IMGUI_API bool IsOver(float* position, float pixelRadius);
enum COLOR
{
DIRECTION_X, // directionColor[0]
DIRECTION_Y, // directionColor[1]
DIRECTION_Z, // directionColor[2]
PLANE_X, // planeColor[0]
PLANE_Y, // planeColor[1]
PLANE_Z, // planeColor[2]
SELECTION, // selectionColor
INACTIVE, // inactiveColor
TRANSLATION_LINE, // translationLineColor
SCALE_LINE,
ROTATION_USING_BORDER,
ROTATION_USING_FILL,
HATCHED_AXIS_LINES,
TEXT,
TEXT_SHADOW,
COUNT
};
struct Style
{
IMGUI_API Style();
float TranslationLineThickness; // Thickness of lines for translation gizmo
float TranslationLineArrowSize; // Size of arrow at the end of lines for translation gizmo
float RotationLineThickness; // Thickness of lines for rotation gizmo
float RotationOuterLineThickness; // Thickness of line surrounding the rotation gizmo
float ScaleLineThickness; // Thickness of lines for scale gizmo
float ScaleLineCircleSize; // Size of circle at the end of lines for scale gizmo
float HatchedAxisLineThickness; // Thickness of hatched axis lines
float CenterCircleSize; // Size of circle at the center of the translate/scale gizmo
ImVec4 Colors[COLOR::COUNT];
};
IMGUI_API Style& GetStyle();
}