UI系统更新

This commit is contained in:
Rowland 2026-06-29 10:52:06 +08:00
parent b78e0275bb
commit 429a574205
4 changed files with 237 additions and 38 deletions

View File

@ -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()

View File

@ -23,7 +23,9 @@
#include <glm/ext/matrix_transform.hpp>
#include <algorithm>
#include <cmath>
#include <limits>
#include <optional>
#include <unordered_map>
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<MetaCorePickBounds> 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<std::size_t>(meshRenderer.ModelNodeIndex) < modelDocument.Nodes.size()) {
const MetaCoreImportedGltfNodeDocument& node =
modelDocument.Nodes[static_cast<std::size_t>(meshRenderer.ModelNodeIndex)];
if (node.MeshIndex >= 0 && static_cast<std::size_t>(node.MeshIndex) < modelDocument.GeneratedMeshAssets.size()) {
meshAsset = &modelDocument.GeneratedMeshAssets[static_cast<std::size_t>(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<MetaCoreIAssetEditingService>& assetEditingService,
std::unordered_map<MetaCoreAssetGuid, std::optional<MetaCoreModelAssetDocument>, 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<float>::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<MetaCoreId, glm::mat4> 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<float>::max();
int bestHierarchyDepth = -1;
const auto assetEditingService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetEditingService>();
std::unordered_map<MetaCoreAssetGuid, std::optional<MetaCoreModelAssetDocument>, MetaCoreAssetGuidHasher> modelAssetCache;
for (const MetaCoreGameObject& gameObject : scene.GetGameObjects()) {
if (!gameObject.HasComponent<MetaCoreMeshRendererComponent>() || !gameObject.GetComponent<MetaCoreMeshRendererComponent>().Visible) {
continue;
}
const MetaCoreMeshRendererComponent& meshRenderer = gameObject.GetComponent<MetaCoreMeshRendererComponent>();
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();
}
}

View File

@ -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

View File

@ -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<MetaCore::MetaCoreTransformComponent>().Position = glm::vec3(0.0F, 5.0F, 0.0F);
modelRoot.AddComponent<MetaCore::MetaCoreMeshRendererComponent>();
MetaCore::MetaCoreGameObject childNode = scene.CreateGameObject("Imported Mesh Child", modelRoot.GetId());
childNode.AddComponent<MetaCore::MetaCoreMeshRendererComponent>();
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;