From 429a5742051902e6d9e7b8391866c5b439836dc3 Mon Sep 17 00:00:00 2001 From: Rowland <975945824@qq.com> Date: Mon, 29 Jun 2026 10:52:06 +0800 Subject: [PATCH] =?UTF-8?q?UI=E7=B3=BB=E7=BB=9F=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Private/MetaCoreEditorApp.cpp | 2 +- .../MetaCoreSceneInteractionService.cpp | 218 +++++++++++++++--- .../MetaCoreSceneInteractionService.h | 2 +- tests/MetaCoreSmokeTests.cpp | 53 +++++ 4 files changed, 237 insertions(+), 38 deletions(-) diff --git a/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp b/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp index 200e813..4d42f7f 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp @@ -2138,7 +2138,7 @@ void MetaCoreEditorApp::DrawEditorFrame() { if (!isGameView && viewportState.Hovered && !gizmoHovering && !gizmoUsing && !ImGui::GetIO().WantCaptureMouse && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { const MetaCoreId pickedObjectId = SceneInteractionService_.PickGameObjectFromViewport( - Scene_, + *EditorContext_, sceneView, viewportState, EditorContext_->GetInput().GetCursorPosition() diff --git a/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp b/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp index e3ce7d5..70406d0 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp @@ -23,7 +23,9 @@ #include #include +#include #include +#include #include namespace MetaCore { @@ -50,6 +52,157 @@ glm::mat4 MetaCoreBuildWorldTransformMatrix(const MetaCoreScene& scene, MetaCore return worldMatrix; } +int MetaCoreGetHierarchyDepth(const MetaCoreScene& scene, MetaCoreId objectId) { + int depth = 0; + MetaCoreGameObject object = scene.FindGameObject(objectId); + while (object && object.GetParentId() != 0) { + ++depth; + object = scene.FindGameObject(object.GetParentId()); + } + return depth; +} + +bool MetaCoreIsFiniteVec3(const glm::vec3& value) { + return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z); +} + +struct MetaCorePickBounds { + glm::vec3 Min{-0.5F, -0.5F, -0.5F}; + glm::vec3 Max{0.5F, 0.5F, 0.5F}; +}; + +bool MetaCoreIsValidPickBounds(const MetaCorePickBounds& bounds) { + if (!MetaCoreIsFiniteVec3(bounds.Min) || !MetaCoreIsFiniteVec3(bounds.Max)) { + return false; + } + + return bounds.Min.x <= bounds.Max.x && + bounds.Min.y <= bounds.Max.y && + bounds.Min.z <= bounds.Max.z; +} + +std::optional MetaCoreResolveMeshAssetPickBounds( + const MetaCoreMeshRendererComponent& meshRenderer, + const MetaCoreModelAssetDocument& modelDocument +) { + const MetaCoreMeshAssetDocument* meshAsset = nullptr; + + if (meshRenderer.MeshAssetGuid.IsValid()) { + const auto meshIterator = std::find_if( + modelDocument.GeneratedMeshAssets.begin(), + modelDocument.GeneratedMeshAssets.end(), + [&](const MetaCoreMeshAssetDocument& candidate) { + return candidate.AssetGuid == meshRenderer.MeshAssetGuid; + } + ); + if (meshIterator != modelDocument.GeneratedMeshAssets.end()) { + meshAsset = &(*meshIterator); + } + } + + if (meshAsset == nullptr && + meshRenderer.ModelNodeIndex >= 0 && + static_cast(meshRenderer.ModelNodeIndex) < modelDocument.Nodes.size()) { + const MetaCoreImportedGltfNodeDocument& node = + modelDocument.Nodes[static_cast(meshRenderer.ModelNodeIndex)]; + if (node.MeshIndex >= 0 && static_cast(node.MeshIndex) < modelDocument.GeneratedMeshAssets.size()) { + meshAsset = &modelDocument.GeneratedMeshAssets[static_cast(node.MeshIndex)]; + } + } + + if (meshAsset == nullptr) { + return std::nullopt; + } + + MetaCorePickBounds bounds; + bounds.Min = meshAsset->BoundingBoxMin; + bounds.Max = meshAsset->BoundingBoxMax; + if (!MetaCoreIsValidPickBounds(bounds)) { + return std::nullopt; + } + + return bounds; +} + +MetaCorePickBounds MetaCoreResolvePickBounds( + const MetaCoreMeshRendererComponent& meshRenderer, + const std::shared_ptr& assetEditingService, + std::unordered_map, MetaCoreAssetGuidHasher>& modelAssetCache +) { + if (meshRenderer.MeshSource == MetaCoreMeshSourceKind::Asset && + meshRenderer.SourceModelAssetGuid.IsValid() && + assetEditingService != nullptr) { + auto modelIterator = modelAssetCache.find(meshRenderer.SourceModelAssetGuid); + if (modelIterator == modelAssetCache.end()) { + modelIterator = modelAssetCache.emplace( + meshRenderer.SourceModelAssetGuid, + assetEditingService->LoadModelAsset(meshRenderer.SourceModelAssetGuid) + ).first; + } + + if (modelIterator->second.has_value()) { + if (const auto resolvedBounds = MetaCoreResolveMeshAssetPickBounds(meshRenderer, *modelIterator->second)) { + return *resolvedBounds; + } + } + } + + if (meshRenderer.MeshSource == MetaCoreMeshSourceKind::Builtin && + meshRenderer.BuiltinMesh == MetaCoreBuiltinMeshType::Plane) { + return MetaCorePickBounds{ + glm::vec3(-0.5F, -0.5F, -0.001F), + glm::vec3(0.5F, 0.5F, 0.001F) + }; + } + + return MetaCorePickBounds{}; +} + +bool MetaCoreIntersectLocalRayWithAabb( + const glm::vec3& rayOrigin, + const glm::vec3& rayDirection, + const MetaCorePickBounds& bounds, + float& outHitDistance +) { + if (!MetaCoreIsFiniteVec3(rayOrigin) || !MetaCoreIsFiniteVec3(rayDirection) || !MetaCoreIsValidPickBounds(bounds)) { + return false; + } + + float nearDistance = 0.0F; + float farDistance = std::numeric_limits::max(); + + for (int axisIndex = 0; axisIndex < 3; ++axisIndex) { + const float rayAxisOrigin = rayOrigin[axisIndex]; + const float rayAxisDirection = rayDirection[axisIndex]; + + if (std::abs(rayAxisDirection) < 0.0001F) { + if (rayAxisOrigin < bounds.Min[axisIndex] || rayAxisOrigin > bounds.Max[axisIndex]) { + return false; + } + continue; + } + + float axisNearDistance = (bounds.Min[axisIndex] - rayAxisOrigin) / rayAxisDirection; + float axisFarDistance = (bounds.Max[axisIndex] - rayAxisOrigin) / rayAxisDirection; + if (axisNearDistance > axisFarDistance) { + std::swap(axisNearDistance, axisFarDistance); + } + + nearDistance = std::max(nearDistance, axisNearDistance); + farDistance = std::min(farDistance, axisFarDistance); + if (nearDistance > farDistance) { + return false; + } + } + + if (farDistance < 0.0F) { + return false; + } + + outHitDistance = nearDistance >= 0.0F ? nearDistance : farDistance; + return outHitDistance >= 0.0F; +} + // 使用 MetaCoreTransformUtils.h 中的 MetaCoreApplyMatrixToTransform ImGuizmo::OPERATION MetaCoreToImGuizmoOperation(MetaCoreGizmoOperation operation) { @@ -87,11 +240,12 @@ void MetaCoreSceneInteractionService::ResetFrameState() { } MetaCoreId MetaCoreSceneInteractionService::PickGameObjectFromViewport( - const MetaCoreScene& scene, + const MetaCoreEditorContext& editorContext, const MetaCoreSceneView& sceneView, const MetaCoreSceneViewportState& viewportState, const glm::vec2& cursorPosition ) const { + const MetaCoreScene& scene = editorContext.GetScene(); if (viewportState.Width <= 1.0F || viewportState.Height <= 1.0F) { return 0; } @@ -111,7 +265,11 @@ MetaCoreId MetaCoreSceneInteractionService::PickGameObjectFromViewport( cursorPosition ); const glm::vec3 rayOrigin = ray.Origin; - const glm::vec3 rayDirection = ray.Direction; + const float rayDirectionLength = glm::length(ray.Direction); + if (!std::isfinite(rayDirectionLength) || rayDirectionLength <= 0.000001F) { + return 0; + } + const glm::vec3 rayDirection = ray.Direction / rayDirectionLength; std::unordered_map worldMatrixCache; const auto buildWorldMatrix = [&](const auto& self, MetaCoreId objectId) -> glm::mat4 { @@ -139,54 +297,42 @@ MetaCoreId MetaCoreSceneInteractionService::PickGameObjectFromViewport( MetaCoreId bestObjectId = 0; float bestHitDistance = std::numeric_limits::max(); + int bestHierarchyDepth = -1; + const auto assetEditingService = editorContext.GetModuleRegistry().ResolveService(); + std::unordered_map, MetaCoreAssetGuidHasher> modelAssetCache; for (const MetaCoreGameObject& gameObject : scene.GetGameObjects()) { if (!gameObject.HasComponent() || !gameObject.GetComponent().Visible) { continue; } + const MetaCoreMeshRendererComponent& meshRenderer = gameObject.GetComponent(); + const MetaCorePickBounds localBounds = MetaCoreResolvePickBounds(meshRenderer, assetEditingService, modelAssetCache); const glm::mat4 worldMatrix = buildWorldMatrix(buildWorldMatrix, gameObject.GetId()); 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))); + const glm::vec3 localRayDirectionRaw = glm::vec3(inverseWorldMatrix * glm::vec4(rayDirection, 0.0F)); + const float localRayDirectionLength = glm::length(localRayDirectionRaw); + if (!std::isfinite(localRayDirectionLength) || localRayDirectionLength <= 0.000001F) { + continue; + } + const glm::vec3 localRayDirection = localRayDirectionRaw / localRayDirectionLength; - 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; - } + float localHitDistance = 0.0F; + if (MetaCoreIntersectLocalRayWithAabb(localRayOrigin, localRayDirection, localBounds, localHitDistance)) { + const glm::vec3 localHitPosition = localRayOrigin + localRayDirection * localHitDistance; + const glm::vec3 worldHitPosition = glm::vec3(worldMatrix * glm::vec4(localHitPosition, 1.0F)); + const float hitDistance = glm::dot(worldHitPosition - rayOrigin, rayDirection); + if (!std::isfinite(hitDistance) || hitDistance < 0.0F) { 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) { + const int hierarchyDepth = MetaCoreGetHierarchyDepth(scene, gameObject.GetId()); + constexpr float hitDistanceTieEpsilon = 0.001F; + if (hitDistance + hitDistanceTieEpsilon < bestHitDistance || + (std::abs(hitDistance - bestHitDistance) <= hitDistanceTieEpsilon && hierarchyDepth > bestHierarchyDepth)) { bestHitDistance = hitDistance; + bestHierarchyDepth = hierarchyDepth; bestObjectId = gameObject.GetId(); } } diff --git a/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreSceneInteractionService.h b/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreSceneInteractionService.h index 754d619..c0145fa 100644 --- a/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreSceneInteractionService.h +++ b/Source/MetaCoreEditor/Public/MetaCoreEditor/MetaCoreSceneInteractionService.h @@ -14,7 +14,7 @@ public: void ResetFrameState(); [[nodiscard]] MetaCoreId PickGameObjectFromViewport( - const MetaCoreScene& scene, + const MetaCoreEditorContext& editorContext, const MetaCoreSceneView& sceneView, const MetaCoreSceneViewportState& viewportState, const glm::vec2& cursorPosition diff --git a/tests/MetaCoreSmokeTests.cpp b/tests/MetaCoreSmokeTests.cpp index a94ccd7..7ca22e7 100644 --- a/tests/MetaCoreSmokeTests.cpp +++ b/tests/MetaCoreSmokeTests.cpp @@ -3,6 +3,7 @@ #include "MetaCoreEditor/MetaCoreEditorContext.h" #include "MetaCoreEditor/MetaCoreEditorModule.h" #include "MetaCoreEditor/MetaCoreEditorServices.h" +#include "MetaCoreEditor/MetaCoreSceneInteractionService.h" #include "MetaCoreFoundation/MetaCoreGeneratedReflection.h" #include "MetaCoreFoundation/MetaCoreLogService.h" #include "MetaCoreFoundation/MetaCoreProject.h" @@ -1596,6 +1597,56 @@ void MetaCoreTestCameraSelectionAndProjectionUtilities() { MetaCoreExpect(std::abs(projectedPoint->y - 50.0F) <= 0.001F, "WorldToScreen Y 应位于中心"); } +void MetaCoreTestViewportPickingPrefersModelChildNode() { + MetaCore::MetaCoreWindow window; + MetaCore::MetaCoreRenderDevice renderDevice; + MetaCore::MetaCoreEditorViewportRenderer viewportRenderer; + MetaCore::MetaCoreScene scene; + MetaCore::MetaCoreLogService logService; + MetaCore::MetaCoreEditorModuleRegistry moduleRegistry; + + MetaCore::MetaCoreEditorContext editorContext( + window, + renderDevice, + viewportRenderer, + scene, + logService, + moduleRegistry + ); + + MetaCore::MetaCoreGameObject modelRoot = scene.CreateGameObject("Imported Model Root"); + modelRoot.GetComponent().Position = glm::vec3(0.0F, 5.0F, 0.0F); + modelRoot.AddComponent(); + + MetaCore::MetaCoreGameObject childNode = scene.CreateGameObject("Imported Mesh Child", modelRoot.GetId()); + childNode.AddComponent(); + + MetaCore::MetaCoreSceneView sceneView; + sceneView.CameraPosition = glm::vec3(0.0F, 0.0F, 0.0F); + sceneView.CameraTarget = glm::vec3(0.0F, 1.0F, 0.0F); + sceneView.CameraUp = glm::vec3(0.0F, 0.0F, 1.0F); + sceneView.VerticalFieldOfViewDegrees = 60.0F; + + const MetaCore::MetaCoreSceneViewportState viewportState{ + 0.0F, + 0.0F, + 100.0F, + 100.0F, + true, + true + }; + + const MetaCore::MetaCoreSceneInteractionService interactionService; + const MetaCore::MetaCoreId pickedObjectId = interactionService.PickGameObjectFromViewport( + editorContext, + sceneView, + viewportState, + glm::vec2(50.0F, 50.0F) + ); + + MetaCoreExpect(pickedObjectId == childNode.GetId(), "视口拾取应优先选中模型内被点击的更具体子节点"); +} + void MetaCoreTestJsonSceneSaveCurrentSceneUsesMcsceneJson() { const std::filesystem::path tempProjectRoot = std::filesystem::temp_directory_path() / "MetaCoreSceneRoundTripProject"; @@ -4926,6 +4977,8 @@ int main() { MetaCoreTestCameraComponentJsonRoundTripAndLegacyDefaults(); std::cout << "[RUN] MetaCoreTestCameraSelectionAndProjectionUtilities..." << std::endl; MetaCoreTestCameraSelectionAndProjectionUtilities(); + std::cout << "[RUN] MetaCoreTestViewportPickingPrefersModelChildNode..." << std::endl; + MetaCoreTestViewportPickingPrefersModelChildNode(); std::cout << "[RUN] MetaCoreTestJsonSceneSaveCurrentSceneUsesMcsceneJson..." << std::endl; MetaCoreTestJsonSceneSaveCurrentSceneUsesMcsceneJson(); std::cout << "[RUN] MetaCoreTestImportPipelineAndCook..." << std::endl;