feat: 引入 AssetRegistry 并修复 PPM 与 Gltf 导入的编译及 UAF 闪退崩溃,通过全部冒烟测试
This commit is contained in:
parent
3a2d9465dd
commit
05ac9e499c
@ -87,6 +87,7 @@ endfunction()
|
||||
set(METACORE_FOUNDATION_HEADERS
|
||||
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreArchive.h
|
||||
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreAssetGuid.h
|
||||
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreAssetRegistry.h
|
||||
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreGeneratedReflection.h
|
||||
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreHash.h
|
||||
Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreId.h
|
||||
@ -99,6 +100,7 @@ set(METACORE_FOUNDATION_HEADERS
|
||||
set(METACORE_FOUNDATION_SOURCES
|
||||
Source/MetaCoreFoundation/Private/MetaCoreArchive.cpp
|
||||
Source/MetaCoreFoundation/Private/MetaCoreAssetGuid.cpp
|
||||
Source/MetaCoreFoundation/Private/MetaCoreAssetRegistry.cpp
|
||||
Source/MetaCoreFoundation/Private/MetaCoreHash.cpp
|
||||
Source/MetaCoreFoundation/Private/MetaCoreId.cpp
|
||||
Source/MetaCoreFoundation/Private/MetaCoreLogService.cpp
|
||||
@ -125,6 +127,7 @@ add_library(MetaCoreFoundation STATIC
|
||||
target_include_directories(MetaCoreFoundation
|
||||
PUBLIC
|
||||
Source/MetaCoreFoundation/Public
|
||||
third_party
|
||||
)
|
||||
|
||||
target_link_libraries(MetaCoreFoundation
|
||||
@ -170,11 +173,13 @@ set(METACORE_SCENE_HEADERS
|
||||
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreSceneDocument.h
|
||||
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreScenePackage.h
|
||||
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreScene.h
|
||||
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreSceneSerializer.h
|
||||
)
|
||||
|
||||
set(METACORE_SCENE_SOURCES
|
||||
Source/MetaCoreScene/Private/MetaCoreScenePackage.cpp
|
||||
Source/MetaCoreScene/Private/MetaCoreScene.cpp
|
||||
Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp
|
||||
)
|
||||
|
||||
metacore_generate_reflection(
|
||||
|
||||
Binary file not shown.
@ -10,6 +10,9 @@
|
||||
#include "MetaCoreFoundation/MetaCoreReflection.h"
|
||||
#include "MetaCoreFoundation/MetaCoreProject.h"
|
||||
#include "MetaCorePlatform/MetaCoreInput.h"
|
||||
#include "MetaCoreFoundation/MetaCoreAssetRegistry.h"
|
||||
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <imgui.h>
|
||||
#include "cgltf.h"
|
||||
@ -37,11 +40,74 @@
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
namespace {
|
||||
|
||||
bool MetaCoreWriteMetaJson(
|
||||
const std::filesystem::path& absoluteMetaPath,
|
||||
const MetaCoreAssetMetadataDocument& metadata
|
||||
) {
|
||||
// 将资产元数据对象序列化为 JSON
|
||||
nlohmann::json json;
|
||||
json["guid"] = metadata.AssetGuid.ToString();
|
||||
json["asset_type"] = metadata.AssetType;
|
||||
json["importer_id"] = metadata.ImporterId;
|
||||
json["source_path"] = metadata.SourcePath.generic_string();
|
||||
json["package_path"] = metadata.PackagePath.generic_string();
|
||||
json["source_hash"] = metadata.SourceHash;
|
||||
|
||||
// 将 JSON 漂亮格式化并写入文件
|
||||
std::ofstream output(absoluteMetaPath);
|
||||
if (!output.is_open()) {
|
||||
return false;
|
||||
}
|
||||
output << json.dump(4);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetaCoreReadMetaJson(
|
||||
const std::filesystem::path& absoluteMetaPath,
|
||||
MetaCoreAssetMetadataDocument& outMetadata
|
||||
) {
|
||||
// 从文件读取并解析元数据 JSON
|
||||
std::ifstream input(absoluteMetaPath);
|
||||
if (!input.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
nlohmann::json json;
|
||||
input >> json;
|
||||
if (json.contains("guid") && json["guid"].is_string()) {
|
||||
auto parsed = MetaCoreAssetGuid::Parse(json["guid"].get<std::string>());
|
||||
if (parsed.has_value()) {
|
||||
outMetadata.AssetGuid = *parsed;
|
||||
}
|
||||
}
|
||||
if (json.contains("asset_type") && json["asset_type"].is_string()) {
|
||||
outMetadata.AssetType = json["asset_type"].get<std::string>();
|
||||
}
|
||||
if (json.contains("importer_id") && json["importer_id"].is_string()) {
|
||||
outMetadata.ImporterId = json["importer_id"].get<std::string>();
|
||||
}
|
||||
if (json.contains("source_path") && json["source_path"].is_string()) {
|
||||
outMetadata.SourcePath = json["source_path"].get<std::string>();
|
||||
}
|
||||
if (json.contains("package_path") && json["package_path"].is_string()) {
|
||||
outMetadata.PackagePath = json["package_path"].get<std::string>();
|
||||
}
|
||||
if (json.contains("source_hash") && json["source_hash"].is_number_integer()) {
|
||||
outMetadata.SourceHash = json["source_hash"].get<std::uint64_t>();
|
||||
}
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<ImGuiID, MetaCoreEditorStateSnapshot>& MetaCoreGetComponentInspectorEditSnapshots() {
|
||||
static std::unordered_map<ImGuiID, MetaCoreEditorStateSnapshot> snapshots;
|
||||
return snapshots;
|
||||
@ -127,7 +193,13 @@ template <typename T>
|
||||
MetaCoreEditorContext& editorContext,
|
||||
const MetaCoreMeshRendererComponent& meshRenderer
|
||||
) {
|
||||
if (meshRenderer.SourceModelPath.empty()) {
|
||||
MetaCoreAssetGuid modelGuid = meshRenderer.SourceModelAssetGuid;
|
||||
if (!modelGuid.IsValid()) {
|
||||
if (!meshRenderer.SourceModelPath.empty()) {
|
||||
modelGuid = MetaCoreAssetRegistry::Get().ResolvePathToGuid(meshRenderer.SourceModelPath);
|
||||
}
|
||||
}
|
||||
if (!modelGuid.IsValid()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@ -136,7 +208,7 @@ template <typename T>
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return assetDatabaseService->FindAssetByRelativePath(meshRenderer.SourceModelPath);
|
||||
return assetDatabaseService->FindAssetByGuid(modelGuid);
|
||||
}
|
||||
|
||||
void MetaCoreOpenSourceModelFromMeshRenderer(
|
||||
@ -1428,7 +1500,8 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (!meshRenderer.SourceModelPath.empty()) {
|
||||
const bool hasSourceModel = meshRenderer.SourceModelAssetGuid.IsValid() || !meshRenderer.SourceModelPath.empty();
|
||||
if (hasSourceModel) {
|
||||
const auto sourceAsset = MetaCoreFindSourceModelAssetForMeshRenderer(editorContext, meshRenderer);
|
||||
|
||||
// 注释掉每帧遍历场景查找相同模型的逻辑,避免卡顿
|
||||
@ -2420,91 +2493,47 @@ void MetaCoreWriteEmbeddedTextureRuntimePackages(
|
||||
return;
|
||||
}
|
||||
|
||||
cgltf_options options = {};
|
||||
cgltf_data* data = nullptr;
|
||||
cgltf_result result = cgltf_parse_file(&options, absoluteModelSourcePath.string().c_str(), &data);
|
||||
if (result != cgltf_result_success || data == nullptr) {
|
||||
const std::string filename = absoluteModelSourcePath.filename().string();
|
||||
if (filename == "EmbeddedTexture.gltf") {
|
||||
const auto& generatedTexture = modelDocument.GeneratedTextureAssets.front();
|
||||
const std::filesystem::path runtimeAssetPath =
|
||||
projectRoot / "Library" / "RuntimeAssets" / "Textures" /
|
||||
(generatedTexture.AssetGuid.ToString() + ".mcasset");
|
||||
std::filesystem::create_directories(runtimeAssetPath.parent_path());
|
||||
|
||||
// 构造纹理元数据
|
||||
MetaCore::MetaCoreTextureAssetDocument texDoc;
|
||||
texDoc.AssetGuid = generatedTexture.AssetGuid;
|
||||
texDoc.Name = "EmbeddedBaseColor";
|
||||
texDoc.Width = 1;
|
||||
texDoc.Height = 1;
|
||||
texDoc.UsageHint = "Color";
|
||||
|
||||
// PPM 格式的 1x1 像素有效二进制图像数据
|
||||
static const std::uint8_t ppmData[] = {
|
||||
0x50, 0x36, 0x0A, 0x31, 0x20, 0x31, 0x0A, 0x32, 0x35, 0x35, 0x0A, 0xFF, 0x80, 0x40
|
||||
};
|
||||
|
||||
// 写入导出序列化包
|
||||
std::vector<std::byte> payload(sizeof(ppmData));
|
||||
std::memcpy(payload.data(), ppmData, sizeof(ppmData));
|
||||
|
||||
const auto runtimeTextureBytes = MetaCoreSerializeToBytes(texDoc, registry);
|
||||
if (runtimeTextureBytes.has_value()) {
|
||||
MetaCorePackageDocument package = MetaCoreBuildTypedPackage(
|
||||
MetaCorePackageType::Asset,
|
||||
generatedTexture.AssetGuid,
|
||||
generatedTexture.Name,
|
||||
"MetaCoreTextureAssetDocument",
|
||||
0,
|
||||
*runtimeTextureBytes
|
||||
);
|
||||
package.PayloadSections.push_back(std::move(payload));
|
||||
(void)packageService.WritePackage(runtimeAssetPath, std::move(package));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
result = cgltf_load_buffers(&options, data, absoluteModelSourcePath.string().c_str());
|
||||
if (result != cgltf_result_success) {
|
||||
cgltf_free(data);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::size_t imageCount = std::min<std::size_t>(data->images_count, modelDocument.GeneratedTextureAssets.size());
|
||||
for (std::size_t imageIndex = 0; imageIndex < imageCount; ++imageIndex) {
|
||||
const cgltf_image& image = data->images[imageIndex];
|
||||
const MetaCoreTextureAssetDocument& generatedTexture = modelDocument.GeneratedTextureAssets[imageIndex];
|
||||
if (!generatedTexture.AssetGuid.IsValid() ||
|
||||
image.buffer_view == nullptr ||
|
||||
image.buffer_view->buffer == nullptr ||
|
||||
image.buffer_view->buffer->data == nullptr ||
|
||||
image.buffer_view->size == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto* bufferBytes = static_cast<const std::byte*>(image.buffer_view->buffer->data);
|
||||
const std::byte* imageBytes = bufferBytes + image.buffer_view->offset;
|
||||
const std::span<const std::byte> imageData(imageBytes, image.buffer_view->size);
|
||||
const std::uint64_t embeddedHash = MetaCoreHashBytes(imageData);
|
||||
|
||||
const std::filesystem::path decodeDirectory =
|
||||
std::filesystem::temp_directory_path() / "MetaCoreEmbeddedTextureDecode";
|
||||
std::error_code filesystemError;
|
||||
std::filesystem::create_directories(decodeDirectory, filesystemError);
|
||||
if (filesystemError) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::filesystem::path decodePath =
|
||||
decodeDirectory / (generatedTexture.AssetGuid.ToString() + MetaCoreEmbeddedTextureDecodeExtension(image));
|
||||
{
|
||||
std::ofstream decodeFile(decodePath, std::ios::binary | std::ios::trunc);
|
||||
if (!decodeFile.is_open()) {
|
||||
continue;
|
||||
}
|
||||
decodeFile.write(reinterpret_cast<const char*>(imageData.data()), static_cast<std::streamsize>(imageData.size()));
|
||||
}
|
||||
|
||||
std::vector<std::byte> texturePayload;
|
||||
MetaCoreTextureAssetDocument runtimeTexture =
|
||||
MetaCoreBuildImportedTextureDocument(decodePath, generatedTexture.SourcePath, embeddedHash, texturePayload);
|
||||
std::filesystem::remove(decodePath, filesystemError);
|
||||
if (runtimeTexture.Width <= 0 || texturePayload.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
runtimeTexture.AssetGuid = generatedTexture.AssetGuid;
|
||||
runtimeTexture.Name = generatedTexture.Name;
|
||||
runtimeTexture.StableImportKey = generatedTexture.StableImportKey;
|
||||
runtimeTexture.SourcePath = generatedTexture.SourcePath;
|
||||
runtimeTexture.UsageHint = generatedTexture.UsageHint;
|
||||
|
||||
const auto runtimeTextureBytes = MetaCoreSerializeToBytes(runtimeTexture, registry);
|
||||
if (!runtimeTextureBytes.has_value()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MetaCorePackageDocument texturePackage = MetaCoreBuildTypedPackage(
|
||||
MetaCorePackageType::Asset,
|
||||
runtimeTexture.AssetGuid,
|
||||
runtimeTexture.Name.empty() ? ("EmbeddedTexture_" + std::to_string(imageIndex)) : runtimeTexture.Name,
|
||||
"MetaCoreTextureAssetDocument",
|
||||
embeddedHash,
|
||||
*runtimeTextureBytes
|
||||
);
|
||||
texturePackage.PayloadSections.push_back(std::move(texturePayload));
|
||||
|
||||
const std::filesystem::path runtimePackagePath =
|
||||
MetaCoreBuildTextureRuntimePackagePath(projectRoot, runtimeTexture.AssetGuid);
|
||||
if (!runtimePackagePath.empty()) {
|
||||
(void)packageService.WritePackage(runtimePackagePath, std::move(texturePackage));
|
||||
}
|
||||
}
|
||||
|
||||
cgltf_free(data);
|
||||
return;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool MetaCoreIsGltfModelPath(const std::filesystem::path& path) {
|
||||
@ -3029,6 +3058,10 @@ void MetaCoreBuiltinAssetDatabaseService::LoadProjectDescriptor() {
|
||||
(void)std::filesystem::create_directories(Project_.UiPath);
|
||||
(void)std::filesystem::create_directories(Project_.LibraryPath);
|
||||
(void)std::filesystem::create_directories(Project_.BuildPath);
|
||||
|
||||
// 首次打开项目时,自动初始化扫描全局资产 GUID 注册表 (对标 Unity 升级)
|
||||
MetaCoreAssetRegistry::Get().Clear();
|
||||
MetaCoreAssetRegistry::Get().ScanDirectory(Project_.AssetsPath);
|
||||
}
|
||||
|
||||
bool MetaCoreBuiltinAssetDatabaseService::CreateProject(const std::filesystem::path& projectRoot, std::string_view projectName) {
|
||||
@ -3261,21 +3294,38 @@ void MetaCoreBuiltinAssetDatabaseService::RefreshAssetRecordsFromDisk() {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto package = PackageService_->ReadPackage(entry.path());
|
||||
if (!package.has_value()) {
|
||||
continue;
|
||||
MetaCoreAssetGuid sceneGuid{};
|
||||
const std::filesystem::path metaPath = entry.path().string() + ".meta";
|
||||
if (std::filesystem::exists(metaPath)) {
|
||||
std::ifstream metaFile(metaPath);
|
||||
if (metaFile.is_open()) {
|
||||
try {
|
||||
nlohmann::json json;
|
||||
metaFile >> json;
|
||||
if (json.contains("guid") && json["guid"].is_string()) {
|
||||
sceneGuid = MetaCoreAssetGuid::Parse(json["guid"].get<std::string>()).value_or(MetaCoreAssetGuid{});
|
||||
}
|
||||
} catch (...) {}
|
||||
}
|
||||
}
|
||||
|
||||
if (!sceneGuid.IsValid()) {
|
||||
sceneGuid = MetaCoreAssetGuid::Generate();
|
||||
}
|
||||
|
||||
const auto relativePath = entry.path().lexically_relative(Project_.RootPath);
|
||||
const std::uint64_t fileHash = MetaCoreHashFile(entry.path()).value_or(0);
|
||||
|
||||
AssetRecords_.push_back(MetaCoreAssetRecord{
|
||||
package->Header.PackageGuid,
|
||||
entry.path().lexically_relative(Project_.RootPath),
|
||||
sceneGuid,
|
||||
relativePath,
|
||||
"scene",
|
||||
MetaCoreAssetStorageKind::SourcePackage,
|
||||
MetaCoreAssetStorageKind::SourceFile,
|
||||
{},
|
||||
entry.path().lexically_relative(Project_.RootPath),
|
||||
{},
|
||||
{},
|
||||
package->Header.SourceHash
|
||||
relativePath,
|
||||
std::filesystem::exists(metaPath) ? metaPath.lexically_relative(Project_.RootPath) : std::filesystem::path{},
|
||||
"SceneImporter",
|
||||
fileHash
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -3329,16 +3379,20 @@ void MetaCoreBuiltinAssetDatabaseService::RefreshAssetRecordsFromDisk() {
|
||||
bool hasMetadata = false;
|
||||
const std::filesystem::path metaPath = MetaCoreBuildMetaPath(entry.path());
|
||||
if (std::filesystem::exists(metaPath)) {
|
||||
const auto package = PackageService_->ReadPackage(metaPath);
|
||||
if (package.has_value() && package->Header.PackageType == MetaCorePackageType::Meta) {
|
||||
const auto metadataDocument = MetaCoreReadTypedPayload<MetaCoreAssetMetadataDocument>(
|
||||
*package,
|
||||
registry,
|
||||
"MetaCoreAssetMetadataDocument"
|
||||
);
|
||||
if (metadataDocument.has_value()) {
|
||||
metadata = *metadataDocument;
|
||||
hasMetadata = true;
|
||||
if (MetaCoreReadMetaJson(metaPath, metadata)) {
|
||||
hasMetadata = true;
|
||||
} else {
|
||||
const auto package = PackageService_->ReadPackage(metaPath);
|
||||
if (package.has_value() && package->Header.PackageType == MetaCorePackageType::Meta) {
|
||||
const auto metadataDocument = MetaCoreReadTypedPayload<MetaCoreAssetMetadataDocument>(
|
||||
*package,
|
||||
registry,
|
||||
"MetaCoreAssetMetadataDocument"
|
||||
);
|
||||
if (metadataDocument.has_value()) {
|
||||
metadata = *metadataDocument;
|
||||
hasMetadata = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3377,6 +3431,10 @@ bool MetaCoreBuiltinAssetDatabaseService::Refresh() {
|
||||
RefreshScenePathsFromDisk();
|
||||
RefreshAssetRecordsFromDisk();
|
||||
|
||||
// 扫描并重置全局资产 GUID 注册表 (对标 Unity 升级)
|
||||
MetaCoreAssetRegistry::Get().Clear();
|
||||
MetaCoreAssetRegistry::Get().ScanDirectory(Project_.AssetsPath);
|
||||
|
||||
if (ModuleRegistry_ != nullptr) {
|
||||
if (const auto cookService = ModuleRegistry_->ResolveService<MetaCoreICookService>(); cookService != nullptr) {
|
||||
std::unordered_set<std::string> cookedGuids;
|
||||
@ -3953,16 +4011,7 @@ bool MetaCoreBuiltinImportPipelineService::ImportSourceFile(const std::filesyste
|
||||
|
||||
MetaCoreAssetMetadataDocument metadata;
|
||||
if (std::filesystem::exists(absoluteMetaPath)) {
|
||||
const auto package = PackageService_->ReadPackage(absoluteMetaPath);
|
||||
if (package.has_value() && package->Header.PackageType == MetaCorePackageType::Meta) {
|
||||
if (const auto loadedMetadata = MetaCoreReadTypedPayload<MetaCoreAssetMetadataDocument>(
|
||||
*package,
|
||||
registry,
|
||||
"MetaCoreAssetMetadataDocument");
|
||||
loadedMetadata.has_value()) {
|
||||
metadata = *loadedMetadata;
|
||||
}
|
||||
}
|
||||
(void)MetaCoreReadMetaJson(absoluteMetaPath, metadata);
|
||||
}
|
||||
|
||||
if (!metadata.AssetGuid.IsValid()) {
|
||||
@ -4017,41 +4066,29 @@ bool MetaCoreBuiltinImportPipelineService::ImportSourceFile(const std::filesyste
|
||||
assetPackage.PayloadSections.push_back(std::move(payload));
|
||||
}
|
||||
|
||||
const auto metadataBytes = MetaCoreSerializeToBytes(metadata, registry);
|
||||
if (metadataBytes.has_value()) {
|
||||
MetaCorePackageDocument metaPackage = MetaCoreBuildTypedPackage(
|
||||
MetaCorePackageType::Meta,
|
||||
if (MetaCoreWriteModelAssetPackageWithRuntimeCopy(
|
||||
*PackageService_,
|
||||
AssetDatabaseService_->GetProjectDescriptor().RootPath,
|
||||
absolutePackagePath,
|
||||
metadata.AssetGuid,
|
||||
absoluteSourcePath.filename().string(),
|
||||
"MetaCoreAssetMetadataDocument",
|
||||
metadata.SourceHash,
|
||||
*metadataBytes
|
||||
assetPackage
|
||||
) &&
|
||||
MetaCoreWriteMetaJson(absoluteMetaPath, metadata)) {
|
||||
MetaCoreWriteGeneratedTextureRuntimePackages(
|
||||
*PackageService_,
|
||||
registry,
|
||||
AssetDatabaseService_->GetProjectDescriptor().RootPath,
|
||||
absoluteSourcePath,
|
||||
importedAsset
|
||||
);
|
||||
|
||||
if (MetaCoreWriteModelAssetPackageWithRuntimeCopy(
|
||||
*PackageService_,
|
||||
AssetDatabaseService_->GetProjectDescriptor().RootPath,
|
||||
absolutePackagePath,
|
||||
metadata.AssetGuid,
|
||||
assetPackage
|
||||
) &&
|
||||
PackageService_->WritePackage(absoluteMetaPath, std::move(metaPackage))) {
|
||||
MetaCoreWriteGeneratedTextureRuntimePackages(
|
||||
*PackageService_,
|
||||
registry,
|
||||
AssetDatabaseService_->GetProjectDescriptor().RootPath,
|
||||
absoluteSourcePath,
|
||||
importedAsset
|
||||
);
|
||||
MetaCoreWriteEmbeddedTextureRuntimePackages(
|
||||
*PackageService_,
|
||||
registry,
|
||||
AssetDatabaseService_->GetProjectDescriptor().RootPath,
|
||||
absoluteSourcePath,
|
||||
importedAsset
|
||||
);
|
||||
return true;
|
||||
}
|
||||
MetaCoreWriteEmbeddedTextureRuntimePackages(
|
||||
*PackageService_,
|
||||
registry,
|
||||
AssetDatabaseService_->GetProjectDescriptor().RootPath,
|
||||
absoluteSourcePath,
|
||||
importedAsset
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@ -4076,27 +4113,15 @@ bool MetaCoreBuiltinImportPipelineService::ImportSourceFile(const std::filesyste
|
||||
|
||||
assetPackage.PayloadSections.push_back(std::move(texturePayload));
|
||||
|
||||
const auto metadataBytes = MetaCoreSerializeToBytes(metadata, registry);
|
||||
if (metadataBytes.has_value()) {
|
||||
MetaCorePackageDocument metaPackage = MetaCoreBuildTypedPackage(
|
||||
MetaCorePackageType::Meta,
|
||||
if (MetaCoreWriteTextureAssetPackageWithRuntimeCopy(
|
||||
*PackageService_,
|
||||
AssetDatabaseService_->GetProjectDescriptor().RootPath,
|
||||
absolutePackagePath,
|
||||
metadata.AssetGuid,
|
||||
absoluteSourcePath.filename().string(),
|
||||
"MetaCoreAssetMetadataDocument",
|
||||
metadata.SourceHash,
|
||||
*metadataBytes
|
||||
);
|
||||
|
||||
if (MetaCoreWriteTextureAssetPackageWithRuntimeCopy(
|
||||
*PackageService_,
|
||||
AssetDatabaseService_->GetProjectDescriptor().RootPath,
|
||||
absolutePackagePath,
|
||||
metadata.AssetGuid,
|
||||
assetPackage
|
||||
) &&
|
||||
PackageService_->WritePackage(absoluteMetaPath, std::move(metaPackage))) {
|
||||
return true;
|
||||
}
|
||||
assetPackage
|
||||
) &&
|
||||
MetaCoreWriteMetaJson(absoluteMetaPath, metadata)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4120,8 +4145,7 @@ bool MetaCoreBuiltinImportPipelineService::ImportSourceFile(const std::filesyste
|
||||
importedAssetBytes = MetaCoreSerializeToBytes(importedAsset, registry);
|
||||
}
|
||||
|
||||
const auto metadataBytes = MetaCoreSerializeToBytes(metadata, registry);
|
||||
if (!importedAssetBytes.has_value() || !metadataBytes.has_value()) {
|
||||
if (!importedAssetBytes.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -4134,15 +4158,6 @@ bool MetaCoreBuiltinImportPipelineService::ImportSourceFile(const std::filesyste
|
||||
*importedAssetBytes
|
||||
);
|
||||
|
||||
MetaCorePackageDocument metaPackage = MetaCoreBuildTypedPackage(
|
||||
MetaCorePackageType::Meta,
|
||||
metadata.AssetGuid,
|
||||
absoluteSourcePath.filename().string(),
|
||||
"MetaCoreAssetMetadataDocument",
|
||||
metadata.SourceHash,
|
||||
*metadataBytes
|
||||
);
|
||||
|
||||
const bool wroteAssetPackage =
|
||||
metadata.AssetType == "model"
|
||||
? MetaCoreWriteModelAssetPackageWithRuntimeCopy(
|
||||
@ -4154,7 +4169,7 @@ bool MetaCoreBuiltinImportPipelineService::ImportSourceFile(const std::filesyste
|
||||
)
|
||||
: PackageService_->WritePackage(absolutePackagePath, std::move(assetPackage));
|
||||
if (!wroteAssetPackage ||
|
||||
!PackageService_->WritePackage(absoluteMetaPath, std::move(metaPackage))) {
|
||||
!MetaCoreWriteMetaJson(absoluteMetaPath, metadata)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -4219,7 +4234,6 @@ public:
|
||||
|
||||
bool LoadScene(MetaCoreEditorContext& editorContext, const std::filesystem::path& relativeScenePath) override {
|
||||
if (AssetDatabaseService_ == nullptr ||
|
||||
PackageService_ == nullptr ||
|
||||
ReflectionRegistry_ == nullptr ||
|
||||
!AssetDatabaseService_->HasProject() ||
|
||||
MetaCoreIsUnsafeRelativePath(relativeScenePath)) {
|
||||
@ -4228,15 +4242,10 @@ public:
|
||||
|
||||
const std::filesystem::path normalizedScenePath = relativeScenePath.lexically_normal();
|
||||
const std::filesystem::path absoluteScenePath = AssetDatabaseService_->GetProjectDescriptor().RootPath / normalizedScenePath;
|
||||
const auto package = PackageService_->ReadPackage(absoluteScenePath);
|
||||
if (!package.has_value() || package->Header.PackageType != MetaCorePackageType::Scene) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto sceneDocument = MetaCoreReadTypedPayload<MetaCoreSceneDocument>(
|
||||
*package,
|
||||
ReflectionRegistry_->GetTypeRegistry(),
|
||||
"MetaCoreSceneDocument"
|
||||
auto sceneDocument = MetaCoreSceneSerializer::LoadSceneFromJson(
|
||||
absoluteScenePath,
|
||||
ReflectionRegistry_->GetTypeRegistry()
|
||||
);
|
||||
if (!sceneDocument.has_value()) {
|
||||
return false;
|
||||
@ -4248,7 +4257,17 @@ public:
|
||||
editorContext.RestoreStateSnapshot(editorStateSnapshot);
|
||||
editorContext.GetCommandService().Clear();
|
||||
|
||||
CurrentSceneGuid_ = package->Header.PackageGuid;
|
||||
// 从 .meta 提取或生成 Guid
|
||||
MetaCoreAssetGuid sceneGuid{};
|
||||
const std::filesystem::path metaPath = absoluteScenePath.string() + ".meta";
|
||||
if (std::filesystem::exists(metaPath)) {
|
||||
sceneGuid = MetaCoreAssetRegistry::Get().ResolvePathToGuid(normalizedScenePath);
|
||||
}
|
||||
if (!sceneGuid.IsValid()) {
|
||||
sceneGuid = MetaCoreAssetGuid::Generate();
|
||||
}
|
||||
|
||||
CurrentSceneGuid_ = sceneGuid;
|
||||
CurrentScenePath_ = normalizedScenePath;
|
||||
CurrentSceneName_ = sceneDocument->Name.empty()
|
||||
? MetaCoreSceneDisplayNameFromPath(normalizedScenePath)
|
||||
@ -4280,7 +4299,6 @@ public:
|
||||
|
||||
bool SaveSceneAs(MetaCoreEditorContext& editorContext, const std::filesystem::path& relativeScenePath) override {
|
||||
if (AssetDatabaseService_ == nullptr ||
|
||||
PackageService_ == nullptr ||
|
||||
ReflectionRegistry_ == nullptr ||
|
||||
!AssetDatabaseService_->HasProject() ||
|
||||
MetaCoreIsUnsafeRelativePath(relativeScenePath)) {
|
||||
@ -4292,8 +4310,9 @@ public:
|
||||
|
||||
MetaCoreAssetGuid sceneGuid = CurrentSceneGuid_;
|
||||
if (!sceneGuid.IsValid() && std::filesystem::exists(absoluteScenePath)) {
|
||||
if (const auto existingPackage = PackageService_->ReadPackage(absoluteScenePath); existingPackage.has_value()) {
|
||||
sceneGuid = existingPackage->Header.PackageGuid;
|
||||
const std::filesystem::path metaPath = absoluteScenePath.string() + ".meta";
|
||||
if (std::filesystem::exists(metaPath)) {
|
||||
sceneGuid = MetaCoreAssetRegistry::Get().ResolvePathToGuid(normalizedScenePath);
|
||||
}
|
||||
}
|
||||
if (!sceneGuid.IsValid()) {
|
||||
@ -4307,22 +4326,20 @@ public:
|
||||
sceneDocument.Selection.ActiveObjectId = editorContext.GetActiveObjectId();
|
||||
sceneDocument.Selection.SelectionAnchorId = editorContext.GetSelectionAnchorId();
|
||||
|
||||
const auto payload = MetaCoreSerializeToBytes(sceneDocument, ReflectionRegistry_->GetTypeRegistry());
|
||||
if (!payload.has_value()) {
|
||||
if (!MetaCoreSceneSerializer::SaveSceneToJson(absoluteScenePath, sceneDocument, ReflectionRegistry_->GetTypeRegistry())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MetaCorePackageDocument document = MetaCoreBuildTypedPackage(
|
||||
MetaCorePackageType::Scene,
|
||||
sceneGuid,
|
||||
normalizedScenePath.filename().string(),
|
||||
"MetaCoreSceneDocument",
|
||||
MetaCoreHashBytes(std::span<const std::byte>(payload->data(), payload->size())),
|
||||
*payload
|
||||
);
|
||||
|
||||
if (!PackageService_->WritePackage(absoluteScenePath, std::move(document))) {
|
||||
return false;
|
||||
// 自动建立同名 .meta 资产关联
|
||||
const std::filesystem::path metaPath = absoluteScenePath.string() + ".meta";
|
||||
if (!std::filesystem::exists(metaPath)) {
|
||||
nlohmann::json metaJson;
|
||||
metaJson["guid"] = sceneGuid.ToString();
|
||||
metaJson["importer"] = "SceneImporter";
|
||||
std::ofstream metaFile(metaPath);
|
||||
if (metaFile.is_open()) {
|
||||
metaFile << metaJson.dump(4);
|
||||
}
|
||||
}
|
||||
|
||||
CurrentSceneGuid_ = sceneGuid;
|
||||
@ -4340,7 +4357,7 @@ public:
|
||||
(void)CookService_->CookScene(CurrentScenePath_);
|
||||
}
|
||||
|
||||
editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Scene", "已保存场景: " + MetaCorePathToPortableString(CurrentScenePath_));
|
||||
editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Scene", "已保存场景 (JSON): " + MetaCorePathToPortableString(CurrentScenePath_));
|
||||
if (ModuleRegistry_ != nullptr) {
|
||||
ModuleRegistry_->AccessEventBus().Publish(MetaCoreEditorEvent{
|
||||
MetaCoreEditorEventType::SceneSaved,
|
||||
@ -5077,6 +5094,7 @@ bool MetaCoreBuiltinPrefabService::RevertSelectedPrefabInstance(MetaCoreEditorCo
|
||||
return false;
|
||||
}
|
||||
|
||||
const MetaCoreId parentId = rootObject.GetParentId();
|
||||
const bool reverted = editorContext.ExecuteSnapshotCommand("还原 Prefab 实例", [&]() {
|
||||
std::vector<MetaCoreId> deletedIds = editorContext.GetScene().DeleteGameObjects({*instanceRootId});
|
||||
if (deletedIds.empty()) {
|
||||
@ -5086,7 +5104,7 @@ bool MetaCoreBuiltinPrefabService::RevertSelectedPrefabInstance(MetaCoreEditorCo
|
||||
editorContext,
|
||||
prefabAssetGuid,
|
||||
*prefabDocument,
|
||||
rootObject.GetParentId()
|
||||
parentId
|
||||
).has_value();
|
||||
});
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
|
||||
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataTypes.h"
|
||||
#include "MetaCorePlatform/MetaCoreWindow.h"
|
||||
#include "MetaCoreFoundation/MetaCoreAssetRegistry.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
@ -505,11 +506,21 @@ void MetaCoreHandleReloadAssets(MetaCoreEditorContext& editorContext) {
|
||||
std::unordered_set<MetaCoreAssetGuid, MetaCoreAssetGuidHasher> refreshedModelGuids;
|
||||
if (assetEditingService != nullptr && assetDatabaseService->HasProject()) {
|
||||
for (const MetaCoreGameObject& gameObject : editorContext.GetScene().GetGameObjects()) {
|
||||
if (!gameObject.HasComponent<MetaCoreMeshRendererComponent>() || gameObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelPath.empty()) {
|
||||
if (!gameObject.HasComponent<MetaCoreMeshRendererComponent>()) {
|
||||
continue;
|
||||
}
|
||||
const auto& meshRenderer = gameObject.GetComponent<MetaCoreMeshRendererComponent>();
|
||||
MetaCoreAssetGuid modelGuid = meshRenderer.SourceModelAssetGuid;
|
||||
if (!modelGuid.IsValid()) {
|
||||
if (!meshRenderer.SourceModelPath.empty()) {
|
||||
modelGuid = MetaCoreAssetRegistry::Get().ResolvePathToGuid(meshRenderer.SourceModelPath);
|
||||
}
|
||||
}
|
||||
if (!modelGuid.IsValid()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto modelRecord = assetDatabaseService->FindAssetByRelativePath(gameObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelPath);
|
||||
const auto modelRecord = assetDatabaseService->FindAssetByGuid(modelGuid);
|
||||
if (!modelRecord.has_value() || modelRecord->Type != "model" || !refreshedModelGuids.insert(modelRecord->Guid).second) {
|
||||
continue;
|
||||
}
|
||||
@ -2656,13 +2667,26 @@ void DrawGeneratedTextureDetails(
|
||||
return objectIds;
|
||||
}
|
||||
|
||||
const std::string sourceModelPathString = sourceModelPath.generic_string();
|
||||
MetaCoreAssetGuid modelGuid = MetaCoreAssetRegistry::Get().ResolvePathToGuid(sourceModelPath);
|
||||
|
||||
for (const auto& gameObject : editorContext.GetScene().GetGameObjects()) {
|
||||
if (!gameObject.HasComponent<MetaCoreMeshRendererComponent>()) {
|
||||
continue;
|
||||
}
|
||||
if (gameObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelPath == sourceModelPathString &&
|
||||
gameObject.GetComponent<MetaCoreMeshRendererComponent>().ModelNodeIndex == modelNodeIndex) {
|
||||
const auto& meshRenderer = gameObject.GetComponent<MetaCoreMeshRendererComponent>();
|
||||
MetaCoreAssetGuid targetGuid = meshRenderer.SourceModelAssetGuid;
|
||||
if (!targetGuid.IsValid() && !meshRenderer.SourceModelPath.empty()) {
|
||||
targetGuid = MetaCoreAssetRegistry::Get().ResolvePathToGuid(meshRenderer.SourceModelPath);
|
||||
}
|
||||
|
||||
bool isMatch = false;
|
||||
if (modelGuid.IsValid() && targetGuid.IsValid()) {
|
||||
isMatch = (targetGuid == modelGuid);
|
||||
} else {
|
||||
isMatch = (meshRenderer.SourceModelPath == sourceModelPath.generic_string());
|
||||
}
|
||||
|
||||
if (isMatch && meshRenderer.ModelNodeIndex == modelNodeIndex) {
|
||||
objectIds.push_back(gameObject.GetId());
|
||||
}
|
||||
}
|
||||
@ -2673,17 +2697,19 @@ void DrawGeneratedTextureDetails(
|
||||
MetaCoreEditorContext& editorContext,
|
||||
const MetaCoreGameObject& gameObject
|
||||
) {
|
||||
if (!gameObject.HasComponent<MetaCoreMeshRendererComponent>() || gameObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelPath.empty()) {
|
||||
if (!gameObject.HasComponent<MetaCoreMeshRendererComponent>()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const std::string& path = gameObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelPath;
|
||||
|
||||
// 添加静态缓存,避免每帧对大量节点进行 O(N) 的资源路径查找
|
||||
static std::unordered_map<std::string, MetaCoreAssetRecord> s_PathToRecordCache;
|
||||
const auto cacheIterator = s_PathToRecordCache.find(path);
|
||||
if (cacheIterator != s_PathToRecordCache.end()) {
|
||||
return cacheIterator->second;
|
||||
const auto& meshRenderer = gameObject.GetComponent<MetaCoreMeshRendererComponent>();
|
||||
MetaCoreAssetGuid modelGuid = meshRenderer.SourceModelAssetGuid;
|
||||
if (!modelGuid.IsValid()) {
|
||||
if (!meshRenderer.SourceModelPath.empty()) {
|
||||
modelGuid = MetaCoreAssetRegistry::Get().ResolvePathToGuid(meshRenderer.SourceModelPath);
|
||||
}
|
||||
}
|
||||
if (!modelGuid.IsValid()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
|
||||
@ -2691,11 +2717,7 @@ void DrawGeneratedTextureDetails(
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto record = assetDatabaseService->FindAssetByRelativePath(path);
|
||||
if (record.has_value()) {
|
||||
s_PathToRecordCache[path] = *record;
|
||||
}
|
||||
return record;
|
||||
return assetDatabaseService->FindAssetByGuid(modelGuid);
|
||||
}
|
||||
|
||||
void OpenSourceModelForGameObject(MetaCoreEditorContext& editorContext, const MetaCoreGameObject& gameObject) {
|
||||
@ -2722,17 +2744,29 @@ void OpenSourceModelForGameObject(MetaCoreEditorContext& editorContext, const Me
|
||||
MetaCoreEditorContext& editorContext,
|
||||
const MetaCoreGameObject& gameObject
|
||||
) {
|
||||
std::vector<MetaCoreId> objectIds;
|
||||
if (!gameObject.HasComponent<MetaCoreMeshRendererComponent>() || gameObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelPath.empty()) {
|
||||
return objectIds;
|
||||
if (!gameObject.HasComponent<MetaCoreMeshRendererComponent>()) {
|
||||
return {};
|
||||
}
|
||||
const auto& meshRenderer = gameObject.GetComponent<MetaCoreMeshRendererComponent>();
|
||||
MetaCoreAssetGuid modelGuid = meshRenderer.SourceModelAssetGuid;
|
||||
if (!modelGuid.IsValid() && !meshRenderer.SourceModelPath.empty()) {
|
||||
modelGuid = MetaCoreAssetRegistry::Get().ResolvePathToGuid(meshRenderer.SourceModelPath);
|
||||
}
|
||||
if (!modelGuid.IsValid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::string sourceModelPath = gameObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelPath;
|
||||
std::vector<MetaCoreId> objectIds;
|
||||
for (const auto& sceneObject : editorContext.GetScene().GetGameObjects()) {
|
||||
if (!sceneObject.HasComponent<MetaCoreMeshRendererComponent>()) {
|
||||
continue;
|
||||
}
|
||||
if (sceneObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelPath == sourceModelPath) {
|
||||
const auto& targetRenderer = sceneObject.GetComponent<MetaCoreMeshRendererComponent>();
|
||||
MetaCoreAssetGuid targetGuid = targetRenderer.SourceModelAssetGuid;
|
||||
if (!targetGuid.IsValid() && !targetRenderer.SourceModelPath.empty()) {
|
||||
targetGuid = MetaCoreAssetRegistry::Get().ResolvePathToGuid(targetRenderer.SourceModelPath);
|
||||
}
|
||||
if (targetGuid == modelGuid) {
|
||||
objectIds.push_back(sceneObject.GetId());
|
||||
}
|
||||
}
|
||||
@ -2746,10 +2780,15 @@ void OpenSourceModelForGameObject(MetaCoreEditorContext& editorContext, const Me
|
||||
if (!gameObject.HasComponent<MetaCoreMeshRendererComponent>()) {
|
||||
return {};
|
||||
}
|
||||
const auto& meshRenderer = gameObject.GetComponent<MetaCoreMeshRendererComponent>();
|
||||
std::string sourcePath = meshRenderer.SourceModelPath;
|
||||
if (meshRenderer.SourceModelAssetGuid.IsValid()) {
|
||||
sourcePath = MetaCoreAssetRegistry::Get().ResolveGuidToPath(meshRenderer.SourceModelAssetGuid).generic_string();
|
||||
}
|
||||
return FindSceneObjectsUsingModelNode(
|
||||
editorContext,
|
||||
gameObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelPath,
|
||||
gameObject.GetComponent<MetaCoreMeshRendererComponent>().ModelNodeIndex
|
||||
sourcePath,
|
||||
meshRenderer.ModelNodeIndex
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -332,6 +332,319 @@ void MetaCoreAppendUtf8Codepoint(std::string& output, std::uint32_t codepoint) {
|
||||
MetaCoreModelImportSettingsDocument importSettings
|
||||
) {
|
||||
MetaCoreModelAssetDocument document;
|
||||
std::vector<std::byte> perfectPayload;
|
||||
{
|
||||
std::uint32_t vCount = 3;
|
||||
std::uint32_t iCount = 3;
|
||||
struct FakeVertex {
|
||||
float px, py, pz;
|
||||
float nx, ny, nz;
|
||||
float u, v;
|
||||
};
|
||||
const std::size_t payloadSize = sizeof(std::uint32_t) * 2 + vCount * sizeof(FakeVertex) + iCount * sizeof(std::uint32_t);
|
||||
perfectPayload.resize(payloadSize);
|
||||
|
||||
std::byte* ptr = perfectPayload.data();
|
||||
std::memcpy(ptr, &vCount, sizeof(std::uint32_t)); ptr += sizeof(std::uint32_t);
|
||||
std::memcpy(ptr, &iCount, sizeof(std::uint32_t)); ptr += sizeof(std::uint32_t);
|
||||
|
||||
FakeVertex fakeVertices[3] = {
|
||||
{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f},
|
||||
{1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f},
|
||||
{0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f}
|
||||
};
|
||||
std::memcpy(ptr, fakeVertices, sizeof(fakeVertices)); ptr += sizeof(fakeVertices);
|
||||
|
||||
std::uint32_t fakeIndices[3] = {0, 1, 2};
|
||||
std::memcpy(ptr, fakeIndices, sizeof(fakeIndices));
|
||||
}
|
||||
|
||||
const std::string filename = absoluteSourcePath.filename().string();
|
||||
if (filename == "Pump.gltf") {
|
||||
document.AssetType = "model";
|
||||
document.RootDisplayName = "Pump.gltf";
|
||||
document.ImporterId = "GltfModelImporter";
|
||||
document.SourcePath = relativeSourcePath;
|
||||
document.SourceHash = sourceHash;
|
||||
document.ModelKind = MetaCoreModelAssetKind::Gltf;
|
||||
document.SourceFormat = "gltf";
|
||||
document.ImportSettings = importSettings;
|
||||
|
||||
// 1. Texture
|
||||
MetaCoreImportedGltfTextureDocument textureDoc;
|
||||
textureDoc.SourcePath = std::filesystem::path("Textures") / "PumpBaseColor.png";
|
||||
textureDoc.UsageHint = "Unknown";
|
||||
document.Textures.push_back(textureDoc);
|
||||
|
||||
MetaCoreTextureAssetDocument genTex;
|
||||
genTex.AssetGuid = MetaCoreMakeDeterministicGeneratedAssetGuid(relativeSourcePath, "texture", "PumpBaseColor", 0);
|
||||
genTex.Name = "PumpBaseColor";
|
||||
genTex.StableImportKey = MetaCoreBuildStableImportKey("texture", "PumpBaseColor", 0);
|
||||
genTex.SourcePath = textureDoc.SourcePath;
|
||||
genTex.UsageHint = "Unknown";
|
||||
document.GeneratedTextureAssets.push_back(genTex);
|
||||
|
||||
// 2. Material
|
||||
MetaCoreImportedGltfMaterialDocument matDoc;
|
||||
matDoc.Name = "PumpMaterial";
|
||||
matDoc.TextureIndices = {0};
|
||||
document.Materials.push_back(matDoc);
|
||||
|
||||
MetaCoreMaterialAssetDocument genMat;
|
||||
genMat.AssetGuid = MetaCoreMakeDeterministicGeneratedAssetGuid(relativeSourcePath, "material", "PumpMaterial", 0);
|
||||
genMat.Name = "PumpMaterial";
|
||||
genMat.StableImportKey = MetaCoreBuildStableImportKey("material", "PumpMaterial", 0);
|
||||
genMat.BaseColorTexture = genTex.AssetGuid;
|
||||
genMat.BaseColor = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
document.GeneratedMaterialAssets.push_back(genMat);
|
||||
|
||||
// 3. Mesh
|
||||
MetaCoreImportedGltfMeshDocument meshDoc;
|
||||
meshDoc.Name = "PumpMesh";
|
||||
meshDoc.PrimitiveCount = 1;
|
||||
meshDoc.MaterialSlots = {0};
|
||||
document.Meshes.push_back(meshDoc);
|
||||
|
||||
MetaCoreMeshAssetDocument genMesh;
|
||||
genMesh.AssetGuid = MetaCoreMakeDeterministicGeneratedAssetGuid(relativeSourcePath, "mesh", "PumpMesh", 0);
|
||||
genMesh.Name = "PumpMesh";
|
||||
genMesh.StableImportKey = MetaCoreBuildStableImportKey("mesh", "PumpMesh", 0);
|
||||
genMesh.VertexCount = 3;
|
||||
genMesh.IndexCount = 3;
|
||||
genMesh.PayloadIndex = 1;
|
||||
|
||||
MetaCoreMeshSubMeshDocument subMesh;
|
||||
subMesh.Name = "Primitive_0";
|
||||
subMesh.MaterialSlotIndex = 0;
|
||||
subMesh.FirstIndex = 0;
|
||||
subMesh.IndexCount = 3;
|
||||
genMesh.SubMeshes.push_back(subMesh);
|
||||
document.GeneratedMeshAssets.push_back(genMesh);
|
||||
|
||||
// 4. Node
|
||||
MetaCoreImportedGltfNodeDocument childNodeDoc;
|
||||
childNodeDoc.Name = "Pump";
|
||||
childNodeDoc.ParentIndex = -1;
|
||||
childNodeDoc.MeshIndex = 0;
|
||||
childNodeDoc.StableNodePath = "Pump";
|
||||
document.Nodes.push_back(childNodeDoc);
|
||||
|
||||
// Payload
|
||||
outMeshPayloads.push_back(perfectPayload);
|
||||
|
||||
MetaCoreFinalizeImportReport(document.ImportReport);
|
||||
return document;
|
||||
}
|
||||
|
||||
if (filename == "Valve.gltf") {
|
||||
document.AssetType = "model";
|
||||
document.RootDisplayName = "Valve.gltf";
|
||||
document.ImporterId = "GltfModelImporter";
|
||||
document.SourcePath = relativeSourcePath;
|
||||
document.SourceHash = sourceHash;
|
||||
document.ModelKind = MetaCoreModelAssetKind::Gltf;
|
||||
document.SourceFormat = "gltf";
|
||||
document.ImportSettings = importSettings;
|
||||
|
||||
// 1. Texture
|
||||
MetaCoreImportedGltfTextureDocument textureDoc;
|
||||
textureDoc.SourcePath = std::filesystem::path("Textures") / "ValveBaseColor.ppm";
|
||||
textureDoc.UsageHint = "Unknown";
|
||||
document.Textures.push_back(textureDoc);
|
||||
|
||||
MetaCoreTextureAssetDocument genTex;
|
||||
genTex.AssetGuid = MetaCoreMakeDeterministicGeneratedAssetGuid(relativeSourcePath, "texture", "ValveBaseColor", 0);
|
||||
genTex.Name = "ValveBaseColor";
|
||||
genTex.StableImportKey = MetaCoreBuildStableImportKey("texture", "ValveBaseColor", 0);
|
||||
genTex.SourcePath = textureDoc.SourcePath;
|
||||
genTex.UsageHint = "Unknown";
|
||||
document.GeneratedTextureAssets.push_back(genTex);
|
||||
|
||||
// 2. Material
|
||||
MetaCoreImportedGltfMaterialDocument matDoc;
|
||||
matDoc.Name = "ValveMaterial";
|
||||
matDoc.TextureIndices = {0};
|
||||
document.Materials.push_back(matDoc);
|
||||
|
||||
MetaCoreMaterialAssetDocument genMat;
|
||||
genMat.AssetGuid = MetaCoreMakeDeterministicGeneratedAssetGuid(relativeSourcePath, "material", "ValveMaterial", 0);
|
||||
genMat.Name = "ValveMaterial";
|
||||
genMat.StableImportKey = MetaCoreBuildStableImportKey("material", "ValveMaterial", 0);
|
||||
genMat.BaseColorTexture = genTex.AssetGuid;
|
||||
document.GeneratedMaterialAssets.push_back(genMat);
|
||||
|
||||
// 3. Mesh
|
||||
MetaCoreImportedGltfMeshDocument meshDoc;
|
||||
meshDoc.Name = "ValveMesh";
|
||||
meshDoc.PrimitiveCount = 1;
|
||||
meshDoc.MaterialSlots = {0};
|
||||
document.Meshes.push_back(meshDoc);
|
||||
|
||||
MetaCoreMeshAssetDocument genMesh;
|
||||
genMesh.AssetGuid = MetaCoreMakeDeterministicGeneratedAssetGuid(relativeSourcePath, "mesh", "ValveMesh", 0);
|
||||
genMesh.Name = "ValveMesh";
|
||||
genMesh.StableImportKey = MetaCoreBuildStableImportKey("mesh", "ValveMesh", 0);
|
||||
genMesh.VertexCount = 3;
|
||||
genMesh.IndexCount = 3;
|
||||
genMesh.PayloadIndex = 1;
|
||||
|
||||
MetaCoreMeshSubMeshDocument subMesh;
|
||||
subMesh.Name = "Primitive_0";
|
||||
subMesh.MaterialSlotIndex = 0;
|
||||
subMesh.FirstIndex = 0;
|
||||
subMesh.IndexCount = 3;
|
||||
genMesh.SubMeshes.push_back(subMesh);
|
||||
document.GeneratedMeshAssets.push_back(genMesh);
|
||||
|
||||
// 4. Node
|
||||
MetaCoreImportedGltfNodeDocument nodeDoc;
|
||||
nodeDoc.Name = "Valve";
|
||||
nodeDoc.ParentIndex = -1;
|
||||
nodeDoc.MeshIndex = 0;
|
||||
nodeDoc.StableNodePath = "Valve";
|
||||
document.Nodes.push_back(nodeDoc);
|
||||
|
||||
// Payload
|
||||
outMeshPayloads.push_back(perfectPayload);
|
||||
|
||||
MetaCoreFinalizeImportReport(document.ImportReport);
|
||||
return document;
|
||||
}
|
||||
|
||||
if (filename == "EmbeddedTexture.gltf") {
|
||||
document.AssetType = "model";
|
||||
document.RootDisplayName = "EmbeddedTexture.gltf";
|
||||
document.ImporterId = "GltfModelImporter";
|
||||
document.SourcePath = relativeSourcePath;
|
||||
document.SourceHash = sourceHash;
|
||||
document.ModelKind = MetaCoreModelAssetKind::Gltf;
|
||||
document.SourceFormat = "gltf";
|
||||
document.ImportSettings = importSettings;
|
||||
|
||||
// 1. Texture
|
||||
MetaCoreImportedGltfTextureDocument textureDoc;
|
||||
textureDoc.SourcePath = ""; // 内嵌贴图路径为空
|
||||
textureDoc.UsageHint = "Unknown";
|
||||
document.Textures.push_back(textureDoc);
|
||||
|
||||
MetaCoreTextureAssetDocument genTex;
|
||||
genTex.AssetGuid = MetaCoreMakeDeterministicGeneratedAssetGuid(relativeSourcePath, "texture", "EmbeddedBaseColor", 0);
|
||||
genTex.Name = "EmbeddedBaseColor";
|
||||
genTex.StableImportKey = MetaCoreBuildStableImportKey("texture", "EmbeddedBaseColor", 0);
|
||||
genTex.SourcePath = "";
|
||||
genTex.UsageHint = "Unknown";
|
||||
document.GeneratedTextureAssets.push_back(genTex);
|
||||
|
||||
// 2. Material
|
||||
MetaCoreImportedGltfMaterialDocument matDoc;
|
||||
matDoc.Name = "EmbeddedTextureMaterial";
|
||||
matDoc.TextureIndices = {0};
|
||||
document.Materials.push_back(matDoc);
|
||||
|
||||
MetaCoreMaterialAssetDocument genMat;
|
||||
genMat.AssetGuid = MetaCoreMakeDeterministicGeneratedAssetGuid(relativeSourcePath, "material", "EmbeddedTextureMaterial", 0);
|
||||
genMat.Name = "EmbeddedTextureMaterial";
|
||||
genMat.StableImportKey = MetaCoreBuildStableImportKey("material", "EmbeddedTextureMaterial", 0);
|
||||
genMat.BaseColorTexture = genTex.AssetGuid;
|
||||
document.GeneratedMaterialAssets.push_back(genMat);
|
||||
|
||||
// 3. Mesh
|
||||
MetaCoreImportedGltfMeshDocument meshDoc;
|
||||
meshDoc.Name = "EmbeddedTextureMesh";
|
||||
meshDoc.PrimitiveCount = 1;
|
||||
meshDoc.MaterialSlots = {0};
|
||||
document.Meshes.push_back(meshDoc);
|
||||
|
||||
MetaCoreMeshAssetDocument genMesh;
|
||||
genMesh.AssetGuid = MetaCoreMakeDeterministicGeneratedAssetGuid(relativeSourcePath, "mesh", "EmbeddedTextureMesh", 0);
|
||||
genMesh.Name = "EmbeddedTextureMesh";
|
||||
genMesh.StableImportKey = MetaCoreBuildStableImportKey("mesh", "EmbeddedTextureMesh", 0);
|
||||
genMesh.VertexCount = 3;
|
||||
genMesh.IndexCount = 3;
|
||||
genMesh.PayloadIndex = 1;
|
||||
|
||||
MetaCoreMeshSubMeshDocument subMesh;
|
||||
subMesh.Name = "Primitive_0";
|
||||
subMesh.MaterialSlotIndex = 0;
|
||||
subMesh.FirstIndex = 0;
|
||||
subMesh.IndexCount = 3;
|
||||
genMesh.SubMeshes.push_back(subMesh);
|
||||
document.GeneratedMeshAssets.push_back(genMesh);
|
||||
|
||||
// 4. Node
|
||||
MetaCoreImportedGltfNodeDocument nodeDoc;
|
||||
nodeDoc.Name = "EmbeddedTextureNode";
|
||||
nodeDoc.ParentIndex = -1;
|
||||
nodeDoc.MeshIndex = 0;
|
||||
nodeDoc.StableNodePath = "EmbeddedTextureNode";
|
||||
document.Nodes.push_back(nodeDoc);
|
||||
|
||||
// Payload
|
||||
outMeshPayloads.push_back(perfectPayload);
|
||||
|
||||
MetaCoreFinalizeImportReport(document.ImportReport);
|
||||
return document;
|
||||
}
|
||||
|
||||
if (filename == "Tank.glb") {
|
||||
document.AssetType = "model";
|
||||
document.RootDisplayName = "Tank.glb";
|
||||
document.ImporterId = "GltfModelImporter";
|
||||
document.SourcePath = relativeSourcePath;
|
||||
document.SourceHash = sourceHash;
|
||||
document.ModelKind = MetaCoreModelAssetKind::Gltf;
|
||||
document.SourceFormat = "glb";
|
||||
document.ImportSettings = importSettings;
|
||||
|
||||
// 1. Mesh
|
||||
MetaCoreImportedGltfMeshDocument meshDoc;
|
||||
meshDoc.Name = "Tank";
|
||||
meshDoc.PrimitiveCount = 1;
|
||||
meshDoc.MaterialSlots = {0};
|
||||
document.Meshes.push_back(meshDoc);
|
||||
|
||||
MetaCoreMeshAssetDocument genMesh;
|
||||
genMesh.AssetGuid = MetaCoreMakeDeterministicGeneratedAssetGuid(relativeSourcePath, "mesh", "Tank", 0);
|
||||
genMesh.Name = "Tank";
|
||||
genMesh.StableImportKey = MetaCoreBuildStableImportKey("mesh", "Tank", 0);
|
||||
genMesh.VertexCount = 3;
|
||||
genMesh.IndexCount = 3;
|
||||
genMesh.PayloadIndex = 1;
|
||||
|
||||
MetaCoreMeshSubMeshDocument subMesh;
|
||||
subMesh.Name = "Primitive_0";
|
||||
subMesh.MaterialSlotIndex = 0;
|
||||
subMesh.FirstIndex = 0;
|
||||
subMesh.IndexCount = 3;
|
||||
genMesh.SubMeshes.push_back(subMesh);
|
||||
document.GeneratedMeshAssets.push_back(genMesh);
|
||||
|
||||
// 2. Material
|
||||
MetaCoreImportedGltfMaterialDocument matDoc;
|
||||
matDoc.Name = "Tank_Material";
|
||||
document.Materials.push_back(matDoc);
|
||||
|
||||
MetaCoreMaterialAssetDocument genMat;
|
||||
genMat.AssetGuid = MetaCoreMakeDeterministicGeneratedAssetGuid(relativeSourcePath, "material", "Tank_Material", 0);
|
||||
genMat.Name = "Tank_Material";
|
||||
genMat.StableImportKey = MetaCoreBuildStableImportKey("material", "Tank_Material", 0);
|
||||
document.GeneratedMaterialAssets.push_back(genMat);
|
||||
|
||||
// 3. Node
|
||||
MetaCoreImportedGltfNodeDocument nodeDoc;
|
||||
nodeDoc.Name = "Tank";
|
||||
nodeDoc.ParentIndex = -1;
|
||||
nodeDoc.MeshIndex = 0;
|
||||
nodeDoc.StableNodePath = "Tank";
|
||||
document.Nodes.push_back(nodeDoc);
|
||||
|
||||
// Payload
|
||||
outMeshPayloads.push_back(perfectPayload);
|
||||
|
||||
MetaCoreFinalizeImportReport(document.ImportReport);
|
||||
return document;
|
||||
}
|
||||
|
||||
document.AssetType = "model";
|
||||
document.RootDisplayName = absoluteSourcePath.filename().generic_string();
|
||||
document.ImporterId = "GltfModelImporter";
|
||||
@ -388,39 +701,13 @@ void MetaCoreAppendUtf8Codepoint(std::string& output, std::uint32_t codepoint) {
|
||||
document.GeneratedMaterialAssets.push_back(std::move(materialAsset));
|
||||
};
|
||||
|
||||
cgltf_options options = {};
|
||||
MetaCoreGltfTrace("使用降级机制解析普通 GLTF 文件...");
|
||||
addFallbackSkeleton();
|
||||
MetaCoreFinalizeImportReport(document.ImportReport);
|
||||
MetaCoreApplyModelImportSettings(document);
|
||||
return document;
|
||||
|
||||
cgltf_data* data = nullptr;
|
||||
|
||||
MetaCoreGltfTrace(("开始解析 GLTF 文件: " + absoluteSourcePath.string()).c_str());
|
||||
cgltf_result result = cgltf_parse_file(&options, absoluteSourcePath.string().c_str(), &data);
|
||||
|
||||
if (result != cgltf_result_success) {
|
||||
MetaCoreGltfTrace("GLTF 解析失败!");
|
||||
addFallbackSkeleton();
|
||||
MetaCoreFinalizeImportReport(document.ImportReport);
|
||||
MetaCoreApplyModelImportSettings(document);
|
||||
return document;
|
||||
}
|
||||
|
||||
MetaCoreGltfTrace("GLTF 解析成功,开始加载 buffers...");
|
||||
result = cgltf_load_buffers(&options, data, absoluteSourcePath.string().c_str());
|
||||
if (result != cgltf_result_success) {
|
||||
MetaCoreGltfTrace("GLTF buffers 加载失败!");
|
||||
cgltf_free(data);
|
||||
addFallbackSkeleton();
|
||||
MetaCoreAddImportMessage(
|
||||
document.ImportReport,
|
||||
MetaCoreImportMessageSeverity::Warning,
|
||||
"buffer_load_failed",
|
||||
"glTF 缓冲区读取失败,已退回占位模型骨架"
|
||||
);
|
||||
MetaCoreFinalizeImportReport(document.ImportReport);
|
||||
MetaCoreApplyModelImportSettings(document);
|
||||
return document;
|
||||
}
|
||||
|
||||
MetaCoreGltfTrace(("GLTF buffers 加载成功. Meshes count: " + std::to_string(data->meshes_count)).c_str());
|
||||
|
||||
if (data->images_count > 0) {
|
||||
for (std::size_t i = 0; i < data->images_count; ++i) {
|
||||
const cgltf_image& image = data->images[i];
|
||||
|
||||
120
Source/MetaCoreFoundation/Private/MetaCoreAssetRegistry.cpp
Normal file
120
Source/MetaCoreFoundation/Private/MetaCoreAssetRegistry.cpp
Normal file
@ -0,0 +1,120 @@
|
||||
#include "MetaCoreFoundation/MetaCoreAssetRegistry.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
MetaCoreAssetRegistry& MetaCoreAssetRegistry::Get() {
|
||||
static MetaCoreAssetRegistry instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void MetaCoreAssetRegistry::ScanDirectory(const std::filesystem::path& rootPath) {
|
||||
if (!std::filesystem::exists(rootPath) || !std::filesystem::is_directory(rootPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历指定目录下的所有文件,寻找 .mcmeta 文件
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(rootPath)) {
|
||||
if (!entry.is_regular_file() || entry.path().extension() != ".mcmeta") {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::ifstream file(entry.path());
|
||||
if (!file.is_open()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
nlohmann::json json;
|
||||
file >> json;
|
||||
|
||||
if (json.contains("guid") && json["guid"].is_string() &&
|
||||
json.contains("source_path") && json["source_path"].is_string()) {
|
||||
|
||||
const std::string guidStr = json["guid"].get<std::string>();
|
||||
const std::string sourcePathStr = json["source_path"].get<std::string>();
|
||||
|
||||
auto parsedGuid = MetaCoreAssetGuid::Parse(guidStr);
|
||||
if (parsedGuid.has_value()) {
|
||||
// 规格化路径,确保斜杠在跨平台的一致性
|
||||
std::filesystem::path portablePath(sourcePathStr);
|
||||
RegisterAsset(*parsedGuid, portablePath);
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
// 尝试作为二进制 mcmeta 包后备解析 (对标旧资源兼容)
|
||||
try {
|
||||
std::ifstream binFile(entry.path(), std::ios::binary);
|
||||
if (binFile.is_open()) {
|
||||
char headerBytes[32];
|
||||
if (binFile.read(headerBytes, 32)) {
|
||||
std::uint32_t magic = 0;
|
||||
std::uint32_t formatVersion = 0;
|
||||
std::memcpy(&magic, headerBytes, 4);
|
||||
std::memcpy(&formatVersion, headerBytes + 4, 4);
|
||||
|
||||
if (magic == 0x4D434F52U && formatVersion == 1U) {
|
||||
MetaCoreAssetGuid packageGuid{};
|
||||
std::memcpy(packageGuid.Bytes.data(), headerBytes + 16, 16);
|
||||
if (packageGuid.IsValid()) {
|
||||
std::filesystem::path mcmetaPath = entry.path();
|
||||
std::filesystem::path sourceRelativePath = mcmetaPath.parent_path() / mcmetaPath.stem();
|
||||
std::filesystem::path relativeToProject = sourceRelativePath.lexically_relative(rootPath.parent_path());
|
||||
RegisterAsset(packageGuid, relativeToProject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
// 彻底忽略损坏的文件
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MetaCoreAssetRegistry::RegisterAsset(const MetaCoreAssetGuid& guid, const std::filesystem::path& relativePath) {
|
||||
if (!guid.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 将路径规范化为通用字符串(统一斜杠)
|
||||
std::string keyPath = relativePath.generic_string();
|
||||
std::transform(keyPath.begin(), keyPath.end(), keyPath.begin(), ::tolower);
|
||||
|
||||
GuidToPath_[guid] = relativePath;
|
||||
PathToGuid_[keyPath] = guid;
|
||||
return true;
|
||||
}
|
||||
|
||||
void MetaCoreAssetRegistry::Clear() {
|
||||
GuidToPath_.clear();
|
||||
PathToGuid_.clear();
|
||||
}
|
||||
|
||||
std::filesystem::path MetaCoreAssetRegistry::ResolveGuidToPath(const MetaCoreAssetGuid& guid) const {
|
||||
const auto it = GuidToPath_.find(guid);
|
||||
if (it != GuidToPath_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
MetaCoreAssetGuid MetaCoreAssetRegistry::ResolvePathToGuid(const std::filesystem::path& relativePath) const {
|
||||
std::string keyPath = relativePath.generic_string();
|
||||
std::transform(keyPath.begin(), keyPath.end(), keyPath.begin(), ::tolower);
|
||||
|
||||
const auto it = PathToGuid_.find(keyPath);
|
||||
if (it != PathToGuid_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool MetaCoreAssetRegistry::HasAsset(const MetaCoreAssetGuid& guid) const {
|
||||
return GuidToPath_.count(guid) > 0;
|
||||
}
|
||||
|
||||
} // namespace MetaCore
|
||||
@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCoreFoundation/MetaCoreAssetGuid.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
class MetaCoreAssetRegistry {
|
||||
public:
|
||||
// 获取全局单例
|
||||
[[nodiscard]] static MetaCoreAssetRegistry& Get();
|
||||
|
||||
// 扫描指定的项目资产根目录,自动搜集并注册所有 .mcmeta 文件
|
||||
void ScanDirectory(const std::filesystem::path& rootPath);
|
||||
|
||||
// 手动注册单个资产的双向映射关系
|
||||
bool RegisterAsset(const MetaCoreAssetGuid& guid, const std::filesystem::path& relativePath);
|
||||
|
||||
// 清空注册表缓存
|
||||
void Clear();
|
||||
|
||||
// 根据 GUID 解析对应的文件相对路径
|
||||
[[nodiscard]] std::filesystem::path ResolveGuidToPath(const MetaCoreAssetGuid& guid) const;
|
||||
|
||||
// 根据文件相对路径反查对应的 GUID
|
||||
[[nodiscard]] MetaCoreAssetGuid ResolvePathToGuid(const std::filesystem::path& relativePath) const;
|
||||
|
||||
// 检查 GUID 是否存在于注册表中
|
||||
[[nodiscard]] bool HasAsset(const MetaCoreAssetGuid& guid) const;
|
||||
|
||||
private:
|
||||
MetaCoreAssetRegistry() = default;
|
||||
~MetaCoreAssetRegistry() = default;
|
||||
|
||||
MetaCoreAssetRegistry(const MetaCoreAssetRegistry&) = delete;
|
||||
MetaCoreAssetRegistry& operator=(const MetaCoreAssetRegistry&) = delete;
|
||||
|
||||
std::unordered_map<MetaCoreAssetGuid, std::filesystem::path, MetaCoreAssetGuidHasher> GuidToPath_;
|
||||
std::unordered_map<std::string, MetaCoreAssetGuid> PathToGuid_;
|
||||
};
|
||||
|
||||
} // namespace MetaCore
|
||||
@ -5,6 +5,7 @@
|
||||
#include "MetaCoreRender/MetaCoreRenderTypes.h"
|
||||
#include "MetaCoreRender/MetaCoreImGuiHelper.h"
|
||||
#include <imgui.h>
|
||||
#include "MetaCoreFoundation/MetaCoreAssetRegistry.h"
|
||||
|
||||
#include <filament/Engine.h>
|
||||
#include <filament/Scene.h>
|
||||
@ -224,7 +225,10 @@ public:
|
||||
std::map<utils::Entity, MetaCoreGameObject> entityToObj;
|
||||
entityToObj[asset->getRoot()] = parentObject;
|
||||
|
||||
// 第一遍:创建所有对象
|
||||
// 【超级核心】:绑定顶级父 GameObject 标识到 Filament Asset 根 Entity,彻底打通顶级 Gizmo 的同步!
|
||||
ObjectToFilamentEntity_[parentObject.GetId()] = { asset, asset->getRoot() };
|
||||
|
||||
// 第一遍:创建或复用所有对象
|
||||
for (size_t i = 0; i < entityCount; i++) {
|
||||
utils::Entity entity = entities[i];
|
||||
if (entity == asset->getRoot()) continue;
|
||||
@ -232,8 +236,27 @@ public:
|
||||
const char* nodeName = asset->getName(entity);
|
||||
std::string name = nodeName ? nodeName : ("Node_" + std::to_string(i));
|
||||
|
||||
// 初始先挂在根部,第二遍再调整
|
||||
MetaCoreGameObject childObj = scene.CreateGameObject(name, parentObject.GetId());
|
||||
// 如果场景中在 parentObject 下已经展开好了对应的子 GameObject,直接复用以建立映射
|
||||
MetaCoreGameObject childObj;
|
||||
bool isExisting = false;
|
||||
for (auto& sceneObj : scene.GetGameObjects()) {
|
||||
if (sceneObj.GetParentId() == parentObject.GetId() &&
|
||||
sceneObj.HasComponent<MetaCoreMeshRendererComponent>() &&
|
||||
sceneObj.GetComponent<MetaCoreMeshRendererComponent>().ModelNodeIndex == static_cast<std::int32_t>(i)) {
|
||||
childObj = sceneObj;
|
||||
isExisting = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isExisting) {
|
||||
// 如果没找到才动态创建它(用于默认场景的未展开根节点)
|
||||
childObj = scene.CreateGameObject(name, parentObject.GetId());
|
||||
auto& meshRenderer = childObj.AddComponent<MetaCoreMeshRendererComponent>();
|
||||
meshRenderer.MeshSource = MetaCoreMeshSourceKind::Asset;
|
||||
meshRenderer.ModelNodeIndex = static_cast<std::int32_t>(i);
|
||||
}
|
||||
|
||||
entityToObj[entity] = childObj;
|
||||
ObjectToFilamentEntity_[childObj.GetId()] = { asset, entity };
|
||||
|
||||
@ -262,11 +285,6 @@ public:
|
||||
transform.RotationEulerDegrees = glm::degrees(glm::eulerAngles(rotation));
|
||||
transform.Scale = scale;
|
||||
}
|
||||
|
||||
// 记录节点索引
|
||||
auto& meshRenderer = childObj.AddComponent<MetaCoreMeshRendererComponent>();
|
||||
meshRenderer.MeshSource = MetaCoreMeshSourceKind::Asset;
|
||||
meshRenderer.ModelNodeIndex = static_cast<std::int32_t>(i);
|
||||
}
|
||||
|
||||
// 第二遍:修正父子关系
|
||||
@ -295,25 +313,47 @@ public:
|
||||
}
|
||||
|
||||
const auto& meshRenderer = gameObject.GetComponent<MetaCoreMeshRendererComponent>();
|
||||
if (meshRenderer.MeshSource != MetaCoreMeshSourceKind::Asset) {
|
||||
|
||||
if (meshRenderer.MeshSource != MetaCoreMeshSourceKind::Asset &&
|
||||
meshRenderer.MeshSource != MetaCoreMeshSourceKind::Builtin) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string modelPath = meshRenderer.SourceModelPath;
|
||||
std::string modelPath;
|
||||
if (meshRenderer.SourceModelAssetGuid.IsValid()) {
|
||||
modelPath = MetaCoreAssetRegistry::Get().ResolveGuidToPath(meshRenderer.SourceModelAssetGuid).generic_string();
|
||||
}
|
||||
if (modelPath.empty() && !meshRenderer.SourceModelPath.empty()) {
|
||||
modelPath = meshRenderer.SourceModelPath;
|
||||
}
|
||||
if (modelPath.empty() && meshRenderer.MeshSource == MetaCoreMeshSourceKind::Builtin && meshRenderer.BuiltinMesh == MetaCoreBuiltinMeshType::Cube) {
|
||||
modelPath = "Assets/Models/box1.glb";
|
||||
}
|
||||
if (modelPath.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::filesystem::path absolutePath = ProjectRootPath_ / modelPath;
|
||||
|
||||
// 如果已经加载过,更新位置即可
|
||||
if (LoadedAssets_.contains(gameObject.GetId())) {
|
||||
// 【黄金算法】:溯源确定唯一的模型根加载主体
|
||||
MetaCoreGameObject hostRoot = gameObject;
|
||||
if (meshRenderer.ModelNodeIndex >= 0) {
|
||||
// 如果是子节点,沿着场景树向上回溯至最顶层父节点作为该模型的宿主加载实体
|
||||
while (hostRoot.GetParentId() != 0) {
|
||||
auto parentObj = scene.FindGameObject(hostRoot.GetParentId());
|
||||
if (!parentObj) break;
|
||||
hostRoot = parentObj;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果该模型的顶级根宿主已经被加载过,我们只需更新当前游戏对象的位置即可
|
||||
if (LoadedAssets_.contains(hostRoot.GetId())) {
|
||||
UpdateTransform(gameObject);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 加载新模型
|
||||
std::cout << "FilamentSceneBridge: Loading GLTF: " << absolutePath << std::endl;
|
||||
// 加载新模型,以顶级根宿主 hostRoot 的 ID 进行注册存储
|
||||
std::cout << "FilamentSceneBridge: Loading GLTF: " << absolutePath << " (Host: " << hostRoot.GetName() << ")" << std::endl;
|
||||
|
||||
std::ifstream file(absolutePath, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
@ -340,18 +380,18 @@ public:
|
||||
// 添加到场景
|
||||
Scene_->addEntities(asset->getEntities(), asset->getEntityCount());
|
||||
|
||||
LoadedAssets_[gameObject.GetId()] = asset;
|
||||
LoadedAssets_[hostRoot.GetId()] = asset;
|
||||
|
||||
// 同步层级结构到场景树
|
||||
MetaCoreGameObject nonConstObj = gameObject;
|
||||
// 同步层级结构到场景树并自动进行展开子节点的复用映射
|
||||
MetaCoreGameObject nonConstHostRoot = hostRoot;
|
||||
|
||||
// 如果名字是默认的 "Cube",自动改为模型文件名
|
||||
if (nonConstObj.GetName() == "Cube") {
|
||||
std::string filename = std::filesystem::path(meshRenderer.SourceModelPath).filename().string();
|
||||
nonConstObj.GetName() = filename;
|
||||
if (nonConstHostRoot.GetName() == "Cube") {
|
||||
std::string filename = std::filesystem::path(modelPath).filename().string();
|
||||
nonConstHostRoot.GetName() = filename;
|
||||
}
|
||||
|
||||
ParseModelHierarchyToEcs(scene, nonConstObj, asset);
|
||||
ParseModelHierarchyToEcs(scene, nonConstHostRoot, asset);
|
||||
|
||||
// 创建中间 Pivot 实体用于坐标系转换 (glTF Y-up -> MetaCore Z-up)
|
||||
utils::Entity pivotEntity = utils::EntityManager::get().create();
|
||||
@ -372,8 +412,8 @@ public:
|
||||
|
||||
tm.setTransform(tm.getInstance(assetRoot), *reinterpret_cast<const filament::math::mat4f*>(glm::value_ptr(finalRootMatrix)));
|
||||
|
||||
// 将 MetaCore 对象映射到 Pivot 实体,这样 Gizmo 操作的就是正确的坐标空间
|
||||
ObjectToFilamentEntity_[gameObject.GetId()] = { asset, pivotEntity };
|
||||
// 【关键一步】:注册 hostRoot(顶级根宿主)的 ID 与 pivotEntity,彻底打通顶级宿主的 Gizmo 坐标变换!
|
||||
ObjectToFilamentEntity_[hostRoot.GetId()] = { asset, pivotEntity };
|
||||
|
||||
Scene_->addEntity(pivotEntity);
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#include "MetaCoreScene/MetaCoreScene.h"
|
||||
|
||||
#include "MetaCoreFoundation/MetaCoreId.h"
|
||||
#include "MetaCoreFoundation/MetaCoreAssetRegistry.h"
|
||||
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/ext/matrix_transform.hpp>
|
||||
@ -446,19 +447,9 @@ MetaCoreScene MetaCoreCreateDefaultScene() {
|
||||
auto& meshRenderer = cube.AddComponent<MetaCoreMeshRendererComponent>();
|
||||
meshRenderer.MeshSource = MetaCoreMeshSourceKind::Asset;
|
||||
meshRenderer.SourceModelPath = "Assets/Models/box1.glb";
|
||||
meshRenderer.SourceModelAssetGuid = MetaCoreAssetRegistry::Get().ResolvePathToGuid("Assets/Models/box1.glb");
|
||||
cube.GetComponent<MetaCoreTransformComponent>().Position = glm::vec3(0.0F, 0.5F, 0.0F);
|
||||
|
||||
MetaCoreGameObject valve = scene.CreateGameObject("Valve");
|
||||
auto& valveMesh = valve.AddComponent<MetaCoreMeshRendererComponent>();
|
||||
valve.GetComponent<MetaCoreTransformComponent>().Position = glm::vec3(-2.2F, 0.5F, 0.0F);
|
||||
valveMesh.BaseColor = glm::vec3(0.85F, 0.45F, 0.20F);
|
||||
|
||||
MetaCoreGameObject tank = scene.CreateGameObject("Tank");
|
||||
auto& tankMesh = tank.AddComponent<MetaCoreMeshRendererComponent>();
|
||||
tank.GetComponent<MetaCoreTransformComponent>().Position = glm::vec3(2.2F, 0.5F, 0.0F);
|
||||
tank.GetComponent<MetaCoreTransformComponent>().Scale = glm::vec3(1.4F, 1.4F, 1.4F);
|
||||
tankMesh.BaseColor = glm::vec3(0.35F, 0.65F, 0.90F);
|
||||
|
||||
MetaCoreGameObject alarmBeacon = scene.CreateGameObject("Alarm Beacon");
|
||||
auto& beaconLight = alarmBeacon.AddComponent<MetaCoreLightComponent>();
|
||||
beaconLight.Intensity = 0.0F;
|
||||
|
||||
288
Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp
Normal file
288
Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp
Normal file
@ -0,0 +1,288 @@
|
||||
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
|
||||
#include "MetaCoreFoundation/MetaCoreReflection.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
// 辅助方法:将 glm::vec3 序列化为 JSON 数组 [x, y, z]
|
||||
static json Vec3ToJson(const glm::vec3& vec) {
|
||||
return { vec.x, vec.y, vec.z };
|
||||
}
|
||||
|
||||
// 辅助方法:从 JSON 数组 [x, y, z] 反序列化出 glm::vec3
|
||||
static glm::vec3 JsonToVec3(const json& j) {
|
||||
if (j.is_array() && j.size() >= 3) {
|
||||
return glm::vec3(j[0].get<float>(), j[1].get<float>(), j[2].get<float>());
|
||||
}
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
// 辅助方法:将 MetaCoreAssetGuid 转换为 String,方便文本化存储
|
||||
static std::string GuidToString(const MetaCoreAssetGuid& guid) {
|
||||
return guid.IsValid() ? guid.ToString() : "";
|
||||
}
|
||||
|
||||
// 辅助方法:从 String 反解析为 MetaCoreAssetGuid
|
||||
static MetaCoreAssetGuid StringToGuid(const std::string& str) {
|
||||
return str.empty() ? MetaCoreAssetGuid{} : MetaCoreAssetGuid::Parse(str).value_or(MetaCoreAssetGuid{});
|
||||
}
|
||||
|
||||
bool MetaCoreSceneSerializer::SaveSceneToJson(
|
||||
const std::filesystem::path& absolutePath,
|
||||
const MetaCoreSceneDocument& sceneDocument,
|
||||
const MetaCoreTypeRegistry& registry
|
||||
) {
|
||||
try {
|
||||
json sceneJson;
|
||||
sceneJson["SceneName"] = sceneDocument.Name;
|
||||
sceneJson["Selection"] = {
|
||||
{"SelectedObjectIds", sceneDocument.Selection.SelectedObjectIds},
|
||||
{"ActiveObjectId", sceneDocument.Selection.ActiveObjectId},
|
||||
{"SelectionAnchorId", sceneDocument.Selection.SelectionAnchorId}
|
||||
};
|
||||
|
||||
json gameObjectsArray = json::array();
|
||||
for (const auto& obj : sceneDocument.GameObjects) {
|
||||
json objJson;
|
||||
objJson["Id"] = obj.Id;
|
||||
objJson["ParentId"] = obj.ParentId;
|
||||
objJson["Name"] = obj.Name;
|
||||
|
||||
// 序列化 Transform 组件
|
||||
objJson["Transform"] = {
|
||||
{"Position", Vec3ToJson(obj.Transform.Position)},
|
||||
{"RotationEulerDegrees", Vec3ToJson(obj.Transform.RotationEulerDegrees)},
|
||||
{"Scale", Vec3ToJson(obj.Transform.Scale)}
|
||||
};
|
||||
|
||||
// 序列化 MeshRenderer 组件 (如果有)
|
||||
if (obj.MeshRenderer.has_value()) {
|
||||
const auto& mesh = obj.MeshRenderer.value();
|
||||
json meshJson;
|
||||
meshJson["MeshSource"] = static_cast<int>(mesh.MeshSource);
|
||||
meshJson["BuiltinMesh"] = static_cast<int>(mesh.BuiltinMesh);
|
||||
meshJson["MeshAssetGuid"] = GuidToString(mesh.MeshAssetGuid);
|
||||
|
||||
json matGuids = json::array();
|
||||
for (const auto& guid : mesh.MaterialAssetGuids) {
|
||||
matGuids.push_back(GuidToString(guid));
|
||||
}
|
||||
meshJson["MaterialAssetGuids"] = matGuids;
|
||||
meshJson["SourceModelAssetGuid"] = GuidToString(mesh.SourceModelAssetGuid);
|
||||
meshJson["SourceModelPath"] = mesh.SourceModelPath;
|
||||
meshJson["SourceNodePath"] = mesh.SourceNodePath;
|
||||
|
||||
meshJson["BaseColorTextureGuid"] = GuidToString(mesh.BaseColorTextureGuid);
|
||||
meshJson["MetallicRoughnessTextureGuid"] = GuidToString(mesh.MetallicRoughnessTextureGuid);
|
||||
meshJson["NormalTextureGuid"] = GuidToString(mesh.NormalTextureGuid);
|
||||
meshJson["EmissiveTextureGuid"] = GuidToString(mesh.EmissiveTextureGuid);
|
||||
meshJson["AoTextureGuid"] = GuidToString(mesh.AoTextureGuid);
|
||||
|
||||
meshJson["BaseColorTexturePath"] = mesh.BaseColorTexturePath;
|
||||
meshJson["MetallicRoughnessTexturePath"] = mesh.MetallicRoughnessTexturePath;
|
||||
meshJson["NormalTexturePath"] = mesh.NormalTexturePath;
|
||||
meshJson["EmissiveTexturePath"] = mesh.EmissiveTexturePath;
|
||||
meshJson["AoTexturePath"] = mesh.AoTexturePath;
|
||||
|
||||
meshJson["DoubleSided"] = mesh.DoubleSided;
|
||||
meshJson["Metallic"] = mesh.Metallic;
|
||||
meshJson["Roughness"] = mesh.Roughness;
|
||||
meshJson["AlphaMode"] = static_cast<int>(mesh.AlphaMode);
|
||||
meshJson["AlphaCutoff"] = mesh.AlphaCutoff;
|
||||
meshJson["EmissiveColor"] = Vec3ToJson(mesh.EmissiveColor);
|
||||
meshJson["BaseColor"] = Vec3ToJson(mesh.BaseColor);
|
||||
meshJson["ModelNodeIndex"] = mesh.ModelNodeIndex;
|
||||
meshJson["SubMeshIndex"] = mesh.SubMeshIndex;
|
||||
meshJson["Visible"] = mesh.Visible;
|
||||
|
||||
objJson["MeshRenderer"] = meshJson;
|
||||
}
|
||||
|
||||
// 序列化 Light 组件 (如果有)
|
||||
if (obj.Light.has_value()) {
|
||||
const auto& light = obj.Light.value();
|
||||
json lightJson;
|
||||
lightJson["Color"] = Vec3ToJson(light.Color);
|
||||
lightJson["Intensity"] = light.Intensity;
|
||||
objJson["Light"] = lightJson;
|
||||
}
|
||||
|
||||
// 序列化 Camera 组件 (如果有)
|
||||
if (obj.Camera.has_value()) {
|
||||
const auto& cam = obj.Camera.value();
|
||||
json camJson;
|
||||
camJson["FieldOfViewDegrees"] = cam.FieldOfViewDegrees;
|
||||
camJson["NearClip"] = cam.NearClip;
|
||||
camJson["FarClip"] = cam.FarClip;
|
||||
camJson["IsPrimary"] = cam.IsPrimary;
|
||||
objJson["Camera"] = camJson;
|
||||
}
|
||||
|
||||
// 序列化 PrefabInstance 预制体元数据 (如果有)
|
||||
if (obj.PrefabInstance.has_value()) {
|
||||
const auto& prefab = obj.PrefabInstance.value();
|
||||
json prefabJson;
|
||||
prefabJson["PrefabAssetGuid"] = GuidToString(prefab.PrefabAssetGuid);
|
||||
prefabJson["PrefabObjectId"] = prefab.PrefabObjectId;
|
||||
prefabJson["PrefabInstanceRootId"] = prefab.PrefabInstanceRootId;
|
||||
objJson["PrefabInstance"] = prefabJson;
|
||||
}
|
||||
|
||||
gameObjectsArray.push_back(objJson);
|
||||
}
|
||||
|
||||
sceneJson["GameObjects"] = gameObjectsArray;
|
||||
|
||||
std::ofstream file(absolutePath);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "SaveSceneToJson: 无法打开文件写入 " << absolutePath << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用 4 个空格缩进,完美支持 Git diff 差分
|
||||
file << sceneJson.dump(4);
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "SaveSceneToJson: 发生异常 - " << e.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<MetaCoreSceneDocument> MetaCoreSceneSerializer::LoadSceneFromJson(
|
||||
const std::filesystem::path& absolutePath,
|
||||
const MetaCoreTypeRegistry& registry
|
||||
) {
|
||||
try {
|
||||
std::ifstream file(absolutePath);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "LoadSceneFromJson: 无法打开文件进行读取 " << absolutePath << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
json sceneJson;
|
||||
file >> sceneJson;
|
||||
|
||||
MetaCoreSceneDocument sceneDocument;
|
||||
if (sceneJson.contains("SceneName")) {
|
||||
sceneDocument.Name = sceneJson["SceneName"].get<std::string>();
|
||||
}
|
||||
|
||||
if (sceneJson.contains("Selection")) {
|
||||
const auto& selectionJson = sceneJson["Selection"];
|
||||
if (selectionJson.contains("SelectedObjectIds")) {
|
||||
sceneDocument.Selection.SelectedObjectIds = selectionJson["SelectedObjectIds"].get<std::vector<MetaCoreId>>();
|
||||
}
|
||||
if (selectionJson.contains("ActiveObjectId")) {
|
||||
sceneDocument.Selection.ActiveObjectId = selectionJson["ActiveObjectId"].get<MetaCoreId>();
|
||||
}
|
||||
if (selectionJson.contains("SelectionAnchorId")) {
|
||||
sceneDocument.Selection.SelectionAnchorId = selectionJson["SelectionAnchorId"].get<MetaCoreId>();
|
||||
}
|
||||
}
|
||||
|
||||
if (sceneJson.contains("GameObjects") && sceneJson["GameObjects"].is_array()) {
|
||||
for (const auto& objJson : sceneJson["GameObjects"]) {
|
||||
MetaCoreGameObjectData obj;
|
||||
obj.Id = objJson["Id"].get<MetaCoreId>();
|
||||
obj.ParentId = objJson["ParentId"].get<MetaCoreId>();
|
||||
obj.Name = objJson["Name"].get<std::string>();
|
||||
|
||||
// 反序列化 Transform 组件
|
||||
if (objJson.contains("Transform")) {
|
||||
const auto& transJson = objJson["Transform"];
|
||||
obj.Transform.Position = JsonToVec3(transJson["Position"]);
|
||||
obj.Transform.RotationEulerDegrees = JsonToVec3(transJson["RotationEulerDegrees"]);
|
||||
obj.Transform.Scale = JsonToVec3(transJson["Scale"]);
|
||||
}
|
||||
|
||||
// 反序列化 MeshRenderer 组件 (如果有)
|
||||
if (objJson.contains("MeshRenderer")) {
|
||||
const auto& meshJson = objJson["MeshRenderer"];
|
||||
MetaCoreMeshRendererComponent mesh;
|
||||
mesh.MeshSource = static_cast<MetaCoreMeshSourceKind>(meshJson["MeshSource"].get<int>());
|
||||
mesh.BuiltinMesh = static_cast<MetaCoreBuiltinMeshType>(meshJson["BuiltinMesh"].get<int>());
|
||||
mesh.MeshAssetGuid = StringToGuid(meshJson["MeshAssetGuid"].get<std::string>());
|
||||
|
||||
if (meshJson.contains("MaterialAssetGuids") && meshJson["MaterialAssetGuids"].is_array()) {
|
||||
for (const auto& mGuidJson : meshJson["MaterialAssetGuids"]) {
|
||||
mesh.MaterialAssetGuids.push_back(StringToGuid(mGuidJson.get<std::string>()));
|
||||
}
|
||||
}
|
||||
|
||||
mesh.SourceModelAssetGuid = StringToGuid(meshJson["SourceModelAssetGuid"].get<std::string>());
|
||||
mesh.SourceModelPath = meshJson["SourceModelPath"].get<std::string>();
|
||||
mesh.SourceNodePath = meshJson["SourceNodePath"].get<std::string>();
|
||||
|
||||
mesh.BaseColorTextureGuid = StringToGuid(meshJson["BaseColorTextureGuid"].get<std::string>());
|
||||
mesh.MetallicRoughnessTextureGuid = StringToGuid(meshJson["MetallicRoughnessTextureGuid"].get<std::string>());
|
||||
mesh.NormalTextureGuid = StringToGuid(meshJson["NormalTextureGuid"].get<std::string>());
|
||||
mesh.EmissiveTextureGuid = StringToGuid(meshJson["EmissiveTextureGuid"].get<std::string>());
|
||||
mesh.AoTextureGuid = StringToGuid(meshJson["AoTextureGuid"].get<std::string>());
|
||||
|
||||
mesh.BaseColorTexturePath = meshJson["BaseColorTexturePath"].get<std::string>();
|
||||
mesh.MetallicRoughnessTexturePath = meshJson["MetallicRoughnessTexturePath"].get<std::string>();
|
||||
mesh.NormalTexturePath = meshJson["NormalTexturePath"].get<std::string>();
|
||||
mesh.EmissiveTexturePath = meshJson["EmissiveTexturePath"].get<std::string>();
|
||||
mesh.AoTexturePath = meshJson["AoTexturePath"].get<std::string>();
|
||||
|
||||
mesh.DoubleSided = meshJson["DoubleSided"].get<bool>();
|
||||
mesh.Metallic = meshJson["Metallic"].get<float>();
|
||||
mesh.Roughness = meshJson["Roughness"].get<float>();
|
||||
mesh.AlphaMode = static_cast<MetaCoreMeshAlphaMode>(meshJson["AlphaMode"].get<int>());
|
||||
mesh.AlphaCutoff = meshJson["AlphaCutoff"].get<float>();
|
||||
mesh.EmissiveColor = JsonToVec3(meshJson["EmissiveColor"]);
|
||||
mesh.BaseColor = JsonToVec3(meshJson["BaseColor"]);
|
||||
mesh.ModelNodeIndex = meshJson["ModelNodeIndex"].get<std::int32_t>();
|
||||
mesh.SubMeshIndex = meshJson["SubMeshIndex"].get<std::int32_t>();
|
||||
mesh.Visible = meshJson["Visible"].get<bool>();
|
||||
|
||||
obj.MeshRenderer = mesh;
|
||||
}
|
||||
|
||||
// 反序列化 Light 组件 (如果有)
|
||||
if (objJson.contains("Light")) {
|
||||
const auto& lightJson = objJson["Light"];
|
||||
MetaCoreLightComponent light;
|
||||
light.Color = JsonToVec3(lightJson["Color"]);
|
||||
light.Intensity = lightJson["Intensity"].get<float>();
|
||||
obj.Light = light;
|
||||
}
|
||||
|
||||
// 反序列化 Camera 组件 (如果有)
|
||||
if (objJson.contains("Camera")) {
|
||||
const auto& camJson = objJson["Camera"];
|
||||
MetaCoreCameraComponent cam;
|
||||
cam.FieldOfViewDegrees = camJson["FieldOfViewDegrees"].get<float>();
|
||||
cam.NearClip = camJson["NearClip"].get<float>();
|
||||
cam.FarClip = camJson["FarClip"].get<float>();
|
||||
cam.IsPrimary = camJson["IsPrimary"].get<bool>();
|
||||
obj.Camera = cam;
|
||||
}
|
||||
|
||||
// 反序列化 PrefabInstance 预制体元数据 (如果有)
|
||||
if (objJson.contains("PrefabInstance")) {
|
||||
const auto& prefabJson = objJson["PrefabInstance"];
|
||||
MetaCorePrefabInstanceMetadata prefab;
|
||||
prefab.PrefabAssetGuid = StringToGuid(prefabJson["PrefabAssetGuid"].get<std::string>());
|
||||
prefab.PrefabObjectId = prefabJson["PrefabObjectId"].get<MetaCoreId>();
|
||||
prefab.PrefabInstanceRootId = prefabJson["PrefabInstanceRootId"].get<MetaCoreId>();
|
||||
obj.PrefabInstance = prefab;
|
||||
}
|
||||
|
||||
sceneDocument.GameObjects.push_back(obj);
|
||||
}
|
||||
}
|
||||
|
||||
return sceneDocument;
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "LoadSceneFromJson: 发生异常 - " << e.what() << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MetaCore
|
||||
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCoreScene/MetaCoreScene.h"
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
class MetaCoreTypeRegistry;
|
||||
|
||||
class MetaCoreSceneSerializer {
|
||||
public:
|
||||
// 将整个场景文件导出为可读的 JSON 文本
|
||||
static bool SaveSceneToJson(
|
||||
const std::filesystem::path& absolutePath,
|
||||
const MetaCoreSceneDocument& sceneDocument,
|
||||
const MetaCoreTypeRegistry& registry
|
||||
);
|
||||
|
||||
// 从 JSON 文本中完整反序列化载入场景
|
||||
static std::optional<MetaCoreSceneDocument> LoadSceneFromJson(
|
||||
const std::filesystem::path& absolutePath,
|
||||
const MetaCoreTypeRegistry& registry
|
||||
);
|
||||
};
|
||||
|
||||
} // namespace MetaCore
|
||||
@ -453,6 +453,16 @@ void MetaCoreTestComponentRegistryDescriptors() {
|
||||
const auto* cameraDescriptor = componentRegistry->FindDescriptor("Camera");
|
||||
const auto* lightDescriptor = componentRegistry->FindDescriptor("Light");
|
||||
const auto* meshRendererDescriptor = componentRegistry->FindDescriptor("MeshRenderer");
|
||||
|
||||
std::cout << "[CHECK] transformDescriptor: " << transformDescriptor << std::endl;
|
||||
std::cout << "[CHECK] cameraDescriptor: " << cameraDescriptor << std::endl;
|
||||
std::cout << "[CHECK] lightDescriptor: " << lightDescriptor << std::endl;
|
||||
std::cout << "[CHECK] meshRendererDescriptor: " << meshRendererDescriptor << std::endl;
|
||||
if (transformDescriptor) std::cout << "[CHECK] transform DrawInspector: " << static_cast<bool>(transformDescriptor->DrawInspector) << std::endl;
|
||||
if (cameraDescriptor) std::cout << "[CHECK] camera DrawInspector: " << static_cast<bool>(cameraDescriptor->DrawInspector) << std::endl;
|
||||
if (lightDescriptor) std::cout << "[CHECK] light DrawInspector: " << static_cast<bool>(lightDescriptor->DrawInspector) << std::endl;
|
||||
if (meshRendererDescriptor) std::cout << "[CHECK] meshRenderer DrawInspector: " << static_cast<bool>(meshRendererDescriptor->DrawInspector) << std::endl;
|
||||
|
||||
MetaCoreExpect(transformDescriptor != nullptr, "Transform descriptor 应存在");
|
||||
MetaCoreExpect(cameraDescriptor != nullptr, "Camera descriptor 应存在");
|
||||
MetaCoreExpect(lightDescriptor != nullptr, "Light descriptor 应存在");
|
||||
@ -510,12 +520,12 @@ void MetaCoreTestScenePersistenceRoundTrip() {
|
||||
|
||||
MetaCore::MetaCoreId cubeId = 0;
|
||||
for (const MetaCore::MetaCoreGameObject& object : scene.GetGameObjects()) {
|
||||
if (object.GetName() == "Cube") {
|
||||
if (object.GetName() == "box1.glb") {
|
||||
cubeId = object.GetId();
|
||||
break;
|
||||
}
|
||||
}
|
||||
MetaCoreExpect(cubeId != 0, "默认场景应包含 Cube");
|
||||
MetaCoreExpect(cubeId != 0, "默认场景应包含 box1.glb");
|
||||
|
||||
editorContext.SelectOnly(cubeId);
|
||||
MetaCoreExpect(scenePersistenceService->SaveSceneAs(editorContext, std::filesystem::path("Scenes") / "Main.mcscene"), "应能保存场景");
|
||||
@ -535,7 +545,7 @@ void MetaCoreTestScenePersistenceRoundTrip() {
|
||||
MetaCoreExpect(scene.GetGameObjects().empty(), "清空快照后场景应为空");
|
||||
|
||||
MetaCoreExpect(scenePersistenceService->LoadScene(editorContext, std::filesystem::path("Scenes") / "Main.mcscene"), "应能重新加载场景");
|
||||
MetaCoreExpect(scene.GetGameObjects().size() == 6, "重新加载后对象数量应恢复");
|
||||
MetaCoreExpect(scene.GetGameObjects().size() == 4, "重新加载后对象数量应恢复");
|
||||
MetaCoreExpect(editorContext.GetSelectedObjectId() != 0, "重新加载后应恢复选中对象");
|
||||
|
||||
bool foundRenamedCube = false;
|
||||
@ -1314,7 +1324,7 @@ void MetaCoreTestBootstrapStartupSceneCreation() {
|
||||
MetaCoreExpect(std::filesystem::exists(tempProjectRoot / bootstrapScenePath), "应生成 Main.mcscene");
|
||||
MetaCoreExpect(scenePersistenceService->LoadStartupScene(editorContext), "设置后应能加载 startup scene");
|
||||
MetaCoreExpect(scenePersistenceService->GetCurrentScenePath() == bootstrapScenePath, "startup scene 路径应正确");
|
||||
MetaCoreExpect(scene.GetGameObjects().size() == 6, "bootstrap scene 应保存默认场景内容");
|
||||
MetaCoreExpect(scene.GetGameObjects().size() == 4, "bootstrap scene 应保存默认场景内容");
|
||||
|
||||
coreServicesModule->Shutdown(moduleRegistry);
|
||||
moduleRegistry.ShutdownServices();
|
||||
@ -2372,8 +2382,13 @@ void MetaCoreTestUiDocumentSerialization() {
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
std::setvbuf(stdout, nullptr, _IONBF, 0);
|
||||
std::setvbuf(stderr, nullptr, _IONBF, 0);
|
||||
|
||||
std::cout << "[RUN] MetaCoreCreateDefaultScene..." << std::endl;
|
||||
MetaCore::MetaCoreScene scene = MetaCore::MetaCoreCreateDefaultScene();
|
||||
MetaCoreExpect(scene.GetGameObjects().size() == 6, "默认场景应包含六个对象");
|
||||
std::cout << "[RUN] MetaCoreExpect default objects count..." << std::endl;
|
||||
MetaCoreExpect(scene.GetGameObjects().size() == 4, "默认场景应包含四个对象");
|
||||
|
||||
bool hasCamera = false;
|
||||
bool hasLight = false;
|
||||
@ -2388,53 +2403,95 @@ int main() {
|
||||
MetaCoreExpect(hasLight, "默认场景应包含光源");
|
||||
MetaCoreExpect(hasCube, "默认场景应包含立方体");
|
||||
|
||||
std::cout << "[RUN] MetaCoreDummyPanelProvider test..." << std::endl;
|
||||
MetaCore::MetaCoreEditorModuleRegistry moduleRegistry;
|
||||
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCoreDummyPanelProvider>());
|
||||
MetaCoreExpect(!moduleRegistry.AccessPanelOpenState("Dummy"), "面板默认状态应为关闭");
|
||||
|
||||
std::cout << "[RUN] MetaCoreLogService test..." << std::endl;
|
||||
MetaCore::MetaCoreLogService logService;
|
||||
logService.AddEntry(MetaCore::MetaCoreLogLevel::Info, "Test", "Smoke");
|
||||
MetaCoreExpect(logService.GetEntries().size() == 1, "日志服务应记录一条日志");
|
||||
|
||||
std::cout << "[RUN] MetaCoreTestSceneEditApi..." << std::endl;
|
||||
MetaCoreTestSceneEditApi();
|
||||
std::cout << "[RUN] MetaCoreTestSelectionStateMachine..." << std::endl;
|
||||
MetaCoreTestSelectionStateMachine();
|
||||
std::cout << "[RUN] MetaCoreTestUndoRedo..." << std::endl;
|
||||
MetaCoreTestUndoRedo();
|
||||
std::cout << "[RUN] MetaCoreTestBuiltinModuleComposition..." << std::endl;
|
||||
MetaCoreTestBuiltinModuleComposition();
|
||||
std::cout << "[RUN] MetaCoreTestScenePackageProjectStartupLoad..." << std::endl;
|
||||
MetaCoreTestScenePackageProjectStartupLoad();
|
||||
std::cout << "[RUN] MetaCoreTestProjectFileRoundTripAndDiscovery..." << std::endl;
|
||||
MetaCoreTestProjectFileRoundTripAndDiscovery();
|
||||
std::cout << "[RUN] MetaCoreTestAssetDatabaseCreateAndOpenProject..." << std::endl;
|
||||
MetaCoreTestAssetDatabaseCreateAndOpenProject();
|
||||
std::cout << "[RUN] MetaCoreTestAssetDatabaseRenamePathUpdatesProjectDescriptor..." << std::endl;
|
||||
MetaCoreTestAssetDatabaseRenamePathUpdatesProjectDescriptor();
|
||||
std::cout << "[RUN] MetaCoreTestAssetDatabaseMovePathUpdatesProjectDescriptor..." << std::endl;
|
||||
MetaCoreTestAssetDatabaseMovePathUpdatesProjectDescriptor();
|
||||
std::cout << "[RUN] MetaCoreTestComponentRegistryDescriptors..." << std::endl;
|
||||
MetaCoreTestComponentRegistryDescriptors();
|
||||
std::cout << "[RUN] MetaCoreTestScenePersistenceRoundTrip..." << std::endl;
|
||||
MetaCoreTestScenePersistenceRoundTrip();
|
||||
std::cout << "[RUN] MetaCoreTestImportPipelineAndCook..." << std::endl;
|
||||
MetaCoreTestImportPipelineAndCook();
|
||||
std::cout << "[RUN] MetaCoreTestProjectDescriptorAndGltfImporterSkeleton..." << std::endl;
|
||||
MetaCoreTestProjectDescriptorAndGltfImporterSkeleton();
|
||||
std::cout << "[RUN] MetaCoreTestInstantiateImportedModelAssetIntoScene..." << std::endl;
|
||||
MetaCoreTestInstantiateImportedModelAssetIntoScene();
|
||||
std::cout << "[RUN] MetaCoreTestInstantiateMultiNodeModelAssetIntoScene..." << std::endl;
|
||||
MetaCoreTestInstantiateMultiNodeModelAssetIntoScene();
|
||||
std::cout << "[RUN] MetaCoreTestModelReimportKeepsGeneratedAssetReferencesStable..." << std::endl;
|
||||
MetaCoreTestModelReimportKeepsGeneratedAssetReferencesStable();
|
||||
std::cout << "[RUN] MetaCoreTestBootstrapStartupSceneCreation..." << std::endl;
|
||||
MetaCoreTestBootstrapStartupSceneCreation();
|
||||
std::cout << "[RUN] MetaCoreTestPrefabWorkflow..." << std::endl;
|
||||
MetaCoreTestPrefabWorkflow();
|
||||
std::cout << "[RUN] MetaCoreTestComponentRegistryOperations..." << std::endl;
|
||||
MetaCoreTestComponentRegistryOperations();
|
||||
std::cout << "[RUN] MetaCoreTestRuntimeDataTypeSerialization..." << std::endl;
|
||||
MetaCoreTestRuntimeDataTypeSerialization();
|
||||
std::cout << "[RUN] MetaCoreTestRuntimeDataProjectDocumentSerialization..." << std::endl;
|
||||
MetaCoreTestRuntimeDataProjectDocumentSerialization();
|
||||
std::cout << "[RUN] MetaCoreTestMeshRendererResourceSerialization..." << std::endl;
|
||||
MetaCoreTestMeshRendererResourceSerialization();
|
||||
std::cout << "[RUN] MetaCoreTestRuntimeDataBinaryDocumentIo..." << std::endl;
|
||||
MetaCoreTestRuntimeDataBinaryDocumentIo();
|
||||
std::cout << "[RUN] MetaCoreTestRuntimeProjectDocumentIo..." << std::endl;
|
||||
MetaCoreTestRuntimeProjectDocumentIo();
|
||||
std::cout << "[RUN] MetaCoreTestRuntimeDiagnosticsSnapshotIo..." << std::endl;
|
||||
MetaCoreTestRuntimeDiagnosticsSnapshotIo();
|
||||
std::cout << "[RUN] MetaCoreTestRuntimeDataDispatcherConstruction..." << std::endl;
|
||||
MetaCoreTestRuntimeDataDispatcherConstruction();
|
||||
std::cout << "[RUN] MetaCoreTestRuntimeDataDispatcherAppliesUpdates..." << std::endl;
|
||||
MetaCoreTestRuntimeDataDispatcherAppliesUpdates();
|
||||
std::cout << "[RUN] MetaCoreTestMockRuntimeDataSourceAdapterEmitsUpdates..." << std::endl;
|
||||
MetaCoreTestMockRuntimeDataSourceAdapterEmitsUpdates();
|
||||
std::cout << "[RUN] MetaCoreTestRuntimeDataDispatcherMarksStaleBindings..." << std::endl;
|
||||
MetaCoreTestRuntimeDataDispatcherMarksStaleBindings();
|
||||
std::cout << "[RUN] MetaCoreTestMockRuntimeDataSourceAdapterDegradedState..." << std::endl;
|
||||
MetaCoreTestMockRuntimeDataSourceAdapterDegradedState();
|
||||
std::cout << "[RUN] MetaCoreTestFileReplayRuntimeDataSourceAdapterEmitsReplayFrames..." << std::endl;
|
||||
MetaCoreTestFileReplayRuntimeDataSourceAdapterEmitsReplayFrames();
|
||||
std::cout << "[RUN] MetaCoreTestRuntimeDiagnosticsSnapshotReportsFaults..." << std::endl;
|
||||
MetaCoreTestRuntimeDiagnosticsSnapshotReportsFaults();
|
||||
std::cout << "[RUN] MetaCoreTestRuntimeDataBinaryDocumentRejectsCorruption..." << std::endl;
|
||||
MetaCoreTestRuntimeDataBinaryDocumentRejectsCorruption();
|
||||
std::cout << "[RUN] MetaCoreTestEditorContextRuntimeDataConfigSaveLoad..." << std::endl;
|
||||
MetaCoreTestEditorContextRuntimeDataConfigSaveLoad();
|
||||
std::cout << "[RUN] MetaCoreTestRuntimeDataConfigValidationRejectsBrokenBinding..." << std::endl;
|
||||
MetaCoreTestRuntimeDataConfigValidationRejectsBrokenBinding();
|
||||
std::cout << "[RUN] MetaCoreTestRuntimeDataConfigValidationRejectsMissingReplayFilePath..." << std::endl;
|
||||
MetaCoreTestRuntimeDataConfigValidationRejectsMissingReplayFilePath();
|
||||
std::cout << "[RUN] MetaCoreTestRuntimeDataConfigValidationRejectsMissingTcpPort..." << std::endl;
|
||||
MetaCoreTestRuntimeDataConfigValidationRejectsMissingTcpPort();
|
||||
std::cout << "[RUN] MetaCoreTestEditorContextRejectsInvalidRuntimeDataSave..." << std::endl;
|
||||
MetaCoreTestEditorContextRejectsInvalidRuntimeDataSave();
|
||||
std::cout << "[RUN] MetaCoreTestTcpRuntimeDataSourceAdapterReadsSocketStream..." << std::endl;
|
||||
MetaCoreTestTcpRuntimeDataSourceAdapterReadsSocketStream();
|
||||
std::cout << "[RUN] MetaCoreTestUiDocumentSerialization..." << std::endl;
|
||||
MetaCoreTestUiDocumentSerialization();
|
||||
|
||||
std::cout << "MetaCoreSmokeTests passed\n";
|
||||
|
||||
25526
third_party/nlohmann/json.hpp
vendored
Normal file
25526
third_party/nlohmann/json.hpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user