diff --git a/SandboxProject/Assets/Models/jxb.glb b/SandboxProject/Assets/Models/jxb.glb new file mode 100644 index 0000000..2546206 Binary files /dev/null and b/SandboxProject/Assets/Models/jxb.glb differ diff --git a/SandboxProject/Assets/Models/jxb.glb.mcmeta b/SandboxProject/Assets/Models/jxb.glb.mcmeta new file mode 100644 index 0000000..7ef1a4b --- /dev/null +++ b/SandboxProject/Assets/Models/jxb.glb.mcmeta @@ -0,0 +1,176 @@ +{ + "asset_type": "model", + "dependencies": [ + "355dbe1a-2ddd-f8d3-59ee-c527d9d0cf27", + "9b8e49e8-f3fe-0859-de57-e3bbea768b40", + "c2ef7844-efb5-b54f-8d14-f67240c1e98d", + "84065ab7-a37c-a7f3-04f7-d6bcdaef6d14", + "29516799-f1d6-9b1f-40bc-059e74eb45b0", + "b04d0c8f-f17f-a50d-fb03-07e133986f20", + "ef0850ab-f1f8-3c40-3504-5b2df8fe73a0", + "fb6c4b06-1f20-eb67-2684-e99d347b2fb2", + "64c7bcfe-1ec7-3f59-dc1e-a948f7837c7e", + "5ee4e269-806a-cdfd-287b-84b39cec028e", + "af626100-9e67-49e6-e5b1-c4df57aba46a", + "655477a1-906b-6777-5de9-106cfe21d1cf", + "15666100-9e69-49e6-b1cb-c7dfd724a76a", + "f84eff01-17ad-7827-652e-77867f51728e", + "ea00828c-f915-47c4-7389-5746bada9fc7", + "d4f8780c-674b-6e87-c0ba-e92498bc0139", + "72fca4a9-adde-dd5e-09d6-79b3d570edce", + "6b556bd8-16f3-f182-92ab-4fc0c83ecb1c", + "6b1cedd6-4af2-ba3f-d22c-da6ee0281680" + ], + "guid": "f808e65c-6950-4639-9e1a-34487f9dfdd1", + "importer_id": "GltfModelImporter", + "package_path": "Assets/Models/jxb.glb.mcasset", + "source_hash": 3452613311117949482, + "source_path": "Assets/Models/jxb.glb", + "sub_assets": [ + { + "guid": "355dbe1a-2ddd-f8d3-59ee-c527d9d0cf27", + "index": 0, + "name": "SM_HeavyPlatform01.001", + "stable_import_key": "mesh:SM_HeavyPlatform01.001:0", + "type": "mesh" + }, + { + "guid": "9b8e49e8-f3fe-0859-de57-e3bbea768b40", + "index": 1, + "name": "SM_RoboArm04_Separate_Parts_SM_Roboarm04_004.001", + "stable_import_key": "mesh:SM_RoboArm04_Separate_Parts_SM_Roboarm04_004.001:1", + "type": "mesh" + }, + { + "guid": "c2ef7844-efb5-b54f-8d14-f67240c1e98d", + "index": 2, + "name": "SM_HeavyPlatform01", + "stable_import_key": "mesh:SM_HeavyPlatform01:2", + "type": "mesh" + }, + { + "guid": "84065ab7-a37c-a7f3-04f7-d6bcdaef6d14", + "index": 3, + "name": "SM_RoboArm04_Separate_Parts_SM_Roboarm04_004", + "stable_import_key": "mesh:SM_RoboArm04_Separate_Parts_SM_Roboarm04_004:3", + "type": "mesh" + }, + { + "guid": "29516799-f1d6-9b1f-40bc-059e74eb45b0", + "index": 4, + "name": "SK_IndustrialRobot.001", + "stable_import_key": "mesh:SK_IndustrialRobot.001:4", + "type": "mesh" + }, + { + "guid": "b04d0c8f-f17f-a50d-fb03-07e133986f20", + "index": 5, + "name": "SK_IndustrialRobot.003", + "stable_import_key": "mesh:SK_IndustrialRobot.003:5", + "type": "mesh" + }, + { + "guid": "ef0850ab-f1f8-3c40-3504-5b2df8fe73a0", + "index": 6, + "name": "SK_IndustrialRobot.005", + "stable_import_key": "mesh:SK_IndustrialRobot.005:6", + "type": "mesh" + }, + { + "guid": "fb6c4b06-1f20-eb67-2684-e99d347b2fb2", + "index": 7, + "name": "SK_RoboArm04.001", + "stable_import_key": "mesh:SK_RoboArm04.001:7", + "type": "mesh" + }, + { + "guid": "64c7bcfe-1ec7-3f59-dc1e-a948f7837c7e", + "index": 8, + "name": "SK_RoboArm04.003", + "stable_import_key": "mesh:SK_RoboArm04.003:8", + "type": "mesh" + }, + { + "dependencies": [ + "ea00828c-f915-47c4-7389-5746bada9fc7" + ], + "guid": "5ee4e269-806a-cdfd-287b-84b39cec028e", + "index": 0, + "name": "MI_HeavyArch", + "stable_import_key": "material:MI_HeavyArch:0", + "type": "material" + }, + { + "dependencies": [ + "72fca4a9-adde-dd5e-09d6-79b3d570edce" + ], + "guid": "af626100-9e67-49e6-e5b1-c4df57aba46a", + "index": 1, + "name": "MI_RoboArm01", + "stable_import_key": "material:MI_RoboArm01:1", + "type": "material" + }, + { + "dependencies": [ + "6b1cedd6-4af2-ba3f-d22c-da6ee0281680" + ], + "guid": "655477a1-906b-6777-5de9-106cfe21d1cf", + "index": 2, + "name": "MI_IndustrialRobot01", + "stable_import_key": "material:MI_IndustrialRobot01:2", + "type": "material" + }, + { + "dependencies": [ + "72fca4a9-adde-dd5e-09d6-79b3d570edce" + ], + "guid": "15666100-9e69-49e6-b1cb-c7dfd724a76a", + "index": 3, + "name": "MI_RoboArm01", + "stable_import_key": "material:MI_RoboArm01:3", + "type": "material" + }, + { + "guid": "f84eff01-17ad-7827-652e-77867f51728e", + "index": 0, + "name": "MI_HeavyArch_Normal", + "stable_import_key": "texture:MI_HeavyArch_Normal:0", + "type": "texture" + }, + { + "guid": "ea00828c-f915-47c4-7389-5746bada9fc7", + "index": 1, + "name": "MI_HeavyArch_BaseColor", + "stable_import_key": "texture:MI_HeavyArch_BaseColor:1", + "type": "texture" + }, + { + "guid": "d4f8780c-674b-6e87-c0ba-e92498bc0139", + "index": 2, + "name": "MI_RoboArm01_Normal", + "stable_import_key": "texture:MI_RoboArm01_Normal:2", + "type": "texture" + }, + { + "guid": "72fca4a9-adde-dd5e-09d6-79b3d570edce", + "index": 3, + "name": "MI_RoboArm01_BaseColor", + "stable_import_key": "texture:MI_RoboArm01_BaseColor:3", + "type": "texture" + }, + { + "guid": "6b556bd8-16f3-f182-92ab-4fc0c83ecb1c", + "index": 4, + "name": "MI_IndustrialRobot01_Normal", + "stable_import_key": "texture:MI_IndustrialRobot01_Normal:4", + "type": "texture" + }, + { + "guid": "6b1cedd6-4af2-ba3f-d22c-da6ee0281680", + "index": 5, + "name": "MI_IndustrialRobot01_BaseColor", + "stable_import_key": "texture:MI_IndustrialRobot01_BaseColor:5", + "type": "texture" + } + ] +} \ No newline at end of file diff --git a/Source/MetaCoreEditor/Private/MetaCoreGltfImporter.cpp b/Source/MetaCoreEditor/Private/MetaCoreGltfImporter.cpp index 98fe8f2..df25036 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreGltfImporter.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreGltfImporter.cpp @@ -2,8 +2,11 @@ #include "MetaCoreFoundation/MetaCoreHash.h" #include "MetaCoreLocalCgltf.h" +#define GLM_ENABLE_EXPERIMENTAL #include #include +#include +#include #include #include @@ -65,6 +68,25 @@ void MetaCoreFinalizeImportReport(MetaCoreModelImportReportDocument& report) { } } +void MetaCoreSetImportedNodeTransformFromCgltfNode(MetaCoreImportedGltfNodeDocument& nodeDoc, const cgltf_node& node) { + float localMatrixValues[16]{}; + cgltf_node_transform_local(&node, localMatrixValues); + + glm::mat4 localMatrix(1.0F); + std::memcpy(glm::value_ptr(localMatrix), localMatrixValues, sizeof(localMatrixValues)); + + glm::vec3 scale{1.0F}; + glm::quat rotation{1.0F, 0.0F, 0.0F, 0.0F}; + glm::vec3 translation{0.0F}; + glm::vec3 skew{0.0F}; + glm::vec4 perspective{0.0F}; + if (glm::decompose(localMatrix, scale, rotation, translation, skew, perspective)) { + nodeDoc.Position = translation; + nodeDoc.RotationEulerDegrees = glm::degrees(glm::eulerAngles(glm::normalize(rotation))); + nodeDoc.Scale = scale; + } +} + [[nodiscard]] MetaCoreAssetGuid MetaCoreMakeDeterministicGeneratedAssetGuid( const std::filesystem::path& relativeSourcePath, std::string_view generatedKind, @@ -1033,17 +1055,7 @@ struct MetaCoreGltfMaterialScalarPresence { nodeDoc.ParentIndex = node.parent ? static_cast(node.parent - data->nodes) : -1; nodeDoc.MeshIndex = node.mesh ? static_cast(node.mesh - data->meshes) : -1; - - if (node.has_translation) { - nodeDoc.Position = glm::vec3(node.translation[0], node.translation[1], node.translation[2]); - } - if (node.has_rotation) { - const glm::quat q(node.rotation[3], node.rotation[0], node.rotation[1], node.rotation[2]); - nodeDoc.RotationEulerDegrees = glm::degrees(glm::eulerAngles(q)); - } - if (node.has_scale) { - nodeDoc.Scale = glm::vec3(node.scale[0], node.scale[1], node.scale[2]); - } + MetaCoreSetImportedNodeTransformFromCgltfNode(nodeDoc, node); document.Nodes.push_back(std::move(nodeDoc)); } for (std::size_t nodeIndex = 0; nodeIndex < document.Nodes.size(); ++nodeIndex) { diff --git a/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp b/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp index 70406d0..6ad171d 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp @@ -23,10 +23,15 @@ #include #include +#include +#include #include +#include +#include #include #include #include +#include namespace MetaCore { namespace { @@ -71,6 +76,28 @@ struct MetaCorePickBounds { glm::vec3 Max{0.5F, 0.5F, 0.5F}; }; +struct MetaCoreResolvedPickGeometry { + MetaCorePickBounds Bounds{}; + const MetaCoreMeshAssetDocument* MeshAsset = nullptr; + MetaCoreAssetGuid SourceModelAssetGuid{}; +}; + +struct MetaCorePickMeshData { + std::vector Positions{}; + std::vector Indices{}; +}; + +struct MetaCorePackedPickVertex { + float px = 0.0F; + float py = 0.0F; + float pz = 0.0F; + float nx = 0.0F; + float ny = 0.0F; + float nz = 0.0F; + float u = 0.0F; + float v = 0.0F; +}; + bool MetaCoreIsValidPickBounds(const MetaCorePickBounds& bounds) { if (!MetaCoreIsFiniteVec3(bounds.Min) || !MetaCoreIsFiniteVec3(bounds.Max)) { return false; @@ -81,7 +108,34 @@ bool MetaCoreIsValidPickBounds(const MetaCorePickBounds& bounds) { bounds.Min.z <= bounds.Max.z; } -std::optional MetaCoreResolveMeshAssetPickBounds( +glm::vec3 MetaCoreGltfPointToEditorPickLocal(const glm::vec3& point) { + return glm::vec3(point.x, point.z, -point.y); +} + +MetaCorePickBounds MetaCoreTransformGltfBoundsToEditorPickLocal(const glm::vec3& boundsMin, const glm::vec3& boundsMax) { + const glm::vec3 corners[] = { + glm::vec3(boundsMin.x, boundsMin.y, boundsMin.z), + glm::vec3(boundsMax.x, boundsMin.y, boundsMin.z), + glm::vec3(boundsMin.x, boundsMax.y, boundsMin.z), + glm::vec3(boundsMax.x, boundsMax.y, boundsMin.z), + glm::vec3(boundsMin.x, boundsMin.y, boundsMax.z), + glm::vec3(boundsMax.x, boundsMin.y, boundsMax.z), + glm::vec3(boundsMin.x, boundsMax.y, boundsMax.z), + glm::vec3(boundsMax.x, boundsMax.y, boundsMax.z) + }; + + MetaCorePickBounds transformedBounds; + transformedBounds.Min = glm::vec3(std::numeric_limits::max()); + transformedBounds.Max = glm::vec3(std::numeric_limits::lowest()); + for (const glm::vec3& corner : corners) { + const glm::vec3 transformedCorner = MetaCoreGltfPointToEditorPickLocal(corner); + transformedBounds.Min = glm::min(transformedBounds.Min, transformedCorner); + transformedBounds.Max = glm::max(transformedBounds.Max, transformedCorner); + } + return transformedBounds; +} + +std::optional MetaCoreResolveMeshAssetPickGeometry( const MetaCoreMeshRendererComponent& meshRenderer, const MetaCoreModelAssetDocument& modelDocument ) { @@ -114,17 +168,18 @@ std::optional MetaCoreResolveMeshAssetPickBounds( return std::nullopt; } - MetaCorePickBounds bounds; - bounds.Min = meshAsset->BoundingBoxMin; - bounds.Max = meshAsset->BoundingBoxMax; - if (!MetaCoreIsValidPickBounds(bounds)) { + MetaCoreResolvedPickGeometry geometry; + geometry.Bounds = MetaCoreTransformGltfBoundsToEditorPickLocal(meshAsset->BoundingBoxMin, meshAsset->BoundingBoxMax); + geometry.MeshAsset = meshAsset; + geometry.SourceModelAssetGuid = meshRenderer.SourceModelAssetGuid; + if (!MetaCoreIsValidPickBounds(geometry.Bounds)) { return std::nullopt; } - return bounds; + return geometry; } -MetaCorePickBounds MetaCoreResolvePickBounds( +MetaCoreResolvedPickGeometry MetaCoreResolvePickGeometry( const MetaCoreMeshRendererComponent& meshRenderer, const std::shared_ptr& assetEditingService, std::unordered_map, MetaCoreAssetGuidHasher>& modelAssetCache @@ -141,21 +196,21 @@ MetaCorePickBounds MetaCoreResolvePickBounds( } if (modelIterator->second.has_value()) { - if (const auto resolvedBounds = MetaCoreResolveMeshAssetPickBounds(meshRenderer, *modelIterator->second)) { - return *resolvedBounds; + if (const auto resolvedGeometry = MetaCoreResolveMeshAssetPickGeometry(meshRenderer, *modelIterator->second)) { + return *resolvedGeometry; } } } 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) - }; + MetaCoreResolvedPickGeometry geometry; + geometry.Bounds.Min = glm::vec3(-0.5F, -0.5F, -0.001F); + geometry.Bounds.Max = glm::vec3(0.5F, 0.5F, 0.001F); + return geometry; } - return MetaCorePickBounds{}; + return MetaCoreResolvedPickGeometry{}; } bool MetaCoreIntersectLocalRayWithAabb( @@ -203,6 +258,200 @@ bool MetaCoreIntersectLocalRayWithAabb( return outHitDistance >= 0.0F; } +std::optional MetaCoreDecodePickMeshPayload( + const std::vector& payload, + const MetaCoreMeshAssetDocument& meshAsset +) { + constexpr std::size_t headerSize = sizeof(std::uint32_t) * 2; + if (payload.size() < headerSize) { + return std::nullopt; + } + + std::uint32_t vertexCount = 0; + std::uint32_t indexCount = 0; + std::memcpy(&vertexCount, payload.data(), sizeof(std::uint32_t)); + std::memcpy(&indexCount, payload.data() + sizeof(std::uint32_t), sizeof(std::uint32_t)); + + if (vertexCount == 0 || indexCount < 3) { + return std::nullopt; + } + + const std::size_t expectedSize = + headerSize + + static_cast(vertexCount) * sizeof(MetaCorePackedPickVertex) + + static_cast(indexCount) * sizeof(std::uint32_t); + if (expectedSize > payload.size()) { + return std::nullopt; + } + + if (meshAsset.VertexCount > 0 && vertexCount != static_cast(meshAsset.VertexCount)) { + return std::nullopt; + } + if (meshAsset.IndexCount > 0 && indexCount != static_cast(meshAsset.IndexCount)) { + return std::nullopt; + } + + MetaCorePickMeshData meshData; + meshData.Positions.resize(vertexCount); + meshData.Indices.resize(indexCount); + + const std::byte* cursor = payload.data() + headerSize; + for (std::uint32_t vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) { + MetaCorePackedPickVertex packedVertex; + std::memcpy(&packedVertex, cursor, sizeof(MetaCorePackedPickVertex)); + cursor += sizeof(MetaCorePackedPickVertex); + meshData.Positions[vertexIndex] = MetaCoreGltfPointToEditorPickLocal( + glm::vec3(packedVertex.px, packedVertex.py, packedVertex.pz) + ); + } + + std::memcpy(meshData.Indices.data(), cursor, static_cast(indexCount) * sizeof(std::uint32_t)); + return meshData; +} + +std::optional MetaCoreLoadPickMeshData( + const MetaCoreResolvedPickGeometry& geometry, + const std::shared_ptr& assetDatabaseService, + const std::shared_ptr& packageService, + std::unordered_map, MetaCoreAssetGuidHasher>& packageCache, + std::unordered_map, MetaCoreAssetGuidHasher>& meshDataCache +) { + if (geometry.MeshAsset == nullptr || + !geometry.MeshAsset->AssetGuid.IsValid() || + !geometry.SourceModelAssetGuid.IsValid() || + assetDatabaseService == nullptr || + packageService == nullptr || + !assetDatabaseService->HasProject()) { + return std::nullopt; + } + + if (const auto meshIterator = meshDataCache.find(geometry.MeshAsset->AssetGuid); meshIterator != meshDataCache.end()) { + return meshIterator->second; + } + + auto packageIterator = packageCache.find(geometry.SourceModelAssetGuid); + if (packageIterator == packageCache.end()) { + std::optional packageDocument; + if (const auto assetRecord = assetDatabaseService->FindAssetByGuid(geometry.SourceModelAssetGuid); assetRecord.has_value()) { + const std::filesystem::path relativePackagePath = + !assetRecord->PackagePath.empty() ? assetRecord->PackagePath : assetRecord->RelativePath; + packageDocument = packageService->ReadPackage(assetDatabaseService->GetProjectDescriptor().RootPath / relativePackagePath); + } + + packageIterator = packageCache.emplace(geometry.SourceModelAssetGuid, std::move(packageDocument)).first; + } + + std::optional meshData; + if (packageIterator->second.has_value() && + geometry.MeshAsset->PayloadIndex < packageIterator->second->PayloadSections.size()) { + meshData = MetaCoreDecodePickMeshPayload( + packageIterator->second->PayloadSections[static_cast(geometry.MeshAsset->PayloadIndex)], + *geometry.MeshAsset + ); + } + + return meshDataCache.emplace(geometry.MeshAsset->AssetGuid, std::move(meshData)).first->second; +} + +bool MetaCoreIntersectRayTriangle( + const glm::vec3& rayOrigin, + const glm::vec3& rayDirection, + const glm::vec3& vertex0, + const glm::vec3& vertex1, + const glm::vec3& vertex2, + float& outHitDistance +) { + constexpr float epsilon = 0.000001F; + const glm::vec3 edge1 = vertex1 - vertex0; + const glm::vec3 edge2 = vertex2 - vertex0; + const glm::vec3 pvec = glm::cross(rayDirection, edge2); + const float determinant = glm::dot(edge1, pvec); + if (std::abs(determinant) < epsilon) { + return false; + } + + const float inverseDeterminant = 1.0F / determinant; + const glm::vec3 tvec = rayOrigin - vertex0; + const float u = glm::dot(tvec, pvec) * inverseDeterminant; + if (u < 0.0F || u > 1.0F) { + return false; + } + + const glm::vec3 qvec = glm::cross(tvec, edge1); + const float v = glm::dot(rayDirection, qvec) * inverseDeterminant; + if (v < 0.0F || u + v > 1.0F) { + return false; + } + + const float hitDistance = glm::dot(edge2, qvec) * inverseDeterminant; + if (hitDistance < 0.0F || !std::isfinite(hitDistance)) { + return false; + } + + outHitDistance = hitDistance; + return true; +} + +bool MetaCoreIntersectLocalRayWithMesh( + const glm::vec3& rayOrigin, + const glm::vec3& rayDirection, + const MetaCorePickMeshData& meshData, + const MetaCoreMeshRendererComponent& meshRenderer, + const MetaCoreMeshAssetDocument& meshAsset, + float& outHitDistance +) { + if (meshData.Positions.empty() || meshData.Indices.size() < 3) { + return false; + } + + std::uint32_t firstIndex = 0; + std::uint32_t indexCount = static_cast(meshData.Indices.size()); + if (meshRenderer.SubMeshIndex >= 0 && + static_cast(meshRenderer.SubMeshIndex) < meshAsset.SubMeshes.size()) { + const MetaCoreMeshSubMeshDocument& subMesh = meshAsset.SubMeshes[static_cast(meshRenderer.SubMeshIndex)]; + firstIndex = subMesh.FirstIndex; + indexCount = subMesh.IndexCount; + } + + const std::uint64_t indexEnd64 = static_cast(firstIndex) + static_cast(indexCount); + if (indexCount < 3 || firstIndex >= meshData.Indices.size() || indexEnd64 > meshData.Indices.size()) { + return false; + } + + bool hit = false; + float bestLocalHitDistance = std::numeric_limits::max(); + const std::uint32_t indexEnd = static_cast(indexEnd64); + for (std::uint32_t index = firstIndex; index + 2 < indexEnd; index += 3) { + const std::uint32_t i0 = meshData.Indices[index]; + const std::uint32_t i1 = meshData.Indices[index + 1]; + const std::uint32_t i2 = meshData.Indices[index + 2]; + if (i0 >= meshData.Positions.size() || i1 >= meshData.Positions.size() || i2 >= meshData.Positions.size()) { + continue; + } + + float triangleHitDistance = 0.0F; + if (MetaCoreIntersectRayTriangle( + rayOrigin, + rayDirection, + meshData.Positions[i0], + meshData.Positions[i1], + meshData.Positions[i2], + triangleHitDistance + ) && + triangleHitDistance < bestLocalHitDistance) { + bestLocalHitDistance = triangleHitDistance; + hit = true; + } + } + + if (!hit) { + return false; + } + + outHitDistance = bestLocalHitDistance; + return true; +} + // 使用 MetaCoreTransformUtils.h 中的 MetaCoreApplyMatrixToTransform ImGuizmo::OPERATION MetaCoreToImGuizmoOperation(MetaCoreGizmoOperation operation) { @@ -299,7 +548,11 @@ MetaCoreId MetaCoreSceneInteractionService::PickGameObjectFromViewport( float bestHitDistance = std::numeric_limits::max(); int bestHierarchyDepth = -1; const auto assetEditingService = editorContext.GetModuleRegistry().ResolveService(); + const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService(); + const auto packageService = editorContext.GetModuleRegistry().ResolveService(); std::unordered_map, MetaCoreAssetGuidHasher> modelAssetCache; + std::unordered_map, MetaCoreAssetGuidHasher> packageCache; + std::unordered_map, MetaCoreAssetGuidHasher> meshDataCache; for (const MetaCoreGameObject& gameObject : scene.GetGameObjects()) { if (!gameObject.HasComponent() || !gameObject.GetComponent().Visible) { @@ -307,7 +560,7 @@ MetaCoreId MetaCoreSceneInteractionService::PickGameObjectFromViewport( } const MetaCoreMeshRendererComponent& meshRenderer = gameObject.GetComponent(); - const MetaCorePickBounds localBounds = MetaCoreResolvePickBounds(meshRenderer, assetEditingService, modelAssetCache); + const MetaCoreResolvedPickGeometry pickGeometry = MetaCoreResolvePickGeometry(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)); @@ -319,7 +572,31 @@ MetaCoreId MetaCoreSceneInteractionService::PickGameObjectFromViewport( const glm::vec3 localRayDirection = localRayDirectionRaw / localRayDirectionLength; float localHitDistance = 0.0F; - if (MetaCoreIntersectLocalRayWithAabb(localRayOrigin, localRayDirection, localBounds, localHitDistance)) { + if (MetaCoreIntersectLocalRayWithAabb(localRayOrigin, localRayDirection, pickGeometry.Bounds, localHitDistance)) { + if (pickGeometry.MeshAsset != nullptr) { + if (const auto meshData = MetaCoreLoadPickMeshData( + pickGeometry, + assetDatabaseService, + packageService, + packageCache, + meshDataCache + ); + meshData.has_value()) { + float triangleHitDistance = 0.0F; + if (!MetaCoreIntersectLocalRayWithMesh( + localRayOrigin, + localRayDirection, + *meshData, + meshRenderer, + *pickGeometry.MeshAsset, + triangleHitDistance + )) { + continue; + } + localHitDistance = triangleHitDistance; + } + } + 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); diff --git a/tests/MetaCoreSmokeTests.cpp b/tests/MetaCoreSmokeTests.cpp index 7ca22e7..b15f031 100644 --- a/tests/MetaCoreSmokeTests.cpp +++ b/tests/MetaCoreSmokeTests.cpp @@ -1647,6 +1647,200 @@ void MetaCoreTestViewportPickingPrefersModelChildNode() { MetaCoreExpect(pickedObjectId == childNode.GetId(), "视口拾取应优先选中模型内被点击的更具体子节点"); } +void MetaCoreTestViewportPickingUsesMeshTrianglesBeforeHierarchyDepth() { + const std::filesystem::path tempProjectRoot = + std::filesystem::temp_directory_path() / "MetaCoreTrianglePickProject"; + std::filesystem::remove_all(tempProjectRoot); + std::filesystem::create_directories(tempProjectRoot / "Assets"); + std::filesystem::create_directories(tempProjectRoot / "Scenes"); + std::filesystem::create_directories(tempProjectRoot / "Library"); + + { + std::ofstream projectFile(tempProjectRoot / "MetaCore.project.json", std::ios::trunc); + projectFile << "{\n" + << " \"name\": \"TrianglePickProject\",\n" + << " \"version\": \"0.1.0\",\n" + << " \"scenes\": [],\n" + << " \"startup_scene\": \"\"\n" + << "}\n"; + } + { + std::ofstream gltfFile(tempProjectRoot / "Assets" / "House.gltf", std::ios::trunc); + gltfFile << "{ \"meshes\": [{\"name\": \"Placeholder\"}] }\n"; + } + + _putenv_s("METACORE_PROJECT_PATH", tempProjectRoot.string().c_str()); + + MetaCore::MetaCoreWindow window; + MetaCore::MetaCoreRenderDevice renderDevice; + MetaCore::MetaCoreEditorViewportRenderer viewportRenderer; + MetaCore::MetaCoreScene scene; + MetaCore::MetaCoreLogService logService; + MetaCore::MetaCoreEditorModuleRegistry moduleRegistry; + auto coreServicesModule = MetaCore::MetaCoreCreateBuiltinCoreServicesModule(); + coreServicesModule->Startup(moduleRegistry); + + MetaCore::MetaCoreEditorContext editorContext( + window, + renderDevice, + viewportRenderer, + scene, + logService, + moduleRegistry + ); + + const auto assetDatabase = moduleRegistry.ResolveService(); + const auto packageService = moduleRegistry.ResolveService(); + const auto reflectionRegistry = moduleRegistry.ResolveService(); + MetaCoreExpect(assetDatabase != nullptr, "三角面拾取测试应解析到 AssetDatabaseService"); + MetaCoreExpect(packageService != nullptr, "三角面拾取测试应解析到 PackageService"); + MetaCoreExpect(reflectionRegistry != nullptr, "三角面拾取测试应解析到 ReflectionRegistry"); + + const auto modelRecord = assetDatabase->FindAssetByRelativePath(std::filesystem::path("Assets") / "House.gltf"); + MetaCoreExpect(modelRecord.has_value(), "三角面拾取测试应能找到模型资源"); + + struct TestPickVertex { + float px, py, pz; + float nx, ny, nz; + float u, v; + }; + const auto makePlanePayload = [](float halfExtent) { + const std::vector vertices{ + {-halfExtent, halfExtent, 0.0F, 0.0F, 0.0F, -1.0F, 0.0F, 0.0F}, + { halfExtent, halfExtent, 0.0F, 0.0F, 0.0F, -1.0F, 1.0F, 0.0F}, + { halfExtent, -halfExtent, 0.0F, 0.0F, 0.0F, -1.0F, 1.0F, 1.0F}, + {-halfExtent, -halfExtent, 0.0F, 0.0F, 0.0F, -1.0F, 0.0F, 1.0F} + }; + const std::vector indices{0, 1, 2, 0, 2, 3}; + std::vector payload(sizeof(std::uint32_t) * 2 + vertices.size() * sizeof(TestPickVertex) + indices.size() * sizeof(std::uint32_t)); + std::byte* cursor = payload.data(); + const std::uint32_t vertexCount = static_cast(vertices.size()); + const std::uint32_t indexCount = static_cast(indices.size()); + std::memcpy(cursor, &vertexCount, sizeof(std::uint32_t)); + cursor += sizeof(std::uint32_t); + std::memcpy(cursor, &indexCount, sizeof(std::uint32_t)); + cursor += sizeof(std::uint32_t); + std::memcpy(cursor, vertices.data(), vertices.size() * sizeof(TestPickVertex)); + cursor += vertices.size() * sizeof(TestPickVertex); + std::memcpy(cursor, indices.data(), indices.size() * sizeof(std::uint32_t)); + return payload; + }; + + MetaCore::MetaCoreModelAssetDocument document; + document.AssetType = "model"; + document.ImporterId = "GltfModelImporter"; + document.SourcePath = std::filesystem::path("Assets") / "House.gltf"; + document.SourceHash = 5678; + document.ModelKind = MetaCore::MetaCoreModelAssetKind::Gltf; + document.SourceFormat = "gltf"; + + MetaCore::MetaCoreImportedGltfNodeDocument shellNode; + shellNode.Name = "Shell"; + shellNode.ParentIndex = -1; + shellNode.MeshIndex = 0; + document.Nodes.push_back(shellNode); + + MetaCore::MetaCoreImportedGltfNodeDocument interiorNode; + interiorNode.Name = "Interior"; + interiorNode.ParentIndex = 0; + interiorNode.MeshIndex = 1; + document.Nodes.push_back(interiorNode); + + MetaCore::MetaCoreImportedGltfMeshDocument shellMeshDocument; + shellMeshDocument.Name = "ShellMesh"; + shellMeshDocument.PrimitiveCount = 1; + document.Meshes.push_back(shellMeshDocument); + + MetaCore::MetaCoreImportedGltfMeshDocument interiorMeshDocument; + interiorMeshDocument.Name = "InteriorMesh"; + interiorMeshDocument.PrimitiveCount = 1; + document.Meshes.push_back(interiorMeshDocument); + + MetaCore::MetaCoreMeshAssetDocument shellMesh; + shellMesh.AssetGuid = MetaCore::MetaCoreAssetGuid::Generate(); + shellMesh.Name = "ShellMesh"; + shellMesh.VertexCount = 4; + shellMesh.IndexCount = 6; + shellMesh.PayloadIndex = 1; + shellMesh.PayloadSize = makePlanePayload(2.0F).size(); + shellMesh.BoundingBoxMin = glm::vec3(-2.0F, -2.0F, 0.0F); + shellMesh.BoundingBoxMax = glm::vec3(2.0F, 2.0F, 0.0F); + document.GeneratedMeshAssets.push_back(shellMesh); + + MetaCore::MetaCoreMeshAssetDocument interiorMesh; + interiorMesh.AssetGuid = MetaCore::MetaCoreAssetGuid::Generate(); + interiorMesh.Name = "InteriorMesh"; + interiorMesh.VertexCount = 4; + interiorMesh.IndexCount = 6; + interiorMesh.PayloadIndex = 2; + interiorMesh.PayloadSize = makePlanePayload(1.0F).size(); + interiorMesh.BoundingBoxMin = glm::vec3(-1.0F, -1.0F, 0.0F); + interiorMesh.BoundingBoxMax = glm::vec3(1.0F, 1.0F, 0.0F); + document.GeneratedMeshAssets.push_back(interiorMesh); + + const auto documentPayload = MetaCore::MetaCoreSerializeToBytes(document, reflectionRegistry->GetTypeRegistry()); + MetaCoreExpect(documentPayload.has_value(), "三角面拾取测试模型文档应能序列化"); + + MetaCore::MetaCorePackageDocument package; + package.Header.PackageType = MetaCore::MetaCorePackageType::Asset; + package.Header.PackageGuid = modelRecord->Guid; + package.Header.SourceHash = document.SourceHash; + package.NameTable.emplace_back("MetaCoreModelAssetDocument"); + package.Exports.push_back(MetaCore::MetaCoreExportEntry{ + modelRecord->Guid, + modelRecord->RelativePath.filename().string(), + MetaCore::MetaCoreMakeTypeId("MetaCoreModelAssetDocument"), + 0, + static_cast(documentPayload->size()) + }); + package.PayloadSections.push_back(*documentPayload); + package.PayloadSections.push_back(makePlanePayload(2.0F)); + package.PayloadSections.push_back(makePlanePayload(1.0F)); + MetaCoreExpect( + packageService->WritePackage(tempProjectRoot / modelRecord->PackagePath, std::move(package)), + "三角面拾取测试应能写入模型包" + ); + MetaCoreExpect(assetDatabase->Refresh(), "三角面拾取测试写入模型包后应能刷新资产数据库"); + + MetaCore::MetaCoreGameObject shellObject = scene.CreateGameObject("Shell"); + shellObject.GetComponent().Position = glm::vec3(0.0F, 5.0F, 0.0F); + auto& shellRenderer = shellObject.AddComponent(); + shellRenderer.MeshSource = MetaCore::MetaCoreMeshSourceKind::Asset; + shellRenderer.SourceModelAssetGuid = modelRecord->Guid; + shellRenderer.MeshAssetGuid = shellMesh.AssetGuid; + shellRenderer.ModelNodeIndex = 0; + + MetaCore::MetaCoreGameObject interiorObject = scene.CreateGameObject("Interior", shellObject.GetId()); + interiorObject.GetComponent().Position = glm::vec3(0.0F, 1.0F, 0.0F); + auto& interiorRenderer = interiorObject.AddComponent(); + interiorRenderer.MeshSource = MetaCore::MetaCoreMeshSourceKind::Asset; + interiorRenderer.SourceModelAssetGuid = modelRecord->Guid; + interiorRenderer.MeshAssetGuid = interiorMesh.AssetGuid; + interiorRenderer.ModelNodeIndex = 1; + + 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 == shellObject.GetId(), "三角面拾取应选择可见的外壳,而不是层级更深的内部部件"); + + coreServicesModule->Shutdown(moduleRegistry); + moduleRegistry.ShutdownServices(); + _putenv_s("METACORE_PROJECT_PATH", ""); + std::filesystem::remove_all(tempProjectRoot); +} + void MetaCoreTestJsonSceneSaveCurrentSceneUsesMcsceneJson() { const std::filesystem::path tempProjectRoot = std::filesystem::temp_directory_path() / "MetaCoreSceneRoundTripProject"; @@ -4979,6 +5173,8 @@ int main() { MetaCoreTestCameraSelectionAndProjectionUtilities(); std::cout << "[RUN] MetaCoreTestViewportPickingPrefersModelChildNode..." << std::endl; MetaCoreTestViewportPickingPrefersModelChildNode(); + std::cout << "[RUN] MetaCoreTestViewportPickingUsesMeshTrianglesBeforeHierarchyDepth..." << std::endl; + MetaCoreTestViewportPickingUsesMeshTrianglesBeforeHierarchyDepth(); std::cout << "[RUN] MetaCoreTestJsonSceneSaveCurrentSceneUsesMcsceneJson..." << std::endl; MetaCoreTestJsonSceneSaveCurrentSceneUsesMcsceneJson(); std::cout << "[RUN] MetaCoreTestImportPipelineAndCook..." << std::endl;