UI系统更新
This commit is contained in:
parent
429a574205
commit
eae20bebb5
BIN
SandboxProject/Assets/Models/jxb.glb
Normal file
BIN
SandboxProject/Assets/Models/jxb.glb
Normal file
Binary file not shown.
176
SandboxProject/Assets/Models/jxb.glb.mcmeta
Normal file
176
SandboxProject/Assets/Models/jxb.glb.mcmeta
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user