UI系统更新

This commit is contained in:
Rowland 2026-06-30 09:46:50 +08:00
parent 429a574205
commit eae20bebb5
5 changed files with 688 additions and 27 deletions

Binary file not shown.

View File

@ -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"
}
]
}

View File

@ -2,8 +2,11 @@
#include "MetaCoreFoundation/MetaCoreHash.h"
#include "MetaCoreLocalCgltf.h"
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/matrix_decompose.hpp>
#include <nlohmann/json.hpp>
#include <filesystem>
@ -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<std::int32_t>(node.parent - data->nodes) : -1;
nodeDoc.MeshIndex = node.mesh ? static_cast<std::int32_t>(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) {

View File

@ -23,10 +23,15 @@
#include <glm/ext/matrix_transform.hpp>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cmath>
#include <cstring>
#include <filesystem>
#include <limits>
#include <optional>
#include <unordered_map>
#include <vector>
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<glm::vec3> Positions{};
std::vector<std::uint32_t> 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<MetaCorePickBounds> 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<float>::max());
transformedBounds.Max = glm::vec3(std::numeric_limits<float>::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<MetaCoreResolvedPickGeometry> MetaCoreResolveMeshAssetPickGeometry(
const MetaCoreMeshRendererComponent& meshRenderer,
const MetaCoreModelAssetDocument& modelDocument
) {
@ -114,17 +168,18 @@ std::optional<MetaCorePickBounds> 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<MetaCoreIAssetEditingService>& assetEditingService,
std::unordered_map<MetaCoreAssetGuid, std::optional<MetaCoreModelAssetDocument>, 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<MetaCorePickMeshData> MetaCoreDecodePickMeshPayload(
const std::vector<std::byte>& 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<std::size_t>(vertexCount) * sizeof(MetaCorePackedPickVertex) +
static_cast<std::size_t>(indexCount) * sizeof(std::uint32_t);
if (expectedSize > payload.size()) {
return std::nullopt;
}
if (meshAsset.VertexCount > 0 && vertexCount != static_cast<std::uint32_t>(meshAsset.VertexCount)) {
return std::nullopt;
}
if (meshAsset.IndexCount > 0 && indexCount != static_cast<std::uint32_t>(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<std::size_t>(indexCount) * sizeof(std::uint32_t));
return meshData;
}
std::optional<MetaCorePickMeshData> MetaCoreLoadPickMeshData(
const MetaCoreResolvedPickGeometry& geometry,
const std::shared_ptr<MetaCoreIAssetDatabaseService>& assetDatabaseService,
const std::shared_ptr<MetaCoreIPackageService>& packageService,
std::unordered_map<MetaCoreAssetGuid, std::optional<MetaCorePackageDocument>, MetaCoreAssetGuidHasher>& packageCache,
std::unordered_map<MetaCoreAssetGuid, std::optional<MetaCorePickMeshData>, 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<MetaCorePackageDocument> 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<MetaCorePickMeshData> meshData;
if (packageIterator->second.has_value() &&
geometry.MeshAsset->PayloadIndex < packageIterator->second->PayloadSections.size()) {
meshData = MetaCoreDecodePickMeshPayload(
packageIterator->second->PayloadSections[static_cast<std::size_t>(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<std::uint32_t>(meshData.Indices.size());
if (meshRenderer.SubMeshIndex >= 0 &&
static_cast<std::size_t>(meshRenderer.SubMeshIndex) < meshAsset.SubMeshes.size()) {
const MetaCoreMeshSubMeshDocument& subMesh = meshAsset.SubMeshes[static_cast<std::size_t>(meshRenderer.SubMeshIndex)];
firstIndex = subMesh.FirstIndex;
indexCount = subMesh.IndexCount;
}
const std::uint64_t indexEnd64 = static_cast<std::uint64_t>(firstIndex) + static_cast<std::uint64_t>(indexCount);
if (indexCount < 3 || firstIndex >= meshData.Indices.size() || indexEnd64 > meshData.Indices.size()) {
return false;
}
bool hit = false;
float bestLocalHitDistance = std::numeric_limits<float>::max();
const std::uint32_t indexEnd = static_cast<std::uint32_t>(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<float>::max();
int bestHierarchyDepth = -1;
const auto assetEditingService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetEditingService>();
const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
const auto packageService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIPackageService>();
std::unordered_map<MetaCoreAssetGuid, std::optional<MetaCoreModelAssetDocument>, MetaCoreAssetGuidHasher> modelAssetCache;
std::unordered_map<MetaCoreAssetGuid, std::optional<MetaCorePackageDocument>, MetaCoreAssetGuidHasher> packageCache;
std::unordered_map<MetaCoreAssetGuid, std::optional<MetaCorePickMeshData>, MetaCoreAssetGuidHasher> meshDataCache;
for (const MetaCoreGameObject& gameObject : scene.GetGameObjects()) {
if (!gameObject.HasComponent<MetaCoreMeshRendererComponent>() || !gameObject.GetComponent<MetaCoreMeshRendererComponent>().Visible) {
@ -307,7 +560,7 @@ MetaCoreId MetaCoreSceneInteractionService::PickGameObjectFromViewport(
}
const MetaCoreMeshRendererComponent& meshRenderer = gameObject.GetComponent<MetaCoreMeshRendererComponent>();
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);

View File

@ -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<MetaCore::MetaCoreIAssetDatabaseService>();
const auto packageService = moduleRegistry.ResolveService<MetaCore::MetaCoreIPackageService>();
const auto reflectionRegistry = moduleRegistry.ResolveService<MetaCore::MetaCoreIReflectionRegistry>();
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<TestPickVertex> 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<std::uint32_t> indices{0, 1, 2, 0, 2, 3};
std::vector<std::byte> 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<std::uint32_t>(vertices.size());
const std::uint32_t indexCount = static_cast<std::uint32_t>(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<std::uint64_t>(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<MetaCore::MetaCoreTransformComponent>().Position = glm::vec3(0.0F, 5.0F, 0.0F);
auto& shellRenderer = shellObject.AddComponent<MetaCore::MetaCoreMeshRendererComponent>();
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<MetaCore::MetaCoreTransformComponent>().Position = glm::vec3(0.0F, 1.0F, 0.0F);
auto& interiorRenderer = interiorObject.AddComponent<MetaCore::MetaCoreMeshRendererComponent>();
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;