feat: 引入 AssetRegistry 并修复 PPM 与 Gltf 导入的编译及 UAF 闪退崩溃,通过全部冒烟测试

This commit is contained in:
ayuan9957 2026-05-18 14:03:53 +08:00
parent 3a2d9465dd
commit 05ac9e499c
13 changed files with 26749 additions and 305 deletions

View File

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

View File

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

View File

@ -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
);
}

View File

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

View 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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

File diff suppressed because it is too large Load Diff