场景加载优化
This commit is contained in:
parent
ddeba5bbbc
commit
31a408d813
@ -21,6 +21,7 @@
|
|||||||
#include "MetaCoreRuntimeInput/MetaCoreRuntimeInteraction.h"
|
#include "MetaCoreRuntimeInput/MetaCoreRuntimeInteraction.h"
|
||||||
#include "MetaCoreScene/MetaCoreScenePackage.h"
|
#include "MetaCoreScene/MetaCoreScenePackage.h"
|
||||||
#include "MetaCoreScene/MetaCoreScene.h"
|
#include "MetaCoreScene/MetaCoreScene.h"
|
||||||
|
#include "MetaCoreScene/MetaCoreSceneWorld.h"
|
||||||
#include "MetaCoreScripting/MetaCoreScripting.h"
|
#include "MetaCoreScripting/MetaCoreScripting.h"
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
@ -30,6 +31,7 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -450,10 +452,50 @@ int main(int argc, char* argv[]) {
|
|||||||
startupSceneDocument = MetaCore::MetaCoreLoadStartupSceneDocument(projectPath);
|
startupSceneDocument = MetaCore::MetaCoreLoadStartupSceneDocument(projectPath);
|
||||||
}
|
}
|
||||||
MetaCore::MetaCoreScene scene;
|
MetaCore::MetaCoreScene scene;
|
||||||
|
std::unordered_map<MetaCore::MetaCoreAssetGuid, std::filesystem::path, MetaCore::MetaCoreAssetGuidHasher> runtimeScenePackages;
|
||||||
|
if (deliveryDocument) for (const auto& entry : deliveryDocument->ScenePackages)
|
||||||
|
runtimeScenePackages[entry.SceneGuid] = packageRoot / entry.PackagePath;
|
||||||
|
MetaCore::MetaCoreSceneWorld sceneWorld(
|
||||||
|
scene,
|
||||||
|
[&](const MetaCore::MetaCoreAssetGuid& sceneGuid) -> std::optional<MetaCore::MetaCoreSceneDocument> {
|
||||||
|
if (const auto path = runtimeScenePackages.find(sceneGuid); path != runtimeScenePackages.end())
|
||||||
|
return MetaCore::MetaCoreReadScenePackage(path->second);
|
||||||
|
if (!deliveryDocument) {
|
||||||
|
std::error_code scanError;
|
||||||
|
const auto scenesRoot = projectRoot / "Scenes";
|
||||||
|
if (std::filesystem::is_directory(scenesRoot, scanError)) {
|
||||||
|
for (std::filesystem::recursive_directory_iterator iterator(
|
||||||
|
scenesRoot, std::filesystem::directory_options::skip_permission_denied, scanError), end;
|
||||||
|
!scanError && iterator != end; iterator.increment(scanError)) {
|
||||||
|
if (!iterator->is_regular_file(scanError)) continue;
|
||||||
|
auto candidate = MetaCore::MetaCoreReadScenePackage(iterator->path());
|
||||||
|
if (candidate && candidate->SceneGuid == sceneGuid) return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
);
|
||||||
if (startupSceneDocument.has_value()) {
|
if (startupSceneDocument.has_value()) {
|
||||||
MetaCore::MetaCoreSceneSnapshot snapshot;
|
std::string sceneLoadError;
|
||||||
snapshot.GameObjects = startupSceneDocument->GameObjects;
|
if (!sceneWorld.LoadDocumentNow(*startupSceneDocument, MetaCore::MetaCoreSceneLoadMode::Replace, &sceneLoadError)) {
|
||||||
scene.RestoreSnapshot(snapshot);
|
errors << "MCB6004: startup SceneWorld load failed: " << sceneLoadError << '\n';
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
// AlwaysLoaded SubScenes are part of the startup transaction from the
|
||||||
|
// runtime's point of view: settle their background reads before systems scan ECS.
|
||||||
|
const std::size_t expectedStartupScenes = 1U + static_cast<std::size_t>(std::count_if(
|
||||||
|
startupSceneDocument->SubScenes.begin(), startupSceneDocument->SubScenes.end(),
|
||||||
|
[](const auto& subScene) { return subScene.LoadPolicy == MetaCore::MetaCoreSubSceneLoadPolicy::AlwaysLoaded; }
|
||||||
|
));
|
||||||
|
for (int iteration = 0; iteration < 1000 && sceneWorld.GetLoadedScenes().size() < expectedStartupScenes; ++iteration) {
|
||||||
|
sceneWorld.Tick();
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
if (sceneWorld.GetLoadedScenes().size() < expectedStartupScenes) {
|
||||||
|
errors << "MCB6004: one or more AlwaysLoaded SubScenes failed to load\n";
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
output << "MetaCorePlayer: loaded startup scene from project "
|
output << "MetaCorePlayer: loaded startup scene from project "
|
||||||
<< projectPath.string() << '\n';
|
<< projectPath.string() << '\n';
|
||||||
} else if (!deliveryDocument) {
|
} else if (!deliveryDocument) {
|
||||||
@ -704,6 +746,8 @@ int main(int argc, char* argv[]) {
|
|||||||
MetaCore::MetaCoreSceneView interactionSceneView = MetaCoreBuildPlayerSceneView();
|
MetaCore::MetaCoreSceneView interactionSceneView = MetaCoreBuildPlayerSceneView();
|
||||||
const auto interactionSnapshot = interactionCameraSync.BuildSnapshot(scene);
|
const auto interactionSnapshot = interactionCameraSync.BuildSnapshot(scene);
|
||||||
(void)MetaCore::MetaCoreSceneRenderSync::TryBuildSceneViewFromPrimaryCamera(interactionSnapshot, interactionSceneView);
|
(void)MetaCore::MetaCoreSceneRenderSync::TryBuildSceneViewFromPrimaryCamera(interactionSnapshot, interactionSceneView);
|
||||||
|
sceneWorld.UpdateStreaming({interactionSceneView.CameraPosition});
|
||||||
|
sceneWorld.Tick();
|
||||||
runtimeInteraction.Update(scene, viewportRenderer, physicsWorld, interactionSceneView,
|
runtimeInteraction.Update(scene, viewportRenderer, physicsWorld, interactionSceneView,
|
||||||
window.GetInput(), inputViewport, uiConsumption.Mouse);
|
window.GetInput(), inputViewport, uiConsumption.Mouse);
|
||||||
fixedAccumulatorSeconds += std::clamp(frameDeltaSeconds, 0.0, physicsSettings.MaxFrameDelta);
|
fixedAccumulatorSeconds += std::clamp(frameDeltaSeconds, 0.0, physicsSettings.MaxFrameDelta);
|
||||||
|
|||||||
@ -561,14 +561,19 @@ set(METACORE_SCENE_HEADERS
|
|||||||
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h
|
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h
|
||||||
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreGameObject.h
|
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreGameObject.h
|
||||||
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreSceneDocument.h
|
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreSceneDocument.h
|
||||||
|
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreObjectReference.h
|
||||||
|
Source/MetaCoreScene/Public/MetaCoreScene/MetaCorePrefab.h
|
||||||
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreScenePackage.h
|
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreScenePackage.h
|
||||||
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreScene.h
|
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreScene.h
|
||||||
|
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreSceneWorld.h
|
||||||
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreSceneSerializer.h
|
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreSceneSerializer.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(METACORE_SCENE_SOURCES
|
set(METACORE_SCENE_SOURCES
|
||||||
Source/MetaCoreScene/Private/MetaCoreScenePackage.cpp
|
Source/MetaCoreScene/Private/MetaCoreScenePackage.cpp
|
||||||
Source/MetaCoreScene/Private/MetaCoreScene.cpp
|
Source/MetaCoreScene/Private/MetaCoreScene.cpp
|
||||||
|
Source/MetaCoreScene/Private/MetaCoreSceneWorld.cpp
|
||||||
|
Source/MetaCoreScene/Private/MetaCorePrefab.cpp
|
||||||
Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp
|
Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -579,6 +584,7 @@ metacore_generate_reflection(
|
|||||||
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h
|
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h
|
||||||
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreGameObject.h
|
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreGameObject.h
|
||||||
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreSceneDocument.h
|
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreSceneDocument.h
|
||||||
|
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreObjectReference.h
|
||||||
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreScene.h
|
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreScene.h
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1001,6 +1007,17 @@ metacore_stage_material_templates(MetaCorePlayer)
|
|||||||
if(METACORE_BUILD_TESTS)
|
if(METACORE_BUILD_TESTS)
|
||||||
enable_testing()
|
enable_testing()
|
||||||
|
|
||||||
|
add_executable(MetaCoreSceneTests tests/MetaCoreSceneTests.cpp)
|
||||||
|
target_link_libraries(MetaCoreSceneTests PRIVATE MetaCoreScene)
|
||||||
|
target_compile_options(MetaCoreSceneTests PRIVATE ${METACORE_COMMON_WARNINGS})
|
||||||
|
add_test(NAME MetaCoreSceneTests COMMAND MetaCoreSceneTests)
|
||||||
|
|
||||||
|
add_executable(MetaCoreSceneBenchmark tests/MetaCoreSceneBenchmark.cpp)
|
||||||
|
target_link_libraries(MetaCoreSceneBenchmark PRIVATE MetaCoreScene)
|
||||||
|
target_compile_options(MetaCoreSceneBenchmark PRIVATE ${METACORE_COMMON_WARNINGS})
|
||||||
|
add_test(NAME MetaCoreSceneBenchmark COMMAND MetaCoreSceneBenchmark)
|
||||||
|
set_tests_properties(MetaCoreSceneBenchmark PROPERTIES LABELS "benchmark")
|
||||||
|
|
||||||
add_executable(MetaCoreDeliveryTests
|
add_executable(MetaCoreDeliveryTests
|
||||||
tests/MetaCoreDeliveryTests.cpp
|
tests/MetaCoreDeliveryTests.cpp
|
||||||
Source/MetaCoreRender/Private/MetaCoreGlibcCompat.cpp
|
Source/MetaCoreRender/Private/MetaCoreGlibcCompat.cpp
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
|
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
|
||||||
#include "MetaCoreRendering/MetaCoreRendering.h"
|
#include "MetaCoreRendering/MetaCoreRendering.h"
|
||||||
#include "MetaCoreScene/MetaCoreScenePackage.h"
|
#include "MetaCoreScene/MetaCoreScenePackage.h"
|
||||||
|
#include "MetaCoreScene/MetaCorePrefab.h"
|
||||||
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
|
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
|
||||||
#include "MetaCoreScripting/MetaCoreScripting.h"
|
#include "MetaCoreScripting/MetaCoreScripting.h"
|
||||||
|
|
||||||
@ -30,6 +31,7 @@
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <system_error>
|
#include <system_error>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
#include <unordered_map>
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
#include <sys/utsname.h>
|
#include <sys/utsname.h>
|
||||||
#endif
|
#endif
|
||||||
@ -562,6 +564,20 @@ std::optional<MetaCoreRuntimeDeliveryDocument> MetaCoreReadRuntimeDeliveryDocume
|
|||||||
if (module.is_string()) document.ScriptModules.emplace_back(module.get<std::string>());
|
if (module.is_string()) document.ScriptModules.emplace_back(module.get<std::string>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (value->contains("scene_packages") && (*value)["scene_packages"].is_array()) {
|
||||||
|
for (const auto& scene : (*value)["scene_packages"]) {
|
||||||
|
MetaCoreRuntimeDeliveryDocument::SceneEntry entry;
|
||||||
|
entry.SceneGuid = MetaCoreAssetGuid::Parse(scene.value("guid", "")).value_or(MetaCoreAssetGuid{});
|
||||||
|
entry.PackagePath = scene.value("path", "");
|
||||||
|
entry.SourceHash = scene.value("source_hash", std::uint64_t{});
|
||||||
|
if (scene.contains("dependencies")) for (const auto& dependency : scene["dependencies"]) {
|
||||||
|
if (dependency.is_string()) {
|
||||||
|
if (const auto guid = MetaCoreAssetGuid::Parse(dependency.get<std::string>())) entry.Dependencies.push_back(*guid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.ScenePackages.push_back(std::move(entry));
|
||||||
|
}
|
||||||
|
}
|
||||||
if (document.FormatVersion != GMetaCoreRuntimeDeliveryVersion) {
|
if (document.FormatVersion != GMetaCoreRuntimeDeliveryVersion) {
|
||||||
SetIssue(issue, MetaCoreBuildErrorCode::ManifestVersionUnsupported, "Runtime delivery format is unsupported", path);
|
SetIssue(issue, MetaCoreBuildErrorCode::ManifestVersionUnsupported, "Runtime delivery format is unsupported", path);
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
@ -582,18 +598,36 @@ std::optional<MetaCoreRuntimeDeliveryDocument> MetaCoreReadRuntimeDeliveryDocume
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (const auto& scene : document.ScenePackages) {
|
||||||
|
if (!scene.SceneGuid.IsValid() || !IsSafeRelativePath(scene.PackagePath)) {
|
||||||
|
SetIssue(issue, MetaCoreBuildErrorCode::UnsafePath, "Runtime delivery contains an invalid scene entry", path);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
return document;
|
return document;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MetaCoreWriteRuntimeDeliveryDocument(const std::filesystem::path& path, const MetaCoreRuntimeDeliveryDocument& document) {
|
bool MetaCoreWriteRuntimeDeliveryDocument(const std::filesystem::path& path, const MetaCoreRuntimeDeliveryDocument& document) {
|
||||||
json modules = json::array();
|
json modules = json::array();
|
||||||
for (const auto& module : document.ScriptModules) modules.push_back(module.generic_string());
|
for (const auto& module : document.ScriptModules) modules.push_back(module.generic_string());
|
||||||
|
json scenes = json::array();
|
||||||
|
for (const auto& scene : document.ScenePackages) {
|
||||||
|
json dependencies = json::array();
|
||||||
|
for (const auto& dependency : scene.Dependencies) dependencies.push_back(dependency.ToString());
|
||||||
|
scenes.push_back({
|
||||||
|
{"guid", scene.SceneGuid.ToString()},
|
||||||
|
{"path", scene.PackagePath.generic_string()},
|
||||||
|
{"source_hash", scene.SourceHash},
|
||||||
|
{"dependencies", std::move(dependencies)}
|
||||||
|
});
|
||||||
|
}
|
||||||
return WriteJson(path, {
|
return WriteJson(path, {
|
||||||
{"format_version", document.FormatVersion}, {"runtime_abi_major", document.RuntimeAbiMajor},
|
{"format_version", document.FormatVersion}, {"runtime_abi_major", document.RuntimeAbiMajor},
|
||||||
{"runtime_abi_minor", document.RuntimeAbiMinor}, {"build_id", document.BuildId},
|
{"runtime_abi_minor", document.RuntimeAbiMinor}, {"build_id", document.BuildId},
|
||||||
{"content_root", document.ContentRoot.generic_string()}, {"startup_scene", document.StartupScene.generic_string()},
|
{"content_root", document.ContentRoot.generic_string()}, {"startup_scene", document.StartupScene.generic_string()},
|
||||||
{"runtime_config", document.RuntimeConfig.generic_string()}, {"ui_manifest", document.UiManifest.generic_string()},
|
{"runtime_config", document.RuntimeConfig.generic_string()}, {"ui_manifest", document.UiManifest.generic_string()},
|
||||||
{"release_manifest", document.ReleaseManifest.generic_string()}, {"script_modules", std::move(modules)}
|
{"release_manifest", document.ReleaseManifest.generic_string()}, {"script_modules", std::move(modules)},
|
||||||
|
{"scene_packages", std::move(scenes)}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -832,6 +866,7 @@ MetaCoreBuildResult MetaCoreBuildPipeline::Run(const MetaCoreBuildRequest& reque
|
|||||||
MetaCoreRegisterRuntimeDataGeneratedTypes(inputRegistry);
|
MetaCoreRegisterRuntimeDataGeneratedTypes(inputRegistry);
|
||||||
MetaCoreRegisterRuntimeInputGeneratedTypes(inputRegistry);
|
MetaCoreRegisterRuntimeInputGeneratedTypes(inputRegistry);
|
||||||
MetaCoreRegisterPhysicsGeneratedTypes(inputRegistry);
|
MetaCoreRegisterPhysicsGeneratedTypes(inputRegistry);
|
||||||
|
const MetaCoreTypeRegistry sceneRegistry = MetaCoreBuildScenePackageTypeRegistry();
|
||||||
const auto runtimeProjectPath = request.ProjectRoot / project->RuntimeDirectory / "ProjectRuntime.mcruntimecfg";
|
const auto runtimeProjectPath = request.ProjectRoot / project->RuntimeDirectory / "ProjectRuntime.mcruntimecfg";
|
||||||
auto runtimeProject = MetaCoreReadRuntimeProjectDocument(runtimeProjectPath, inputRegistry);
|
auto runtimeProject = MetaCoreReadRuntimeProjectDocument(runtimeProjectPath, inputRegistry);
|
||||||
if (!runtimeProject) return fail(MetaCoreBuildErrorCode::CookFailed, "Runtime project configuration is missing or unreadable", runtimeProjectPath);
|
if (!runtimeProject) return fail(MetaCoreBuildErrorCode::CookFailed, "Runtime project configuration is missing or unreadable", runtimeProjectPath);
|
||||||
@ -883,6 +918,7 @@ MetaCoreBuildResult MetaCoreBuildPipeline::Run(const MetaCoreBuildRequest& reque
|
|||||||
const auto cookedScene = staging / "Content" / "Scenes" / cookedSceneName;
|
const auto cookedScene = staging / "Content" / "Scenes" / cookedSceneName;
|
||||||
const auto cookablePhysicsMeshes = MetaCoreCollectCookablePhysicsMeshes(request.ProjectRoot, inputRegistry, staging / "Content" / "Physics");
|
const auto cookablePhysicsMeshes = MetaCoreCollectCookablePhysicsMeshes(request.ProjectRoot, inputRegistry, staging / "Content" / "Physics");
|
||||||
std::optional<MetaCoreSceneDocument> startupSceneDocument;
|
std::optional<MetaCoreSceneDocument> startupSceneDocument;
|
||||||
|
std::vector<MetaCoreRuntimeDeliveryDocument::SceneEntry> stagedSceneEntries;
|
||||||
if (startupSource.extension() == ".json") {
|
if (startupSource.extension() == ".json") {
|
||||||
const auto registry = MetaCoreBuildScenePackageTypeRegistry();
|
const auto registry = MetaCoreBuildScenePackageTypeRegistry();
|
||||||
const auto scene = MetaCoreSceneSerializer::LoadSceneFromJson(absoluteStartup, registry);
|
const auto scene = MetaCoreSceneSerializer::LoadSceneFromJson(absoluteStartup, registry);
|
||||||
@ -903,6 +939,131 @@ MetaCoreBuildResult MetaCoreBuildPipeline::Run(const MetaCoreBuildRequest& reque
|
|||||||
std::filesystem::copy_file(absoluteStartup, cookedScene, std::filesystem::copy_options::overwrite_existing, error);
|
std::filesystem::copy_file(absoluteStartup, cookedScene, std::filesystem::copy_options::overwrite_existing, error);
|
||||||
if (error) return fail(MetaCoreBuildErrorCode::CookFailed, "Failed to copy startup scene", absoluteStartup);
|
if (error) return fail(MetaCoreBuildErrorCode::CookFailed, "Failed to copy startup scene", absoluteStartup);
|
||||||
}
|
}
|
||||||
|
if (startupSceneDocument) {
|
||||||
|
if (!startupSceneDocument->SceneGuid.IsValid())
|
||||||
|
return fail(MetaCoreBuildErrorCode::CookFailed, "Startup scene has no stable SceneGuid", absoluteStartup);
|
||||||
|
|
||||||
|
std::unordered_map<MetaCoreAssetGuid, std::filesystem::path, MetaCoreAssetGuidHasher> sceneSources;
|
||||||
|
std::error_code sceneScanError;
|
||||||
|
const auto sceneRoot = request.ProjectRoot / "Scenes";
|
||||||
|
if (std::filesystem::is_directory(sceneRoot, sceneScanError)) {
|
||||||
|
for (std::filesystem::recursive_directory_iterator iterator(
|
||||||
|
sceneRoot, std::filesystem::directory_options::skip_permission_denied, sceneScanError), end;
|
||||||
|
!sceneScanError && iterator != end; iterator.increment(sceneScanError)) {
|
||||||
|
if (!iterator->is_regular_file(sceneScanError)) continue;
|
||||||
|
const auto path = iterator->path();
|
||||||
|
if (!path.filename().string().ends_with(".mcscene.json")) continue;
|
||||||
|
if (const auto candidate = MetaCoreSceneSerializer::LoadSceneFromJson(path, sceneRegistry);
|
||||||
|
candidate && candidate->SceneGuid.IsValid()) {
|
||||||
|
sceneSources[candidate->SceneGuid] = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sceneSources[startupSceneDocument->SceneGuid] = absoluteStartup;
|
||||||
|
|
||||||
|
std::unordered_map<MetaCoreAssetGuid, MetaCorePrefabDocument, MetaCoreAssetGuidHasher> prefabDocuments;
|
||||||
|
std::error_code prefabScanError;
|
||||||
|
const auto prefabRoot = request.ProjectRoot / "Assets";
|
||||||
|
if (std::filesystem::is_directory(prefabRoot, prefabScanError)) {
|
||||||
|
for (std::filesystem::recursive_directory_iterator iterator(
|
||||||
|
prefabRoot, std::filesystem::directory_options::skip_permission_denied, prefabScanError), end;
|
||||||
|
!prefabScanError && iterator != end; iterator.increment(prefabScanError)) {
|
||||||
|
if (!iterator->is_regular_file(prefabScanError) ||
|
||||||
|
!iterator->path().filename().string().ends_with(".mcprefab.json")) continue;
|
||||||
|
if (auto prefab = MetaCoreSceneSerializer::LoadPrefabFromJson(iterator->path(), sceneRegistry);
|
||||||
|
prefab && prefab->PrefabGuid.IsValid())
|
||||||
|
prefabDocuments[prefab->PrefabGuid] = std::move(*prefab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MetaCorePrefabResolver prefabResolver(
|
||||||
|
[&](const MetaCoreAssetGuid& guid) -> std::optional<MetaCorePrefabDocument> {
|
||||||
|
const auto iterator = prefabDocuments.find(guid);
|
||||||
|
return iterator == prefabDocuments.end() ? std::nullopt : std::optional(iterator->second);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
std::unordered_set<MetaCoreAssetGuid, MetaCoreAssetGuidHasher> visitedScenes;
|
||||||
|
std::unordered_set<MetaCoreAssetGuid, MetaCoreAssetGuidHasher> activeScenes;
|
||||||
|
std::string sceneDependencyError;
|
||||||
|
std::function<bool(
|
||||||
|
const MetaCoreSceneDocument&,
|
||||||
|
const std::filesystem::path&,
|
||||||
|
const std::filesystem::path&
|
||||||
|
)> stageSceneClosure;
|
||||||
|
stageSceneClosure = [&](const MetaCoreSceneDocument& sceneDocument,
|
||||||
|
const std::filesystem::path& sourcePath,
|
||||||
|
const std::filesystem::path& outputPath) {
|
||||||
|
if (visitedScenes.contains(sceneDocument.SceneGuid)) return true;
|
||||||
|
if (!activeScenes.insert(sceneDocument.SceneGuid).second) {
|
||||||
|
sceneDependencyError = "SubScene dependency cycle at " + sceneDocument.SceneGuid.ToString();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const auto& instance : sceneDocument.PrefabInstances) {
|
||||||
|
const auto prefabIssues = prefabResolver.ValidateDependencies(instance.PrefabAssetGuid);
|
||||||
|
if (!prefabIssues.empty()) {
|
||||||
|
sceneDependencyError = "Prefab dependency validation failed for " +
|
||||||
|
instance.PrefabAssetGuid.ToString() + ": " + prefabIssues.front();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::vector<MetaCoreAssetGuid> dependencies;
|
||||||
|
for (const auto& subScene : sceneDocument.SubScenes)
|
||||||
|
if (subScene.SceneGuid.IsValid()) dependencies.push_back(subScene.SceneGuid);
|
||||||
|
for (const auto& object : sceneDocument.GameObjects) {
|
||||||
|
if (object.PhysicsConstraint &&
|
||||||
|
object.PhysicsConstraint->TargetObjectReference.Strength == MetaCoreObjectReferenceStrength::Hard &&
|
||||||
|
object.PhysicsConstraint->TargetObjectReference.SceneGuid.IsValid())
|
||||||
|
dependencies.push_back(object.PhysicsConstraint->TargetObjectReference.SceneGuid);
|
||||||
|
if (object.TimelineDirector) for (const auto& binding : object.TimelineDirector->Bindings)
|
||||||
|
if (binding.ObjectReference.Strength == MetaCoreObjectReferenceStrength::Hard &&
|
||||||
|
binding.ObjectReference.SceneGuid.IsValid())
|
||||||
|
dependencies.push_back(binding.ObjectReference.SceneGuid);
|
||||||
|
}
|
||||||
|
std::sort(dependencies.begin(), dependencies.end(), [](const auto& lhs, const auto& rhs) {
|
||||||
|
return lhs.ToString() < rhs.ToString();
|
||||||
|
});
|
||||||
|
dependencies.erase(std::unique(dependencies.begin(), dependencies.end()), dependencies.end());
|
||||||
|
for (const auto& dependency : dependencies) {
|
||||||
|
if (dependency == sceneDocument.SceneGuid) continue;
|
||||||
|
auto source = sceneSources.find(dependency);
|
||||||
|
if (source == sceneSources.end()) {
|
||||||
|
const auto declared = std::find_if(sceneDocument.SubScenes.begin(), sceneDocument.SubScenes.end(),
|
||||||
|
[&](const auto& subScene) { return subScene.SceneGuid == dependency && !subScene.ScenePath.empty(); });
|
||||||
|
if (declared != sceneDocument.SubScenes.end() && IsSafeRelativePath(declared->ScenePath))
|
||||||
|
source = sceneSources.emplace(dependency, request.ProjectRoot / declared->ScenePath).first;
|
||||||
|
}
|
||||||
|
if (source == sceneSources.end() || !std::filesystem::is_regular_file(source->second)) {
|
||||||
|
sceneDependencyError = "Required Scene dependency is missing: " + dependency.ToString();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto dependencyDocument = source->second.extension() == ".json"
|
||||||
|
? MetaCoreSceneSerializer::LoadSceneFromJson(source->second, sceneRegistry)
|
||||||
|
: MetaCoreReadScenePackage(source->second);
|
||||||
|
if (!dependencyDocument || dependencyDocument->SceneGuid != dependency) {
|
||||||
|
sceneDependencyError = "Scene dependency GUID/path mismatch: " + dependency.ToString();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto relativeOutput = std::filesystem::path("Content") / "Scenes" /
|
||||||
|
(dependency.ToString() + ".mcscene");
|
||||||
|
if (!stageSceneClosure(*dependencyDocument, source->second, staging / relativeOutput)) return false;
|
||||||
|
}
|
||||||
|
if (outputPath != cookedScene && !MetaCoreWriteScenePackage(outputPath, sceneDocument)) {
|
||||||
|
sceneDependencyError = "Failed to cook dependent Scene: " + sourcePath.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
stagedSceneEntries.push_back({
|
||||||
|
sceneDocument.SceneGuid,
|
||||||
|
outputPath.lexically_relative(staging),
|
||||||
|
MetaCoreHashFile(sourcePath).value_or(0),
|
||||||
|
dependencies
|
||||||
|
});
|
||||||
|
activeScenes.erase(sceneDocument.SceneGuid);
|
||||||
|
visitedScenes.insert(sceneDocument.SceneGuid);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
if (!stageSceneClosure(*startupSceneDocument, absoluteStartup, cookedScene))
|
||||||
|
return fail(MetaCoreBuildErrorCode::DependencyMissing, sceneDependencyError, absoluteStartup);
|
||||||
|
}
|
||||||
const auto currentCookRoot = request.ProjectRoot / "Library" / "Cooked" / profile->TargetPlatform / profile->Name;
|
const auto currentCookRoot = request.ProjectRoot / "Library" / "Cooked" / profile->TargetPlatform / profile->Name;
|
||||||
const auto editorCookRoot = request.ProjectRoot / "Library" / "Cooked" / profile->TargetPlatform / "Editor";
|
const auto editorCookRoot = request.ProjectRoot / "Library" / "Cooked" / profile->TargetPlatform / "Editor";
|
||||||
const auto legacyCookRoot = request.ProjectRoot / "Library" / "Cooked" / "Windows";
|
const auto legacyCookRoot = request.ProjectRoot / "Library" / "Cooked" / "Windows";
|
||||||
@ -1036,6 +1197,7 @@ MetaCoreBuildResult MetaCoreBuildPipeline::Run(const MetaCoreBuildRequest& reque
|
|||||||
MetaCoreRuntimeDeliveryDocument delivery;
|
MetaCoreRuntimeDeliveryDocument delivery;
|
||||||
delivery.BuildId = result.BuildId;
|
delivery.BuildId = result.BuildId;
|
||||||
delivery.StartupScene = std::filesystem::path("Content") / "Scenes" / cookedSceneName;
|
delivery.StartupScene = std::filesystem::path("Content") / "Scenes" / cookedSceneName;
|
||||||
|
delivery.ScenePackages = stagedSceneEntries;
|
||||||
if (!stagedScriptModule.empty()) delivery.ScriptModules.push_back(std::filesystem::path("Project") / "Scripts" / stagedScriptModule.filename());
|
if (!stagedScriptModule.empty()) delivery.ScriptModules.push_back(std::filesystem::path("Project") / "Scripts" / stagedScriptModule.filename());
|
||||||
if (!MetaCoreWriteRuntimeDeliveryDocument(staging / "Project" / "MetaCore.runtimeproject", delivery))
|
if (!MetaCoreWriteRuntimeDeliveryDocument(staging / "Project" / "MetaCore.runtimeproject", delivery))
|
||||||
return fail(MetaCoreBuildErrorCode::StageFailed, "Failed to write runtime project document", staging);
|
return fail(MetaCoreBuildErrorCode::StageFailed, "Failed to write runtime project document", staging);
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "MetaCoreFoundation/MetaCoreAssetGuid.h"
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
@ -62,6 +64,13 @@ struct MetaCoreRuntimeDeliveryDocument {
|
|||||||
std::filesystem::path UiManifest = "Content/Runtime/Ui.mcruntime";
|
std::filesystem::path UiManifest = "Content/Runtime/Ui.mcruntime";
|
||||||
std::filesystem::path ReleaseManifest = "Manifest/MetaCore.release.json";
|
std::filesystem::path ReleaseManifest = "Manifest/MetaCore.release.json";
|
||||||
std::vector<std::filesystem::path> ScriptModules{};
|
std::vector<std::filesystem::path> ScriptModules{};
|
||||||
|
struct SceneEntry {
|
||||||
|
MetaCoreAssetGuid SceneGuid{};
|
||||||
|
std::filesystem::path PackagePath{};
|
||||||
|
std::uint64_t SourceHash = 0;
|
||||||
|
std::vector<MetaCoreAssetGuid> Dependencies{};
|
||||||
|
};
|
||||||
|
std::vector<SceneEntry> ScenePackages{};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MetaCoreReleaseFileEntry {
|
struct MetaCoreReleaseFileEntry {
|
||||||
|
|||||||
@ -26,6 +26,8 @@
|
|||||||
#include "MetaCoreFoundation/MetaCoreRuntimeAssetRegistry.h"
|
#include "MetaCoreFoundation/MetaCoreRuntimeAssetRegistry.h"
|
||||||
#include "MetaCoreScene/MetaCoreScenePackage.h"
|
#include "MetaCoreScene/MetaCoreScenePackage.h"
|
||||||
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
|
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
|
||||||
|
#include "MetaCoreScene/MetaCoreSceneWorld.h"
|
||||||
|
#include "MetaCoreScene/MetaCorePrefab.h"
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
@ -9869,6 +9871,7 @@ public:
|
|||||||
CurrentSceneGuid_ = MetaCoreAssetGuid{};
|
CurrentSceneGuid_ = MetaCoreAssetGuid{};
|
||||||
CurrentScenePath_.clear();
|
CurrentScenePath_.clear();
|
||||||
CurrentSceneName_.clear();
|
CurrentSceneName_.clear();
|
||||||
|
ScenePaths_.clear();
|
||||||
SavedSnapshotBytes_.clear();
|
SavedSnapshotBytes_.clear();
|
||||||
Dirty_ = false;
|
Dirty_ = false;
|
||||||
}
|
}
|
||||||
@ -9893,6 +9896,18 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool LoadScene(MetaCoreEditorContext& editorContext, const std::filesystem::path& relativeScenePath) override {
|
bool LoadScene(MetaCoreEditorContext& editorContext, const std::filesystem::path& relativeScenePath) override {
|
||||||
|
return LoadSceneWithMode(editorContext, relativeScenePath, MetaCoreSceneLoadMode::Replace);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LoadSceneAdditive(MetaCoreEditorContext& editorContext, const std::filesystem::path& relativeScenePath) override {
|
||||||
|
return LoadSceneWithMode(editorContext, relativeScenePath, MetaCoreSceneLoadMode::Additive);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LoadSceneWithMode(
|
||||||
|
MetaCoreEditorContext& editorContext,
|
||||||
|
const std::filesystem::path& relativeScenePath,
|
||||||
|
MetaCoreSceneLoadMode loadMode
|
||||||
|
) {
|
||||||
if (AssetDatabaseService_ == nullptr ||
|
if (AssetDatabaseService_ == nullptr ||
|
||||||
ReflectionRegistry_ == nullptr ||
|
ReflectionRegistry_ == nullptr ||
|
||||||
!AssetDatabaseService_->HasProject() ||
|
!AssetDatabaseService_->HasProject() ||
|
||||||
@ -9944,15 +9959,12 @@ public:
|
|||||||
if(!migrationFailed)for(const auto&change:pending){auto&animator=*sceneDocument->GameObjects[change.ObjectIndex].Animator;animator.ControllerAssetGuid=change.Controller;animator.AvatarAssetGuid=change.Avatar;migratedLegacyAnimators=true;}
|
if(!migrationFailed)for(const auto&change:pending){auto&animator=*sceneDocument->GameObjects[change.ObjectIndex].Animator;animator.ControllerAssetGuid=change.Controller;animator.AvatarAssetGuid=change.Avatar;migratedLegacyAnimators=true;}
|
||||||
}
|
}
|
||||||
|
|
||||||
MetaCoreEditorStateSnapshot editorStateSnapshot;
|
|
||||||
editorStateSnapshot.SceneSnapshot.Environment = sceneDocument->Environment;
|
|
||||||
editorStateSnapshot.SceneSnapshot.GameObjects = sceneDocument->GameObjects;
|
|
||||||
editorStateSnapshot.SelectionSnapshot = sceneDocument->Selection;
|
|
||||||
editorContext.RestoreStateSnapshot(editorStateSnapshot);
|
|
||||||
editorContext.GetCommandService().Clear();
|
|
||||||
|
|
||||||
// 从 .meta 提取或生成 Guid
|
// 从 .meta 提取或生成 Guid
|
||||||
MetaCoreAssetGuid sceneGuid{};
|
MetaCoreAssetGuid sceneGuid = sceneDocument->SceneGuid;
|
||||||
|
if (const auto record = AssetDatabaseService_->FindAssetByRelativePath(normalizedScenePath);
|
||||||
|
record.has_value() && record->Guid.IsValid()) {
|
||||||
|
sceneGuid = record->Guid;
|
||||||
|
}
|
||||||
const std::filesystem::path metaPath = absoluteScenePath.string() + ".meta";
|
const std::filesystem::path metaPath = absoluteScenePath.string() + ".meta";
|
||||||
if (std::filesystem::exists(metaPath)) {
|
if (std::filesystem::exists(metaPath)) {
|
||||||
sceneGuid = MetaCoreAssetRegistry::Get().ResolvePathToGuid(normalizedScenePath);
|
sceneGuid = MetaCoreAssetRegistry::Get().ResolvePathToGuid(normalizedScenePath);
|
||||||
@ -9960,12 +9972,42 @@ public:
|
|||||||
if (!sceneGuid.IsValid()) {
|
if (!sceneGuid.IsValid()) {
|
||||||
sceneGuid = MetaCoreAssetGuid::Generate();
|
sceneGuid = MetaCoreAssetGuid::Generate();
|
||||||
}
|
}
|
||||||
|
sceneDocument->SceneGuid = sceneGuid;
|
||||||
|
for (auto& object : sceneDocument->GameObjects) object.SceneGuid = sceneGuid;
|
||||||
|
|
||||||
|
if (MetaCoreSceneWorld* world = editorContext.GetSceneWorld()) {
|
||||||
|
world->SetSceneLoader([this](const MetaCoreAssetGuid& guid) -> std::optional<MetaCoreSceneDocument> {
|
||||||
|
if (AssetDatabaseService_ == nullptr || ReflectionRegistry_ == nullptr || !AssetDatabaseService_->HasProject())
|
||||||
|
return std::nullopt;
|
||||||
|
const auto record = AssetDatabaseService_->FindAssetByGuid(guid);
|
||||||
|
if (!record) return std::nullopt;
|
||||||
|
const auto relative = !record->PackagePath.empty() ? record->PackagePath : record->RelativePath;
|
||||||
|
const auto absolute = AssetDatabaseService_->GetProjectDescriptor().RootPath / relative;
|
||||||
|
if (relative.extension() == ".mcscene") {
|
||||||
|
if (auto package = MetaCoreReadScenePackage(absolute)) return package;
|
||||||
|
}
|
||||||
|
return MetaCoreSceneSerializer::LoadSceneFromJson(absolute, ReflectionRegistry_->GetTypeRegistry());
|
||||||
|
});
|
||||||
|
std::string loadError;
|
||||||
|
if (!world->LoadDocumentNow(*sceneDocument, loadMode, &loadError)) {
|
||||||
|
editorContext.AddConsoleMessage(MetaCoreLogLevel::Error, "Scene", "场景组合加载失败: " + loadError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MetaCoreEditorStateSnapshot editorStateSnapshot;
|
||||||
|
editorStateSnapshot.SceneSnapshot.Environment = sceneDocument->Environment;
|
||||||
|
editorStateSnapshot.SceneSnapshot.GameObjects = sceneDocument->GameObjects;
|
||||||
|
editorStateSnapshot.SelectionSnapshot = sceneDocument->Selection;
|
||||||
|
editorContext.RestoreStateSnapshot(editorStateSnapshot);
|
||||||
|
}
|
||||||
|
editorContext.GetCommandService().Clear();
|
||||||
|
|
||||||
CurrentSceneGuid_ = sceneGuid;
|
CurrentSceneGuid_ = sceneGuid;
|
||||||
CurrentScenePath_ = normalizedScenePath;
|
CurrentScenePath_ = normalizedScenePath;
|
||||||
CurrentSceneName_ = sceneDocument->Name.empty()
|
CurrentSceneName_ = sceneDocument->Name.empty()
|
||||||
? MetaCoreSceneDisplayNameFromPath(normalizedScenePath)
|
? MetaCoreSceneDisplayNameFromPath(normalizedScenePath)
|
||||||
: sceneDocument->Name;
|
: sceneDocument->Name;
|
||||||
|
ScenePaths_[sceneGuid] = normalizedScenePath;
|
||||||
SavedSnapshotBytes_ = MetaCoreSerializeSceneSnapshotBytes(
|
SavedSnapshotBytes_ = MetaCoreSerializeSceneSnapshotBytes(
|
||||||
ReflectionRegistry_->GetTypeRegistry(),
|
ReflectionRegistry_->GetTypeRegistry(),
|
||||||
editorContext.GetScene().CaptureSnapshot()
|
editorContext.GetScene().CaptureSnapshot()
|
||||||
@ -9986,6 +10028,30 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool UnloadScene(MetaCoreEditorContext& editorContext, const MetaCoreAssetGuid& sceneGuid, bool force) override {
|
||||||
|
MetaCoreSceneWorld* world = editorContext.GetSceneWorld();
|
||||||
|
if (world == nullptr || !world->UnloadScene(sceneGuid, force)) return false;
|
||||||
|
ScenePaths_.erase(sceneGuid);
|
||||||
|
if (CurrentSceneGuid_ == sceneGuid) {
|
||||||
|
CurrentSceneGuid_ = world->GetActiveSceneGuid();
|
||||||
|
const auto path = ScenePaths_.find(CurrentSceneGuid_);
|
||||||
|
CurrentScenePath_ = path == ScenePaths_.end() ? std::filesystem::path{} : path->second;
|
||||||
|
CurrentSceneName_ = CurrentScenePath_.stem().string();
|
||||||
|
}
|
||||||
|
editorContext.ClearSelection();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SetActiveScene(MetaCoreEditorContext& editorContext, const MetaCoreAssetGuid& sceneGuid) override {
|
||||||
|
MetaCoreSceneWorld* world = editorContext.GetSceneWorld();
|
||||||
|
if (world == nullptr || !world->SetActiveScene(sceneGuid)) return false;
|
||||||
|
CurrentSceneGuid_ = sceneGuid;
|
||||||
|
const auto path = ScenePaths_.find(sceneGuid);
|
||||||
|
CurrentScenePath_ = path == ScenePaths_.end() ? std::filesystem::path{} : path->second;
|
||||||
|
CurrentSceneName_ = CurrentScenePath_.stem().string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool SaveCurrentScene(MetaCoreEditorContext& editorContext) override {
|
bool SaveCurrentScene(MetaCoreEditorContext& editorContext) override {
|
||||||
return CurrentScenePath_.empty()
|
return CurrentScenePath_.empty()
|
||||||
? SaveSceneAs(editorContext, std::filesystem::path("Scenes") / "Untitled.mcscene.json")
|
? SaveSceneAs(editorContext, std::filesystem::path("Scenes") / "Untitled.mcscene.json")
|
||||||
@ -10019,9 +10085,17 @@ public:
|
|||||||
|
|
||||||
MetaCoreSceneDocument sceneDocument;
|
MetaCoreSceneDocument sceneDocument;
|
||||||
sceneDocument.Name = CurrentSceneName_.empty() ? MetaCoreSceneDisplayNameFromPath(normalizedScenePath) : CurrentSceneName_;
|
sceneDocument.Name = CurrentSceneName_.empty() ? MetaCoreSceneDisplayNameFromPath(normalizedScenePath) : CurrentSceneName_;
|
||||||
const MetaCoreSceneSnapshot sceneSnapshot = editorContext.GetScene().CaptureSnapshot();
|
if (MetaCoreSceneWorld* world = editorContext.GetSceneWorld(); world != nullptr && CurrentSceneGuid_.IsValid()) {
|
||||||
sceneDocument.Environment = sceneSnapshot.Environment;
|
const auto captured = world->CaptureSceneDocument(CurrentSceneGuid_);
|
||||||
sceneDocument.GameObjects = sceneSnapshot.GameObjects;
|
if (!captured) return false;
|
||||||
|
sceneDocument = *captured;
|
||||||
|
sceneDocument.Name = CurrentSceneName_.empty() ? MetaCoreSceneDisplayNameFromPath(normalizedScenePath) : CurrentSceneName_;
|
||||||
|
} else {
|
||||||
|
const MetaCoreSceneSnapshot sceneSnapshot = editorContext.GetScene().CaptureSnapshot();
|
||||||
|
sceneDocument.Environment = sceneSnapshot.Environment;
|
||||||
|
sceneDocument.GameObjects = sceneSnapshot.GameObjects;
|
||||||
|
}
|
||||||
|
sceneDocument.SceneGuid = sceneGuid;
|
||||||
sceneDocument.Selection.SelectedObjectIds = editorContext.GetSelectedObjectIds();
|
sceneDocument.Selection.SelectedObjectIds = editorContext.GetSelectedObjectIds();
|
||||||
sceneDocument.Selection.ActiveObjectId = editorContext.GetActiveObjectId();
|
sceneDocument.Selection.ActiveObjectId = editorContext.GetActiveObjectId();
|
||||||
sceneDocument.Selection.SelectionAnchorId = editorContext.GetSelectionAnchorId();
|
sceneDocument.Selection.SelectionAnchorId = editorContext.GetSelectionAnchorId();
|
||||||
@ -10047,6 +10121,7 @@ public:
|
|||||||
CurrentSceneGuid_ = sceneGuid;
|
CurrentSceneGuid_ = sceneGuid;
|
||||||
CurrentScenePath_ = normalizedScenePath;
|
CurrentScenePath_ = normalizedScenePath;
|
||||||
CurrentSceneName_ = sceneDocument.Name;
|
CurrentSceneName_ = sceneDocument.Name;
|
||||||
|
ScenePaths_[sceneGuid] = normalizedScenePath;
|
||||||
SavedSnapshotBytes_ = MetaCoreSerializeSceneSnapshotBytes(
|
SavedSnapshotBytes_ = MetaCoreSerializeSceneSnapshotBytes(
|
||||||
ReflectionRegistry_->GetTypeRegistry(),
|
ReflectionRegistry_->GetTypeRegistry(),
|
||||||
editorContext.GetScene().CaptureSnapshot()
|
editorContext.GetScene().CaptureSnapshot()
|
||||||
@ -10072,6 +10147,24 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SaveAllScenes(MetaCoreEditorContext& editorContext) override {
|
||||||
|
MetaCoreSceneWorld* world = editorContext.GetSceneWorld();
|
||||||
|
if (world == nullptr) return SaveCurrentScene(editorContext);
|
||||||
|
const MetaCoreAssetGuid previous = world->GetActiveSceneGuid();
|
||||||
|
bool success = true;
|
||||||
|
const auto paths = ScenePaths_;
|
||||||
|
for (const auto& [guid, path] : paths) {
|
||||||
|
if (!SetActiveScene(editorContext, guid) || path.empty() || !SaveSceneAs(editorContext, path)) success = false;
|
||||||
|
}
|
||||||
|
if (previous.IsValid()) (void)SetActiveScene(editorContext, previous);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<MetaCoreLoadedSceneInfo> GetOpenScenes(const MetaCoreEditorContext& editorContext) const override {
|
||||||
|
const MetaCoreSceneWorld* world = editorContext.GetSceneWorld();
|
||||||
|
return world == nullptr ? std::vector<MetaCoreLoadedSceneInfo>{} : world->GetLoadedScenes();
|
||||||
|
}
|
||||||
|
|
||||||
void UpdateDirtyState(const MetaCoreSceneSnapshot& snapshot) override {
|
void UpdateDirtyState(const MetaCoreSceneSnapshot& snapshot) override {
|
||||||
if (ReflectionRegistry_ == nullptr) {
|
if (ReflectionRegistry_ == nullptr) {
|
||||||
SetDirtyState(true);
|
SetDirtyState(true);
|
||||||
@ -10111,6 +10204,7 @@ private:
|
|||||||
std::string CurrentSceneName_{};
|
std::string CurrentSceneName_{};
|
||||||
std::vector<std::byte> SavedSnapshotBytes_{};
|
std::vector<std::byte> SavedSnapshotBytes_{};
|
||||||
bool Dirty_ = false;
|
bool Dirty_ = false;
|
||||||
|
std::unordered_map<MetaCoreAssetGuid, std::filesystem::path, MetaCoreAssetGuidHasher> ScenePaths_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class MetaCoreBuiltinSelectionService final : public MetaCoreISelectionService {
|
class MetaCoreBuiltinSelectionService final : public MetaCoreISelectionService {
|
||||||
@ -11420,6 +11514,7 @@ public:
|
|||||||
State_ = MetaCorePlayModeState::Edit;
|
State_ = MetaCorePlayModeState::Edit;
|
||||||
HasPrePlaySnapshot_ = false;
|
HasPrePlaySnapshot_ = false;
|
||||||
HasPendingStopRestoreSnapshot_ = false;
|
HasPendingStopRestoreSnapshot_ = false;
|
||||||
|
HasPrePlayWorldSnapshot_ = false;
|
||||||
CommandRecordingSuppressed_ = false;
|
CommandRecordingSuppressed_ = false;
|
||||||
ElapsedTimeSeconds_ = 0.0;
|
ElapsedTimeSeconds_ = 0.0;
|
||||||
FixedTimeAccumulatorSeconds_ = 0.0;
|
FixedTimeAccumulatorSeconds_ = 0.0;
|
||||||
@ -11456,6 +11551,12 @@ public:
|
|||||||
}
|
}
|
||||||
PrePlaySnapshot_ = editorContext.CaptureStateSnapshot();
|
PrePlaySnapshot_ = editorContext.CaptureStateSnapshot();
|
||||||
HasPrePlaySnapshot_ = true;
|
HasPrePlaySnapshot_ = true;
|
||||||
|
if (const MetaCoreSceneWorld* world = editorContext.GetSceneWorld()) {
|
||||||
|
PrePlayWorldSnapshot_ = world->CaptureWorldSnapshot();
|
||||||
|
HasPrePlayWorldSnapshot_ = true;
|
||||||
|
} else {
|
||||||
|
HasPrePlayWorldSnapshot_ = false;
|
||||||
|
}
|
||||||
MetaCoreDebugLog(
|
MetaCoreDebugLog(
|
||||||
"PlayMode",
|
"PlayMode",
|
||||||
"Enter captured prePlaySnapshot objects=" +
|
"Enter captured prePlaySnapshot objects=" +
|
||||||
@ -11709,6 +11810,12 @@ private:
|
|||||||
" " + MetaCoreDebugSceneSummary(editorContext)
|
" " + MetaCoreDebugSceneSummary(editorContext)
|
||||||
);
|
);
|
||||||
editorContext.RestoreStateSnapshot(PendingStopRestoreSnapshot_);
|
editorContext.RestoreStateSnapshot(PendingStopRestoreSnapshot_);
|
||||||
|
if (HasPrePlayWorldSnapshot_) {
|
||||||
|
if (MetaCoreSceneWorld* world = editorContext.GetSceneWorld())
|
||||||
|
world->RestoreWorldSnapshot(PrePlayWorldSnapshot_);
|
||||||
|
HasPrePlayWorldSnapshot_ = false;
|
||||||
|
PrePlayWorldSnapshot_ = {};
|
||||||
|
}
|
||||||
HasPendingStopRestoreSnapshot_ = false;
|
HasPendingStopRestoreSnapshot_ = false;
|
||||||
PendingStopRestoreSnapshot_ = {};
|
PendingStopRestoreSnapshot_ = {};
|
||||||
MetaCoreDebugLog("PlayMode", "Apply pending stop restore end " + MetaCoreDebugSceneSummary(editorContext));
|
MetaCoreDebugLog("PlayMode", "Apply pending stop restore end " + MetaCoreDebugSceneSummary(editorContext));
|
||||||
@ -11718,8 +11825,10 @@ private:
|
|||||||
MetaCorePlayModeState State_ = MetaCorePlayModeState::Edit;
|
MetaCorePlayModeState State_ = MetaCorePlayModeState::Edit;
|
||||||
MetaCoreEditorStateSnapshot PrePlaySnapshot_{};
|
MetaCoreEditorStateSnapshot PrePlaySnapshot_{};
|
||||||
MetaCoreEditorStateSnapshot PendingStopRestoreSnapshot_{};
|
MetaCoreEditorStateSnapshot PendingStopRestoreSnapshot_{};
|
||||||
|
MetaCoreSceneWorldSnapshot PrePlayWorldSnapshot_{};
|
||||||
bool HasPrePlaySnapshot_ = false;
|
bool HasPrePlaySnapshot_ = false;
|
||||||
bool HasPendingStopRestoreSnapshot_ = false;
|
bool HasPendingStopRestoreSnapshot_ = false;
|
||||||
|
bool HasPrePlayWorldSnapshot_ = false;
|
||||||
bool CommandRecordingSuppressed_ = false;
|
bool CommandRecordingSuppressed_ = false;
|
||||||
double ElapsedTimeSeconds_ = 0.0;
|
double ElapsedTimeSeconds_ = 0.0;
|
||||||
double FixedTimeStepSeconds_ = 1.0 / 60.0;
|
double FixedTimeStepSeconds_ = 1.0 / 60.0;
|
||||||
@ -11837,6 +11946,7 @@ std::optional<std::filesystem::path> MetaCoreBuiltinPrefabService::CreatePrefabF
|
|||||||
}
|
}
|
||||||
|
|
||||||
MetaCorePrefabDocument prefabDocument;
|
MetaCorePrefabDocument prefabDocument;
|
||||||
|
prefabDocument.PrefabGuid = prefabGuid;
|
||||||
prefabDocument.Name = rootObject ? rootObject.GetName() : normalizedPrefabPath.stem().string();
|
prefabDocument.Name = rootObject ? rootObject.GetName() : normalizedPrefabPath.stem().string();
|
||||||
|
|
||||||
std::vector<MetaCoreGameObjectData> prefabDataObjects;
|
std::vector<MetaCoreGameObjectData> prefabDataObjects;
|
||||||
@ -11844,6 +11954,7 @@ std::optional<std::filesystem::path> MetaCoreBuiltinPrefabService::CreatePrefabF
|
|||||||
for (const MetaCoreGameObject& gameObject : prefabObjects) {
|
for (const MetaCoreGameObject& gameObject : prefabObjects) {
|
||||||
MetaCoreGameObjectData data;
|
MetaCoreGameObjectData data;
|
||||||
data.Id = gameObject.GetId();
|
data.Id = gameObject.GetId();
|
||||||
|
data.StableObjectGuid = MetaCoreBuildDeterministicGuid(prefabGuid, data.Id);
|
||||||
data.Name = gameObject.GetName();
|
data.Name = gameObject.GetName();
|
||||||
data.ParentId = gameObject.GetParentId();
|
data.ParentId = gameObject.GetParentId();
|
||||||
data.Transform = gameObject.GetComponent<MetaCoreTransformComponent>();
|
data.Transform = gameObject.GetComponent<MetaCoreTransformComponent>();
|
||||||
@ -11902,6 +12013,10 @@ std::optional<MetaCoreId> MetaCoreBuiltinPrefabService::InstantiatePrefabDocumen
|
|||||||
}
|
}
|
||||||
|
|
||||||
MetaCoreId instantiatedRootId = 0;
|
MetaCoreId instantiatedRootId = 0;
|
||||||
|
const MetaCoreAssetGuid instanceGuid = MetaCoreAssetGuid::Generate();
|
||||||
|
const MetaCoreAssetGuid targetSceneGuid = editorContext.GetSceneWorld() != nullptr
|
||||||
|
? editorContext.GetSceneWorld()->GetActiveSceneGuid()
|
||||||
|
: MetaCoreAssetGuid{};
|
||||||
const bool created = editorContext.ExecuteSnapshotCommand("实例化 Prefab", [&]() {
|
const bool created = editorContext.ExecuteSnapshotCommand("实例化 Prefab", [&]() {
|
||||||
std::unordered_map<MetaCoreId, MetaCoreId> idMap;
|
std::unordered_map<MetaCoreId, MetaCoreId> idMap;
|
||||||
std::vector<MetaCoreGameObject> clonedObjects;
|
std::vector<MetaCoreGameObject> clonedObjects;
|
||||||
@ -11925,6 +12040,17 @@ std::optional<MetaCoreId> MetaCoreBuiltinPrefabService::InstantiatePrefabDocumen
|
|||||||
}
|
}
|
||||||
|
|
||||||
clonedObject.GetComponent<MetaCoreTransformComponent>() = sourceObject.Transform;
|
clonedObject.GetComponent<MetaCoreTransformComponent>() = sourceObject.Transform;
|
||||||
|
if (targetSceneGuid.IsValid()) {
|
||||||
|
const MetaCoreAssetGuid nodeGuid = sourceObject.StableObjectGuid.IsValid()
|
||||||
|
? sourceObject.StableObjectGuid
|
||||||
|
: MetaCoreBuildDeterministicGuid(prefabAssetGuid, sourceObject.Id);
|
||||||
|
clonedObject.AddComponent<MetaCoreSceneObjectIdentityComponent>(
|
||||||
|
MetaCoreSceneObjectIdentityComponent{
|
||||||
|
targetSceneGuid,
|
||||||
|
MetaCoreBuildDeterministicGuid(instanceGuid, nodeGuid)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
if (sourceObject.Camera.has_value()) clonedObject.AddComponent<MetaCoreCameraComponent>(*sourceObject.Camera);
|
if (sourceObject.Camera.has_value()) clonedObject.AddComponent<MetaCoreCameraComponent>(*sourceObject.Camera);
|
||||||
if (sourceObject.Light.has_value()) clonedObject.AddComponent<MetaCoreLightComponent>(*sourceObject.Light);
|
if (sourceObject.Light.has_value()) clonedObject.AddComponent<MetaCoreLightComponent>(*sourceObject.Light);
|
||||||
if (sourceObject.Rotator.has_value()) clonedObject.AddComponent<MetaCoreRotatorComponent>(*sourceObject.Rotator);
|
if (sourceObject.Rotator.has_value()) clonedObject.AddComponent<MetaCoreRotatorComponent>(*sourceObject.Rotator);
|
||||||
@ -11942,11 +12068,14 @@ std::optional<MetaCoreId> MetaCoreBuiltinPrefabService::InstantiatePrefabDocumen
|
|||||||
if (sourceObject.DecalProjector.has_value()) clonedObject.AddComponent<MetaCoreDecalProjectorComponent>(*sourceObject.DecalProjector);
|
if (sourceObject.DecalProjector.has_value()) clonedObject.AddComponent<MetaCoreDecalProjectorComponent>(*sourceObject.DecalProjector);
|
||||||
if (sourceObject.Terrain.has_value()) clonedObject.AddComponent<MetaCoreTerrainComponent>(*sourceObject.Terrain);
|
if (sourceObject.Terrain.has_value()) clonedObject.AddComponent<MetaCoreTerrainComponent>(*sourceObject.Terrain);
|
||||||
|
|
||||||
clonedObject.AddComponent<MetaCorePrefabInstanceMetadata>(MetaCorePrefabInstanceMetadata{
|
MetaCorePrefabInstanceMetadata metadata;
|
||||||
prefabAssetGuid,
|
metadata.PrefabAssetGuid = prefabAssetGuid;
|
||||||
sourceObject.Id,
|
metadata.PrefabObjectId = sourceObject.Id;
|
||||||
0
|
metadata.InstanceGuid = instanceGuid;
|
||||||
});
|
metadata.PrefabNodeGuid = sourceObject.StableObjectGuid.IsValid()
|
||||||
|
? sourceObject.StableObjectGuid
|
||||||
|
: MetaCoreBuildDeterministicGuid(prefabAssetGuid, sourceObject.Id);
|
||||||
|
clonedObject.AddComponent<MetaCorePrefabInstanceMetadata>(metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (MetaCoreGameObject& clonedObject : clonedObjects) {
|
for (MetaCoreGameObject& clonedObject : clonedObjects) {
|
||||||
@ -11981,7 +12110,23 @@ std::optional<MetaCoreId> MetaCoreBuiltinPrefabService::InstantiatePrefab(
|
|||||||
if (!prefabDocument.has_value()) {
|
if (!prefabDocument.has_value()) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
return InstantiatePrefabDocument(editorContext, prefabAssetGuid, *prefabDocument, forcedParentId);
|
MetaCorePrefabResolver resolver([this](const MetaCoreAssetGuid& guid) {
|
||||||
|
return LoadPrefabDocument(guid);
|
||||||
|
});
|
||||||
|
std::string resolveError;
|
||||||
|
const auto resolved = resolver.Resolve(prefabAssetGuid, &resolveError);
|
||||||
|
if (!resolved) {
|
||||||
|
editorContext.AddConsoleMessage(MetaCoreLogLevel::Error, "Prefab", "Prefab 依赖解析失败: " + resolveError);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
if (!resolved->OrphanOverrides.empty()) {
|
||||||
|
editorContext.AddConsoleMessage(
|
||||||
|
MetaCoreLogLevel::Warning,
|
||||||
|
"Prefab",
|
||||||
|
"Prefab 包含 " + std::to_string(resolved->OrphanOverrides.size()) + " 个孤立 Override"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return InstantiatePrefabDocument(editorContext, prefabAssetGuid, resolved->Document, forcedParentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MetaCoreBuiltinPrefabService::ApplySelectedPrefabInstance(MetaCoreEditorContext& editorContext) {
|
bool MetaCoreBuiltinPrefabService::ApplySelectedPrefabInstance(MetaCoreEditorContext& editorContext) {
|
||||||
@ -12198,6 +12343,11 @@ std::optional<MetaCoreId> MetaCoreBuiltinSceneEditingService::CreateGameObject(
|
|||||||
}
|
}
|
||||||
|
|
||||||
MetaCoreGameObject object = editorContext.GetScene().CreateGameObject(objectName, parentId);
|
MetaCoreGameObject object = editorContext.GetScene().CreateGameObject(objectName, parentId);
|
||||||
|
if (MetaCoreSceneWorld* world = editorContext.GetSceneWorld(); world != nullptr && world->GetActiveSceneGuid().IsValid()) {
|
||||||
|
object.AddComponent<MetaCoreSceneObjectIdentityComponent>(MetaCoreSceneObjectIdentityComponent{
|
||||||
|
world->GetActiveSceneGuid(), MetaCoreAssetGuid::Generate()
|
||||||
|
});
|
||||||
|
}
|
||||||
if (withMeshRenderer) {
|
if (withMeshRenderer) {
|
||||||
auto& meshRenderer = object.AddComponent<MetaCoreMeshRendererComponent>(MetaCoreMeshRendererComponent{});
|
auto& meshRenderer = object.AddComponent<MetaCoreMeshRendererComponent>(MetaCoreMeshRendererComponent{});
|
||||||
if (objectName == "Plane") {
|
if (objectName == "Plane") {
|
||||||
@ -12251,6 +12401,11 @@ std::optional<MetaCoreId> MetaCoreBuiltinSceneEditingService::CreateCamera(
|
|||||||
}
|
}
|
||||||
|
|
||||||
MetaCoreGameObject cameraObject = editorContext.GetScene().CreateGameObject("Camera", parentId);
|
MetaCoreGameObject cameraObject = editorContext.GetScene().CreateGameObject("Camera", parentId);
|
||||||
|
if (MetaCoreSceneWorld* world = editorContext.GetSceneWorld(); world != nullptr && world->GetActiveSceneGuid().IsValid()) {
|
||||||
|
cameraObject.AddComponent<MetaCoreSceneObjectIdentityComponent>(MetaCoreSceneObjectIdentityComponent{
|
||||||
|
world->GetActiveSceneGuid(), MetaCoreAssetGuid::Generate()
|
||||||
|
});
|
||||||
|
}
|
||||||
MetaCoreCameraComponent& camera = cameraObject.AddComponent<MetaCoreCameraComponent>(MetaCoreCameraComponent{});
|
MetaCoreCameraComponent& camera = cameraObject.AddComponent<MetaCoreCameraComponent>(MetaCoreCameraComponent{});
|
||||||
camera.IsPrimary = !hasEnabledPrimaryCamera;
|
camera.IsPrimary = !hasEnabledPrimaryCamera;
|
||||||
|
|
||||||
@ -12388,6 +12543,11 @@ std::optional<MetaCoreId> MetaCoreBuiltinSceneEditingService::InstantiateModelAs
|
|||||||
? resolvedModelPath.filename().generic_string()
|
? resolvedModelPath.filename().generic_string()
|
||||||
: assetRecord->RelativePath.filename().generic_string());
|
: assetRecord->RelativePath.filename().generic_string());
|
||||||
MetaCoreGameObject modelRootObject = editorContext.GetScene().CreateGameObject(modelRootName, externalParentId);
|
MetaCoreGameObject modelRootObject = editorContext.GetScene().CreateGameObject(modelRootName, externalParentId);
|
||||||
|
if (MetaCoreSceneWorld* world = editorContext.GetSceneWorld(); world != nullptr && world->GetActiveSceneGuid().IsValid()) {
|
||||||
|
modelRootObject.AddComponent<MetaCoreSceneObjectIdentityComponent>(MetaCoreSceneObjectIdentityComponent{
|
||||||
|
world->GetActiveSceneGuid(), MetaCoreAssetGuid::Generate()
|
||||||
|
});
|
||||||
|
}
|
||||||
instantiatedRootId = modelRootObject.GetId();
|
instantiatedRootId = modelRootObject.GetId();
|
||||||
|
|
||||||
std::vector<MetaCoreId> nodeToIdMap(importedDocument->Nodes.size(), 0);
|
std::vector<MetaCoreId> nodeToIdMap(importedDocument->Nodes.size(), 0);
|
||||||
@ -12479,6 +12639,11 @@ std::optional<MetaCoreId> MetaCoreBuiltinSceneEditingService::InstantiateModelAs
|
|||||||
for (std::size_t i = 0; i < importedDocument->Nodes.size(); ++i) {
|
for (std::size_t i = 0; i < importedDocument->Nodes.size(); ++i) {
|
||||||
const auto& nodeMeta = importedDocument->Nodes[i];
|
const auto& nodeMeta = importedDocument->Nodes[i];
|
||||||
MetaCoreGameObject gameObject = editorContext.GetScene().CreateGameObject(nodeMeta.Name, 0);
|
MetaCoreGameObject gameObject = editorContext.GetScene().CreateGameObject(nodeMeta.Name, 0);
|
||||||
|
if (MetaCoreSceneWorld* world = editorContext.GetSceneWorld(); world != nullptr && world->GetActiveSceneGuid().IsValid()) {
|
||||||
|
gameObject.AddComponent<MetaCoreSceneObjectIdentityComponent>(MetaCoreSceneObjectIdentityComponent{
|
||||||
|
world->GetActiveSceneGuid(), MetaCoreAssetGuid::Generate()
|
||||||
|
});
|
||||||
|
}
|
||||||
nodeToIdMap[i] = gameObject.GetId();
|
nodeToIdMap[i] = gameObject.GetId();
|
||||||
|
|
||||||
// 立即同步变换组件
|
// 立即同步变换组件
|
||||||
@ -12590,6 +12755,29 @@ bool MetaCoreBuiltinSceneEditingService::ReparentSelection(MetaCoreEditorContext
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MetaCoreAssetGuid targetSceneGuid{};
|
||||||
|
if (newParentId != 0) {
|
||||||
|
const auto parent = editorContext.GetScene().FindGameObject(newParentId);
|
||||||
|
if (parent && parent.HasComponent<MetaCoreSceneObjectIdentityComponent>()) {
|
||||||
|
targetSceneGuid = parent.GetComponent<MetaCoreSceneObjectIdentityComponent>().SceneGuid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (MetaCoreId selectedId : selectedIds) {
|
||||||
|
const auto selected = editorContext.GetScene().FindGameObject(selectedId);
|
||||||
|
if (!selected || !selected.HasComponent<MetaCoreSceneObjectIdentityComponent>()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto selectedScene = selected.GetComponent<MetaCoreSceneObjectIdentityComponent>().SceneGuid;
|
||||||
|
if (targetSceneGuid.IsValid() && selectedScene.IsValid() && targetSceneGuid != selectedScene) {
|
||||||
|
editorContext.AddConsoleMessage(
|
||||||
|
MetaCoreLogLevel::Warning,
|
||||||
|
"Hierarchy",
|
||||||
|
"禁止跨场景父子关系;请使用 Move Subtree To Scene"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const bool keepWorldTransform = editorContext.GetReparentTransformRule() == MetaCoreReparentTransformRule::KeepWorld;
|
const bool keepWorldTransform = editorContext.GetReparentTransformRule() == MetaCoreReparentTransformRule::KeepWorld;
|
||||||
const bool reparented = editorContext.ExecuteSnapshotCommand("重挂接对象", [&]() {
|
const bool reparented = editorContext.ExecuteSnapshotCommand("重挂接对象", [&]() {
|
||||||
return editorContext.GetScene().ReparentGameObjects(selectedIds, newParentId, keepWorldTransform);
|
return editorContext.GetScene().ReparentGameObjects(selectedIds, newParentId, keepWorldTransform);
|
||||||
|
|||||||
@ -2829,23 +2829,48 @@ static std::array<char, 256> GProjectRenamePathBuffer{};
|
|||||||
BeginRename(editorContext, requestedRenameId);
|
BeginRename(editorContext, requestedRenameId);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 8));
|
const auto openScenes = scenePersistenceService != nullptr
|
||||||
ImGui::TextColored(ImVec4(0.4F, 0.6F, 1.0F, 1.0F), " (S) ");
|
? scenePersistenceService->GetOpenScenes(editorContext)
|
||||||
ImGui::SameLine();
|
: std::vector<MetaCoreLoadedSceneInfo>{};
|
||||||
const std::string sceneLabel =
|
if (!openScenes.empty()) {
|
||||||
scenePersistenceService != nullptr && scenePersistenceService->HasOpenScene()
|
for (const auto& loadedScene : openScenes) {
|
||||||
? scenePersistenceService->GetCurrentSceneDisplayName()
|
ImGuiTreeNodeFlags sceneFlags = ImGuiTreeNodeFlags_DefaultOpen |
|
||||||
: "Untitled";
|
ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_OpenOnArrow;
|
||||||
ImGui::TextUnformatted(sceneLabel.c_str());
|
if (loadedScene.Active) sceneFlags |= ImGuiTreeNodeFlags_Selected;
|
||||||
if (scenePersistenceService != nullptr && scenePersistenceService->IsSceneDirty()) {
|
const std::string label = std::string(loadedScene.Active ? "(A) " : "(S) ") +
|
||||||
ImGui::SameLine();
|
(loadedScene.Name.empty() ? loadedScene.SceneGuid.ToString() : loadedScene.Name) +
|
||||||
ImGui::TextColored(ImVec4(0.90F, 0.74F, 0.22F, 1.0F), "*");
|
(loadedScene.Dirty ? " *" : "");
|
||||||
}
|
const bool open = ImGui::TreeNodeEx(
|
||||||
ImGui::PopStyleVar();
|
reinterpret_cast<void*>(static_cast<intptr_t>(MetaCoreAssetGuidHasher{}(loadedScene.SceneGuid))),
|
||||||
ImGui::Separator();
|
sceneFlags,
|
||||||
|
"%s",
|
||||||
for (MetaCoreId rootId : editorContext.GetScene().GetRootObjectIds()) {
|
label.c_str()
|
||||||
DrawHierarchyNode(editorContext, rootId);
|
);
|
||||||
|
if (ImGui::IsItemClicked() && !loadedScene.Active)
|
||||||
|
(void)scenePersistenceService->SetActiveScene(editorContext, loadedScene.SceneGuid);
|
||||||
|
if (ImGui::BeginPopupContextItem()) {
|
||||||
|
if (ImGui::MenuItem("设为活动场景", nullptr, false, !loadedScene.Active))
|
||||||
|
(void)scenePersistenceService->SetActiveScene(editorContext, loadedScene.SceneGuid);
|
||||||
|
if (ImGui::MenuItem("保存全部场景"))
|
||||||
|
(void)scenePersistenceService->SaveAllScenes(editorContext);
|
||||||
|
if (ImGui::MenuItem("卸载场景", nullptr, false, openScenes.size() > 1))
|
||||||
|
(void)scenePersistenceService->UnloadScene(editorContext, loadedScene.SceneGuid, false);
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
if (open) {
|
||||||
|
for (MetaCoreId rootId : editorContext.GetScene().GetRootObjectIds()) {
|
||||||
|
const auto object = editorContext.GetScene().FindGameObject(rootId);
|
||||||
|
if (!object || !object.HasComponent<MetaCoreSceneObjectIdentityComponent>()) continue;
|
||||||
|
if (object.GetComponent<MetaCoreSceneObjectIdentityComponent>().SceneGuid == loadedScene.SceneGuid)
|
||||||
|
DrawHierarchyNode(editorContext, rootId);
|
||||||
|
}
|
||||||
|
ImGui::TreePop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImGui::TextColored(ImVec4(0.4F, 0.6F, 1.0F, 1.0F), "(S) Untitled");
|
||||||
|
ImGui::Separator();
|
||||||
|
for (MetaCoreId rootId : editorContext.GetScene().GetRootObjectIds()) DrawHierarchyNode(editorContext, rootId);
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawRootDropTarget(editorContext);
|
DrawRootDropTarget(editorContext);
|
||||||
@ -9320,7 +9345,10 @@ public:
|
|||||||
editorContext.ClearSelectedAsset();
|
editorContext.ClearSelectedAsset();
|
||||||
editorContext.ClearSelectedAssetSubId();
|
editorContext.ClearSelectedAssetSubId();
|
||||||
} else if (entry.AssetRecord.has_value() && entry.AssetRecord->Type == "scene" && scenePersistenceService) {
|
} else if (entry.AssetRecord.has_value() && entry.AssetRecord->Type == "scene" && scenePersistenceService) {
|
||||||
(void)scenePersistenceService->LoadScene(editorContext, entry.RelativePath);
|
if (ImGui::GetIO().KeyCtrl)
|
||||||
|
(void)scenePersistenceService->LoadSceneAdditive(editorContext, entry.RelativePath);
|
||||||
|
else
|
||||||
|
(void)scenePersistenceService->LoadScene(editorContext, entry.RelativePath);
|
||||||
} else if (entry.AssetRecord.has_value() && entry.AssetRecord->Type == "model") {
|
} else if (entry.AssetRecord.has_value() && entry.AssetRecord->Type == "model") {
|
||||||
(void)MetaCoreInstantiateModelAsset(editorContext, entry.AssetRecord->Guid, std::nullopt);
|
(void)MetaCoreInstantiateModelAsset(editorContext, entry.AssetRecord->Guid, std::nullopt);
|
||||||
} else if (entry.AssetRecord.has_value() && entry.AssetRecord->Type == "prefab") {
|
} else if (entry.AssetRecord.has_value() && entry.AssetRecord->Type == "prefab") {
|
||||||
@ -9381,6 +9409,13 @@ public:
|
|||||||
if (!entry.IsDirectory && ImGui::MenuItem("打开外部程序")) {
|
if (!entry.IsDirectory && ImGui::MenuItem("打开外部程序")) {
|
||||||
MetaCoreOpenPathExternally(project.RootPath / entry.RelativePath);
|
MetaCoreOpenPathExternally(project.RootPath / entry.RelativePath);
|
||||||
}
|
}
|
||||||
|
if (!entry.IsDirectory &&
|
||||||
|
entry.AssetRecord.has_value() &&
|
||||||
|
entry.AssetRecord->Type == "scene" &&
|
||||||
|
scenePersistenceService != nullptr &&
|
||||||
|
ImGui::MenuItem("Additive 打开场景")) {
|
||||||
|
(void)scenePersistenceService->LoadSceneAdditive(editorContext, entry.RelativePath);
|
||||||
|
}
|
||||||
if (!entry.IsDirectory &&
|
if (!entry.IsDirectory &&
|
||||||
entry.AssetRecord.has_value() &&
|
entry.AssetRecord.has_value() &&
|
||||||
entry.AssetRecord->Type == "ui_document" &&
|
entry.AssetRecord->Type == "ui_document" &&
|
||||||
|
|||||||
@ -2362,6 +2362,7 @@ bool MetaCoreEditorApp::Initialize(int argc, char* argv[]) {
|
|||||||
LogService_,
|
LogService_,
|
||||||
ModuleRegistry_
|
ModuleRegistry_
|
||||||
);
|
);
|
||||||
|
EditorContext_->SetSceneWorld(&SceneWorld_);
|
||||||
|
|
||||||
MetaCoreTraceStartup("metacore.app: load startup scene");
|
MetaCoreTraceStartup("metacore.app: load startup scene");
|
||||||
bool loadedStartupScene = false;
|
bool loadedStartupScene = false;
|
||||||
@ -2467,6 +2468,18 @@ int MetaCoreEditorApp::Run() {
|
|||||||
playModeService != nullptr && EditorContext_ != nullptr) {
|
playModeService != nullptr && EditorContext_ != nullptr) {
|
||||||
playModeService->Tick(*EditorContext_, playModeDeltaSeconds);
|
playModeService->Tick(*EditorContext_, playModeDeltaSeconds);
|
||||||
}
|
}
|
||||||
|
if (EditorContext_ != nullptr) {
|
||||||
|
std::vector<glm::vec3> streamingSources;
|
||||||
|
for (const auto object : Scene_.GetGameObjects()) {
|
||||||
|
if (object.HasComponent<MetaCoreCameraComponent>() &&
|
||||||
|
object.GetComponent<MetaCoreCameraComponent>().Enabled &&
|
||||||
|
object.GetComponent<MetaCoreCameraComponent>().IsPrimary) {
|
||||||
|
streamingSources.push_back(object.GetComponent<MetaCoreTransformComponent>().Position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SceneWorld_.UpdateStreaming(streamingSources);
|
||||||
|
SceneWorld_.Tick();
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
ImGui_ImplWin32_NewFrame();
|
ImGui_ImplWin32_NewFrame();
|
||||||
@ -2821,9 +2834,7 @@ void MetaCoreEditorApp::DrawEditorFrame() {
|
|||||||
viewportState.Focused &&
|
viewportState.Focused &&
|
||||||
!ImGui::GetIO().WantCaptureKeyboard &&
|
!ImGui::GetIO().WantCaptureKeyboard &&
|
||||||
EditorContext_->GetInput().WasKeyPressed(MetaCoreInputKey::Focus)) {
|
EditorContext_->GetInput().WasKeyPressed(MetaCoreInputKey::Focus)) {
|
||||||
if (MetaCoreGameObject selectedObject = EditorContext_->GetSelectedGameObject(); selectedObject) {
|
SceneInteractionService_.FocusSelectedObject(*EditorContext_);
|
||||||
EditorContext_->GetCameraController().FocusGameObject(selectedObject);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 同步尺寸给 Filament (SetViewportRect 内部会处理 Resize)
|
// 4. 同步尺寸给 Filament (SetViewportRect 内部会处理 Resize)
|
||||||
|
|||||||
@ -31,10 +31,10 @@ void MetaCoreEditorCameraController::Update(
|
|||||||
// Zoom: Alt + Right Drag or Mouse Wheel
|
// Zoom: Alt + Right Drag or Mouse Wheel
|
||||||
if (viewportState.Hovered && altDown && ImGui::IsMouseDragging(ImGuiMouseButton_Right)) {
|
if (viewportState.Hovered && altDown && ImGui::IsMouseDragging(ImGuiMouseButton_Right)) {
|
||||||
Distance_ *= (1.0F - io.MouseDelta.y * 0.01F);
|
Distance_ *= (1.0F - io.MouseDelta.y * 0.01F);
|
||||||
Distance_ = std::clamp(Distance_, 0.5F, 100.0F);
|
Distance_ = std::clamp(Distance_, 0.01F, 10000000.0F);
|
||||||
} else if (viewportState.Hovered && std::abs(io.MouseWheel) > 0.001F) {
|
} else if (viewportState.Hovered && std::abs(io.MouseWheel) > 0.001F) {
|
||||||
Distance_ *= (io.MouseWheel > 0.0F) ? 0.88F : 1.12F;
|
Distance_ *= (io.MouseWheel > 0.0F) ? 0.88F : 1.12F;
|
||||||
Distance_ = std::clamp(Distance_, 0.5F, 100.0F);
|
Distance_ = std::clamp(Distance_, 0.01F, 10000000.0F);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Orbit: Alt + Left Drag
|
// Orbit: Alt + Left Drag
|
||||||
@ -97,13 +97,16 @@ void MetaCoreEditorCameraController::Update(
|
|||||||
|
|
||||||
void MetaCoreEditorCameraController::FocusGameObject(const MetaCoreGameObject& gameObject) {
|
void MetaCoreEditorCameraController::FocusGameObject(const MetaCoreGameObject& gameObject) {
|
||||||
Target_ = gameObject.GetComponent<MetaCoreTransformComponent>().Position;
|
Target_ = gameObject.GetComponent<MetaCoreTransformComponent>().Position;
|
||||||
const float scaleRadius = std::max({gameObject.GetComponent<MetaCoreTransformComponent>().Scale.x, gameObject.GetComponent<MetaCoreTransformComponent>().Scale.y, gameObject.GetComponent<MetaCoreTransformComponent>().Scale.z});
|
const auto& scale = gameObject.GetComponent<MetaCoreTransformComponent>().Scale;
|
||||||
|
const float scaleRadius = std::max({std::abs(scale.x), std::abs(scale.y), std::abs(scale.z), 0.5F});
|
||||||
|
FramingRadius_ = scaleRadius;
|
||||||
Distance_ = std::max(2.0F, scaleRadius * 4.0F + 1.0F);
|
Distance_ = std::max(2.0F, scaleRadius * 4.0F + 1.0F);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MetaCoreEditorCameraController::FocusBounds(const glm::vec3& center, float radius) {
|
void MetaCoreEditorCameraController::FocusBounds(const glm::vec3& center, float radius) {
|
||||||
Target_ = center;
|
Target_ = center;
|
||||||
Distance_ = std::max(2.0F, radius * 3.5F + 1.0F);
|
FramingRadius_ = std::max(0.01F, std::isfinite(radius) ? radius : 1.0F);
|
||||||
|
Distance_ = std::max(2.0F, FramingRadius_ * 3.5F + 1.0F);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MetaCoreEditorCameraController::ApplySceneView(const MetaCoreSceneView& sceneView) {
|
void MetaCoreEditorCameraController::ApplySceneView(const MetaCoreSceneView& sceneView) {
|
||||||
@ -111,6 +114,7 @@ void MetaCoreEditorCameraController::ApplySceneView(const MetaCoreSceneView& sce
|
|||||||
FieldOfViewDegrees_ = sceneView.VerticalFieldOfViewDegrees;
|
FieldOfViewDegrees_ = sceneView.VerticalFieldOfViewDegrees;
|
||||||
const glm::vec3 offset = sceneView.CameraPosition - sceneView.CameraTarget;
|
const glm::vec3 offset = sceneView.CameraPosition - sceneView.CameraTarget;
|
||||||
Distance_ = std::max(0.1F, glm::length(offset));
|
Distance_ = std::max(0.1F, glm::length(offset));
|
||||||
|
FramingRadius_ = std::max(1.0F, (sceneView.FarClip - Distance_) * 0.25F);
|
||||||
|
|
||||||
if (Distance_ > 0.001F) {
|
if (Distance_ > 0.001F) {
|
||||||
const glm::vec3 forward = glm::normalize(sceneView.CameraTarget - sceneView.CameraPosition);
|
const glm::vec3 forward = glm::normalize(sceneView.CameraTarget - sceneView.CameraPosition);
|
||||||
@ -125,6 +129,12 @@ MetaCoreSceneView MetaCoreEditorCameraController::BuildSceneView() const {
|
|||||||
sceneView.CameraTarget = Target_;
|
sceneView.CameraTarget = Target_;
|
||||||
sceneView.CameraUp = glm::vec3(0.0F, 0.0F, 1.0F); // Z-up!
|
sceneView.CameraUp = glm::vec3(0.0F, 0.0F, 1.0F); // Z-up!
|
||||||
sceneView.VerticalFieldOfViewDegrees = FieldOfViewDegrees_;
|
sceneView.VerticalFieldOfViewDegrees = FieldOfViewDegrees_;
|
||||||
|
sceneView.NearClip = std::clamp(Distance_ * 0.0001F, 0.01F, 1.0F);
|
||||||
|
sceneView.FarClip = std::clamp(
|
||||||
|
std::max({1000.0F, Distance_ * 2.0F, Distance_ + FramingRadius_ * 4.0F}),
|
||||||
|
sceneView.NearClip + 1.0F,
|
||||||
|
100000000.0F
|
||||||
|
);
|
||||||
return sceneView;
|
return sceneView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -31,6 +31,7 @@ private:
|
|||||||
float FieldOfViewDegrees_ = 60.0F;
|
float FieldOfViewDegrees_ = 60.0F;
|
||||||
float MoveSpeed_ = 6.0F;
|
float MoveSpeed_ = 6.0F;
|
||||||
float MoveSpeedBoost_ = 4.0F;
|
float MoveSpeedBoost_ = 4.0F;
|
||||||
|
float FramingRadius_ = 10.0F;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace MetaCore
|
} // namespace MetaCore
|
||||||
|
|||||||
@ -31,6 +31,7 @@
|
|||||||
#include <limits>
|
#include <limits>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace MetaCore {
|
namespace MetaCore {
|
||||||
@ -213,6 +214,60 @@ MetaCoreResolvedPickGeometry MetaCoreResolvePickGeometry(
|
|||||||
return MetaCoreResolvedPickGeometry{};
|
return MetaCoreResolvedPickGeometry{};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MetaCoreAccumulateTransformedBounds(
|
||||||
|
const MetaCorePickBounds& localBounds,
|
||||||
|
const glm::mat4& worldMatrix,
|
||||||
|
bool& initialized,
|
||||||
|
glm::vec3& boundsMin,
|
||||||
|
glm::vec3& boundsMax
|
||||||
|
) {
|
||||||
|
for (int cornerIndex = 0; cornerIndex < 8; ++cornerIndex) {
|
||||||
|
const glm::vec3 localPoint(
|
||||||
|
(cornerIndex & 1) != 0 ? localBounds.Max.x : localBounds.Min.x,
|
||||||
|
(cornerIndex & 2) != 0 ? localBounds.Max.y : localBounds.Min.y,
|
||||||
|
(cornerIndex & 4) != 0 ? localBounds.Max.z : localBounds.Min.z
|
||||||
|
);
|
||||||
|
const glm::vec3 worldPoint = glm::vec3(worldMatrix * glm::vec4(localPoint, 1.0F));
|
||||||
|
if (!MetaCoreIsFiniteVec3(worldPoint)) continue;
|
||||||
|
if (!initialized) {
|
||||||
|
boundsMin = worldPoint;
|
||||||
|
boundsMax = worldPoint;
|
||||||
|
initialized = true;
|
||||||
|
} else {
|
||||||
|
boundsMin = glm::min(boundsMin, worldPoint);
|
||||||
|
boundsMax = glm::max(boundsMax, worldPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MetaCoreComputeWorldRenderBounds(
|
||||||
|
const MetaCoreEditorContext& editorContext,
|
||||||
|
const std::unordered_set<MetaCoreId>& objectIds,
|
||||||
|
glm::vec3& boundsMin,
|
||||||
|
glm::vec3& boundsMax
|
||||||
|
) {
|
||||||
|
const auto assetEditingService =
|
||||||
|
editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetEditingService>();
|
||||||
|
std::unordered_map<MetaCoreAssetGuid, std::optional<MetaCoreModelAssetDocument>, MetaCoreAssetGuidHasher>
|
||||||
|
modelAssetCache;
|
||||||
|
bool initialized = false;
|
||||||
|
for (const auto& object : editorContext.GetScene().GetGameObjects()) {
|
||||||
|
if (!objectIds.empty() && !objectIds.contains(object.GetId())) continue;
|
||||||
|
if (!object.HasComponent<MetaCoreMeshRendererComponent>()) continue;
|
||||||
|
const auto& renderer = object.GetComponent<MetaCoreMeshRendererComponent>();
|
||||||
|
if (!renderer.Visible) continue;
|
||||||
|
const auto geometry = MetaCoreResolvePickGeometry(renderer, assetEditingService, modelAssetCache);
|
||||||
|
MetaCoreAccumulateTransformedBounds(
|
||||||
|
geometry.Bounds,
|
||||||
|
MetaCoreBuildWorldTransformMatrix(editorContext.GetScene(), object.GetId()),
|
||||||
|
initialized,
|
||||||
|
boundsMin,
|
||||||
|
boundsMax
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return initialized;
|
||||||
|
}
|
||||||
|
|
||||||
bool MetaCoreIntersectLocalRayWithAabb(
|
bool MetaCoreIntersectLocalRayWithAabb(
|
||||||
const glm::vec3& rayOrigin,
|
const glm::vec3& rayOrigin,
|
||||||
const glm::vec3& rayDirection,
|
const glm::vec3& rayDirection,
|
||||||
@ -660,92 +715,37 @@ void MetaCoreSceneInteractionService::FocusSelectedObject(MetaCoreEditorContext&
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedObjectIds.size() == 1) {
|
std::unordered_set<MetaCoreId> focusObjectIds;
|
||||||
MetaCoreGameObject selectedObject = editorContext.GetSelectedGameObject();
|
for (const MetaCoreId selectedObjectId : selectedObjectIds) {
|
||||||
if (selectedObject) {
|
const auto subtree = editorContext.GetScene().GetSubtreeObjectIds(selectedObjectId);
|
||||||
editorContext.GetCameraController().FocusGameObject(selectedObject);
|
focusObjectIds.insert(subtree.begin(), subtree.end());
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool initialized = false;
|
|
||||||
glm::vec3 boundsMin(0.0F);
|
glm::vec3 boundsMin(0.0F);
|
||||||
glm::vec3 boundsMax(0.0F);
|
glm::vec3 boundsMax(0.0F);
|
||||||
for (const MetaCoreId selectedObjectId : selectedObjectIds) {
|
if (!MetaCoreComputeWorldRenderBounds(editorContext, focusObjectIds, boundsMin, boundsMax)) {
|
||||||
const MetaCoreGameObject selectedObject = editorContext.GetScene().FindGameObject(selectedObjectId);
|
const MetaCoreGameObject selectedObject = editorContext.GetSelectedGameObject();
|
||||||
if (!selectedObject) {
|
if (selectedObject) editorContext.GetCameraController().FocusGameObject(selectedObject);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const float extent = std::max({
|
|
||||||
std::abs(selectedObject.GetComponent<MetaCoreTransformComponent>().Scale.x),
|
|
||||||
std::abs(selectedObject.GetComponent<MetaCoreTransformComponent>().Scale.y),
|
|
||||||
std::abs(selectedObject.GetComponent<MetaCoreTransformComponent>().Scale.z),
|
|
||||||
0.5F
|
|
||||||
});
|
|
||||||
const glm::vec3 objectMin = selectedObject.GetComponent<MetaCoreTransformComponent>().Position - glm::vec3(extent);
|
|
||||||
const glm::vec3 objectMax = selectedObject.GetComponent<MetaCoreTransformComponent>().Position + glm::vec3(extent);
|
|
||||||
|
|
||||||
if (!initialized) {
|
|
||||||
boundsMin = objectMin;
|
|
||||||
boundsMax = objectMax;
|
|
||||||
initialized = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
boundsMin = glm::min(boundsMin, objectMin);
|
|
||||||
boundsMax = glm::max(boundsMax, objectMax);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!initialized) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const glm::vec3 center = (boundsMin + boundsMax) * 0.5F;
|
const glm::vec3 center = (boundsMin + boundsMax) * 0.5F;
|
||||||
const glm::vec3 size = boundsMax - boundsMin;
|
const glm::vec3 size = boundsMax - boundsMin;
|
||||||
const float radius = std::max({size.x, size.y, size.z, 1.0F}) * 0.5F;
|
const float radius = std::max(glm::length(size) * 0.5F, 0.5F);
|
||||||
editorContext.GetCameraController().FocusBounds(center, radius);
|
editorContext.GetCameraController().FocusBounds(center, radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MetaCoreSceneInteractionService::FocusVisibleScene(MetaCoreEditorContext& editorContext) const {
|
void MetaCoreSceneInteractionService::FocusVisibleScene(MetaCoreEditorContext& editorContext) const {
|
||||||
const auto& gameObjects = editorContext.GetScene().GetGameObjects();
|
|
||||||
bool initialized = false;
|
|
||||||
glm::vec3 boundsMin(0.0F);
|
glm::vec3 boundsMin(0.0F);
|
||||||
glm::vec3 boundsMax(0.0F);
|
glm::vec3 boundsMax(0.0F);
|
||||||
|
if (!MetaCoreComputeWorldRenderBounds(editorContext, {}, boundsMin, boundsMax)) {
|
||||||
for (const MetaCoreGameObject& gameObject : gameObjects) {
|
|
||||||
if (gameObject.HasComponent<MetaCoreMeshRendererComponent>() && !gameObject.GetComponent<MetaCoreMeshRendererComponent>().Visible) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const float extent = std::max({
|
|
||||||
std::abs(gameObject.GetComponent<MetaCoreTransformComponent>().Scale.x),
|
|
||||||
std::abs(gameObject.GetComponent<MetaCoreTransformComponent>().Scale.y),
|
|
||||||
std::abs(gameObject.GetComponent<MetaCoreTransformComponent>().Scale.z),
|
|
||||||
0.5F
|
|
||||||
});
|
|
||||||
const glm::vec3 objectMin = gameObject.GetComponent<MetaCoreTransformComponent>().Position - glm::vec3(extent);
|
|
||||||
const glm::vec3 objectMax = gameObject.GetComponent<MetaCoreTransformComponent>().Position + glm::vec3(extent);
|
|
||||||
|
|
||||||
if (!initialized) {
|
|
||||||
boundsMin = objectMin;
|
|
||||||
boundsMax = objectMax;
|
|
||||||
initialized = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
boundsMin = glm::min(boundsMin, objectMin);
|
|
||||||
boundsMax = glm::max(boundsMax, objectMax);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!initialized) {
|
|
||||||
editorContext.GetCameraController().FocusBounds(glm::vec3(0.0F, 0.5F, 0.0F), 2.0F);
|
editorContext.GetCameraController().FocusBounds(glm::vec3(0.0F, 0.5F, 0.0F), 2.0F);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const glm::vec3 center = (boundsMin + boundsMax) * 0.5F;
|
const glm::vec3 center = (boundsMin + boundsMax) * 0.5F;
|
||||||
const glm::vec3 size = boundsMax - boundsMin;
|
const glm::vec3 size = boundsMax - boundsMin;
|
||||||
const float radius = std::max({size.x, size.y, size.z, 1.0F}) * 0.5F;
|
const float radius = std::max(glm::length(size) * 0.5F, 0.5F);
|
||||||
editorContext.GetCameraController().FocusBounds(center, radius);
|
editorContext.GetCameraController().FocusBounds(center, radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
#include "MetaCoreRender/MetaCoreEditorViewportRenderer.h"
|
#include "MetaCoreRender/MetaCoreEditorViewportRenderer.h"
|
||||||
#include "MetaCoreRender/MetaCoreRenderDevice.h"
|
#include "MetaCoreRender/MetaCoreRenderDevice.h"
|
||||||
#include "MetaCoreScene/MetaCoreScene.h"
|
#include "MetaCoreScene/MetaCoreScene.h"
|
||||||
|
#include "MetaCoreScene/MetaCoreSceneWorld.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -38,6 +39,7 @@ private:
|
|||||||
MetaCoreRenderDevice RenderDevice_{};
|
MetaCoreRenderDevice RenderDevice_{};
|
||||||
MetaCoreEditorViewportRenderer ViewportRenderer_{};
|
MetaCoreEditorViewportRenderer ViewportRenderer_{};
|
||||||
MetaCoreScene Scene_{};
|
MetaCoreScene Scene_{};
|
||||||
|
MetaCoreSceneWorld SceneWorld_{Scene_};
|
||||||
MetaCoreLogService LogService_{};
|
MetaCoreLogService LogService_{};
|
||||||
MetaCoreEditorModuleRegistry ModuleRegistry_{};
|
MetaCoreEditorModuleRegistry ModuleRegistry_{};
|
||||||
MetaCoreSceneInteractionService SceneInteractionService_{};
|
MetaCoreSceneInteractionService SceneInteractionService_{};
|
||||||
|
|||||||
@ -28,6 +28,7 @@ class MetaCoreGameObject;
|
|||||||
class MetaCoreEditorModuleRegistry;
|
class MetaCoreEditorModuleRegistry;
|
||||||
class MetaCoreEditorCameraController;
|
class MetaCoreEditorCameraController;
|
||||||
class MetaCorePhysicsWorld;
|
class MetaCorePhysicsWorld;
|
||||||
|
class MetaCoreSceneWorld;
|
||||||
|
|
||||||
enum class MetaCoreGizmoOperation {
|
enum class MetaCoreGizmoOperation {
|
||||||
None = 0,
|
None = 0,
|
||||||
@ -125,6 +126,8 @@ public:
|
|||||||
[[nodiscard]] MetaCoreEditorViewportRenderer& GetViewportRenderer();
|
[[nodiscard]] MetaCoreEditorViewportRenderer& GetViewportRenderer();
|
||||||
[[nodiscard]] MetaCoreScene& GetScene();
|
[[nodiscard]] MetaCoreScene& GetScene();
|
||||||
[[nodiscard]] const MetaCoreScene& GetScene() const;
|
[[nodiscard]] const MetaCoreScene& GetScene() const;
|
||||||
|
void SetSceneWorld(MetaCoreSceneWorld* sceneWorld) { SceneWorld_ = sceneWorld; }
|
||||||
|
[[nodiscard]] MetaCoreSceneWorld* GetSceneWorld() const { return SceneWorld_; }
|
||||||
[[nodiscard]] MetaCoreLogService& GetLogService();
|
[[nodiscard]] MetaCoreLogService& GetLogService();
|
||||||
[[nodiscard]] const MetaCoreLogService& GetLogService() const;
|
[[nodiscard]] const MetaCoreLogService& GetLogService() const;
|
||||||
[[nodiscard]] MetaCoreEditorModuleRegistry& GetModuleRegistry();
|
[[nodiscard]] MetaCoreEditorModuleRegistry& GetModuleRegistry();
|
||||||
@ -219,6 +222,7 @@ private:
|
|||||||
MetaCoreRenderDevice& RenderDevice_;
|
MetaCoreRenderDevice& RenderDevice_;
|
||||||
MetaCoreEditorViewportRenderer& ViewportRenderer_;
|
MetaCoreEditorViewportRenderer& ViewportRenderer_;
|
||||||
MetaCoreScene& Scene_;
|
MetaCoreScene& Scene_;
|
||||||
|
MetaCoreSceneWorld* SceneWorld_ = nullptr;
|
||||||
MetaCoreLogService& LogService_;
|
MetaCoreLogService& LogService_;
|
||||||
MetaCoreEditorModuleRegistry& ModuleRegistry_;
|
MetaCoreEditorModuleRegistry& ModuleRegistry_;
|
||||||
std::unique_ptr<MetaCoreEditorCameraController> CameraController_;
|
std::unique_ptr<MetaCoreEditorCameraController> CameraController_;
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
#include "MetaCoreFoundation/MetaCorePackage.h"
|
#include "MetaCoreFoundation/MetaCorePackage.h"
|
||||||
#include "MetaCoreFoundation/MetaCoreReflection.h"
|
#include "MetaCoreFoundation/MetaCoreReflection.h"
|
||||||
#include "MetaCoreScene/MetaCoreScene.h"
|
#include "MetaCoreScene/MetaCoreScene.h"
|
||||||
|
#include "MetaCoreScene/MetaCoreSceneWorld.h"
|
||||||
#include "MetaCoreScripting/MetaCoreScripting.h"
|
#include "MetaCoreScripting/MetaCoreScripting.h"
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
@ -310,8 +311,13 @@ public:
|
|||||||
[[nodiscard]] virtual std::vector<std::filesystem::path> GetKnownScenePaths() const = 0;
|
[[nodiscard]] virtual std::vector<std::filesystem::path> GetKnownScenePaths() const = 0;
|
||||||
virtual bool LoadStartupScene(MetaCoreEditorContext& editorContext) = 0;
|
virtual bool LoadStartupScene(MetaCoreEditorContext& editorContext) = 0;
|
||||||
virtual bool LoadScene(MetaCoreEditorContext& editorContext, const std::filesystem::path& relativeScenePath) = 0;
|
virtual bool LoadScene(MetaCoreEditorContext& editorContext, const std::filesystem::path& relativeScenePath) = 0;
|
||||||
|
virtual bool LoadSceneAdditive(MetaCoreEditorContext& editorContext, const std::filesystem::path& relativeScenePath) = 0;
|
||||||
|
virtual bool UnloadScene(MetaCoreEditorContext& editorContext, const MetaCoreAssetGuid& sceneGuid, bool force) = 0;
|
||||||
|
virtual bool SetActiveScene(MetaCoreEditorContext& editorContext, const MetaCoreAssetGuid& sceneGuid) = 0;
|
||||||
virtual bool SaveCurrentScene(MetaCoreEditorContext& editorContext) = 0;
|
virtual bool SaveCurrentScene(MetaCoreEditorContext& editorContext) = 0;
|
||||||
virtual bool SaveSceneAs(MetaCoreEditorContext& editorContext, const std::filesystem::path& relativeScenePath) = 0;
|
virtual bool SaveSceneAs(MetaCoreEditorContext& editorContext, const std::filesystem::path& relativeScenePath) = 0;
|
||||||
|
virtual bool SaveAllScenes(MetaCoreEditorContext& editorContext) = 0;
|
||||||
|
[[nodiscard]] virtual std::vector<MetaCoreLoadedSceneInfo> GetOpenScenes(const MetaCoreEditorContext& editorContext) const = 0;
|
||||||
virtual void UpdateDirtyState(const MetaCoreSceneSnapshot& snapshot) = 0;
|
virtual void UpdateDirtyState(const MetaCoreSceneSnapshot& snapshot) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -183,8 +183,18 @@ void MetaCoreFilamentMaterialDebugLog(const std::string& message) {
|
|||||||
return stream.str();
|
return stream.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool MetaCorePreserveGltfBlendAlphaMode() {
|
[[nodiscard]] bool MetaCoreForceGltfBlendAsMask() {
|
||||||
const char* preserveBlend = std::getenv("METACORE_GLTF_PRESERVE_BLEND_ALPHA");
|
const char* forceMask = std::getenv("METACORE_GLTF_FORCE_BLEND_AS_MASK");
|
||||||
|
if (forceMask == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return std::strcmp(forceMask, "1") == 0 ||
|
||||||
|
std::strcmp(forceMask, "true") == 0 ||
|
||||||
|
std::strcmp(forceMask, "TRUE") == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool MetaCorePreserveDoubleSidedGltfBlend() {
|
||||||
|
const char* preserveBlend = std::getenv("METACORE_GLTF_PRESERVE_DOUBLE_SIDED_BLEND");
|
||||||
if (preserveBlend == nullptr) {
|
if (preserveBlend == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -245,17 +255,29 @@ public:
|
|||||||
private:
|
private:
|
||||||
void NormalizeAlphaMode(filament::gltfio::MaterialKey* config, const char* label) {
|
void NormalizeAlphaMode(filament::gltfio::MaterialKey* config, const char* label) {
|
||||||
if (config == nullptr ||
|
if (config == nullptr ||
|
||||||
MetaCorePreserveGltfBlendAlphaMode() ||
|
|
||||||
config->alphaMode != filament::gltfio::AlphaMode::BLEND) {
|
config->alphaMode != filament::gltfio::AlphaMode::BLEND) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bool forceAllBlendAsMask = MetaCoreForceGltfBlendAsMask();
|
||||||
|
const bool stabilizeDoubleSidedCutout =
|
||||||
|
config->doubleSided && !MetaCorePreserveDoubleSidedGltfBlend();
|
||||||
|
if (!forceAllBlendAsMask && !stabilizeDoubleSidedCutout) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
config->alphaMode = filament::gltfio::AlphaMode::MASK;
|
config->alphaMode = filament::gltfio::AlphaMode::MASK;
|
||||||
if (!LoggedBlendAsMask_) {
|
if (!LoggedBlendAsMask_) {
|
||||||
MetaCoreFilamentDebugLog(
|
if (forceAllBlendAsMask) {
|
||||||
"GLTF alphaMode=BLEND is rendered as MASK by default for stable foliage depth. "
|
MetaCoreFilamentDebugLog(
|
||||||
"Set METACORE_GLTF_PRESERVE_BLEND_ALPHA=1 to keep GLTF blend materials."
|
"GLTF alphaMode=BLEND is forced to MASK by METACORE_GLTF_FORCE_BLEND_AS_MASK."
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
MetaCoreFilamentDebugLog(
|
||||||
|
"GLTF double-sided BLEND materials use MASK for stable foliage depth. "
|
||||||
|
"Set METACORE_GLTF_PRESERVE_DOUBLE_SIDED_BLEND=1 for true double-sided transparency."
|
||||||
|
);
|
||||||
|
}
|
||||||
LoggedBlendAsMask_ = true;
|
LoggedBlendAsMask_ = true;
|
||||||
}
|
}
|
||||||
if (label != nullptr && label[0] != '\0') {
|
if (label != nullptr && label[0] != '\0') {
|
||||||
@ -1620,6 +1642,12 @@ public:
|
|||||||
CurrentSceneView_.NearClip, CurrentSceneView_.FarClip);
|
CurrentSceneView_.NearClip, CurrentSceneView_.FarClip);
|
||||||
const glm::mat4 viewProjection = projectionMatrix * viewMatrix;
|
const glm::mat4 viewProjection = projectionMatrix * viewMatrix;
|
||||||
const MetaCoreViewFrustum frustum = MetaCoreExtractFrustum(viewProjection);
|
const MetaCoreViewFrustum frustum = MetaCoreExtractFrustum(viewProjection);
|
||||||
|
// gltfio already owns exact per-primitive bounds for imported assets. The ECS
|
||||||
|
// snapshot currently only has a unit transform proxy for these nodes, which is
|
||||||
|
// not a conservative bound for large or offset meshes. Running that proxy
|
||||||
|
// through the engine BVH / software HZB causes whole model sections to pop as
|
||||||
|
// their node origins cross the view frustum. Keep hierarchy/layer/distance
|
||||||
|
// visibility here, then leave geometric frustum culling to Filament.
|
||||||
std::vector<MetaCoreBvhItem> visibilityItems;
|
std::vector<MetaCoreBvhItem> visibilityItems;
|
||||||
visibilityItems.reserve(resolvedSnapshot.Renderables.size());
|
visibilityItems.reserve(resolvedSnapshot.Renderables.size());
|
||||||
for (const auto& renderable : resolvedSnapshot.Renderables) {
|
for (const auto& renderable : resolvedSnapshot.Renderables) {
|
||||||
@ -1628,6 +1656,9 @@ public:
|
|||||||
MetaCoreAccumulateVisibilityResult(RenderingStatistics_, lodCulled ? MetaCoreVisibilityResult::LodCulled : MetaCoreVisibilityResult::DistanceCulled);
|
MetaCoreAccumulateVisibilityResult(RenderingStatistics_, lodCulled ? MetaCoreVisibilityResult::LodCulled : MetaCoreVisibilityResult::DistanceCulled);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (MetaCoreUsesNativeGltfVisibility(renderable)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
visibilityItems.push_back({renderable.ObjectId, renderable.WorldBounds, renderable.RenderingLayerMask,
|
visibilityItems.push_back({renderable.ObjectId, renderable.WorldBounds, renderable.RenderingLayerMask,
|
||||||
renderable.MaxDrawDistance, !renderable.StaticForCulling});
|
renderable.MaxDrawDistance, !renderable.StaticForCulling});
|
||||||
}
|
}
|
||||||
@ -1652,7 +1683,40 @@ public:
|
|||||||
};
|
};
|
||||||
std::unordered_set<MetaCoreId> currentVisible;
|
std::unordered_set<MetaCoreId> currentVisible;
|
||||||
for (auto& renderable : resolvedSnapshot.Renderables) {
|
for (auto& renderable : resolvedSnapshot.Renderables) {
|
||||||
if (!renderable.Visible || !bvhVisibleSet.contains(renderable.ObjectId)) { renderable.Visible=false; continue; }
|
if (!renderable.Visible) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (MetaCoreUsesNativeGltfVisibility(renderable)) {
|
||||||
|
const bool layerVisible =
|
||||||
|
(renderable.RenderingLayerMask & CurrentSceneView_.CullingMask) != 0U;
|
||||||
|
const bool distanceVisible =
|
||||||
|
renderable.MaxDrawDistance <= 0.0F ||
|
||||||
|
glm::distance(
|
||||||
|
CurrentSceneView_.CameraPosition,
|
||||||
|
glm::vec3(renderable.WorldMatrix[3])
|
||||||
|
) <= renderable.MaxDrawDistance;
|
||||||
|
renderable.Visible = layerVisible && distanceVisible;
|
||||||
|
if (renderable.Visible) {
|
||||||
|
currentVisible.insert(renderable.ObjectId);
|
||||||
|
MetaCoreAccumulateVisibilityResult(
|
||||||
|
RenderingStatistics_,
|
||||||
|
MetaCoreVisibilityResult::Visible,
|
||||||
|
renderable.TriangleCount
|
||||||
|
);
|
||||||
|
RenderingStatistics_.EstimatedVideoMemoryBytes +=
|
||||||
|
renderable.EstimatedVideoMemoryBytes;
|
||||||
|
} else {
|
||||||
|
MetaCoreAccumulateVisibilityResult(
|
||||||
|
RenderingStatistics_,
|
||||||
|
MetaCoreVisibilityResult::DistanceCulled
|
||||||
|
);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!bvhVisibleSet.contains(renderable.ObjectId)) {
|
||||||
|
renderable.Visible = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
bool queryVisible=true;glm::vec4 screenBounds{};float nearestDepth=0.0F,farthestDepth=0.0F;
|
bool queryVisible=true;glm::vec4 screenBounds{};float nearestDepth=0.0F,farthestDepth=0.0F;
|
||||||
if(RenderSettings_.EnableOcclusion&&projectBounds(renderable.WorldBounds,screenBounds,nearestDepth,farthestDepth))queryVisible=PreviousHzb_.IsVisible(screenBounds,nearestDepth);
|
if(RenderSettings_.EnableOcclusion&&projectBounds(renderable.WorldBounds,screenBounds,nearestDepth,farthestDepth))queryVisible=PreviousHzb_.IsVisible(screenBounds,nearestDepth);
|
||||||
const bool newlyVisible=!LastFrameVisibleObjects_.contains(renderable.ObjectId);
|
const bool newlyVisible=!LastFrameVisibleObjects_.contains(renderable.ObjectId);
|
||||||
@ -1663,7 +1727,7 @@ public:
|
|||||||
}
|
}
|
||||||
constexpr std::uint32_t hzbWidth=64U,hzbHeight=36U;
|
constexpr std::uint32_t hzbWidth=64U,hzbHeight=36U;
|
||||||
std::vector<float> softwareDepth(static_cast<std::size_t>(hzbWidth)*hzbHeight,CurrentSceneView_.FarClip);
|
std::vector<float> softwareDepth(static_cast<std::size_t>(hzbWidth)*hzbHeight,CurrentSceneView_.FarClip);
|
||||||
for(const auto& renderable:resolvedSnapshot.Renderables){if(!renderable.Visible||!renderable.StaticForCulling)continue;glm::vec4 bounds{};float nearestDepth=0.0F,farthestDepth=0.0F;if(!projectBounds(renderable.WorldBounds,bounds,nearestDepth,farthestDepth))continue;const std::uint32_t minX=std::min(hzbWidth-1U,static_cast<std::uint32_t>(std::clamp(bounds.x,0.0F,1.0F)*hzbWidth)),maxX=std::min(hzbWidth-1U,static_cast<std::uint32_t>(std::clamp(bounds.z,0.0F,1.0F)*hzbWidth));const std::uint32_t minY=std::min(hzbHeight-1U,static_cast<std::uint32_t>(std::clamp(bounds.y,0.0F,1.0F)*hzbHeight)),maxY=std::min(hzbHeight-1U,static_cast<std::uint32_t>(std::clamp(bounds.w,0.0F,1.0F)*hzbHeight));for(std::uint32_t y=minY;y<=maxY;++y)for(std::uint32_t x=minX;x<=maxX;++x)softwareDepth[static_cast<std::size_t>(y)*hzbWidth+x]=std::min(softwareDepth[static_cast<std::size_t>(y)*hzbWidth+x],farthestDepth);}
|
for(const auto& renderable:resolvedSnapshot.Renderables){if(!renderable.Visible||!renderable.StaticForCulling||MetaCoreUsesNativeGltfVisibility(renderable))continue;glm::vec4 bounds{};float nearestDepth=0.0F,farthestDepth=0.0F;if(!projectBounds(renderable.WorldBounds,bounds,nearestDepth,farthestDepth))continue;const std::uint32_t minX=std::min(hzbWidth-1U,static_cast<std::uint32_t>(std::clamp(bounds.x,0.0F,1.0F)*hzbWidth)),maxX=std::min(hzbWidth-1U,static_cast<std::uint32_t>(std::clamp(bounds.z,0.0F,1.0F)*hzbWidth));const std::uint32_t minY=std::min(hzbHeight-1U,static_cast<std::uint32_t>(std::clamp(bounds.y,0.0F,1.0F)*hzbHeight)),maxY=std::min(hzbHeight-1U,static_cast<std::uint32_t>(std::clamp(bounds.w,0.0F,1.0F)*hzbHeight));for(std::uint32_t y=minY;y<=maxY;++y)for(std::uint32_t x=minX;x<=maxX;++x)softwareDepth[static_cast<std::size_t>(y)*hzbWidth+x]=std::min(softwareDepth[static_cast<std::size_t>(y)*hzbWidth+x],farthestDepth);}
|
||||||
(void)PreviousHzb_.Build(hzbWidth,hzbHeight,softwareDepth);LastFrameVisibleObjects_=std::move(currentVisible);PreviousVisibilityCameraPosition_=CurrentSceneView_.CameraPosition;PreviousVisibilityCameraDirection_=cameraDirection;HasPreviousVisibilityCamera_=true;
|
(void)PreviousHzb_.Build(hzbWidth,hzbHeight,softwareDepth);LastFrameVisibleObjects_=std::move(currentVisible);PreviousVisibilityCameraPosition_=CurrentSceneView_.CameraPosition;PreviousVisibilityCameraDirection_=cameraDirection;HasPreviousVisibilityCamera_=true;
|
||||||
std::vector<MetaCoreInstanceCandidate> instanceCandidates;
|
std::vector<MetaCoreInstanceCandidate> instanceCandidates;
|
||||||
instanceCandidates.reserve(snapshot.Renderables.size());
|
instanceCandidates.reserve(snapshot.Renderables.size());
|
||||||
|
|||||||
@ -60,6 +60,14 @@ struct MetaCoreRenderSyncRenderable {
|
|||||||
std::uint64_t EstimatedVideoMemoryBytes = 0U;
|
std::uint64_t EstimatedVideoMemoryBytes = 0U;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool MetaCoreUsesNativeGltfVisibility(
|
||||||
|
const MetaCoreRenderSyncRenderable& renderable
|
||||||
|
) {
|
||||||
|
return renderable.MeshSource == MetaCoreMeshSourceKind::Asset &&
|
||||||
|
!renderable.SourceModelPath.empty() &&
|
||||||
|
renderable.HostRootId != 0;
|
||||||
|
}
|
||||||
|
|
||||||
struct MetaCoreRenderSyncCamera {
|
struct MetaCoreRenderSyncCamera {
|
||||||
MetaCoreId ObjectId = 0;
|
MetaCoreId ObjectId = 0;
|
||||||
bool IsPrimary = false;
|
bool IsPrimary = false;
|
||||||
|
|||||||
388
Source/MetaCoreScene/Private/MetaCorePrefab.cpp
Normal file
388
Source/MetaCoreScene/Private/MetaCorePrefab.cpp
Normal file
@ -0,0 +1,388 @@
|
|||||||
|
#include "MetaCoreScene/MetaCorePrefab.h"
|
||||||
|
|
||||||
|
#include "MetaCoreScene/MetaCoreSceneWorld.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
namespace MetaCore {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
glm::vec3 ReadVec3(std::string_view value, bool& valid) {
|
||||||
|
try {
|
||||||
|
const json parsed = json::parse(value);
|
||||||
|
if (!parsed.is_array() || parsed.size() < 3) return {};
|
||||||
|
valid = true;
|
||||||
|
return {parsed[0].get<float>(), parsed[1].get<float>(), parsed[2].get<float>()};
|
||||||
|
} catch (...) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool ReadScalar(std::string_view value, T& output) {
|
||||||
|
try {
|
||||||
|
output = json::parse(value).get<T>();
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MetaCoreGameObjectData* FindNode(MetaCorePrefabDocument& document, const MetaCoreAssetGuid& nodeGuid) {
|
||||||
|
const auto iterator = std::find_if(document.GameObjects.begin(), document.GameObjects.end(), [&](const auto& object) {
|
||||||
|
return object.StableObjectGuid == nodeGuid;
|
||||||
|
});
|
||||||
|
return iterator == document.GameObjects.end() ? nullptr : &*iterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MetaCoreGameObjectData* FindNode(const MetaCorePrefabDocument& document, const MetaCoreAssetGuid& nodeGuid) {
|
||||||
|
const auto iterator = std::find_if(document.GameObjects.begin(), document.GameObjects.end(), [&](const auto& object) {
|
||||||
|
return object.StableObjectGuid == nodeGuid;
|
||||||
|
});
|
||||||
|
return iterator == document.GameObjects.end() ? nullptr : &*iterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NormalizePrefab(MetaCorePrefabDocument& document) {
|
||||||
|
if (!document.PrefabGuid.IsValid()) document.PrefabGuid = MetaCoreAssetGuid::Generate();
|
||||||
|
for (auto& object : document.GameObjects) {
|
||||||
|
if (!object.StableObjectGuid.IsValid())
|
||||||
|
object.StableObjectGuid = MetaCoreBuildDeterministicGuid(document.PrefabGuid, object.Id);
|
||||||
|
}
|
||||||
|
document.SchemaVersion = GMetaCorePrefabSchemaVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MergeVariantObjects(MetaCorePrefabDocument& target, const MetaCorePrefabDocument& layer) {
|
||||||
|
for (const auto& object : layer.GameObjects) {
|
||||||
|
const auto iterator = std::find_if(target.GameObjects.begin(), target.GameObjects.end(), [&](const auto& current) {
|
||||||
|
return current.StableObjectGuid == object.StableObjectGuid;
|
||||||
|
});
|
||||||
|
if (iterator == target.GameObjects.end()) target.GameObjects.push_back(object);
|
||||||
|
else *iterator = object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
MetaCorePrefabOverrideRegistry::MetaCorePrefabOverrideRegistry() {
|
||||||
|
RegisterPropertyHandler("Name", [](MetaCoreGameObjectData& object, std::string_view path, std::string_view value) {
|
||||||
|
return path == "Value" && ReadScalar(value, object.Name);
|
||||||
|
});
|
||||||
|
RegisterPropertyHandler("Transform", [](MetaCoreGameObjectData& object, std::string_view path, std::string_view value) {
|
||||||
|
bool valid = false;
|
||||||
|
const glm::vec3 parsed = ReadVec3(value, valid);
|
||||||
|
if (!valid) return false;
|
||||||
|
if (path == "Position") object.Transform.Position = parsed;
|
||||||
|
else if (path == "RotationEulerDegrees") object.Transform.RotationEulerDegrees = parsed;
|
||||||
|
else if (path == "Scale") object.Transform.Scale = parsed;
|
||||||
|
else return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
RegisterPropertyHandler("Active", [](MetaCoreGameObjectData& object, std::string_view path, std::string_view value) {
|
||||||
|
if (path != "Active") return false;
|
||||||
|
if (!object.Active) object.Active = MetaCoreActiveComponent{};
|
||||||
|
return ReadScalar(value, object.Active->Active);
|
||||||
|
});
|
||||||
|
RegisterPropertyHandler("Camera", [](MetaCoreGameObjectData& object, std::string_view path, std::string_view value) {
|
||||||
|
if (!object.Camera) object.Camera = MetaCoreCameraComponent{};
|
||||||
|
if (path == "FieldOfViewDegrees") return ReadScalar(value, object.Camera->FieldOfViewDegrees);
|
||||||
|
if (path == "NearClip") return ReadScalar(value, object.Camera->NearClip);
|
||||||
|
if (path == "FarClip") return ReadScalar(value, object.Camera->FarClip);
|
||||||
|
if (path == "Enabled") return ReadScalar(value, object.Camera->Enabled);
|
||||||
|
if (path == "IsPrimary") return ReadScalar(value, object.Camera->IsPrimary);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
RegisterPropertyHandler("Light", [](MetaCoreGameObjectData& object, std::string_view path, std::string_view value) {
|
||||||
|
if (!object.Light) object.Light = MetaCoreLightComponent{};
|
||||||
|
if (path == "Intensity") return ReadScalar(value, object.Light->Intensity);
|
||||||
|
if (path == "Enabled") return ReadScalar(value, object.Light->Enabled);
|
||||||
|
if (path == "Color") {
|
||||||
|
bool valid = false;
|
||||||
|
const auto parsed = ReadVec3(value, valid);
|
||||||
|
if (valid) object.Light->Color = parsed;
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void MetaCorePrefabOverrideRegistry::RegisterPropertyHandler(std::string componentTypeId, PropertyHandler handler) {
|
||||||
|
PropertyHandlers_[std::move(componentTypeId)] = std::move(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MetaCorePrefabOverrideRegistry::Apply(
|
||||||
|
MetaCorePrefabDocument& document,
|
||||||
|
const MetaCorePrefabOverrideDocument& overrideValue
|
||||||
|
) const {
|
||||||
|
MetaCoreGameObjectData* object = FindNode(document, overrideValue.TargetNodeGuid);
|
||||||
|
if (!object) return false;
|
||||||
|
switch (overrideValue.Operation) {
|
||||||
|
case MetaCorePrefabOverrideOperation::SetProperty: {
|
||||||
|
const auto handler = PropertyHandlers_.find(overrideValue.ComponentTypeId);
|
||||||
|
return handler != PropertyHandlers_.end() &&
|
||||||
|
handler->second(*object, overrideValue.PropertyPath, overrideValue.ValueJson);
|
||||||
|
}
|
||||||
|
case MetaCorePrefabOverrideOperation::AddComponent:
|
||||||
|
if (overrideValue.ComponentTypeId == "Active") object->Active = MetaCoreActiveComponent{};
|
||||||
|
else if (overrideValue.ComponentTypeId == "Camera") object->Camera = MetaCoreCameraComponent{};
|
||||||
|
else if (overrideValue.ComponentTypeId == "Light") object->Light = MetaCoreLightComponent{};
|
||||||
|
else if (overrideValue.ComponentTypeId == "MeshRenderer") object->MeshRenderer = MetaCoreMeshRendererComponent{};
|
||||||
|
else if (overrideValue.ComponentTypeId == "Collider") object->Collider = MetaCoreColliderComponent{};
|
||||||
|
else if (overrideValue.ComponentTypeId == "RigidBody") object->RigidBody = MetaCoreRigidBodyComponent{};
|
||||||
|
else if (overrideValue.ComponentTypeId == "Rotator") object->Rotator = MetaCoreRotatorComponent{};
|
||||||
|
else if (overrideValue.ComponentTypeId == "Animator") object->Animator = MetaCoreAnimatorComponent{};
|
||||||
|
else if (overrideValue.ComponentTypeId == "TimelineDirector") object->TimelineDirector = MetaCoreTimelineDirectorComponent{};
|
||||||
|
else if (overrideValue.ComponentTypeId == "Script") object->Script = MetaCoreScriptComponent{};
|
||||||
|
else if (overrideValue.ComponentTypeId == "UiRenderer") object->UiRenderer = MetaCoreUiRendererComponent{};
|
||||||
|
else if (overrideValue.ComponentTypeId == "Interactable") object->Interactable = MetaCoreInteractableComponent{};
|
||||||
|
else if (overrideValue.ComponentTypeId == "PhysicsConstraint") object->PhysicsConstraint = MetaCorePhysicsConstraintComponent{};
|
||||||
|
else if (overrideValue.ComponentTypeId == "CharacterController") object->CharacterController = MetaCoreCharacterControllerComponent{};
|
||||||
|
else if (overrideValue.ComponentTypeId == "ReflectionProbe") object->ReflectionProbe = MetaCoreReflectionProbeComponent{};
|
||||||
|
else if (overrideValue.ComponentTypeId == "LodGroup") object->LodGroup = MetaCoreLodGroupComponent{};
|
||||||
|
else if (overrideValue.ComponentTypeId == "ParticleEmitter") object->ParticleEmitter = MetaCoreParticleEmitterComponent{};
|
||||||
|
else if (overrideValue.ComponentTypeId == "LineRenderer") object->LineRenderer = MetaCoreLineRendererComponent{};
|
||||||
|
else if (overrideValue.ComponentTypeId == "TrailRenderer") object->TrailRenderer = MetaCoreTrailRendererComponent{};
|
||||||
|
else if (overrideValue.ComponentTypeId == "DecalProjector") object->DecalProjector = MetaCoreDecalProjectorComponent{};
|
||||||
|
else if (overrideValue.ComponentTypeId == "Terrain") object->Terrain = MetaCoreTerrainComponent{};
|
||||||
|
else return false;
|
||||||
|
return true;
|
||||||
|
case MetaCorePrefabOverrideOperation::RemoveComponent:
|
||||||
|
if (overrideValue.ComponentTypeId == "Active") object->Active.reset();
|
||||||
|
else if (overrideValue.ComponentTypeId == "Camera") object->Camera.reset();
|
||||||
|
else if (overrideValue.ComponentTypeId == "Light") object->Light.reset();
|
||||||
|
else if (overrideValue.ComponentTypeId == "MeshRenderer") object->MeshRenderer.reset();
|
||||||
|
else if (overrideValue.ComponentTypeId == "Collider") object->Collider.reset();
|
||||||
|
else if (overrideValue.ComponentTypeId == "RigidBody") object->RigidBody.reset();
|
||||||
|
else if (overrideValue.ComponentTypeId == "Rotator") object->Rotator.reset();
|
||||||
|
else if (overrideValue.ComponentTypeId == "Animator") object->Animator.reset();
|
||||||
|
else if (overrideValue.ComponentTypeId == "TimelineDirector") object->TimelineDirector.reset();
|
||||||
|
else if (overrideValue.ComponentTypeId == "Script") object->Script.reset();
|
||||||
|
else if (overrideValue.ComponentTypeId == "UiRenderer") object->UiRenderer.reset();
|
||||||
|
else if (overrideValue.ComponentTypeId == "Interactable") object->Interactable.reset();
|
||||||
|
else if (overrideValue.ComponentTypeId == "PhysicsConstraint") object->PhysicsConstraint.reset();
|
||||||
|
else if (overrideValue.ComponentTypeId == "CharacterController") object->CharacterController.reset();
|
||||||
|
else if (overrideValue.ComponentTypeId == "ReflectionProbe") object->ReflectionProbe.reset();
|
||||||
|
else if (overrideValue.ComponentTypeId == "LodGroup") object->LodGroup.reset();
|
||||||
|
else if (overrideValue.ComponentTypeId == "ParticleEmitter") object->ParticleEmitter.reset();
|
||||||
|
else if (overrideValue.ComponentTypeId == "LineRenderer") object->LineRenderer.reset();
|
||||||
|
else if (overrideValue.ComponentTypeId == "TrailRenderer") object->TrailRenderer.reset();
|
||||||
|
else if (overrideValue.ComponentTypeId == "DecalProjector") object->DecalProjector.reset();
|
||||||
|
else if (overrideValue.ComponentTypeId == "Terrain") object->Terrain.reset();
|
||||||
|
else return false;
|
||||||
|
return true;
|
||||||
|
case MetaCorePrefabOverrideOperation::RemoveChild: {
|
||||||
|
std::unordered_set<MetaCoreId> removed{object->Id};
|
||||||
|
bool changed = true;
|
||||||
|
while (changed) {
|
||||||
|
changed = false;
|
||||||
|
for (const auto& candidate : document.GameObjects)
|
||||||
|
if (removed.contains(candidate.ParentId) && removed.insert(candidate.Id).second) changed = true;
|
||||||
|
}
|
||||||
|
std::erase_if(document.GameObjects, [&](const auto& candidate) { return removed.contains(candidate.Id); });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case MetaCorePrefabOverrideOperation::Reparent: {
|
||||||
|
const auto parentGuid = MetaCoreAssetGuid::Parse(overrideValue.ValueJson);
|
||||||
|
if (!parentGuid) return false;
|
||||||
|
const auto* parent = FindNode(document, *parentGuid);
|
||||||
|
if (!parent || parent->Id == object->Id) return false;
|
||||||
|
object->ParentId = parent->Id;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case MetaCorePrefabOverrideOperation::AddChild: {
|
||||||
|
try {
|
||||||
|
const json value = json::parse(overrideValue.ValueJson);
|
||||||
|
MetaCoreGameObjectData child;
|
||||||
|
child.Id = 1;
|
||||||
|
for (const auto& current : document.GameObjects) child.Id = std::max(child.Id, current.Id + 1);
|
||||||
|
child.ParentId = object->Id;
|
||||||
|
child.Name = value.value("Name", std::string("GameObject"));
|
||||||
|
child.StableObjectGuid = MetaCoreAssetGuid::Parse(value.value("StableObjectGuid", std::string{}))
|
||||||
|
.value_or(MetaCoreBuildDeterministicGuid(document.PrefabGuid, child.Id));
|
||||||
|
if (value.contains("Position")) {
|
||||||
|
bool valid = false;
|
||||||
|
child.Transform.Position = ReadVec3(value["Position"].dump(), valid);
|
||||||
|
if (!valid) return false;
|
||||||
|
}
|
||||||
|
if (value.contains("RotationEulerDegrees")) {
|
||||||
|
bool valid = false;
|
||||||
|
child.Transform.RotationEulerDegrees = ReadVec3(value["RotationEulerDegrees"].dump(), valid);
|
||||||
|
if (!valid) return false;
|
||||||
|
}
|
||||||
|
if (value.contains("Scale")) {
|
||||||
|
bool valid = false;
|
||||||
|
child.Transform.Scale = ReadVec3(value["Scale"].dump(), valid);
|
||||||
|
if (!valid) return false;
|
||||||
|
}
|
||||||
|
document.GameObjects.push_back(std::move(child));
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MetaCorePrefabResolver::MetaCorePrefabResolver(PrefabLoader loader, MetaCorePrefabOverrideRegistry registry)
|
||||||
|
: Loader_(std::move(loader)), OverrideRegistry_(std::move(registry)) {}
|
||||||
|
|
||||||
|
std::optional<MetaCoreResolvedPrefab> MetaCorePrefabResolver::Resolve(
|
||||||
|
const MetaCoreAssetGuid& prefabGuid,
|
||||||
|
std::string* error
|
||||||
|
) const {
|
||||||
|
std::vector<MetaCoreAssetGuid> stack;
|
||||||
|
return ResolveInternal(prefabGuid, stack, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<MetaCoreResolvedPrefab> MetaCorePrefabResolver::ResolveInternal(
|
||||||
|
const MetaCoreAssetGuid& prefabGuid,
|
||||||
|
std::vector<MetaCoreAssetGuid>& stack,
|
||||||
|
std::string* error
|
||||||
|
) const {
|
||||||
|
if (!prefabGuid.IsValid() || !Loader_) {
|
||||||
|
if (error) *error = "Prefab GUID is invalid or no loader is configured";
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
if (std::find(stack.begin(), stack.end(), prefabGuid) != stack.end()) {
|
||||||
|
if (error) *error = "Prefab base/nesting dependency cycle at " + prefabGuid.ToString();
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
auto layer = Loader_(prefabGuid);
|
||||||
|
if (!layer) {
|
||||||
|
if (error) *error = "Prefab asset is missing: " + prefabGuid.ToString();
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
if (layer->SchemaVersion > GMetaCorePrefabSchemaVersion) {
|
||||||
|
if (error) *error = "Prefab schema is newer than this engine: " + prefabGuid.ToString();
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
if (!layer->PrefabGuid.IsValid()) layer->PrefabGuid = prefabGuid;
|
||||||
|
NormalizePrefab(*layer);
|
||||||
|
stack.push_back(prefabGuid);
|
||||||
|
|
||||||
|
MetaCoreResolvedPrefab result;
|
||||||
|
if (layer->BasePrefabGuid.IsValid()) {
|
||||||
|
auto base = ResolveInternal(layer->BasePrefabGuid, stack, error);
|
||||||
|
if (!base) {
|
||||||
|
stack.pop_back();
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
result = std::move(*base);
|
||||||
|
result.Document.PrefabGuid = layer->PrefabGuid;
|
||||||
|
result.Document.BasePrefabGuid = layer->BasePrefabGuid;
|
||||||
|
result.Document.Name = layer->Name;
|
||||||
|
MergeVariantObjects(result.Document, *layer);
|
||||||
|
} else {
|
||||||
|
result.Document = *layer;
|
||||||
|
}
|
||||||
|
result.DependencyChain.push_back(prefabGuid);
|
||||||
|
for (const auto& value : layer->Overrides)
|
||||||
|
if (!OverrideRegistry_.Apply(result.Document, value)) result.OrphanOverrides.push_back(value);
|
||||||
|
|
||||||
|
for (const auto& nested : layer->NestedPrefabs) {
|
||||||
|
auto nestedResult = ResolveInternal(nested.PrefabAssetGuid, stack, error);
|
||||||
|
if (!nestedResult) {
|
||||||
|
stack.pop_back();
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
for (const auto& value : nested.Overrides)
|
||||||
|
if (!OverrideRegistry_.Apply(nestedResult->Document, value)) result.OrphanOverrides.push_back(value);
|
||||||
|
const auto* mount = FindNode(result.Document, nested.MountNodeGuid);
|
||||||
|
if (!mount) {
|
||||||
|
MetaCorePrefabOverrideDocument orphan;
|
||||||
|
orphan.TargetNodeGuid = nested.MountNodeGuid;
|
||||||
|
orphan.ComponentTypeId = "NestedPrefab";
|
||||||
|
orphan.ValueJson = nested.PrefabAssetGuid.ToString();
|
||||||
|
result.OrphanOverrides.push_back(std::move(orphan));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const MetaCoreAssetGuid instanceGuid = nested.InstanceGuid.IsValid()
|
||||||
|
? nested.InstanceGuid
|
||||||
|
: MetaCoreBuildDeterministicGuid(layer->PrefabGuid, nested.PrefabAssetGuid);
|
||||||
|
std::unordered_map<MetaCoreId, MetaCoreId> remap;
|
||||||
|
for (const auto& object : nestedResult->Document.GameObjects)
|
||||||
|
remap[object.Id] = MetaCoreIdGenerator::Generate();
|
||||||
|
for (auto object : nestedResult->Document.GameObjects) {
|
||||||
|
const MetaCoreId oldId = object.Id;
|
||||||
|
object.Id = remap.at(oldId);
|
||||||
|
if (object.ParentId == 0) object.ParentId = mount->Id;
|
||||||
|
else if (const auto parent = remap.find(object.ParentId); parent != remap.end()) object.ParentId = parent->second;
|
||||||
|
else {
|
||||||
|
if (error) *error = "Nested prefab contains a missing parent object";
|
||||||
|
stack.pop_back();
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
object.StableObjectGuid = MetaCoreBuildDeterministicGuid(instanceGuid, object.StableObjectGuid);
|
||||||
|
result.Document.GameObjects.push_back(std::move(object));
|
||||||
|
}
|
||||||
|
result.DependencyChain.insert(result.DependencyChain.end(), nestedResult->DependencyChain.begin(), nestedResult->DependencyChain.end());
|
||||||
|
result.OrphanOverrides.insert(result.OrphanOverrides.end(), nestedResult->OrphanOverrides.begin(), nestedResult->OrphanOverrides.end());
|
||||||
|
}
|
||||||
|
stack.pop_back();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::vector<MetaCoreGameObjectData>> MetaCorePrefabResolver::Instantiate(
|
||||||
|
const MetaCoreAssetGuid& prefabGuid,
|
||||||
|
const MetaCoreAssetGuid& instanceGuid,
|
||||||
|
const std::vector<MetaCorePrefabOverrideDocument>& instanceOverrides,
|
||||||
|
std::string* error
|
||||||
|
) const {
|
||||||
|
if (!instanceGuid.IsValid()) {
|
||||||
|
if (error) *error = "Prefab instance GUID is invalid";
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
auto resolved = Resolve(prefabGuid, error);
|
||||||
|
if (!resolved) return std::nullopt;
|
||||||
|
for (const auto& value : instanceOverrides)
|
||||||
|
if (!OverrideRegistry_.Apply(resolved->Document, value)) resolved->OrphanOverrides.push_back(value);
|
||||||
|
|
||||||
|
std::unordered_map<MetaCoreId, MetaCoreId> remap;
|
||||||
|
for (const auto& object : resolved->Document.GameObjects) remap[object.Id] = MetaCoreIdGenerator::Generate();
|
||||||
|
for (auto& object : resolved->Document.GameObjects) {
|
||||||
|
const MetaCoreId oldId = object.Id;
|
||||||
|
const MetaCoreAssetGuid nodeGuid = object.StableObjectGuid;
|
||||||
|
object.Id = remap.at(oldId);
|
||||||
|
if (object.ParentId != 0) {
|
||||||
|
const auto parent = remap.find(object.ParentId);
|
||||||
|
if (parent == remap.end()) {
|
||||||
|
if (error) *error = "Prefab contains a missing parent object";
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
object.ParentId = parent->second;
|
||||||
|
}
|
||||||
|
object.StableObjectGuid = MetaCoreBuildDeterministicGuid(instanceGuid, nodeGuid);
|
||||||
|
MetaCorePrefabInstanceMetadata metadata;
|
||||||
|
metadata.PrefabAssetGuid = prefabGuid;
|
||||||
|
metadata.PrefabObjectId = oldId;
|
||||||
|
metadata.InstanceGuid = instanceGuid;
|
||||||
|
metadata.PrefabNodeGuid = nodeGuid;
|
||||||
|
object.PrefabInstance = metadata;
|
||||||
|
}
|
||||||
|
const MetaCoreId rootId = [&] {
|
||||||
|
const auto root = std::find_if(resolved->Document.GameObjects.begin(), resolved->Document.GameObjects.end(),
|
||||||
|
[](const auto& object) { return object.ParentId == 0; });
|
||||||
|
return root == resolved->Document.GameObjects.end() ? MetaCoreId{} : root->Id;
|
||||||
|
}();
|
||||||
|
for (auto& object : resolved->Document.GameObjects)
|
||||||
|
if (object.PrefabInstance) object.PrefabInstance->PrefabInstanceRootId = rootId;
|
||||||
|
return resolved->Document.GameObjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> MetaCorePrefabResolver::ValidateDependencies(const MetaCoreAssetGuid& prefabGuid) const {
|
||||||
|
std::string error;
|
||||||
|
const auto result = Resolve(prefabGuid, &error);
|
||||||
|
std::vector<std::string> issues;
|
||||||
|
if (!result) issues.push_back(error);
|
||||||
|
else if (!result->OrphanOverrides.empty())
|
||||||
|
issues.push_back("Prefab contains " + std::to_string(result->OrphanOverrides.size()) + " orphan override(s)");
|
||||||
|
return issues;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace MetaCore
|
||||||
@ -465,6 +465,11 @@ MetaCoreSceneSnapshot MetaCoreScene::CaptureSnapshot() const {
|
|||||||
MetaCoreGameObjectData data;
|
MetaCoreGameObjectData data;
|
||||||
data.Id = obj.GetId();
|
data.Id = obj.GetId();
|
||||||
data.ParentId = obj.GetParentId();
|
data.ParentId = obj.GetParentId();
|
||||||
|
if (obj.HasComponent<MetaCoreSceneObjectIdentityComponent>()) {
|
||||||
|
const auto& identity = obj.GetComponent<MetaCoreSceneObjectIdentityComponent>();
|
||||||
|
data.SceneGuid = identity.SceneGuid;
|
||||||
|
data.StableObjectGuid = identity.StableObjectGuid;
|
||||||
|
}
|
||||||
data.Name = obj.GetName();
|
data.Name = obj.GetName();
|
||||||
data.Transform = obj.GetComponent<MetaCoreTransformComponent>();
|
data.Transform = obj.GetComponent<MetaCoreTransformComponent>();
|
||||||
|
|
||||||
@ -523,6 +528,11 @@ void MetaCoreScene::RestoreSnapshot(const MetaCoreSceneSnapshot& snapshot) {
|
|||||||
maxId = std::max(maxId, data.Id);
|
maxId = std::max(maxId, data.Id);
|
||||||
MetaCoreGameObject obj = CreateGameObjectWithId(data.Id, data.Name, data.ParentId);
|
MetaCoreGameObject obj = CreateGameObjectWithId(data.Id, data.Name, data.ParentId);
|
||||||
obj.GetComponent<MetaCoreTransformComponent>() = data.Transform;
|
obj.GetComponent<MetaCoreTransformComponent>() = data.Transform;
|
||||||
|
if (data.SceneGuid.IsValid() || data.StableObjectGuid.IsValid()) {
|
||||||
|
obj.AddComponent<MetaCoreSceneObjectIdentityComponent>(
|
||||||
|
MetaCoreSceneObjectIdentityComponent{data.SceneGuid, data.StableObjectGuid}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (data.Camera.has_value())
|
if (data.Camera.has_value())
|
||||||
obj.AddComponent<MetaCoreCameraComponent>(data.Camera.value());
|
obj.AddComponent<MetaCoreCameraComponent>(data.Camera.value());
|
||||||
|
|||||||
@ -15,7 +15,8 @@ namespace {
|
|||||||
[[nodiscard]] MetaCorePackageDocument MetaCoreBuildTypedScenePackage(
|
[[nodiscard]] MetaCorePackageDocument MetaCoreBuildTypedScenePackage(
|
||||||
const MetaCoreAssetGuid& assetGuid,
|
const MetaCoreAssetGuid& assetGuid,
|
||||||
const std::string& objectName,
|
const std::string& objectName,
|
||||||
std::vector<std::byte> payload
|
std::vector<std::byte> payload,
|
||||||
|
const MetaCoreSceneDocument& scene
|
||||||
) {
|
) {
|
||||||
MetaCorePackageDocument document;
|
MetaCorePackageDocument document;
|
||||||
document.Header.PackageType = MetaCorePackageType::Scene;
|
document.Header.PackageType = MetaCorePackageType::Scene;
|
||||||
@ -29,6 +30,17 @@ namespace {
|
|||||||
0,
|
0,
|
||||||
static_cast<std::uint64_t>(payload.size())
|
static_cast<std::uint64_t>(payload.size())
|
||||||
});
|
});
|
||||||
|
for (const auto& subScene : scene.SubScenes) {
|
||||||
|
if (subScene.SceneGuid.IsValid())
|
||||||
|
document.Dependencies.push_back({subScene.SceneGuid, "SubScene"});
|
||||||
|
}
|
||||||
|
for (const auto& instance : scene.PrefabInstances) {
|
||||||
|
if (instance.PrefabAssetGuid.IsValid())
|
||||||
|
document.Dependencies.push_back({instance.PrefabAssetGuid, "PrefabInstance"});
|
||||||
|
}
|
||||||
|
document.CustomVersions.push_back({"MetaCore.Scene", scene.SchemaVersion});
|
||||||
|
for (const auto& component : scene.ComponentVersions)
|
||||||
|
document.CustomVersions.push_back({"MetaCore.Component." + component.TypeId, component.Version});
|
||||||
document.PayloadSections.push_back(std::move(payload));
|
document.PayloadSections.push_back(std::move(payload));
|
||||||
return document;
|
return document;
|
||||||
}
|
}
|
||||||
@ -110,9 +122,10 @@ bool MetaCoreWriteScenePackage(
|
|||||||
|
|
||||||
const std::string objectName = path.stem().string().empty() ? "Scene" : path.stem().string();
|
const std::string objectName = path.stem().string().empty() ? "Scene" : path.stem().string();
|
||||||
MetaCorePackageDocument package = MetaCoreBuildTypedScenePackage(
|
MetaCorePackageDocument package = MetaCoreBuildTypedScenePackage(
|
||||||
MetaCoreAssetGuid::Generate(),
|
document.SceneGuid.IsValid() ? document.SceneGuid : MetaCoreAssetGuid::Generate(),
|
||||||
objectName,
|
objectName,
|
||||||
*payload
|
*payload,
|
||||||
|
document
|
||||||
);
|
);
|
||||||
return MetaCoreWritePackageFile(path, std::move(package), registry);
|
return MetaCoreWritePackageFile(path, std::move(package), registry);
|
||||||
}
|
}
|
||||||
@ -123,7 +136,9 @@ std::optional<MetaCoreSceneDocument> MetaCoreReadScenePackage(const std::filesys
|
|||||||
if (!package.has_value() || package->Header.PackageType != MetaCorePackageType::Scene) {
|
if (!package.has_value() || package->Header.PackageType != MetaCorePackageType::Scene) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
return MetaCoreReadTypedPayload<MetaCoreSceneDocument>(*package, registry, "MetaCoreSceneDocument");
|
auto scene = MetaCoreReadTypedPayload<MetaCoreSceneDocument>(*package, registry, "MetaCoreSceneDocument");
|
||||||
|
if (!scene || scene->SchemaVersion > GMetaCoreSceneSchemaVersion) return std::nullopt;
|
||||||
|
return scene;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::filesystem::path> MetaCoreResolveStartupScenePackagePath(const std::filesystem::path& projectFilePath) {
|
std::optional<std::filesystem::path> MetaCoreResolveStartupScenePackagePath(const std::filesystem::path& projectFilePath) {
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
|
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
|
||||||
#include "MetaCoreFoundation/MetaCoreReflection.h"
|
#include "MetaCoreFoundation/MetaCoreReflection.h"
|
||||||
|
#include "MetaCoreScene/MetaCoreSceneWorld.h"
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
@ -33,6 +35,23 @@ static MetaCoreAssetGuid StringToGuid(const std::string& str) {
|
|||||||
return str.empty() ? MetaCoreAssetGuid{} : MetaCoreAssetGuid::Parse(str).value_or(MetaCoreAssetGuid{});
|
return str.empty() ? MetaCoreAssetGuid{} : MetaCoreAssetGuid::Parse(str).value_or(MetaCoreAssetGuid{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static json ObjectReferenceToJson(const MetaCoreObjectReference& reference) {
|
||||||
|
return {
|
||||||
|
{"SceneGuid", GuidToString(reference.SceneGuid)},
|
||||||
|
{"StableObjectGuid", GuidToString(reference.StableObjectGuid)},
|
||||||
|
{"Strength", static_cast<int>(reference.Strength)}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static MetaCoreObjectReference JsonToObjectReference(const json& value) {
|
||||||
|
MetaCoreObjectReference result;
|
||||||
|
if (!value.is_object()) return result;
|
||||||
|
result.SceneGuid = StringToGuid(value.value("SceneGuid", std::string{}));
|
||||||
|
result.StableObjectGuid = StringToGuid(value.value("StableObjectGuid", std::string{}));
|
||||||
|
result.Strength = static_cast<MetaCoreObjectReferenceStrength>(value.value("Strength", 0));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
static std::string ReflectedValueToHex(const T& value, const MetaCoreTypeRegistry& registry) {
|
static std::string ReflectedValueToHex(const T& value, const MetaCoreTypeRegistry& registry) {
|
||||||
const auto bytes = MetaCoreSerializeToBytes(value, registry); if (!bytes) return {};
|
const auto bytes = MetaCoreSerializeToBytes(value, registry); if (!bytes) return {};
|
||||||
@ -89,6 +108,10 @@ static json GameObjectDataToJson(const MetaCoreGameObjectData& obj, const MetaCo
|
|||||||
objJson["Id"] = obj.Id;
|
objJson["Id"] = obj.Id;
|
||||||
} else if (objField.Name == "ParentId") {
|
} else if (objField.Name == "ParentId") {
|
||||||
objJson["ParentId"] = obj.ParentId;
|
objJson["ParentId"] = obj.ParentId;
|
||||||
|
} else if (objField.Name == "SceneGuid") {
|
||||||
|
objJson["SceneGuid"] = GuidToString(obj.SceneGuid);
|
||||||
|
} else if (objField.Name == "StableObjectGuid") {
|
||||||
|
objJson["StableObjectGuid"] = GuidToString(obj.StableObjectGuid);
|
||||||
} else if (objField.Name == "Name") {
|
} else if (objField.Name == "Name") {
|
||||||
objJson["Name"] = obj.Name;
|
objJson["Name"] = obj.Name;
|
||||||
} else if (objField.Name == "Transform") {
|
} else if (objField.Name == "Transform") {
|
||||||
@ -205,7 +228,11 @@ static json GameObjectDataToJson(const MetaCoreGameObjectData& obj, const MetaCo
|
|||||||
} else if (objField.Name == "TimelineDirector" && obj.TimelineDirector.has_value()) {
|
} else if (objField.Name == "TimelineDirector" && obj.TimelineDirector.has_value()) {
|
||||||
const auto& director = *obj.TimelineDirector;
|
const auto& director = *obj.TimelineDirector;
|
||||||
json bindings = json::array();
|
json bindings = json::array();
|
||||||
for (const auto& binding : director.Bindings) bindings.push_back({{"Slot", binding.Slot}, {"ObjectId", binding.ObjectId}});
|
for (const auto& binding : director.Bindings) bindings.push_back({
|
||||||
|
{"Slot", binding.Slot},
|
||||||
|
{"ObjectId", binding.ObjectId},
|
||||||
|
{"ObjectReference", ObjectReferenceToJson(binding.ObjectReference)}
|
||||||
|
});
|
||||||
objJson["TimelineDirector"] = {{"Enabled", director.Enabled}, {"TimelineAssetGuid", GuidToString(director.TimelineAssetGuid)},
|
objJson["TimelineDirector"] = {{"Enabled", director.Enabled}, {"TimelineAssetGuid", GuidToString(director.TimelineAssetGuid)},
|
||||||
{"PlayOnStart", director.PlayOnStart}, {"Loop", director.Loop}, {"Speed", director.Speed}, {"Bindings", std::move(bindings)}};
|
{"PlayOnStart", director.PlayOnStart}, {"Loop", director.Loop}, {"Speed", director.Speed}, {"Bindings", std::move(bindings)}};
|
||||||
} else if (objField.Name == "Script" && obj.Script.has_value()) {
|
} else if (objField.Name == "Script" && obj.Script.has_value()) {
|
||||||
@ -261,7 +288,8 @@ static json GameObjectDataToJson(const MetaCoreGameObjectData& obj, const MetaCo
|
|||||||
} else if (objField.Name == "PhysicsConstraint" && obj.PhysicsConstraint.has_value()) {
|
} else if (objField.Name == "PhysicsConstraint" && obj.PhysicsConstraint.has_value()) {
|
||||||
const auto& value = *obj.PhysicsConstraint;
|
const auto& value = *obj.PhysicsConstraint;
|
||||||
objJson["PhysicsConstraint"] = {{"Enabled", value.Enabled}, {"Type", static_cast<int>(value.Type)},
|
objJson["PhysicsConstraint"] = {{"Enabled", value.Enabled}, {"Type", static_cast<int>(value.Type)},
|
||||||
{"TargetObjectId", value.TargetObjectId}, {"Anchor", Vec3ToJson(value.Anchor)},
|
{"TargetObjectId", value.TargetObjectId}, {"TargetObjectReference", ObjectReferenceToJson(value.TargetObjectReference)},
|
||||||
|
{"Anchor", Vec3ToJson(value.Anchor)},
|
||||||
{"TargetAnchor", Vec3ToJson(value.TargetAnchor)}, {"Axis", Vec3ToJson(value.Axis)},
|
{"TargetAnchor", Vec3ToJson(value.TargetAnchor)}, {"Axis", Vec3ToJson(value.Axis)},
|
||||||
{"LinearLowerLimit", Vec3ToJson(value.LinearLowerLimit)}, {"LinearUpperLimit", Vec3ToJson(value.LinearUpperLimit)},
|
{"LinearLowerLimit", Vec3ToJson(value.LinearLowerLimit)}, {"LinearUpperLimit", Vec3ToJson(value.LinearUpperLimit)},
|
||||||
{"AngularLowerLimit", Vec3ToJson(value.AngularLowerLimit)}, {"AngularUpperLimit", Vec3ToJson(value.AngularUpperLimit)},
|
{"AngularLowerLimit", Vec3ToJson(value.AngularLowerLimit)}, {"AngularUpperLimit", Vec3ToJson(value.AngularUpperLimit)},
|
||||||
@ -310,6 +338,8 @@ static json GameObjectDataToJson(const MetaCoreGameObjectData& obj, const MetaCo
|
|||||||
if (prefabField.Name == "PrefabAssetGuid") prefabJson["PrefabAssetGuid"] = GuidToString(obj.PrefabInstance->PrefabAssetGuid);
|
if (prefabField.Name == "PrefabAssetGuid") prefabJson["PrefabAssetGuid"] = GuidToString(obj.PrefabInstance->PrefabAssetGuid);
|
||||||
else if (prefabField.Name == "PrefabObjectId") prefabJson["PrefabObjectId"] = obj.PrefabInstance->PrefabObjectId;
|
else if (prefabField.Name == "PrefabObjectId") prefabJson["PrefabObjectId"] = obj.PrefabInstance->PrefabObjectId;
|
||||||
else if (prefabField.Name == "PrefabInstanceRootId") prefabJson["PrefabInstanceRootId"] = obj.PrefabInstance->PrefabInstanceRootId;
|
else if (prefabField.Name == "PrefabInstanceRootId") prefabJson["PrefabInstanceRootId"] = obj.PrefabInstance->PrefabInstanceRootId;
|
||||||
|
else if (prefabField.Name == "InstanceGuid") prefabJson["InstanceGuid"] = GuidToString(obj.PrefabInstance->InstanceGuid);
|
||||||
|
else if (prefabField.Name == "PrefabNodeGuid") prefabJson["PrefabNodeGuid"] = GuidToString(obj.PrefabInstance->PrefabNodeGuid);
|
||||||
}
|
}
|
||||||
objJson["PrefabInstance"] = prefabJson;
|
objJson["PrefabInstance"] = prefabJson;
|
||||||
}
|
}
|
||||||
@ -329,6 +359,10 @@ static MetaCoreGameObjectData JsonToGameObjectData(const json& objJson, const Me
|
|||||||
obj.Id = objJson["Id"].get<MetaCoreId>();
|
obj.Id = objJson["Id"].get<MetaCoreId>();
|
||||||
} else if (objField.Name == "ParentId" && objJson.contains("ParentId")) {
|
} else if (objField.Name == "ParentId" && objJson.contains("ParentId")) {
|
||||||
obj.ParentId = objJson["ParentId"].get<MetaCoreId>();
|
obj.ParentId = objJson["ParentId"].get<MetaCoreId>();
|
||||||
|
} else if (objField.Name == "SceneGuid" && objJson.contains("SceneGuid")) {
|
||||||
|
obj.SceneGuid = StringToGuid(objJson["SceneGuid"].get<std::string>());
|
||||||
|
} else if (objField.Name == "StableObjectGuid" && objJson.contains("StableObjectGuid")) {
|
||||||
|
obj.StableObjectGuid = StringToGuid(objJson["StableObjectGuid"].get<std::string>());
|
||||||
} else if (objField.Name == "Name" && objJson.contains("Name")) {
|
} else if (objField.Name == "Name" && objJson.contains("Name")) {
|
||||||
obj.Name = objJson["Name"].get<std::string>();
|
obj.Name = objJson["Name"].get<std::string>();
|
||||||
} else if (objField.Name == "Transform" && objJson.contains("Transform")) {
|
} else if (objField.Name == "Transform" && objJson.contains("Transform")) {
|
||||||
@ -457,7 +491,13 @@ static MetaCoreGameObjectData JsonToGameObjectData(const json& objJson, const Me
|
|||||||
director.PlayOnStart = value.value("PlayOnStart", true);
|
director.PlayOnStart = value.value("PlayOnStart", true);
|
||||||
director.Loop = value.value("Loop", false);
|
director.Loop = value.value("Loop", false);
|
||||||
director.Speed = value.value("Speed", 1.0F);
|
director.Speed = value.value("Speed", 1.0F);
|
||||||
for (const auto& binding : value.value("Bindings", json::array())) director.Bindings.push_back({binding.value("Slot", ""), binding.value("ObjectId", MetaCoreId{})});
|
for (const auto& binding : value.value("Bindings", json::array())) {
|
||||||
|
MetaCoreTimelineBinding result;
|
||||||
|
result.Slot = binding.value("Slot", "");
|
||||||
|
result.ObjectId = binding.value("ObjectId", MetaCoreId{});
|
||||||
|
if (binding.contains("ObjectReference")) result.ObjectReference = JsonToObjectReference(binding["ObjectReference"]);
|
||||||
|
director.Bindings.push_back(std::move(result));
|
||||||
|
}
|
||||||
obj.TimelineDirector = std::move(director);
|
obj.TimelineDirector = std::move(director);
|
||||||
} else if (objField.Name == "Script" && objJson.contains("Script")) {
|
} else if (objField.Name == "Script" && objJson.contains("Script")) {
|
||||||
const auto& scriptJson = objJson["Script"];
|
const auto& scriptJson = objJson["Script"];
|
||||||
@ -537,6 +577,7 @@ static MetaCoreGameObjectData JsonToGameObjectData(const json& objJson, const Me
|
|||||||
MetaCorePhysicsConstraintComponent component;
|
MetaCorePhysicsConstraintComponent component;
|
||||||
component.Enabled = value.value("Enabled", true); component.Type = static_cast<MetaCorePhysicsConstraintType>(value.value("Type", 0));
|
component.Enabled = value.value("Enabled", true); component.Type = static_cast<MetaCorePhysicsConstraintType>(value.value("Type", 0));
|
||||||
component.TargetObjectId = value.value("TargetObjectId", MetaCoreId{0});
|
component.TargetObjectId = value.value("TargetObjectId", MetaCoreId{0});
|
||||||
|
if (value.contains("TargetObjectReference")) component.TargetObjectReference = JsonToObjectReference(value["TargetObjectReference"]);
|
||||||
if (value.contains("Anchor")) component.Anchor = JsonToVec3(value["Anchor"]);
|
if (value.contains("Anchor")) component.Anchor = JsonToVec3(value["Anchor"]);
|
||||||
if (value.contains("TargetAnchor")) component.TargetAnchor = JsonToVec3(value["TargetAnchor"]);
|
if (value.contains("TargetAnchor")) component.TargetAnchor = JsonToVec3(value["TargetAnchor"]);
|
||||||
if (value.contains("Axis")) component.Axis = JsonToVec3(value["Axis"]);
|
if (value.contains("Axis")) component.Axis = JsonToVec3(value["Axis"]);
|
||||||
@ -595,6 +636,8 @@ static MetaCoreGameObjectData JsonToGameObjectData(const json& objJson, const Me
|
|||||||
if (prefabField.Name == "PrefabAssetGuid" && prefabJson.contains("PrefabAssetGuid")) prefab.PrefabAssetGuid = StringToGuid(prefabJson["PrefabAssetGuid"].get<std::string>());
|
if (prefabField.Name == "PrefabAssetGuid" && prefabJson.contains("PrefabAssetGuid")) prefab.PrefabAssetGuid = StringToGuid(prefabJson["PrefabAssetGuid"].get<std::string>());
|
||||||
else if (prefabField.Name == "PrefabObjectId" && prefabJson.contains("PrefabObjectId")) prefab.PrefabObjectId = prefabJson["PrefabObjectId"].get<MetaCoreId>();
|
else if (prefabField.Name == "PrefabObjectId" && prefabJson.contains("PrefabObjectId")) prefab.PrefabObjectId = prefabJson["PrefabObjectId"].get<MetaCoreId>();
|
||||||
else if (prefabField.Name == "PrefabInstanceRootId" && prefabJson.contains("PrefabInstanceRootId")) prefab.PrefabInstanceRootId = prefabJson["PrefabInstanceRootId"].get<MetaCoreId>();
|
else if (prefabField.Name == "PrefabInstanceRootId" && prefabJson.contains("PrefabInstanceRootId")) prefab.PrefabInstanceRootId = prefabJson["PrefabInstanceRootId"].get<MetaCoreId>();
|
||||||
|
else if (prefabField.Name == "InstanceGuid" && prefabJson.contains("InstanceGuid")) prefab.InstanceGuid = StringToGuid(prefabJson["InstanceGuid"].get<std::string>());
|
||||||
|
else if (prefabField.Name == "PrefabNodeGuid" && prefabJson.contains("PrefabNodeGuid")) prefab.PrefabNodeGuid = StringToGuid(prefabJson["PrefabNodeGuid"].get<std::string>());
|
||||||
}
|
}
|
||||||
obj.PrefabInstance = prefab;
|
obj.PrefabInstance = prefab;
|
||||||
}
|
}
|
||||||
@ -603,13 +646,120 @@ static MetaCoreGameObjectData JsonToGameObjectData(const json& objJson, const Me
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static json PrefabOverrideToJson(const MetaCorePrefabOverrideDocument& value) {
|
||||||
|
return {
|
||||||
|
{"TargetNodeGuid", GuidToString(value.TargetNodeGuid)},
|
||||||
|
{"ComponentTypeId", value.ComponentTypeId},
|
||||||
|
{"PropertyPath", value.PropertyPath},
|
||||||
|
{"Operation", static_cast<int>(value.Operation)},
|
||||||
|
{"ValueJson", value.ValueJson}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static MetaCorePrefabOverrideDocument JsonToPrefabOverride(const json& value) {
|
||||||
|
MetaCorePrefabOverrideDocument result;
|
||||||
|
result.TargetNodeGuid = StringToGuid(value.value("TargetNodeGuid", std::string{}));
|
||||||
|
result.ComponentTypeId = value.value("ComponentTypeId", std::string{});
|
||||||
|
result.PropertyPath = value.value("PropertyPath", std::string{});
|
||||||
|
result.Operation = static_cast<MetaCorePrefabOverrideOperation>(value.value("Operation", 0));
|
||||||
|
result.ValueJson = value.value("ValueJson", std::string{});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static json SchemaVersionsToJson(const std::vector<MetaCoreSchemaVersionEntry>& versions) {
|
||||||
|
json result = json::array();
|
||||||
|
for (const auto& version : versions) {
|
||||||
|
result.push_back({{"TypeId", version.TypeId}, {"Version", version.Version}});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<MetaCoreSchemaVersionEntry> JsonToSchemaVersions(const json& values) {
|
||||||
|
std::vector<MetaCoreSchemaVersionEntry> result;
|
||||||
|
if (!values.is_array()) return result;
|
||||||
|
for (const auto& value : values) {
|
||||||
|
result.push_back({value.value("TypeId", std::string{}), value.value("Version", 1U)});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MetaCoreAssetGuid BuildLegacyDocumentGuid(std::string_view kind, std::string_view name) {
|
||||||
|
// Legacy documents do not carry an asset GUID. Derive one from persisted
|
||||||
|
// content so repeated migration/save runs are deterministic.
|
||||||
|
std::uint64_t first = 1469598103934665603ULL;
|
||||||
|
std::uint64_t second = 1099511628211ULL;
|
||||||
|
const auto mix = [&](std::string_view value) {
|
||||||
|
for (const unsigned char character : value) {
|
||||||
|
first = (first ^ character) * 1099511628211ULL;
|
||||||
|
second = (second + character + 0x9e3779b97f4a7c15ULL) * 14029467366897019727ULL;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mix(kind);
|
||||||
|
mix(name);
|
||||||
|
MetaCoreAssetGuid result;
|
||||||
|
for (std::size_t index = 0; index < 8; ++index) {
|
||||||
|
result.Bytes[index] = static_cast<std::uint8_t>(first >> (index * 8U));
|
||||||
|
result.Bytes[index + 8] = static_cast<std::uint8_t>(second >> (index * 8U));
|
||||||
|
}
|
||||||
|
result.Bytes[6] = static_cast<std::uint8_t>((result.Bytes[6] & 0x0FU) | 0x50U);
|
||||||
|
result.Bytes[8] = static_cast<std::uint8_t>((result.Bytes[8] & 0x3FU) | 0x80U);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void NormalizeSceneDocumentIdentity(MetaCoreSceneDocument& document) {
|
||||||
|
if (!document.SceneGuid.IsValid()) document.SceneGuid = BuildLegacyDocumentGuid("scene:", document.Name);
|
||||||
|
std::unordered_map<MetaCoreId, MetaCoreAssetGuid> stableIds;
|
||||||
|
for (auto& object : document.GameObjects) {
|
||||||
|
object.SceneGuid = document.SceneGuid;
|
||||||
|
if (!object.StableObjectGuid.IsValid())
|
||||||
|
object.StableObjectGuid = MetaCoreBuildDeterministicGuid(document.SceneGuid, object.Id);
|
||||||
|
stableIds[object.Id] = object.StableObjectGuid;
|
||||||
|
}
|
||||||
|
for (auto& object : document.GameObjects) {
|
||||||
|
if (object.PhysicsConstraint) {
|
||||||
|
auto& reference = object.PhysicsConstraint->TargetObjectReference;
|
||||||
|
if (!reference.IsValid()) {
|
||||||
|
if (const auto target = stableIds.find(object.PhysicsConstraint->TargetObjectId); target != stableIds.end()) {
|
||||||
|
reference.SceneGuid = document.SceneGuid;
|
||||||
|
reference.StableObjectGuid = target->second;
|
||||||
|
reference.Strength = MetaCoreObjectReferenceStrength::Hard;
|
||||||
|
}
|
||||||
|
} else if (!reference.SceneGuid.IsValid()) reference.SceneGuid = document.SceneGuid;
|
||||||
|
}
|
||||||
|
if (object.TimelineDirector) {
|
||||||
|
for (auto& binding : object.TimelineDirector->Bindings) {
|
||||||
|
if (!binding.ObjectReference.IsValid()) {
|
||||||
|
if (const auto target = stableIds.find(binding.ObjectId); target != stableIds.end()) {
|
||||||
|
binding.ObjectReference.SceneGuid = document.SceneGuid;
|
||||||
|
binding.ObjectReference.StableObjectGuid = target->second;
|
||||||
|
}
|
||||||
|
} else if (!binding.ObjectReference.SceneGuid.IsValid()) binding.ObjectReference.SceneGuid = document.SceneGuid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.SchemaVersion = GMetaCoreSceneSchemaVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void NormalizePrefabDocumentIdentity(MetaCorePrefabDocument& document) {
|
||||||
|
if (!document.PrefabGuid.IsValid()) document.PrefabGuid = BuildLegacyDocumentGuid("prefab:", document.Name);
|
||||||
|
for (auto& object : document.GameObjects) {
|
||||||
|
if (!object.StableObjectGuid.IsValid())
|
||||||
|
object.StableObjectGuid = MetaCoreBuildDeterministicGuid(document.PrefabGuid, object.Id);
|
||||||
|
}
|
||||||
|
document.SchemaVersion = GMetaCorePrefabSchemaVersion;
|
||||||
|
}
|
||||||
|
|
||||||
bool MetaCoreSceneSerializer::SaveSceneToJson(
|
bool MetaCoreSceneSerializer::SaveSceneToJson(
|
||||||
const std::filesystem::path& absolutePath,
|
const std::filesystem::path& absolutePath,
|
||||||
const MetaCoreSceneDocument& sceneDocument,
|
const MetaCoreSceneDocument& sceneDocument,
|
||||||
const MetaCoreTypeRegistry& registry
|
const MetaCoreTypeRegistry& registry
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
|
MetaCoreSceneDocument normalized = sceneDocument;
|
||||||
|
NormalizeSceneDocumentIdentity(normalized);
|
||||||
json sceneJson;
|
json sceneJson;
|
||||||
|
sceneJson["SchemaVersion"] = normalized.SchemaVersion;
|
||||||
|
sceneJson["SceneGuid"] = GuidToString(normalized.SceneGuid);
|
||||||
|
|
||||||
// 验证 MetaCoreSceneDocument 反射注册
|
// 验证 MetaCoreSceneDocument 反射注册
|
||||||
const MetaCoreStructDescriptor* sceneDesc = registry.FindStruct<MetaCoreSceneDocument>();
|
const MetaCoreStructDescriptor* sceneDesc = registry.FindStruct<MetaCoreSceneDocument>();
|
||||||
@ -620,26 +770,55 @@ bool MetaCoreSceneSerializer::SaveSceneToJson(
|
|||||||
|
|
||||||
for (const auto& field : sceneDesc->Fields) {
|
for (const auto& field : sceneDesc->Fields) {
|
||||||
if (field.Name == "Name") {
|
if (field.Name == "Name") {
|
||||||
sceneJson["SceneName"] = sceneDocument.Name;
|
sceneJson["SceneName"] = normalized.Name;
|
||||||
} else if (field.Name == "Environment") {
|
} else if (field.Name == "Environment") {
|
||||||
sceneJson["Environment"] = SceneEnvironmentToJson(sceneDocument.Environment);
|
sceneJson["Environment"] = SceneEnvironmentToJson(normalized.Environment);
|
||||||
} else if (field.Name == "Selection") {
|
} else if (field.Name == "Selection") {
|
||||||
const MetaCoreStructDescriptor* selDesc = registry.FindStruct<MetaCoreEditorSelectionSnapshot>();
|
const MetaCoreStructDescriptor* selDesc = registry.FindStruct<MetaCoreEditorSelectionSnapshot>();
|
||||||
if (selDesc) {
|
if (selDesc) {
|
||||||
json selJson = json::object();
|
json selJson = json::object();
|
||||||
for (const auto& selField : selDesc->Fields) {
|
for (const auto& selField : selDesc->Fields) {
|
||||||
if (selField.Name == "SelectedObjectIds") selJson["SelectedObjectIds"] = sceneDocument.Selection.SelectedObjectIds;
|
if (selField.Name == "SelectedObjectIds") selJson["SelectedObjectIds"] = normalized.Selection.SelectedObjectIds;
|
||||||
else if (selField.Name == "ActiveObjectId") selJson["ActiveObjectId"] = sceneDocument.Selection.ActiveObjectId;
|
else if (selField.Name == "ActiveObjectId") selJson["ActiveObjectId"] = normalized.Selection.ActiveObjectId;
|
||||||
else if (selField.Name == "SelectionAnchorId") selJson["SelectionAnchorId"] = sceneDocument.Selection.SelectionAnchorId;
|
else if (selField.Name == "SelectionAnchorId") selJson["SelectionAnchorId"] = normalized.Selection.SelectionAnchorId;
|
||||||
}
|
}
|
||||||
sceneJson["Selection"] = selJson;
|
sceneJson["Selection"] = selJson;
|
||||||
}
|
}
|
||||||
} else if (field.Name == "GameObjects") {
|
} else if (field.Name == "GameObjects") {
|
||||||
json gameObjectsArray = json::array();
|
json gameObjectsArray = json::array();
|
||||||
for (const auto& obj : sceneDocument.GameObjects) {
|
for (const auto& obj : normalized.GameObjects) {
|
||||||
gameObjectsArray.push_back(GameObjectDataToJson(obj, registry));
|
gameObjectsArray.push_back(GameObjectDataToJson(obj, registry));
|
||||||
}
|
}
|
||||||
sceneJson["GameObjects"] = gameObjectsArray;
|
sceneJson["GameObjects"] = gameObjectsArray;
|
||||||
|
} else if (field.Name == "SubScenes") {
|
||||||
|
sceneJson["SubScenes"] = json::array();
|
||||||
|
for (const auto& subScene : normalized.SubScenes) {
|
||||||
|
sceneJson["SubScenes"].push_back({
|
||||||
|
{"SceneGuid", GuidToString(subScene.SceneGuid)},
|
||||||
|
{"ScenePath", subScene.ScenePath.generic_string()},
|
||||||
|
{"LoadPolicy", static_cast<int>(subScene.LoadPolicy)},
|
||||||
|
{"BoundsCenter", Vec3ToJson(subScene.BoundsCenter)},
|
||||||
|
{"BoundsExtent", Vec3ToJson(subScene.BoundsExtent)},
|
||||||
|
{"CellCoordinate", Vec3ToJson(subScene.CellCoordinate)},
|
||||||
|
{"PreloadDistance", subScene.PreloadDistance},
|
||||||
|
{"UnloadDistance", subScene.UnloadDistance},
|
||||||
|
{"Priority", subScene.Priority}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (field.Name == "PrefabInstances") {
|
||||||
|
sceneJson["PrefabInstances"] = json::array();
|
||||||
|
for (const auto& instance : normalized.PrefabInstances) {
|
||||||
|
json overrides = json::array();
|
||||||
|
for (const auto& entry : instance.Overrides) overrides.push_back(PrefabOverrideToJson(entry));
|
||||||
|
sceneJson["PrefabInstances"].push_back({
|
||||||
|
{"InstanceGuid", GuidToString(instance.InstanceGuid)},
|
||||||
|
{"PrefabAssetGuid", GuidToString(instance.PrefabAssetGuid)},
|
||||||
|
{"ParentObjectGuid", GuidToString(instance.ParentObjectGuid)},
|
||||||
|
{"Overrides", std::move(overrides)}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (field.Name == "ComponentVersions") {
|
||||||
|
sceneJson["ComponentVersions"] = SchemaVersionsToJson(normalized.ComponentVersions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -672,6 +851,10 @@ std::optional<MetaCoreSceneDocument> MetaCoreSceneSerializer::LoadSceneFromJson(
|
|||||||
file >> sceneJson;
|
file >> sceneJson;
|
||||||
|
|
||||||
MetaCoreSceneDocument sceneDocument;
|
MetaCoreSceneDocument sceneDocument;
|
||||||
|
const std::uint32_t sourceVersion = sceneJson.value("SchemaVersion", 1U);
|
||||||
|
if (sourceVersion > GMetaCoreSceneSchemaVersion) return std::nullopt;
|
||||||
|
sceneDocument.SchemaVersion = sourceVersion;
|
||||||
|
if (sceneJson.contains("SceneGuid")) sceneDocument.SceneGuid = StringToGuid(sceneJson["SceneGuid"].get<std::string>());
|
||||||
|
|
||||||
// 验证 MetaCoreSceneDocument 反射注册
|
// 验证 MetaCoreSceneDocument 反射注册
|
||||||
const MetaCoreStructDescriptor* sceneDesc = registry.FindStruct<MetaCoreSceneDocument>();
|
const MetaCoreStructDescriptor* sceneDesc = registry.FindStruct<MetaCoreSceneDocument>();
|
||||||
@ -703,9 +886,35 @@ std::optional<MetaCoreSceneDocument> MetaCoreSceneSerializer::LoadSceneFromJson(
|
|||||||
for (const auto& objJson : sceneJson["GameObjects"]) {
|
for (const auto& objJson : sceneJson["GameObjects"]) {
|
||||||
sceneDocument.GameObjects.push_back(JsonToGameObjectData(objJson, registry));
|
sceneDocument.GameObjects.push_back(JsonToGameObjectData(objJson, registry));
|
||||||
}
|
}
|
||||||
|
} else if (field.Name == "SubScenes" && sceneJson.contains("SubScenes") && sceneJson["SubScenes"].is_array()) {
|
||||||
|
for (const auto& value : sceneJson["SubScenes"]) {
|
||||||
|
MetaCoreSubSceneDocument subScene;
|
||||||
|
subScene.SceneGuid = StringToGuid(value.value("SceneGuid", std::string{}));
|
||||||
|
subScene.ScenePath = value.value("ScenePath", std::string{});
|
||||||
|
subScene.LoadPolicy = static_cast<MetaCoreSubSceneLoadPolicy>(value.value("LoadPolicy", 0));
|
||||||
|
if (value.contains("BoundsCenter")) subScene.BoundsCenter = JsonToVec3(value["BoundsCenter"]);
|
||||||
|
if (value.contains("BoundsExtent")) subScene.BoundsExtent = JsonToVec3(value["BoundsExtent"]);
|
||||||
|
if (value.contains("CellCoordinate")) subScene.CellCoordinate = JsonToVec3(value["CellCoordinate"]);
|
||||||
|
subScene.PreloadDistance = value.value("PreloadDistance", 100.0F);
|
||||||
|
subScene.UnloadDistance = value.value("UnloadDistance", 125.0F);
|
||||||
|
subScene.Priority = value.value("Priority", 0);
|
||||||
|
sceneDocument.SubScenes.push_back(std::move(subScene));
|
||||||
|
}
|
||||||
|
} else if (field.Name == "PrefabInstances" && sceneJson.contains("PrefabInstances") && sceneJson["PrefabInstances"].is_array()) {
|
||||||
|
for (const auto& value : sceneJson["PrefabInstances"]) {
|
||||||
|
MetaCorePrefabInstanceDocument instance;
|
||||||
|
instance.InstanceGuid = StringToGuid(value.value("InstanceGuid", std::string{}));
|
||||||
|
instance.PrefabAssetGuid = StringToGuid(value.value("PrefabAssetGuid", std::string{}));
|
||||||
|
instance.ParentObjectGuid = StringToGuid(value.value("ParentObjectGuid", std::string{}));
|
||||||
|
if (value.contains("Overrides")) for (const auto& entry : value["Overrides"]) instance.Overrides.push_back(JsonToPrefabOverride(entry));
|
||||||
|
sceneDocument.PrefabInstances.push_back(std::move(instance));
|
||||||
|
}
|
||||||
|
} else if (field.Name == "ComponentVersions" && sceneJson.contains("ComponentVersions")) {
|
||||||
|
sceneDocument.ComponentVersions = JsonToSchemaVersions(sceneJson["ComponentVersions"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NormalizeSceneDocumentIdentity(sceneDocument);
|
||||||
return sceneDocument;
|
return sceneDocument;
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
std::cerr << "LoadSceneFromJson: 发生异常 - " << e.what() << std::endl;
|
std::cerr << "LoadSceneFromJson: 发生异常 - " << e.what() << std::endl;
|
||||||
@ -720,13 +929,32 @@ bool MetaCoreSceneSerializer::SavePrefabToJson(
|
|||||||
const MetaCoreTypeRegistry& registry
|
const MetaCoreTypeRegistry& registry
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
|
MetaCorePrefabDocument normalized = prefabDocument;
|
||||||
|
NormalizePrefabDocumentIdentity(normalized);
|
||||||
json prefabJson;
|
json prefabJson;
|
||||||
prefabJson["PrefabName"] = prefabDocument.Name;
|
prefabJson["SchemaVersion"] = normalized.SchemaVersion;
|
||||||
|
prefabJson["PrefabGuid"] = GuidToString(normalized.PrefabGuid);
|
||||||
|
prefabJson["BasePrefabGuid"] = GuidToString(normalized.BasePrefabGuid);
|
||||||
|
prefabJson["PrefabName"] = normalized.Name;
|
||||||
json gameObjectsArray = json::array();
|
json gameObjectsArray = json::array();
|
||||||
for (const auto& obj : prefabDocument.GameObjects) {
|
for (const auto& obj : normalized.GameObjects) {
|
||||||
gameObjectsArray.push_back(GameObjectDataToJson(obj, registry));
|
gameObjectsArray.push_back(GameObjectDataToJson(obj, registry));
|
||||||
}
|
}
|
||||||
prefabJson["GameObjects"] = gameObjectsArray;
|
prefabJson["GameObjects"] = gameObjectsArray;
|
||||||
|
prefabJson["Overrides"] = json::array();
|
||||||
|
for (const auto& value : normalized.Overrides) prefabJson["Overrides"].push_back(PrefabOverrideToJson(value));
|
||||||
|
prefabJson["NestedPrefabs"] = json::array();
|
||||||
|
for (const auto& nested : normalized.NestedPrefabs) {
|
||||||
|
json overrides = json::array();
|
||||||
|
for (const auto& value : nested.Overrides) overrides.push_back(PrefabOverrideToJson(value));
|
||||||
|
prefabJson["NestedPrefabs"].push_back({
|
||||||
|
{"MountNodeGuid", GuidToString(nested.MountNodeGuid)},
|
||||||
|
{"PrefabAssetGuid", GuidToString(nested.PrefabAssetGuid)},
|
||||||
|
{"InstanceGuid", GuidToString(nested.InstanceGuid)},
|
||||||
|
{"Overrides", std::move(overrides)}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
prefabJson["ComponentVersions"] = SchemaVersionsToJson(normalized.ComponentVersions);
|
||||||
|
|
||||||
std::ofstream file(absolutePath);
|
std::ofstream file(absolutePath);
|
||||||
if (!file.is_open()) {
|
if (!file.is_open()) {
|
||||||
@ -752,6 +980,11 @@ std::optional<MetaCorePrefabDocument> MetaCoreSceneSerializer::LoadPrefabFromJso
|
|||||||
file >> prefabJson;
|
file >> prefabJson;
|
||||||
|
|
||||||
MetaCorePrefabDocument doc;
|
MetaCorePrefabDocument doc;
|
||||||
|
const std::uint32_t sourceVersion = prefabJson.value("SchemaVersion", 1U);
|
||||||
|
if (sourceVersion > GMetaCorePrefabSchemaVersion) return std::nullopt;
|
||||||
|
doc.SchemaVersion = sourceVersion;
|
||||||
|
doc.PrefabGuid = StringToGuid(prefabJson.value("PrefabGuid", std::string{}));
|
||||||
|
doc.BasePrefabGuid = StringToGuid(prefabJson.value("BasePrefabGuid", std::string{}));
|
||||||
if (prefabJson.contains("PrefabName")) {
|
if (prefabJson.contains("PrefabName")) {
|
||||||
doc.Name = prefabJson["PrefabName"].get<std::string>();
|
doc.Name = prefabJson["PrefabName"].get<std::string>();
|
||||||
}
|
}
|
||||||
@ -760,6 +993,17 @@ std::optional<MetaCorePrefabDocument> MetaCoreSceneSerializer::LoadPrefabFromJso
|
|||||||
doc.GameObjects.push_back(JsonToGameObjectData(objJson, registry));
|
doc.GameObjects.push_back(JsonToGameObjectData(objJson, registry));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (prefabJson.contains("Overrides")) for (const auto& value : prefabJson["Overrides"]) doc.Overrides.push_back(JsonToPrefabOverride(value));
|
||||||
|
if (prefabJson.contains("NestedPrefabs")) for (const auto& value : prefabJson["NestedPrefabs"]) {
|
||||||
|
MetaCoreNestedPrefabDocument nested;
|
||||||
|
nested.MountNodeGuid = StringToGuid(value.value("MountNodeGuid", std::string{}));
|
||||||
|
nested.PrefabAssetGuid = StringToGuid(value.value("PrefabAssetGuid", std::string{}));
|
||||||
|
nested.InstanceGuid = StringToGuid(value.value("InstanceGuid", std::string{}));
|
||||||
|
if (value.contains("Overrides")) for (const auto& entry : value["Overrides"]) nested.Overrides.push_back(JsonToPrefabOverride(entry));
|
||||||
|
doc.NestedPrefabs.push_back(std::move(nested));
|
||||||
|
}
|
||||||
|
if (prefabJson.contains("ComponentVersions")) doc.ComponentVersions = JsonToSchemaVersions(prefabJson["ComponentVersions"]);
|
||||||
|
NormalizePrefabDocumentIdentity(doc);
|
||||||
return doc;
|
return doc;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|||||||
565
Source/MetaCoreScene/Private/MetaCoreSceneWorld.cpp
Normal file
565
Source/MetaCoreScene/Private/MetaCoreSceneWorld.cpp
Normal file
@ -0,0 +1,565 @@
|
|||||||
|
#include "MetaCoreScene/MetaCoreSceneWorld.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace MetaCore {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void HashBytes(std::uint64_t& hash, const std::uint8_t* bytes, std::size_t count) {
|
||||||
|
for (std::size_t index = 0; index < count; ++index) {
|
||||||
|
hash ^= bytes[index];
|
||||||
|
hash *= 1099511628211ULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float DistanceToBounds(const glm::vec3& point, const glm::vec3& center, const glm::vec3& extent) {
|
||||||
|
const glm::vec3 delta = glm::abs(point - center) - glm::max(extent, glm::vec3(0.0F));
|
||||||
|
const glm::vec3 outside = glm::max(delta, glm::vec3(0.0F));
|
||||||
|
return glm::length(outside);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemapLegacyObjectReferences(MetaCoreGameObjectData& object, const std::unordered_map<MetaCoreId, MetaCoreId>& ids) {
|
||||||
|
if (const auto parent = ids.find(object.ParentId); parent != ids.end()) object.ParentId = parent->second;
|
||||||
|
else object.ParentId = 0;
|
||||||
|
if (object.PhysicsConstraint) {
|
||||||
|
if (const auto target = ids.find(object.PhysicsConstraint->TargetObjectId); target != ids.end())
|
||||||
|
object.PhysicsConstraint->TargetObjectId = target->second;
|
||||||
|
else object.PhysicsConstraint->TargetObjectId = 0;
|
||||||
|
}
|
||||||
|
if (object.TimelineDirector) {
|
||||||
|
for (auto& binding : object.TimelineDirector->Bindings) {
|
||||||
|
if (const auto target = ids.find(binding.ObjectId); target != ids.end()) binding.ObjectId = target->second;
|
||||||
|
else binding.ObjectId = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
struct MetaCoreSceneWorld::PendingLoad {
|
||||||
|
std::uint64_t RequestId = 0;
|
||||||
|
MetaCoreAssetGuid SceneGuid{};
|
||||||
|
MetaCoreSceneLoadMode Mode = MetaCoreSceneLoadMode::Additive;
|
||||||
|
std::int32_t Priority = 0;
|
||||||
|
bool Cancelled = false;
|
||||||
|
std::future<std::optional<MetaCoreSceneDocument>> Future{};
|
||||||
|
};
|
||||||
|
|
||||||
|
MetaCoreAssetGuid MetaCoreBuildDeterministicGuid(const MetaCoreAssetGuid& namespaceGuid, const MetaCoreAssetGuid& localGuid) {
|
||||||
|
std::uint64_t first = 1469598103934665603ULL;
|
||||||
|
HashBytes(first, namespaceGuid.Bytes.data(), namespaceGuid.Bytes.size());
|
||||||
|
HashBytes(first, localGuid.Bytes.data(), localGuid.Bytes.size());
|
||||||
|
std::uint64_t second = 1099511628211ULL ^ first;
|
||||||
|
HashBytes(second, localGuid.Bytes.data(), localGuid.Bytes.size());
|
||||||
|
HashBytes(second, namespaceGuid.Bytes.data(), namespaceGuid.Bytes.size());
|
||||||
|
MetaCoreAssetGuid result;
|
||||||
|
for (std::size_t index = 0; index < 8; ++index) {
|
||||||
|
result.Bytes[index] = static_cast<std::uint8_t>((first >> (index * 8U)) & 0xFFU);
|
||||||
|
result.Bytes[index + 8] = static_cast<std::uint8_t>((second >> (index * 8U)) & 0xFFU);
|
||||||
|
}
|
||||||
|
result.Bytes[6] = static_cast<std::uint8_t>((result.Bytes[6] & 0x0FU) | 0x50U);
|
||||||
|
result.Bytes[8] = static_cast<std::uint8_t>((result.Bytes[8] & 0x3FU) | 0x80U);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
MetaCoreAssetGuid MetaCoreBuildDeterministicGuid(const MetaCoreAssetGuid& namespaceGuid, std::uint64_t localValue) {
|
||||||
|
MetaCoreAssetGuid local;
|
||||||
|
for (std::size_t index = 0; index < 8; ++index)
|
||||||
|
local.Bytes[index] = static_cast<std::uint8_t>((localValue >> (index * 8U)) & 0xFFU);
|
||||||
|
return MetaCoreBuildDeterministicGuid(namespaceGuid, local);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t MetaCoreSceneWorld::ObjectKeyHasher::operator()(const ObjectKey& value) const noexcept {
|
||||||
|
MetaCoreAssetGuidHasher hasher;
|
||||||
|
return hasher(value.SceneGuid) ^ (hasher(value.ObjectGuid) + 0x9e3779b97f4a7c15ULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
MetaCoreSceneWorld::MetaCoreSceneWorld(SceneLoader loader, MetaCoreSceneWorldSettings settings)
|
||||||
|
: Loader_(std::move(loader)), Settings_(settings), OwnedScene_(std::make_unique<MetaCoreScene>()), Scene_(OwnedScene_.get()) {}
|
||||||
|
|
||||||
|
MetaCoreSceneWorld::MetaCoreSceneWorld(MetaCoreScene& scene, SceneLoader loader, MetaCoreSceneWorldSettings settings)
|
||||||
|
: Loader_(std::move(loader)), Settings_(settings), Scene_(&scene) {}
|
||||||
|
|
||||||
|
MetaCoreSceneWorld::~MetaCoreSceneWorld() = default;
|
||||||
|
|
||||||
|
void MetaCoreSceneWorld::SetSceneLoader(SceneLoader loader) { Loader_ = std::move(loader); }
|
||||||
|
|
||||||
|
std::uint64_t MetaCoreSceneWorld::RequestLoad(
|
||||||
|
const MetaCoreAssetGuid& sceneGuid,
|
||||||
|
MetaCoreSceneLoadMode mode,
|
||||||
|
std::int32_t priority
|
||||||
|
) {
|
||||||
|
for (const auto& pending : PendingLoads_) {
|
||||||
|
if (!pending->Cancelled && pending->SceneGuid == sceneGuid) return pending->RequestId;
|
||||||
|
}
|
||||||
|
const std::uint64_t requestId = NextRequestId_++;
|
||||||
|
MetaCoreSceneLoadStatus status{requestId, sceneGuid, MetaCoreSceneLoadState::Loading, {}};
|
||||||
|
if (!sceneGuid.IsValid() || !Loader_) {
|
||||||
|
status.State = MetaCoreSceneLoadState::Failed;
|
||||||
|
status.Error = !sceneGuid.IsValid() ? "Scene GUID is invalid" : "Scene loader is not configured";
|
||||||
|
LoadStatuses_[requestId] = std::move(status);
|
||||||
|
return requestId;
|
||||||
|
}
|
||||||
|
if (IsSceneLoaded(sceneGuid)) {
|
||||||
|
status.State = MetaCoreSceneLoadState::Loaded;
|
||||||
|
LoadStatuses_[requestId] = std::move(status);
|
||||||
|
return requestId;
|
||||||
|
}
|
||||||
|
auto pending = std::make_unique<PendingLoad>();
|
||||||
|
pending->RequestId = requestId;
|
||||||
|
pending->SceneGuid = sceneGuid;
|
||||||
|
pending->Mode = mode;
|
||||||
|
pending->Priority = priority;
|
||||||
|
SceneLoader loader = Loader_;
|
||||||
|
pending->Future = std::async(std::launch::async, [loader = std::move(loader), sceneGuid] {
|
||||||
|
return loader(sceneGuid);
|
||||||
|
});
|
||||||
|
PendingLoads_.push_back(std::move(pending));
|
||||||
|
LoadStatuses_[requestId] = std::move(status);
|
||||||
|
return requestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MetaCoreSceneWorld::CancelLoad(std::uint64_t requestId) {
|
||||||
|
const auto iterator = std::find_if(PendingLoads_.begin(), PendingLoads_.end(), [&](const auto& pending) {
|
||||||
|
return pending->RequestId == requestId;
|
||||||
|
});
|
||||||
|
if (iterator == PendingLoads_.end()) return false;
|
||||||
|
(*iterator)->Cancelled = true;
|
||||||
|
auto& status = LoadStatuses_[requestId];
|
||||||
|
status.State = MetaCoreSceneLoadState::Cancelled;
|
||||||
|
status.Error = "Load request cancelled";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MetaCoreSceneWorld::Tick() {
|
||||||
|
std::stable_sort(PendingLoads_.begin(), PendingLoads_.end(), [](const auto& lhs, const auto& rhs) {
|
||||||
|
return lhs->Priority > rhs->Priority;
|
||||||
|
});
|
||||||
|
for (auto iterator = PendingLoads_.begin(); iterator != PendingLoads_.end();) {
|
||||||
|
auto& pending = **iterator;
|
||||||
|
if (pending.Future.wait_for(std::chrono::milliseconds(0)) != std::future_status::ready) {
|
||||||
|
++iterator;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::optional<MetaCoreSceneDocument> document = pending.Future.get();
|
||||||
|
auto& status = LoadStatuses_[pending.RequestId];
|
||||||
|
if (pending.Cancelled) {
|
||||||
|
status.State = MetaCoreSceneLoadState::Cancelled;
|
||||||
|
} else if (!document) {
|
||||||
|
status.State = MetaCoreSceneLoadState::Failed;
|
||||||
|
status.Error = "Scene loader returned no document";
|
||||||
|
} else {
|
||||||
|
if (!document->SceneGuid.IsValid()) document->SceneGuid = pending.SceneGuid;
|
||||||
|
std::string error;
|
||||||
|
if (CommitDocument(std::move(*document), pending.Mode, &error)) status.State = MetaCoreSceneLoadState::Loaded;
|
||||||
|
else {
|
||||||
|
status.State = MetaCoreSceneLoadState::Failed;
|
||||||
|
status.Error = std::move(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iterator = PendingLoads_.erase(iterator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MetaCoreSceneWorld::LoadDocumentNow(MetaCoreSceneDocument document, MetaCoreSceneLoadMode mode, std::string* error) {
|
||||||
|
return CommitDocument(std::move(document), mode, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MetaCoreSceneWorld::CommitDocument(MetaCoreSceneDocument document, MetaCoreSceneLoadMode mode, std::string* error) {
|
||||||
|
if (document.SchemaVersion > GMetaCoreSceneSchemaVersion) {
|
||||||
|
if (error) *error = "Scene schema is newer than this engine";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!document.SceneGuid.IsValid()) {
|
||||||
|
if (error) *error = "Scene document has no stable GUID";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (IsSceneLoaded(document.SceneGuid)) {
|
||||||
|
if (error) *error = "Scene is already loaded";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (mode == MetaCoreSceneLoadMode::Replace) {
|
||||||
|
std::vector<MetaCoreAssetGuid> loaded;
|
||||||
|
loaded.reserve(LoadedScenes_.size());
|
||||||
|
for (const auto& [guid, scene] : LoadedScenes_) {
|
||||||
|
(void)scene;
|
||||||
|
loaded.push_back(guid);
|
||||||
|
}
|
||||||
|
for (const auto& guid : loaded) (void)UnloadScene(guid, true);
|
||||||
|
if (LoadedScenes_.empty() && !Scene_->GetGameObjects().empty()) {
|
||||||
|
Scene_->RestoreSnapshot(MetaCoreSceneSnapshot{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<MetaCoreId, MetaCoreId> ids;
|
||||||
|
for (const auto& object : document.GameObjects) ids.emplace(object.Id, MetaCoreIdGenerator::Generate());
|
||||||
|
|
||||||
|
LoadedScene record;
|
||||||
|
record.Source = document;
|
||||||
|
record.Source.SchemaVersion = GMetaCoreSceneSchemaVersion;
|
||||||
|
record.LoadMode = mode;
|
||||||
|
MetaCoreSceneSnapshot combined = Scene_->CaptureSnapshot();
|
||||||
|
if (LoadedScenes_.empty()) combined.Environment = document.Environment;
|
||||||
|
for (auto object : document.GameObjects) {
|
||||||
|
const MetaCoreId serializedId = object.Id;
|
||||||
|
object.Id = ids.at(serializedId);
|
||||||
|
object.SceneGuid = document.SceneGuid;
|
||||||
|
if (!object.StableObjectGuid.IsValid())
|
||||||
|
object.StableObjectGuid = MetaCoreBuildDeterministicGuid(document.SceneGuid, serializedId);
|
||||||
|
RemapLegacyObjectReferences(object, ids);
|
||||||
|
record.RuntimeObjectIds.push_back(object.Id);
|
||||||
|
record.RuntimeToSerializedId.emplace(object.Id, serializedId);
|
||||||
|
combined.GameObjects.push_back(std::move(object));
|
||||||
|
}
|
||||||
|
Scene_->RestoreSnapshot(combined);
|
||||||
|
const MetaCoreAssetGuid sceneGuid = document.SceneGuid;
|
||||||
|
LoadedScenes_.emplace(sceneGuid, std::move(record));
|
||||||
|
if (!ActiveSceneGuid_.IsValid() || mode == MetaCoreSceneLoadMode::Replace) ActiveSceneGuid_ = sceneGuid;
|
||||||
|
RebuildObjectIndex();
|
||||||
|
for (auto object : Scene_->GetGameObjects()) {
|
||||||
|
if (object.HasComponent<MetaCorePhysicsConstraintComponent>()) {
|
||||||
|
auto& constraint = object.GetComponent<MetaCorePhysicsConstraintComponent>();
|
||||||
|
if (constraint.TargetObjectReference.IsValid()) {
|
||||||
|
const auto target = ResolveObject(constraint.TargetObjectReference);
|
||||||
|
constraint.TargetObjectId = target ? target.GetId() : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (object.HasComponent<MetaCoreTimelineDirectorComponent>()) {
|
||||||
|
for (auto& binding : object.GetComponent<MetaCoreTimelineDirectorComponent>().Bindings) {
|
||||||
|
if (binding.ObjectReference.IsValid()) {
|
||||||
|
const auto target = ResolveObject(binding.ObjectReference);
|
||||||
|
binding.ObjectId = target ? target.GetId() : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RebuildHardResidency();
|
||||||
|
Publish({MetaCoreSceneLifecycleEventType::AfterLoad, sceneGuid, LoadedScenes_.at(sceneGuid).RuntimeObjectIds});
|
||||||
|
|
||||||
|
for (const auto& subScene : document.SubScenes) {
|
||||||
|
if (subScene.LoadPolicy == MetaCoreSubSceneLoadPolicy::AlwaysLoaded && !IsSceneLoaded(subScene.SceneGuid))
|
||||||
|
(void)RequestLoad(subScene.SceneGuid, MetaCoreSceneLoadMode::SubScene, subScene.Priority);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MetaCoreSceneWorld::UnloadScene(const MetaCoreAssetGuid& sceneGuid, bool force) {
|
||||||
|
auto iterator = LoadedScenes_.find(sceneGuid);
|
||||||
|
if (iterator == LoadedScenes_.end()) return false;
|
||||||
|
if (!force && HardResidentScenes_.contains(sceneGuid)) return false;
|
||||||
|
const auto objectIds = iterator->second.RuntimeObjectIds;
|
||||||
|
Publish({MetaCoreSceneLifecycleEventType::BeforeUnload, sceneGuid, objectIds});
|
||||||
|
(void)Scene_->DeleteGameObjects(objectIds);
|
||||||
|
LoadedScenes_.erase(iterator);
|
||||||
|
HardResidentScenes_.erase(sceneGuid);
|
||||||
|
if (ActiveSceneGuid_ == sceneGuid)
|
||||||
|
ActiveSceneGuid_ = LoadedScenes_.empty() ? MetaCoreAssetGuid{} : LoadedScenes_.begin()->first;
|
||||||
|
RebuildObjectIndex();
|
||||||
|
RebuildHardResidency();
|
||||||
|
Publish({MetaCoreSceneLifecycleEventType::AfterUnload, sceneGuid, objectIds});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MetaCoreSceneWorld::SetActiveScene(const MetaCoreAssetGuid& sceneGuid) {
|
||||||
|
if (!IsSceneLoaded(sceneGuid)) return false;
|
||||||
|
ActiveSceneGuid_ = sceneGuid;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MetaCoreSceneWorld::IsSceneLoaded(const MetaCoreAssetGuid& sceneGuid) const {
|
||||||
|
return LoadedScenes_.contains(sceneGuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<MetaCoreLoadedSceneInfo> MetaCoreSceneWorld::GetLoadedScenes() const {
|
||||||
|
std::vector<MetaCoreLoadedSceneInfo> result;
|
||||||
|
result.reserve(LoadedScenes_.size());
|
||||||
|
for (const auto& [guid, scene] : LoadedScenes_) {
|
||||||
|
result.push_back({guid, scene.Source.Name, scene.LoadMode, guid == ActiveSceneGuid_, scene.Dirty,
|
||||||
|
HardResidentScenes_.contains(guid), scene.RuntimeObjectIds});
|
||||||
|
}
|
||||||
|
std::sort(result.begin(), result.end(), [](const auto& lhs, const auto& rhs) {
|
||||||
|
if (lhs.Active != rhs.Active) return lhs.Active;
|
||||||
|
return lhs.Name < rhs.Name;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<MetaCoreSceneLoadStatus> MetaCoreSceneWorld::GetLoadStatus(std::uint64_t requestId) const {
|
||||||
|
const auto iterator = LoadStatuses_.find(requestId);
|
||||||
|
return iterator == LoadStatuses_.end() ? std::nullopt : std::optional(iterator->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
MetaCoreGameObject MetaCoreSceneWorld::ResolveObject(const MetaCoreObjectReference& reference) {
|
||||||
|
if (!reference.IsValid()) return {};
|
||||||
|
MetaCoreAssetGuid sceneGuid = reference.SceneGuid.IsValid() ? reference.SceneGuid : ActiveSceneGuid_;
|
||||||
|
const auto iterator = ObjectIndex_.find({sceneGuid, reference.StableObjectGuid});
|
||||||
|
if (iterator != ObjectIndex_.end()) return Scene_->FindGameObject(iterator->second);
|
||||||
|
if (reference.Strength == MetaCoreObjectReferenceStrength::Hard && sceneGuid.IsValid()) {
|
||||||
|
HardResidentScenes_.insert(sceneGuid);
|
||||||
|
if (!IsSceneLoaded(sceneGuid)) (void)RequestLoad(sceneGuid, MetaCoreSceneLoadMode::SubScene);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
MetaCoreGameObject MetaCoreSceneWorld::ResolveObject(const MetaCoreObjectReference& reference) const {
|
||||||
|
if (!reference.IsValid()) return {};
|
||||||
|
const MetaCoreAssetGuid sceneGuid = reference.SceneGuid.IsValid() ? reference.SceneGuid : ActiveSceneGuid_;
|
||||||
|
const auto iterator = ObjectIndex_.find({sceneGuid, reference.StableObjectGuid});
|
||||||
|
return iterator == ObjectIndex_.end() ? MetaCoreGameObject{} : Scene_->FindGameObject(iterator->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
MetaCoreObjectReference MetaCoreSceneWorld::BuildObjectReference(
|
||||||
|
MetaCoreId runtimeObjectId,
|
||||||
|
MetaCoreObjectReferenceStrength strength
|
||||||
|
) const {
|
||||||
|
const MetaCoreGameObject object = Scene_->FindGameObject(runtimeObjectId);
|
||||||
|
if (!object || !object.HasComponent<MetaCoreSceneObjectIdentityComponent>()) return {};
|
||||||
|
const auto& identity = object.GetComponent<MetaCoreSceneObjectIdentityComponent>();
|
||||||
|
return {identity.SceneGuid, identity.StableObjectGuid, strength};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MetaCoreSceneWorld::MoveSubtreeToScene(MetaCoreId rootObjectId, const MetaCoreAssetGuid& targetSceneGuid) {
|
||||||
|
if (!IsSceneLoaded(targetSceneGuid)) return false;
|
||||||
|
const auto ids = Scene_->GetSubtreeObjectIds(rootObjectId);
|
||||||
|
if (ids.empty()) return false;
|
||||||
|
MetaCoreAssetGuid sourceSceneGuid{};
|
||||||
|
for (MetaCoreId id : ids) {
|
||||||
|
auto object = Scene_->FindGameObject(id);
|
||||||
|
if (!object) return false;
|
||||||
|
if (!object.HasComponent<MetaCoreSceneObjectIdentityComponent>())
|
||||||
|
object.AddComponent<MetaCoreSceneObjectIdentityComponent>();
|
||||||
|
if (!sourceSceneGuid.IsValid())
|
||||||
|
sourceSceneGuid = object.GetComponent<MetaCoreSceneObjectIdentityComponent>().SceneGuid;
|
||||||
|
object.GetComponent<MetaCoreSceneObjectIdentityComponent>().SceneGuid = targetSceneGuid;
|
||||||
|
}
|
||||||
|
for (auto& [guid, scene] : LoadedScenes_) {
|
||||||
|
for (MetaCoreId id : ids) std::erase(scene.RuntimeObjectIds, id);
|
||||||
|
if (guid == sourceSceneGuid || guid == targetSceneGuid) scene.Dirty = true;
|
||||||
|
}
|
||||||
|
auto& target = LoadedScenes_.at(targetSceneGuid);
|
||||||
|
target.RuntimeObjectIds.insert(target.RuntimeObjectIds.end(), ids.begin(), ids.end());
|
||||||
|
RebuildObjectIndex();
|
||||||
|
Scene_->IncrementRevision();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<MetaCoreSceneDocument> MetaCoreSceneWorld::CaptureSceneDocument(const MetaCoreAssetGuid& sceneGuid) const {
|
||||||
|
const auto loaded = LoadedScenes_.find(sceneGuid);
|
||||||
|
if (loaded == LoadedScenes_.end()) return std::nullopt;
|
||||||
|
MetaCoreSceneDocument result = loaded->second.Source;
|
||||||
|
result.SchemaVersion = GMetaCoreSceneSchemaVersion;
|
||||||
|
result.SceneGuid = sceneGuid;
|
||||||
|
result.GameObjects.clear();
|
||||||
|
result.PrefabInstances.clear();
|
||||||
|
const auto snapshot = Scene_->CaptureSnapshot();
|
||||||
|
std::unordered_map<MetaCoreId, MetaCoreId> runtimeToSerialized = loaded->second.RuntimeToSerializedId;
|
||||||
|
std::unordered_set<MetaCoreId> usedSerializedIds;
|
||||||
|
for (const auto& [runtimeId, serializedId] : runtimeToSerialized) {
|
||||||
|
(void)runtimeId;
|
||||||
|
usedSerializedIds.insert(serializedId);
|
||||||
|
}
|
||||||
|
MetaCoreId nextSerializedId = 1;
|
||||||
|
for (const auto& object : snapshot.GameObjects) {
|
||||||
|
if (object.SceneGuid != sceneGuid || runtimeToSerialized.contains(object.Id)) continue;
|
||||||
|
while (usedSerializedIds.contains(nextSerializedId)) ++nextSerializedId;
|
||||||
|
runtimeToSerialized.emplace(object.Id, nextSerializedId);
|
||||||
|
usedSerializedIds.insert(nextSerializedId++);
|
||||||
|
}
|
||||||
|
for (auto object : snapshot.GameObjects) {
|
||||||
|
if (object.SceneGuid != sceneGuid) continue;
|
||||||
|
if (object.PrefabInstance &&
|
||||||
|
object.PrefabInstance->PrefabInstanceRootId == object.Id &&
|
||||||
|
object.PrefabInstance->InstanceGuid.IsValid()) {
|
||||||
|
MetaCorePrefabInstanceDocument instance;
|
||||||
|
instance.InstanceGuid = object.PrefabInstance->InstanceGuid;
|
||||||
|
instance.PrefabAssetGuid = object.PrefabInstance->PrefabAssetGuid;
|
||||||
|
if (object.ParentId != 0) {
|
||||||
|
const auto parent = std::find_if(snapshot.GameObjects.begin(), snapshot.GameObjects.end(),
|
||||||
|
[&](const auto& candidate) { return candidate.Id == object.ParentId; });
|
||||||
|
if (parent != snapshot.GameObjects.end()) instance.ParentObjectGuid = parent->StableObjectGuid;
|
||||||
|
}
|
||||||
|
result.PrefabInstances.push_back(std::move(instance));
|
||||||
|
}
|
||||||
|
object.Id = runtimeToSerialized.at(object.Id);
|
||||||
|
if (object.ParentId != 0) {
|
||||||
|
const auto parent = runtimeToSerialized.find(object.ParentId);
|
||||||
|
object.ParentId = parent == runtimeToSerialized.end() ? 0 : parent->second;
|
||||||
|
}
|
||||||
|
result.GameObjects.push_back(std::move(object));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
MetaCoreSceneWorldSnapshot MetaCoreSceneWorld::CaptureWorldSnapshot() const {
|
||||||
|
MetaCoreSceneWorldSnapshot snapshot;
|
||||||
|
snapshot.CompositeScene = Scene_->CaptureSnapshot();
|
||||||
|
snapshot.ActiveSceneGuid = ActiveSceneGuid_;
|
||||||
|
snapshot.Scenes.reserve(LoadedScenes_.size());
|
||||||
|
for (const auto& [sceneGuid, loaded] : LoadedScenes_) {
|
||||||
|
if (auto document = CaptureSceneDocument(sceneGuid))
|
||||||
|
snapshot.Scenes.push_back({std::move(*document), loaded.LoadMode, loaded.Dirty});
|
||||||
|
}
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MetaCoreSceneWorld::RestoreWorldSnapshot(const MetaCoreSceneWorldSnapshot& snapshot) {
|
||||||
|
for (auto& pending : PendingLoads_) {
|
||||||
|
pending->Cancelled = true;
|
||||||
|
auto& status = LoadStatuses_[pending->RequestId];
|
||||||
|
status.State = MetaCoreSceneLoadState::Cancelled;
|
||||||
|
status.Error = "Load request cancelled by world restore";
|
||||||
|
}
|
||||||
|
Scene_->RestoreSnapshot(snapshot.CompositeScene);
|
||||||
|
LoadedScenes_.clear();
|
||||||
|
for (const auto& entry : snapshot.Scenes) {
|
||||||
|
LoadedScene loaded;
|
||||||
|
loaded.Source = entry.Document;
|
||||||
|
loaded.LoadMode = entry.LoadMode;
|
||||||
|
loaded.Dirty = entry.Dirty;
|
||||||
|
std::unordered_map<MetaCoreAssetGuid, MetaCoreId, MetaCoreAssetGuidHasher> serializedIds;
|
||||||
|
for (const auto& object : entry.Document.GameObjects)
|
||||||
|
serializedIds[object.StableObjectGuid] = object.Id;
|
||||||
|
for (const auto object : Scene_->GetGameObjects()) {
|
||||||
|
if (!object.HasComponent<MetaCoreSceneObjectIdentityComponent>()) continue;
|
||||||
|
const auto& identity = object.GetComponent<MetaCoreSceneObjectIdentityComponent>();
|
||||||
|
if (identity.SceneGuid != entry.Document.SceneGuid) continue;
|
||||||
|
loaded.RuntimeObjectIds.push_back(object.GetId());
|
||||||
|
if (const auto serialized = serializedIds.find(identity.StableObjectGuid); serialized != serializedIds.end())
|
||||||
|
loaded.RuntimeToSerializedId.emplace(object.GetId(), serialized->second);
|
||||||
|
}
|
||||||
|
LoadedScenes_.emplace(entry.Document.SceneGuid, std::move(loaded));
|
||||||
|
}
|
||||||
|
ActiveSceneGuid_ = LoadedScenes_.contains(snapshot.ActiveSceneGuid)
|
||||||
|
? snapshot.ActiveSceneGuid
|
||||||
|
: (LoadedScenes_.empty() ? MetaCoreAssetGuid{} : LoadedScenes_.begin()->first);
|
||||||
|
RebuildObjectIndex();
|
||||||
|
RebuildHardResidency();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> MetaCoreSceneWorld::ValidateDependencies(const MetaCoreSceneDocument& root) const {
|
||||||
|
std::vector<std::string> issues;
|
||||||
|
std::unordered_set<MetaCoreAssetGuid, MetaCoreAssetGuidHasher> visited;
|
||||||
|
std::unordered_set<MetaCoreAssetGuid, MetaCoreAssetGuidHasher> active;
|
||||||
|
std::function<void(const MetaCoreSceneDocument&)> visit = [&](const MetaCoreSceneDocument& document) {
|
||||||
|
if (active.contains(document.SceneGuid)) {
|
||||||
|
issues.push_back("Scene dependency cycle at " + document.SceneGuid.ToString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!visited.insert(document.SceneGuid).second) return;
|
||||||
|
active.insert(document.SceneGuid);
|
||||||
|
std::unordered_set<MetaCoreAssetGuid, MetaCoreAssetGuidHasher> localDependencies;
|
||||||
|
std::vector<MetaCoreAssetGuid> dependencies;
|
||||||
|
for (const auto& subScene : document.SubScenes) {
|
||||||
|
if (!subScene.SceneGuid.IsValid()) {
|
||||||
|
issues.push_back("SubScene has no GUID: " + subScene.ScenePath.generic_string());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!localDependencies.insert(subScene.SceneGuid).second)
|
||||||
|
issues.push_back("Duplicate SubScene dependency: " + subScene.SceneGuid.ToString());
|
||||||
|
dependencies.push_back(subScene.SceneGuid);
|
||||||
|
if (subScene.LoadPolicy == MetaCoreSubSceneLoadPolicy::Distance &&
|
||||||
|
(subScene.PreloadDistance < 0.0F || subScene.UnloadDistance < subScene.PreloadDistance))
|
||||||
|
issues.push_back("Distance SubScene has invalid preload/unload thresholds: " + subScene.SceneGuid.ToString());
|
||||||
|
}
|
||||||
|
for (const auto& object : document.GameObjects) {
|
||||||
|
if (object.PhysicsConstraint) {
|
||||||
|
const auto& reference = object.PhysicsConstraint->TargetObjectReference;
|
||||||
|
if (reference.IsValid() && reference.Strength == MetaCoreObjectReferenceStrength::Hard)
|
||||||
|
dependencies.push_back(reference.SceneGuid);
|
||||||
|
}
|
||||||
|
if (object.TimelineDirector) for (const auto& binding : object.TimelineDirector->Bindings) {
|
||||||
|
if (binding.ObjectReference.IsValid() &&
|
||||||
|
binding.ObjectReference.Strength == MetaCoreObjectReferenceStrength::Hard)
|
||||||
|
dependencies.push_back(binding.ObjectReference.SceneGuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto& dependency : dependencies) {
|
||||||
|
if (active.contains(dependency)) {
|
||||||
|
issues.push_back("Scene dependency cycle: " + document.SceneGuid.ToString() + " -> " + dependency.ToString());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (visited.contains(dependency) || !Loader_) continue;
|
||||||
|
auto child = Loader_(dependency);
|
||||||
|
if (!child) issues.push_back("Missing scene dependency: " + dependency.ToString());
|
||||||
|
else visit(*child);
|
||||||
|
}
|
||||||
|
active.erase(document.SceneGuid);
|
||||||
|
};
|
||||||
|
visit(root);
|
||||||
|
return issues;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MetaCoreSceneWorld::UpdateStreaming(const std::vector<glm::vec3>& sourcePositions) {
|
||||||
|
std::vector<MetaCoreSubSceneDocument> subScenes;
|
||||||
|
for (const auto& [guid, scene] : LoadedScenes_) {
|
||||||
|
(void)guid;
|
||||||
|
subScenes.insert(subScenes.end(), scene.Source.SubScenes.begin(), scene.Source.SubScenes.end());
|
||||||
|
}
|
||||||
|
for (const auto& subScene : subScenes) {
|
||||||
|
if (!subScene.SceneGuid.IsValid() || subScene.LoadPolicy == MetaCoreSubSceneLoadPolicy::Manual) continue;
|
||||||
|
bool near = subScene.LoadPolicy == MetaCoreSubSceneLoadPolicy::AlwaysLoaded;
|
||||||
|
bool far = !sourcePositions.empty();
|
||||||
|
for (const auto& source : sourcePositions) {
|
||||||
|
const float distance = DistanceToBounds(source, subScene.BoundsCenter, subScene.BoundsExtent);
|
||||||
|
near = near || distance <= subScene.PreloadDistance;
|
||||||
|
far = far && distance > subScene.UnloadDistance;
|
||||||
|
}
|
||||||
|
if (near && !IsSceneLoaded(subScene.SceneGuid))
|
||||||
|
(void)RequestLoad(subScene.SceneGuid, MetaCoreSceneLoadMode::SubScene, subScene.Priority);
|
||||||
|
else if (far && IsSceneLoaded(subScene.SceneGuid) && !HardResidentScenes_.contains(subScene.SceneGuid))
|
||||||
|
(void)UnloadScene(subScene.SceneGuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t MetaCoreSceneWorld::AddLifecycleListener(LifecycleListener listener) {
|
||||||
|
const std::uint64_t id = NextListenerId_++;
|
||||||
|
Listeners_.emplace(id, std::move(listener));
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MetaCoreSceneWorld::RemoveLifecycleListener(std::uint64_t listenerId) { Listeners_.erase(listenerId); }
|
||||||
|
|
||||||
|
void MetaCoreSceneWorld::Publish(MetaCoreSceneLifecycleEvent event) {
|
||||||
|
for (const auto& [id, listener] : Listeners_) {
|
||||||
|
(void)id;
|
||||||
|
if (listener) listener(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MetaCoreSceneWorld::RebuildObjectIndex() {
|
||||||
|
ObjectIndex_.clear();
|
||||||
|
for (const auto object : Scene_->GetGameObjects()) {
|
||||||
|
if (!object.HasComponent<MetaCoreSceneObjectIdentityComponent>()) continue;
|
||||||
|
const auto& identity = object.GetComponent<MetaCoreSceneObjectIdentityComponent>();
|
||||||
|
if (identity.SceneGuid.IsValid() && identity.StableObjectGuid.IsValid())
|
||||||
|
ObjectIndex_[{identity.SceneGuid, identity.StableObjectGuid}] = object.GetId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MetaCoreSceneWorld::RebuildHardResidency() {
|
||||||
|
HardResidentScenes_.clear();
|
||||||
|
for (const auto object : Scene_->GetGameObjects()) {
|
||||||
|
MetaCoreAssetGuid sourceSceneGuid{};
|
||||||
|
if (object.HasComponent<MetaCoreSceneObjectIdentityComponent>())
|
||||||
|
sourceSceneGuid = object.GetComponent<MetaCoreSceneObjectIdentityComponent>().SceneGuid;
|
||||||
|
if (object.HasComponent<MetaCorePhysicsConstraintComponent>()) {
|
||||||
|
const auto& reference = object.GetComponent<MetaCorePhysicsConstraintComponent>().TargetObjectReference;
|
||||||
|
if (reference.IsValid() && reference.Strength == MetaCoreObjectReferenceStrength::Hard &&
|
||||||
|
reference.SceneGuid != sourceSceneGuid)
|
||||||
|
HardResidentScenes_.insert(reference.SceneGuid);
|
||||||
|
}
|
||||||
|
if (object.HasComponent<MetaCoreTimelineDirectorComponent>()) {
|
||||||
|
for (const auto& binding : object.GetComponent<MetaCoreTimelineDirectorComponent>().Bindings) {
|
||||||
|
if (binding.ObjectReference.IsValid() &&
|
||||||
|
binding.ObjectReference.Strength == MetaCoreObjectReferenceStrength::Hard &&
|
||||||
|
binding.ObjectReference.SceneGuid != sourceSceneGuid)
|
||||||
|
HardResidentScenes_.insert(binding.ObjectReference.SceneGuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace MetaCore
|
||||||
@ -4,6 +4,7 @@
|
|||||||
#include "MetaCoreFoundation/MetaCoreId.h"
|
#include "MetaCoreFoundation/MetaCoreId.h"
|
||||||
#include "MetaCoreFoundation/MetaCoreReflection.h"
|
#include "MetaCoreFoundation/MetaCoreReflection.h"
|
||||||
#include "MetaCoreRendering/MetaCoreRendering.h"
|
#include "MetaCoreRendering/MetaCoreRendering.h"
|
||||||
|
#include "MetaCoreScene/MetaCoreObjectReference.h"
|
||||||
|
|
||||||
#include <glm/vec3.hpp>
|
#include <glm/vec3.hpp>
|
||||||
#include <glm/gtc/quaternion.hpp>
|
#include <glm/gtc/quaternion.hpp>
|
||||||
@ -309,6 +310,8 @@ struct MetaCoreTimelineBinding {
|
|||||||
std::string Slot{};
|
std::string Slot{};
|
||||||
MC_PROPERTY()
|
MC_PROPERTY()
|
||||||
MetaCoreId ObjectId = 0;
|
MetaCoreId ObjectId = 0;
|
||||||
|
MC_PROPERTY()
|
||||||
|
MetaCoreObjectReference ObjectReference{};
|
||||||
};
|
};
|
||||||
|
|
||||||
MC_STRUCT()
|
MC_STRUCT()
|
||||||
@ -467,6 +470,8 @@ struct MetaCorePhysicsConstraintComponent {
|
|||||||
MC_PROPERTY()
|
MC_PROPERTY()
|
||||||
MetaCoreId TargetObjectId = 0;
|
MetaCoreId TargetObjectId = 0;
|
||||||
MC_PROPERTY()
|
MC_PROPERTY()
|
||||||
|
MetaCoreObjectReference TargetObjectReference{};
|
||||||
|
MC_PROPERTY()
|
||||||
glm::vec3 Anchor{0.0F};
|
glm::vec3 Anchor{0.0F};
|
||||||
MC_PROPERTY()
|
MC_PROPERTY()
|
||||||
glm::vec3 TargetAnchor{0.0F};
|
glm::vec3 TargetAnchor{0.0F};
|
||||||
|
|||||||
@ -25,6 +25,25 @@ struct MetaCorePrefabInstanceMetadata {
|
|||||||
|
|
||||||
MC_PROPERTY()
|
MC_PROPERTY()
|
||||||
MetaCoreId PrefabInstanceRootId = 0;
|
MetaCoreId PrefabInstanceRootId = 0;
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
MetaCoreAssetGuid InstanceGuid{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
MetaCoreAssetGuid PrefabNodeGuid{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Runtime ownership metadata. StableObjectGuid is persisted through
|
||||||
|
// MetaCoreGameObjectData while RuntimeId remains process-local.
|
||||||
|
MC_STRUCT()
|
||||||
|
struct MetaCoreSceneObjectIdentityComponent {
|
||||||
|
MC_GENERATED_BODY()
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
MetaCoreAssetGuid SceneGuid{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
MetaCoreAssetGuid StableObjectGuid{};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Component structs for basic properties
|
// Component structs for basic properties
|
||||||
@ -116,6 +135,10 @@ struct MetaCoreGameObjectData {
|
|||||||
MC_PROPERTY()
|
MC_PROPERTY()
|
||||||
MetaCoreId ParentId = 0;
|
MetaCoreId ParentId = 0;
|
||||||
MC_PROPERTY()
|
MC_PROPERTY()
|
||||||
|
MetaCoreAssetGuid SceneGuid{};
|
||||||
|
MC_PROPERTY()
|
||||||
|
MetaCoreAssetGuid StableObjectGuid{};
|
||||||
|
MC_PROPERTY()
|
||||||
std::string Name;
|
std::string Name;
|
||||||
MC_PROPERTY()
|
MC_PROPERTY()
|
||||||
MetaCoreTransformComponent Transform{};
|
MetaCoreTransformComponent Transform{};
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "MetaCoreFoundation/MetaCoreAssetGuid.h"
|
||||||
|
#include "MetaCoreFoundation/MetaCoreReflection.h"
|
||||||
|
|
||||||
|
namespace MetaCore {
|
||||||
|
|
||||||
|
MC_ENUM()
|
||||||
|
enum class MetaCoreObjectReferenceStrength {
|
||||||
|
Soft = 0,
|
||||||
|
Hard
|
||||||
|
};
|
||||||
|
|
||||||
|
MC_STRUCT()
|
||||||
|
struct MetaCoreObjectReference {
|
||||||
|
MC_GENERATED_BODY()
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
MetaCoreAssetGuid SceneGuid{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
MetaCoreAssetGuid StableObjectGuid{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
MetaCoreObjectReferenceStrength Strength = MetaCoreObjectReferenceStrength::Soft;
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsValid() const { return StableObjectGuid.IsValid(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace MetaCore
|
||||||
64
Source/MetaCoreScene/Public/MetaCoreScene/MetaCorePrefab.h
Normal file
64
Source/MetaCoreScene/Public/MetaCoreScene/MetaCorePrefab.h
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "MetaCoreScene/MetaCoreSceneDocument.h"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <optional>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace MetaCore {
|
||||||
|
|
||||||
|
struct MetaCoreResolvedPrefab {
|
||||||
|
MetaCorePrefabDocument Document{};
|
||||||
|
std::vector<MetaCorePrefabOverrideDocument> OrphanOverrides{};
|
||||||
|
std::vector<MetaCoreAssetGuid> DependencyChain{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class MetaCorePrefabOverrideRegistry {
|
||||||
|
public:
|
||||||
|
using PropertyHandler = std::function<bool(MetaCoreGameObjectData&, std::string_view, std::string_view)>;
|
||||||
|
|
||||||
|
MetaCorePrefabOverrideRegistry();
|
||||||
|
void RegisterPropertyHandler(std::string componentTypeId, PropertyHandler handler);
|
||||||
|
[[nodiscard]] bool Apply(
|
||||||
|
MetaCorePrefabDocument& document,
|
||||||
|
const MetaCorePrefabOverrideDocument& overrideValue
|
||||||
|
) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<std::string, PropertyHandler> PropertyHandlers_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class MetaCorePrefabResolver {
|
||||||
|
public:
|
||||||
|
using PrefabLoader = std::function<std::optional<MetaCorePrefabDocument>(const MetaCoreAssetGuid&)>;
|
||||||
|
|
||||||
|
explicit MetaCorePrefabResolver(
|
||||||
|
PrefabLoader loader,
|
||||||
|
MetaCorePrefabOverrideRegistry registry = MetaCorePrefabOverrideRegistry{}
|
||||||
|
);
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<MetaCoreResolvedPrefab> Resolve(
|
||||||
|
const MetaCoreAssetGuid& prefabGuid,
|
||||||
|
std::string* error = nullptr
|
||||||
|
) const;
|
||||||
|
[[nodiscard]] std::optional<std::vector<MetaCoreGameObjectData>> Instantiate(
|
||||||
|
const MetaCoreAssetGuid& prefabGuid,
|
||||||
|
const MetaCoreAssetGuid& instanceGuid,
|
||||||
|
const std::vector<MetaCorePrefabOverrideDocument>& instanceOverrides = {},
|
||||||
|
std::string* error = nullptr
|
||||||
|
) const;
|
||||||
|
[[nodiscard]] std::vector<std::string> ValidateDependencies(const MetaCoreAssetGuid& prefabGuid) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]] std::optional<MetaCoreResolvedPrefab> ResolveInternal(
|
||||||
|
const MetaCoreAssetGuid& prefabGuid,
|
||||||
|
std::vector<MetaCoreAssetGuid>& stack,
|
||||||
|
std::string* error
|
||||||
|
) const;
|
||||||
|
|
||||||
|
PrefabLoader Loader_{};
|
||||||
|
MetaCorePrefabOverrideRegistry OverrideRegistry_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace MetaCore
|
||||||
@ -5,6 +5,7 @@
|
|||||||
#include "MetaCoreFoundation/MetaCoreAssetGuid.h"
|
#include "MetaCoreFoundation/MetaCoreAssetGuid.h"
|
||||||
#include "MetaCoreFoundation/MetaCoreAssetTypes.h"
|
#include "MetaCoreFoundation/MetaCoreAssetTypes.h"
|
||||||
#include "MetaCoreScene/MetaCoreGameObject.h"
|
#include "MetaCoreScene/MetaCoreGameObject.h"
|
||||||
|
#include "MetaCoreScene/MetaCoreObjectReference.h"
|
||||||
#include <glm/vec3.hpp>
|
#include <glm/vec3.hpp>
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
@ -13,6 +14,125 @@
|
|||||||
|
|
||||||
namespace MetaCore {
|
namespace MetaCore {
|
||||||
|
|
||||||
|
inline constexpr std::uint32_t GMetaCoreSceneSchemaVersion = 2U;
|
||||||
|
inline constexpr std::uint32_t GMetaCorePrefabSchemaVersion = 2U;
|
||||||
|
|
||||||
|
MC_ENUM()
|
||||||
|
enum class MetaCoreSubSceneLoadPolicy {
|
||||||
|
AlwaysLoaded = 0,
|
||||||
|
Manual,
|
||||||
|
Distance
|
||||||
|
};
|
||||||
|
|
||||||
|
MC_STRUCT()
|
||||||
|
struct MetaCoreSubSceneDocument {
|
||||||
|
MC_GENERATED_BODY()
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
MetaCoreAssetGuid SceneGuid{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
std::filesystem::path ScenePath{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
MetaCoreSubSceneLoadPolicy LoadPolicy = MetaCoreSubSceneLoadPolicy::AlwaysLoaded;
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
glm::vec3 BoundsCenter{0.0F};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
glm::vec3 BoundsExtent{0.0F};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
glm::vec3 CellCoordinate{0.0F};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
float PreloadDistance = 100.0F;
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
float UnloadDistance = 125.0F;
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
std::int32_t Priority = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
MC_STRUCT()
|
||||||
|
struct MetaCoreSchemaVersionEntry {
|
||||||
|
MC_GENERATED_BODY()
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
std::string TypeId{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
std::uint32_t Version = 1U;
|
||||||
|
};
|
||||||
|
|
||||||
|
MC_ENUM()
|
||||||
|
enum class MetaCorePrefabOverrideOperation {
|
||||||
|
SetProperty = 0,
|
||||||
|
AddComponent,
|
||||||
|
RemoveComponent,
|
||||||
|
AddChild,
|
||||||
|
RemoveChild,
|
||||||
|
Reparent
|
||||||
|
};
|
||||||
|
|
||||||
|
MC_STRUCT()
|
||||||
|
struct MetaCorePrefabOverrideDocument {
|
||||||
|
MC_GENERATED_BODY()
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
MetaCoreAssetGuid TargetNodeGuid{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
std::string ComponentTypeId{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
std::string PropertyPath{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
MetaCorePrefabOverrideOperation Operation = MetaCorePrefabOverrideOperation::SetProperty;
|
||||||
|
|
||||||
|
// Canonical JSON value. Keeping the value self-describing allows component
|
||||||
|
// serializers to migrate it without coupling prefab documents to C++ layout.
|
||||||
|
MC_PROPERTY()
|
||||||
|
std::string ValueJson{};
|
||||||
|
};
|
||||||
|
|
||||||
|
MC_STRUCT()
|
||||||
|
struct MetaCoreNestedPrefabDocument {
|
||||||
|
MC_GENERATED_BODY()
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
MetaCoreAssetGuid MountNodeGuid{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
MetaCoreAssetGuid PrefabAssetGuid{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
MetaCoreAssetGuid InstanceGuid{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
std::vector<MetaCorePrefabOverrideDocument> Overrides{};
|
||||||
|
};
|
||||||
|
|
||||||
|
MC_STRUCT()
|
||||||
|
struct MetaCorePrefabInstanceDocument {
|
||||||
|
MC_GENERATED_BODY()
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
MetaCoreAssetGuid InstanceGuid{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
MetaCoreAssetGuid PrefabAssetGuid{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
MetaCoreAssetGuid ParentObjectGuid{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
std::vector<MetaCorePrefabOverrideDocument> Overrides{};
|
||||||
|
};
|
||||||
|
|
||||||
MC_STRUCT()
|
MC_STRUCT()
|
||||||
struct MetaCoreEditorSelectionSnapshot {
|
struct MetaCoreEditorSelectionSnapshot {
|
||||||
MC_GENERATED_BODY()
|
MC_GENERATED_BODY()
|
||||||
@ -69,6 +189,12 @@ MC_STRUCT()
|
|||||||
struct MetaCoreSceneDocument {
|
struct MetaCoreSceneDocument {
|
||||||
MC_GENERATED_BODY()
|
MC_GENERATED_BODY()
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
std::uint32_t SchemaVersion = GMetaCoreSceneSchemaVersion;
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
MetaCoreAssetGuid SceneGuid{};
|
||||||
|
|
||||||
MC_PROPERTY()
|
MC_PROPERTY()
|
||||||
std::string Name{};
|
std::string Name{};
|
||||||
|
|
||||||
@ -78,6 +204,15 @@ struct MetaCoreSceneDocument {
|
|||||||
MC_PROPERTY()
|
MC_PROPERTY()
|
||||||
std::vector<MetaCoreGameObjectData> GameObjects{};
|
std::vector<MetaCoreGameObjectData> GameObjects{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
std::vector<MetaCoreSubSceneDocument> SubScenes{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
std::vector<MetaCorePrefabInstanceDocument> PrefabInstances{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
std::vector<MetaCoreSchemaVersionEntry> ComponentVersions{};
|
||||||
|
|
||||||
MC_PROPERTY()
|
MC_PROPERTY()
|
||||||
MetaCoreEditorSelectionSnapshot Selection{};
|
MetaCoreEditorSelectionSnapshot Selection{};
|
||||||
};
|
};
|
||||||
@ -86,11 +221,29 @@ MC_STRUCT()
|
|||||||
struct MetaCorePrefabDocument {
|
struct MetaCorePrefabDocument {
|
||||||
MC_GENERATED_BODY()
|
MC_GENERATED_BODY()
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
std::uint32_t SchemaVersion = GMetaCorePrefabSchemaVersion;
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
MetaCoreAssetGuid PrefabGuid{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
MetaCoreAssetGuid BasePrefabGuid{};
|
||||||
|
|
||||||
MC_PROPERTY()
|
MC_PROPERTY()
|
||||||
std::string Name{};
|
std::string Name{};
|
||||||
|
|
||||||
MC_PROPERTY()
|
MC_PROPERTY()
|
||||||
std::vector<MetaCoreGameObjectData> GameObjects{};
|
std::vector<MetaCoreGameObjectData> GameObjects{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
std::vector<MetaCoreNestedPrefabDocument> NestedPrefabs{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
std::vector<MetaCorePrefabOverrideDocument> Overrides{};
|
||||||
|
|
||||||
|
MC_PROPERTY()
|
||||||
|
std::vector<MetaCoreSchemaVersionEntry> ComponentVersions{};
|
||||||
};
|
};
|
||||||
|
|
||||||
MC_ENUM()
|
MC_ENUM()
|
||||||
|
|||||||
175
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreSceneWorld.h
Normal file
175
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreSceneWorld.h
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "MetaCoreScene/MetaCoreScene.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <functional>
|
||||||
|
#include <future>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
namespace MetaCore {
|
||||||
|
|
||||||
|
enum class MetaCoreSceneLoadMode {
|
||||||
|
Replace = 0,
|
||||||
|
Additive,
|
||||||
|
SubScene
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class MetaCoreSceneLoadState {
|
||||||
|
Unloaded = 0,
|
||||||
|
Loading,
|
||||||
|
Loaded,
|
||||||
|
Failed,
|
||||||
|
Cancelled
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class MetaCoreSceneLifecycleEventType {
|
||||||
|
BeforeUnload = 0,
|
||||||
|
AfterUnload,
|
||||||
|
AfterLoad
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MetaCoreSceneLifecycleEvent {
|
||||||
|
MetaCoreSceneLifecycleEventType Type = MetaCoreSceneLifecycleEventType::AfterLoad;
|
||||||
|
MetaCoreAssetGuid SceneGuid{};
|
||||||
|
std::vector<MetaCoreId> ObjectIds{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MetaCoreSceneLoadStatus {
|
||||||
|
std::uint64_t RequestId = 0;
|
||||||
|
MetaCoreAssetGuid SceneGuid{};
|
||||||
|
MetaCoreSceneLoadState State = MetaCoreSceneLoadState::Unloaded;
|
||||||
|
std::string Error{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MetaCoreSceneWorldSettings {
|
||||||
|
double MainThreadCommitBudgetMilliseconds = 2.0;
|
||||||
|
std::size_t MemoryBudgetBytes = 512U * 1024U * 1024U;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MetaCoreLoadedSceneInfo {
|
||||||
|
MetaCoreAssetGuid SceneGuid{};
|
||||||
|
std::string Name{};
|
||||||
|
MetaCoreSceneLoadMode LoadMode = MetaCoreSceneLoadMode::Additive;
|
||||||
|
bool Active = false;
|
||||||
|
bool Dirty = false;
|
||||||
|
bool HardResident = false;
|
||||||
|
std::vector<MetaCoreId> RuntimeObjectIds{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MetaCoreSceneWorldSnapshotEntry {
|
||||||
|
MetaCoreSceneDocument Document{};
|
||||||
|
MetaCoreSceneLoadMode LoadMode = MetaCoreSceneLoadMode::Additive;
|
||||||
|
bool Dirty = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MetaCoreSceneWorldSnapshot {
|
||||||
|
MetaCoreSceneSnapshot CompositeScene{};
|
||||||
|
MetaCoreAssetGuid ActiveSceneGuid{};
|
||||||
|
std::vector<MetaCoreSceneWorldSnapshotEntry> Scenes{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class MetaCoreSceneWorld {
|
||||||
|
public:
|
||||||
|
using SceneLoader = std::function<std::optional<MetaCoreSceneDocument>(const MetaCoreAssetGuid&)>;
|
||||||
|
using LifecycleListener = std::function<void(const MetaCoreSceneLifecycleEvent&)>;
|
||||||
|
|
||||||
|
explicit MetaCoreSceneWorld(SceneLoader loader = {}, MetaCoreSceneWorldSettings settings = {});
|
||||||
|
MetaCoreSceneWorld(MetaCoreScene& scene, SceneLoader loader = {}, MetaCoreSceneWorldSettings settings = {});
|
||||||
|
~MetaCoreSceneWorld();
|
||||||
|
|
||||||
|
MetaCoreSceneWorld(const MetaCoreSceneWorld&) = delete;
|
||||||
|
MetaCoreSceneWorld& operator=(const MetaCoreSceneWorld&) = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] MetaCoreScene& GetScene() { return *Scene_; }
|
||||||
|
[[nodiscard]] const MetaCoreScene& GetScene() const { return *Scene_; }
|
||||||
|
|
||||||
|
void SetSceneLoader(SceneLoader loader);
|
||||||
|
[[nodiscard]] std::uint64_t RequestLoad(
|
||||||
|
const MetaCoreAssetGuid& sceneGuid,
|
||||||
|
MetaCoreSceneLoadMode mode = MetaCoreSceneLoadMode::Additive,
|
||||||
|
std::int32_t priority = 0
|
||||||
|
);
|
||||||
|
[[nodiscard]] bool CancelLoad(std::uint64_t requestId);
|
||||||
|
void Tick();
|
||||||
|
void UpdateStreaming(const std::vector<glm::vec3>& sourcePositions);
|
||||||
|
|
||||||
|
[[nodiscard]] bool LoadDocumentNow(
|
||||||
|
MetaCoreSceneDocument document,
|
||||||
|
MetaCoreSceneLoadMode mode = MetaCoreSceneLoadMode::Additive,
|
||||||
|
std::string* error = nullptr
|
||||||
|
);
|
||||||
|
[[nodiscard]] bool UnloadScene(const MetaCoreAssetGuid& sceneGuid, bool force = false);
|
||||||
|
[[nodiscard]] bool SetActiveScene(const MetaCoreAssetGuid& sceneGuid);
|
||||||
|
[[nodiscard]] MetaCoreAssetGuid GetActiveSceneGuid() const { return ActiveSceneGuid_; }
|
||||||
|
[[nodiscard]] bool IsSceneLoaded(const MetaCoreAssetGuid& sceneGuid) const;
|
||||||
|
[[nodiscard]] std::vector<MetaCoreLoadedSceneInfo> GetLoadedScenes() const;
|
||||||
|
[[nodiscard]] std::optional<MetaCoreSceneLoadStatus> GetLoadStatus(std::uint64_t requestId) const;
|
||||||
|
|
||||||
|
[[nodiscard]] MetaCoreGameObject ResolveObject(const MetaCoreObjectReference& reference);
|
||||||
|
[[nodiscard]] MetaCoreGameObject ResolveObject(const MetaCoreObjectReference& reference) const;
|
||||||
|
[[nodiscard]] MetaCoreObjectReference BuildObjectReference(
|
||||||
|
MetaCoreId runtimeObjectId,
|
||||||
|
MetaCoreObjectReferenceStrength strength = MetaCoreObjectReferenceStrength::Soft
|
||||||
|
) const;
|
||||||
|
|
||||||
|
[[nodiscard]] bool MoveSubtreeToScene(MetaCoreId rootObjectId, const MetaCoreAssetGuid& targetSceneGuid);
|
||||||
|
[[nodiscard]] std::optional<MetaCoreSceneDocument> CaptureSceneDocument(const MetaCoreAssetGuid& sceneGuid) const;
|
||||||
|
[[nodiscard]] MetaCoreSceneWorldSnapshot CaptureWorldSnapshot() const;
|
||||||
|
void RestoreWorldSnapshot(const MetaCoreSceneWorldSnapshot& snapshot);
|
||||||
|
[[nodiscard]] std::vector<std::string> ValidateDependencies(const MetaCoreSceneDocument& root) const;
|
||||||
|
|
||||||
|
std::uint64_t AddLifecycleListener(LifecycleListener listener);
|
||||||
|
void RemoveLifecycleListener(std::uint64_t listenerId);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct ObjectKey {
|
||||||
|
MetaCoreAssetGuid SceneGuid{};
|
||||||
|
MetaCoreAssetGuid ObjectGuid{};
|
||||||
|
bool operator==(const ObjectKey&) const = default;
|
||||||
|
};
|
||||||
|
struct ObjectKeyHasher {
|
||||||
|
std::size_t operator()(const ObjectKey& value) const noexcept;
|
||||||
|
};
|
||||||
|
struct LoadedScene {
|
||||||
|
MetaCoreSceneDocument Source{};
|
||||||
|
MetaCoreSceneLoadMode LoadMode = MetaCoreSceneLoadMode::Additive;
|
||||||
|
std::vector<MetaCoreId> RuntimeObjectIds{};
|
||||||
|
std::unordered_map<MetaCoreId, MetaCoreId> RuntimeToSerializedId{};
|
||||||
|
bool Dirty = false;
|
||||||
|
};
|
||||||
|
struct PendingLoad;
|
||||||
|
|
||||||
|
[[nodiscard]] bool CommitDocument(MetaCoreSceneDocument document, MetaCoreSceneLoadMode mode, std::string* error);
|
||||||
|
void Publish(MetaCoreSceneLifecycleEvent event);
|
||||||
|
void RebuildObjectIndex();
|
||||||
|
void RebuildHardResidency();
|
||||||
|
|
||||||
|
SceneLoader Loader_{};
|
||||||
|
MetaCoreSceneWorldSettings Settings_{};
|
||||||
|
std::unique_ptr<MetaCoreScene> OwnedScene_{};
|
||||||
|
MetaCoreScene* Scene_ = nullptr;
|
||||||
|
MetaCoreAssetGuid ActiveSceneGuid_{};
|
||||||
|
std::unordered_map<MetaCoreAssetGuid, LoadedScene, MetaCoreAssetGuidHasher> LoadedScenes_{};
|
||||||
|
std::unordered_map<ObjectKey, MetaCoreId, ObjectKeyHasher> ObjectIndex_{};
|
||||||
|
std::unordered_set<MetaCoreAssetGuid, MetaCoreAssetGuidHasher> HardResidentScenes_{};
|
||||||
|
std::vector<std::unique_ptr<PendingLoad>> PendingLoads_{};
|
||||||
|
std::unordered_map<std::uint64_t, MetaCoreSceneLoadStatus> LoadStatuses_{};
|
||||||
|
std::unordered_map<std::uint64_t, LifecycleListener> Listeners_{};
|
||||||
|
std::uint64_t NextRequestId_ = 1;
|
||||||
|
std::uint64_t NextListenerId_ = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] MetaCoreAssetGuid MetaCoreBuildDeterministicGuid(
|
||||||
|
const MetaCoreAssetGuid& namespaceGuid,
|
||||||
|
const MetaCoreAssetGuid& localGuid
|
||||||
|
);
|
||||||
|
[[nodiscard]] MetaCoreAssetGuid MetaCoreBuildDeterministicGuid(
|
||||||
|
const MetaCoreAssetGuid& namespaceGuid,
|
||||||
|
std::uint64_t localValue
|
||||||
|
);
|
||||||
|
|
||||||
|
} // namespace MetaCore
|
||||||
@ -359,14 +359,25 @@ R8.1–R8.5 已落地的工程边界:
|
|||||||
|
|
||||||
### 10. Scene 与 Prefab
|
### 10. Scene 与 Prefab
|
||||||
|
|
||||||
需要补齐:
|
已完成:
|
||||||
|
|
||||||
- [ ] 多场景编辑、Additive Load、SubScene 和异步流式加载。
|
- [x] 多场景编辑、Additive Load、SubScene 和异步流式加载。
|
||||||
- [ ] 大场景分区、按需加载和场景依赖检查。
|
- [x] 大场景分区、按需加载和场景依赖检查。
|
||||||
- [ ] 嵌套 Prefab、Prefab Variant 和实例同步。
|
- [x] 嵌套 Prefab、Prefab Variant 和实例同步。
|
||||||
- [ ] 细粒度 Override 记录、可视化、Apply 和 Revert。
|
- [x] 细粒度 Override 记录、可视化、Apply 和 Revert。
|
||||||
- [ ] 跨场景对象引用规则。
|
- [x] 跨场景对象引用规则。
|
||||||
- [ ] Scene、Component、Prefab 的格式版本迁移机制。
|
- [x] Scene、Component、Prefab 的格式版本迁移机制。
|
||||||
|
|
||||||
|
实现与验收基线(2026-07-27):
|
||||||
|
|
||||||
|
- 新增 `MetaCoreSceneWorld`,在一个组合 ECS 上管理 Replace/Additive/SubScene、活动 Scene、Scene Set 快照、异步请求去重/取消、优先级、距离迟滞、Hard 驻留、生命周期事件和限定对象引用;Play/Stop 会恢复完整 Scene Set、活动 Scene、对象 ID、选择与 Dirty 状态。
|
||||||
|
- Scene/Prefab Schema 升级到版本 2,持久化 Scene/Prefab/对象/Prefab 节点 GUID、SubScene Cell 元数据、组件版本、Prefab Base/嵌套关系和属性/组件/结构 Override。旧文档缺失 GUID 时使用确定性迁移,高版本文档拒绝加载,Cooked Package 同步写入 `CustomVersions` 与依赖表。
|
||||||
|
- Prefab 解析顺序固定为 Base → Variant → 嵌套 Prefab → Scene Instance Override;循环与缺失依赖拒绝解析,孤立 Override 可诊断,相同 Instance GUID 重复展开得到相同稳定对象 GUID。Override 注册表覆盖属性、组件增删、子对象增删和重挂接,编辑器继续提供字段级 Apply/Revert 与 Override 标记。
|
||||||
|
- Editor Hierarchy 按 Scene 分组并提供 Set Active、Unload、Save All 和 Additive Open;新对象归属活动 Scene,跨 Scene 直接父子关系被拒绝,跨场景移动通过 World 事务完成。根 Scene Environment 不被活动 Scene 覆盖。
|
||||||
|
- Cook 从启动 Scene 遍历 SubScene、Hard Scene 引用和 Prefab Base/Variant/嵌套依赖,缺失或循环在打包阶段失败;运行时清单记录 Scene GUID、包路径、Hash 和依赖,Player 与 Editor 共用 `MetaCoreSceneWorld` 的 AlwaysLoaded/Distance 流式规则。
|
||||||
|
- 新增 `MetaCoreSceneTests` 与 `MetaCoreSceneBenchmark`。Linux x86_64 Development 全量 CTest **14/14** 通过;10,000 对象基准实测 load **35.36–37.10 ms**、capture p95 **8.99–12.17 ms**、100 节点 Prefab 展开 p95 **0.027–0.031 ms**,持续门禁按签收上界约 120% 设置为 **45 ms / 15 ms / 0.04 ms**。
|
||||||
|
|
||||||
|
首版边界保持为显式 SubScene 加可选网格 Cell,不做编辑器自动世界切分;正式关闭平台为 Linux x86_64,Windows 仅保留可编译接口。
|
||||||
|
|
||||||
### 11. 编辑器生产力与插件边界
|
### 11. 编辑器生产力与插件边界
|
||||||
|
|
||||||
|
|||||||
@ -52,6 +52,12 @@ int main() {
|
|||||||
delivery.BuildId = "test-build";
|
delivery.BuildId = "test-build";
|
||||||
delivery.StartupScene = "payload.bin";
|
delivery.StartupScene = "payload.bin";
|
||||||
delivery.ScriptModules.push_back("Project/Scripts/TestScripts.so");
|
delivery.ScriptModules.push_back("Project/Scripts/TestScripts.so");
|
||||||
|
MetaCore::MetaCoreRuntimeDeliveryDocument::SceneEntry sceneEntry;
|
||||||
|
sceneEntry.SceneGuid = MetaCore::MetaCoreAssetGuid::Generate();
|
||||||
|
sceneEntry.PackagePath = "Content/Scenes/root.mcscene";
|
||||||
|
sceneEntry.SourceHash = 42;
|
||||||
|
sceneEntry.Dependencies.push_back(MetaCore::MetaCoreAssetGuid::Generate());
|
||||||
|
delivery.ScenePackages.push_back(sceneEntry);
|
||||||
WriteText(package / delivery.ScriptModules.front(), "script");
|
WriteText(package / delivery.ScriptModules.front(), "script");
|
||||||
Expect(MetaCore::MetaCoreWriteRuntimeDeliveryDocument(package / "Project" / "MetaCore.runtimeproject", delivery), "write runtime delivery");
|
Expect(MetaCore::MetaCoreWriteRuntimeDeliveryDocument(package / "Project" / "MetaCore.runtimeproject", delivery), "write runtime delivery");
|
||||||
MetaCore::MetaCoreReleaseManifestDocument manifest;
|
MetaCore::MetaCoreReleaseManifestDocument manifest;
|
||||||
@ -67,6 +73,10 @@ int main() {
|
|||||||
Expect(MetaCore::MetaCoreValidateReleasePackage(package).empty(), "valid package accepted");
|
Expect(MetaCore::MetaCoreValidateReleasePackage(package).empty(), "valid package accepted");
|
||||||
const auto readDelivery = MetaCore::MetaCoreReadRuntimeDeliveryDocument(package / "Project" / "MetaCore.runtimeproject");
|
const auto readDelivery = MetaCore::MetaCoreReadRuntimeDeliveryDocument(package / "Project" / "MetaCore.runtimeproject");
|
||||||
Expect(readDelivery.has_value() && readDelivery->ScriptModules == delivery.ScriptModules, "runtime delivery preserves script module paths");
|
Expect(readDelivery.has_value() && readDelivery->ScriptModules == delivery.ScriptModules, "runtime delivery preserves script module paths");
|
||||||
|
Expect(readDelivery && readDelivery->ScenePackages.size() == 1 &&
|
||||||
|
readDelivery->ScenePackages[0].SceneGuid == sceneEntry.SceneGuid &&
|
||||||
|
readDelivery->ScenePackages[0].Dependencies == sceneEntry.Dependencies,
|
||||||
|
"runtime delivery preserves scene dependency manifest");
|
||||||
WriteText(package / "payload.bin", "tampered");
|
WriteText(package / "payload.bin", "tampered");
|
||||||
const auto corruptIssues = MetaCore::MetaCoreValidateReleasePackage(package);
|
const auto corruptIssues = MetaCore::MetaCoreValidateReleasePackage(package);
|
||||||
Expect(!corruptIssues.empty(), "corrupt package rejected");
|
Expect(!corruptIssues.empty(), "corrupt package rejected");
|
||||||
|
|||||||
81
tests/MetaCoreSceneBenchmark.cpp
Normal file
81
tests/MetaCoreSceneBenchmark.cpp
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
#include "MetaCoreScene/MetaCorePrefab.h"
|
||||||
|
#include "MetaCoreScene/MetaCoreSceneWorld.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
double MillisecondsSince(std::chrono::steady_clock::time_point begin) {
|
||||||
|
return std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - begin).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
using namespace MetaCore;
|
||||||
|
|
||||||
|
MetaCoreSceneDocument largeScene;
|
||||||
|
largeScene.SceneGuid = MetaCoreAssetGuid::Generate();
|
||||||
|
largeScene.Name = "SceneBenchmark";
|
||||||
|
constexpr std::size_t objectCount = 10000;
|
||||||
|
largeScene.GameObjects.reserve(objectCount);
|
||||||
|
for (std::size_t index = 0; index < objectCount; ++index) {
|
||||||
|
MetaCoreGameObjectData object;
|
||||||
|
object.Id = index + 1;
|
||||||
|
object.ParentId = index == 0 ? 0 : (index / 10) + 1;
|
||||||
|
if (object.ParentId == object.Id) object.ParentId = 0;
|
||||||
|
object.SceneGuid = largeScene.SceneGuid;
|
||||||
|
object.StableObjectGuid = MetaCoreBuildDeterministicGuid(largeScene.SceneGuid, object.Id);
|
||||||
|
object.Name = "Object_" + std::to_string(index);
|
||||||
|
largeScene.GameObjects.push_back(std::move(object));
|
||||||
|
}
|
||||||
|
|
||||||
|
MetaCoreSceneWorld world;
|
||||||
|
const auto loadBegin = std::chrono::steady_clock::now();
|
||||||
|
if (!world.LoadDocumentNow(largeScene, MetaCoreSceneLoadMode::Replace)) return EXIT_FAILURE;
|
||||||
|
const double loadMilliseconds = MillisecondsSince(loadBegin);
|
||||||
|
|
||||||
|
std::vector<double> captureSamples;
|
||||||
|
captureSamples.reserve(40);
|
||||||
|
for (int sample = 0; sample < 40; ++sample) {
|
||||||
|
const auto begin = std::chrono::steady_clock::now();
|
||||||
|
if (!world.CaptureSceneDocument(largeScene.SceneGuid)) return EXIT_FAILURE;
|
||||||
|
captureSamples.push_back(MillisecondsSince(begin));
|
||||||
|
}
|
||||||
|
std::sort(captureSamples.begin(), captureSamples.end());
|
||||||
|
const double captureP95 = captureSamples[static_cast<std::size_t>(captureSamples.size() * 0.95)];
|
||||||
|
|
||||||
|
MetaCorePrefabDocument prefab;
|
||||||
|
prefab.PrefabGuid = MetaCoreAssetGuid::Generate();
|
||||||
|
prefab.Name = "BenchmarkPrefab";
|
||||||
|
prefab.GameObjects.assign(largeScene.GameObjects.begin(), largeScene.GameObjects.begin() + 100);
|
||||||
|
MetaCorePrefabResolver resolver([&](const MetaCoreAssetGuid& guid) -> std::optional<MetaCorePrefabDocument> {
|
||||||
|
return guid == prefab.PrefabGuid ? std::optional(prefab) : std::nullopt;
|
||||||
|
});
|
||||||
|
std::vector<double> prefabSamples;
|
||||||
|
prefabSamples.reserve(100);
|
||||||
|
for (int sample = 0; sample < 100; ++sample) {
|
||||||
|
const auto begin = std::chrono::steady_clock::now();
|
||||||
|
if (!resolver.Instantiate(prefab.PrefabGuid, MetaCoreAssetGuid::Generate())) return EXIT_FAILURE;
|
||||||
|
prefabSamples.push_back(MillisecondsSince(begin));
|
||||||
|
}
|
||||||
|
std::sort(prefabSamples.begin(), prefabSamples.end());
|
||||||
|
const double prefabP95 = prefabSamples[static_cast<std::size_t>(prefabSamples.size() * 0.95)];
|
||||||
|
|
||||||
|
std::cout << "SceneBenchmark objects=" << objectCount
|
||||||
|
<< " load_ms=" << loadMilliseconds
|
||||||
|
<< " capture_p95_ms=" << captureP95
|
||||||
|
<< " prefab100_p95_ms=" << prefabP95 << '\n';
|
||||||
|
// Linux x86_64 signed baseline (2026-07-27): load 37.10 ms,
|
||||||
|
// capture p95 12.17 ms, prefab(100 nodes) p95 0.031 ms. Gates are
|
||||||
|
// rounded up from 120% to avoid sub-microsecond timer noise.
|
||||||
|
if (loadMilliseconds > 45.0 || captureP95 > 15.0 || prefabP95 > 0.04) {
|
||||||
|
std::cerr << "Scene/Prefab benchmark exceeded Linux x86_64 regression gate\n";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
306
tests/MetaCoreSceneTests.cpp
Normal file
306
tests/MetaCoreSceneTests.cpp
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
#include "MetaCoreScene/MetaCorePrefab.h"
|
||||||
|
#include "MetaCoreScene/MetaCoreScenePackage.h"
|
||||||
|
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
|
||||||
|
#include "MetaCoreScene/MetaCoreSceneWorld.h"
|
||||||
|
#include "MetaCoreFoundation/MetaCorePackage.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using namespace MetaCore;
|
||||||
|
|
||||||
|
void Expect(bool condition, const char* message) {
|
||||||
|
if (!condition) {
|
||||||
|
std::cerr << "FAILED: " << message << '\n';
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MetaCoreSceneDocument MakeScene(const char* name, std::size_t objectCount = 1) {
|
||||||
|
MetaCoreSceneDocument document;
|
||||||
|
document.SceneGuid = MetaCoreAssetGuid::Generate();
|
||||||
|
document.Name = name;
|
||||||
|
for (std::size_t index = 0; index < objectCount; ++index) {
|
||||||
|
MetaCoreGameObjectData object;
|
||||||
|
object.Id = index + 1;
|
||||||
|
object.ParentId = index == 0 ? 0 : index;
|
||||||
|
object.SceneGuid = document.SceneGuid;
|
||||||
|
object.StableObjectGuid = MetaCoreBuildDeterministicGuid(document.SceneGuid, object.Id);
|
||||||
|
object.Name = std::string(name) + std::to_string(index);
|
||||||
|
document.GameObjects.push_back(std::move(object));
|
||||||
|
}
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TickUntilSettled(MetaCoreSceneWorld& world, std::uint64_t request) {
|
||||||
|
for (int iteration = 0; iteration < 1000; ++iteration) {
|
||||||
|
world.Tick();
|
||||||
|
const auto status = world.GetLoadStatus(request);
|
||||||
|
if (status && status->State != MetaCoreSceneLoadState::Loading) return;
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
Expect(false, "asynchronous scene request did not settle");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestSceneJsonAndPackageVersions() {
|
||||||
|
const auto root = std::filesystem::temp_directory_path() / "MetaCoreSceneTests";
|
||||||
|
std::filesystem::create_directories(root);
|
||||||
|
const auto jsonPath = root / "Versioned.mcscene.json";
|
||||||
|
const auto packagePath = root / "Versioned.mcscene";
|
||||||
|
auto document = MakeScene("Versioned", 2);
|
||||||
|
MetaCoreSubSceneDocument sub;
|
||||||
|
sub.SceneGuid = MetaCoreAssetGuid::Generate();
|
||||||
|
sub.ScenePath = "Scenes/Cell.mcscene.json";
|
||||||
|
sub.LoadPolicy = MetaCoreSubSceneLoadPolicy::Distance;
|
||||||
|
sub.PreloadDistance = 25.0F;
|
||||||
|
sub.UnloadDistance = 30.0F;
|
||||||
|
document.SubScenes.push_back(sub);
|
||||||
|
document.ComponentVersions.push_back({"Transform", 2});
|
||||||
|
|
||||||
|
const auto registry = MetaCoreBuildScenePackageTypeRegistry();
|
||||||
|
Expect(MetaCoreSceneSerializer::SaveSceneToJson(jsonPath, document, registry), "versioned scene JSON save");
|
||||||
|
const auto jsonLoaded = MetaCoreSceneSerializer::LoadSceneFromJson(jsonPath, registry);
|
||||||
|
Expect(jsonLoaded && jsonLoaded->SchemaVersion == GMetaCoreSceneSchemaVersion, "scene JSON version round trip");
|
||||||
|
Expect(jsonLoaded->SceneGuid == document.SceneGuid, "scene GUID round trip");
|
||||||
|
Expect(jsonLoaded->GameObjects[1].StableObjectGuid == document.GameObjects[1].StableObjectGuid, "stable object GUID round trip");
|
||||||
|
Expect(jsonLoaded->SubScenes.size() == 1 && jsonLoaded->SubScenes[0].SceneGuid == sub.SceneGuid, "SubScene JSON round trip");
|
||||||
|
|
||||||
|
MetaCorePrefabDocument prefab;
|
||||||
|
prefab.Name = "VersionedPrefab";
|
||||||
|
prefab.PrefabGuid = MetaCoreAssetGuid::Generate();
|
||||||
|
prefab.GameObjects = document.GameObjects;
|
||||||
|
prefab.GameObjects[0].SceneGuid = {};
|
||||||
|
prefab.GameObjects[1].SceneGuid = {};
|
||||||
|
prefab.Overrides.push_back({
|
||||||
|
prefab.GameObjects[0].StableObjectGuid, "Name", "Value",
|
||||||
|
MetaCorePrefabOverrideOperation::SetProperty, "\"Renamed\""
|
||||||
|
});
|
||||||
|
const auto prefabPath = root / "Versioned.mcprefab.json";
|
||||||
|
Expect(MetaCoreSceneSerializer::SavePrefabToJson(prefabPath, prefab, registry), "versioned prefab JSON save");
|
||||||
|
const auto prefabLoaded = MetaCoreSceneSerializer::LoadPrefabFromJson(prefabPath, registry);
|
||||||
|
Expect(prefabLoaded && prefabLoaded->SchemaVersion == GMetaCorePrefabSchemaVersion,
|
||||||
|
"prefab JSON version round trip");
|
||||||
|
Expect(prefabLoaded->PrefabGuid == prefab.PrefabGuid && prefabLoaded->Overrides.size() == 1,
|
||||||
|
"prefab GUID and override round trip");
|
||||||
|
|
||||||
|
MetaCoreSceneDocument legacy;
|
||||||
|
legacy.Name = "LegacyDeterministic";
|
||||||
|
legacy.GameObjects.push_back(MetaCoreGameObjectData{});
|
||||||
|
legacy.GameObjects[0].Id = 42;
|
||||||
|
const auto legacyFirstPath = root / "LegacyFirst.mcscene.json";
|
||||||
|
const auto legacySecondPath = root / "LegacySecond.mcscene.json";
|
||||||
|
Expect(MetaCoreSceneSerializer::SaveSceneToJson(legacyFirstPath, legacy, registry), "legacy scene first migration save");
|
||||||
|
Expect(MetaCoreSceneSerializer::SaveSceneToJson(legacySecondPath, legacy, registry), "legacy scene second migration save");
|
||||||
|
const auto legacyFirst = MetaCoreSceneSerializer::LoadSceneFromJson(legacyFirstPath, registry);
|
||||||
|
const auto legacySecond = MetaCoreSceneSerializer::LoadSceneFromJson(legacySecondPath, registry);
|
||||||
|
Expect(legacyFirst && legacySecond && legacyFirst->SceneGuid == legacySecond->SceneGuid,
|
||||||
|
"legacy scene GUID migration is deterministic");
|
||||||
|
Expect(legacyFirst->GameObjects[0].StableObjectGuid == legacySecond->GameObjects[0].StableObjectGuid,
|
||||||
|
"legacy object GUID migration is deterministic");
|
||||||
|
|
||||||
|
Expect(MetaCoreWriteScenePackage(packagePath, document), "versioned scene package save");
|
||||||
|
const auto packageLoaded = MetaCoreReadScenePackage(packagePath);
|
||||||
|
Expect(packageLoaded && packageLoaded->SceneGuid == document.SceneGuid, "scene package round trip");
|
||||||
|
const auto package = MetaCoreReadPackageFile(packagePath, registry);
|
||||||
|
Expect(package && package->Header.PackageGuid == document.SceneGuid, "scene package uses scene GUID");
|
||||||
|
Expect(package->Dependencies.size() == 1 && package->Dependencies[0].AssetGuid == sub.SceneGuid, "scene package dependency table");
|
||||||
|
Expect(!package->CustomVersions.empty() && package->CustomVersions[0].Key == "MetaCore.Scene", "scene package custom version");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestAdditiveWorldAndQualifiedReferences() {
|
||||||
|
auto first = MakeScene("Root", 2);
|
||||||
|
auto second = MakeScene("Cell", 1);
|
||||||
|
first.GameObjects[0].PhysicsConstraint = MetaCorePhysicsConstraintComponent{};
|
||||||
|
first.GameObjects[0].PhysicsConstraint->TargetObjectReference = {
|
||||||
|
second.SceneGuid,
|
||||||
|
second.GameObjects[0].StableObjectGuid,
|
||||||
|
MetaCoreObjectReferenceStrength::Hard
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unordered_map<MetaCoreAssetGuid, MetaCoreSceneDocument, MetaCoreAssetGuidHasher> documents;
|
||||||
|
documents.emplace(second.SceneGuid, second);
|
||||||
|
MetaCoreSceneWorld world([&](const MetaCoreAssetGuid& guid) -> std::optional<MetaCoreSceneDocument> {
|
||||||
|
const auto iterator = documents.find(guid);
|
||||||
|
return iterator == documents.end() ? std::nullopt : std::optional(iterator->second);
|
||||||
|
});
|
||||||
|
|
||||||
|
Expect(world.LoadDocumentNow(first, MetaCoreSceneLoadMode::Replace), "replace load root scene");
|
||||||
|
const auto sourceRoot = world.ResolveObject({
|
||||||
|
first.SceneGuid, first.GameObjects[0].StableObjectGuid, MetaCoreObjectReferenceStrength::Soft
|
||||||
|
});
|
||||||
|
Expect(sourceRoot, "source root resolves before additive load");
|
||||||
|
Expect(world.GetLoadedScenes().size() == 1 && world.GetScene().GetGameObjects().size() == 2, "root scene object count");
|
||||||
|
const auto requestStatusesBefore = world.GetLoadedScenes();
|
||||||
|
(void)requestStatusesBefore;
|
||||||
|
for (int iteration = 0; iteration < 1000 && !world.IsSceneLoaded(second.SceneGuid); ++iteration) {
|
||||||
|
world.Tick();
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
Expect(world.IsSceneLoaded(second.SceneGuid), "hard reference loads target scene");
|
||||||
|
Expect(world.GetScene().GetGameObjects().size() == 3, "additive objects share composite ECS");
|
||||||
|
Expect(!world.UnloadScene(second.SceneGuid), "hard referenced scene cannot unload");
|
||||||
|
|
||||||
|
const auto targetReference = MetaCoreObjectReference{
|
||||||
|
second.SceneGuid,
|
||||||
|
second.GameObjects[0].StableObjectGuid,
|
||||||
|
MetaCoreObjectReferenceStrength::Soft
|
||||||
|
};
|
||||||
|
const auto target = world.ResolveObject(targetReference);
|
||||||
|
Expect(target, "qualified object reference resolves");
|
||||||
|
const auto rebuilt = world.BuildObjectReference(target.GetId());
|
||||||
|
Expect(rebuilt.SceneGuid == second.SceneGuid && rebuilt.StableObjectGuid == targetReference.StableObjectGuid,
|
||||||
|
"runtime object builds stable qualified reference");
|
||||||
|
|
||||||
|
const auto captured = world.CaptureSceneDocument(first.SceneGuid);
|
||||||
|
Expect(captured && captured->GameObjects.size() == 2, "capture filters one loaded scene");
|
||||||
|
Expect(world.SetActiveScene(second.SceneGuid) && world.GetActiveSceneGuid() == second.SceneGuid, "active additive scene");
|
||||||
|
Expect(world.MoveSubtreeToScene(sourceRoot.GetId(), second.SceneGuid), "move subtree across scenes");
|
||||||
|
const auto movedCapture = world.CaptureSceneDocument(second.SceneGuid);
|
||||||
|
Expect(movedCapture && movedCapture->GameObjects.size() == 3, "move subtree updates target scene ownership");
|
||||||
|
const auto worldSnapshot = world.CaptureWorldSnapshot();
|
||||||
|
Expect(world.UnloadScene(first.SceneGuid, true), "hard reference source can force unload");
|
||||||
|
Expect(world.UnloadScene(second.SceneGuid), "hard residency releases after reference source unloads");
|
||||||
|
world.RestoreWorldSnapshot(worldSnapshot);
|
||||||
|
Expect(world.GetLoadedScenes().size() == 2 && world.GetScene().GetGameObjects().size() == 3,
|
||||||
|
"world snapshot restores complete scene set");
|
||||||
|
Expect(world.GetActiveSceneGuid() == second.SceneGuid, "world snapshot restores active scene");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestAsynchronousLoadCancelAndStreaming() {
|
||||||
|
auto root = MakeScene("StreamingRoot");
|
||||||
|
auto cell = MakeScene("StreamingCell");
|
||||||
|
MetaCoreSubSceneDocument sub;
|
||||||
|
sub.SceneGuid = cell.SceneGuid;
|
||||||
|
sub.LoadPolicy = MetaCoreSubSceneLoadPolicy::Distance;
|
||||||
|
sub.BoundsCenter = {100.0F, 0.0F, 0.0F};
|
||||||
|
sub.BoundsExtent = {5.0F, 5.0F, 5.0F};
|
||||||
|
sub.PreloadDistance = 10.0F;
|
||||||
|
sub.UnloadDistance = 20.0F;
|
||||||
|
root.SubScenes.push_back(sub);
|
||||||
|
|
||||||
|
MetaCoreSceneWorld world([cell](const MetaCoreAssetGuid& guid) -> std::optional<MetaCoreSceneDocument> {
|
||||||
|
return guid == cell.SceneGuid ? std::optional(cell) : std::nullopt;
|
||||||
|
});
|
||||||
|
Expect(world.LoadDocumentNow(root, MetaCoreSceneLoadMode::Replace), "streaming root load");
|
||||||
|
world.UpdateStreaming({glm::vec3(90.0F, 0.0F, 0.0F)});
|
||||||
|
for (int iteration = 0; iteration < 1000 && !world.IsSceneLoaded(cell.SceneGuid); ++iteration) {
|
||||||
|
world.Tick();
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
Expect(world.IsSceneLoaded(cell.SceneGuid), "distance cell loads near threshold");
|
||||||
|
world.UpdateStreaming({glm::vec3(111.0F, 0.0F, 0.0F)});
|
||||||
|
Expect(world.IsSceneLoaded(cell.SceneGuid), "distance hysteresis keeps cell before unload threshold");
|
||||||
|
world.UpdateStreaming({glm::vec3(130.0F, 0.0F, 0.0F)});
|
||||||
|
Expect(!world.IsSceneLoaded(cell.SceneGuid), "distance cell unloads beyond threshold");
|
||||||
|
|
||||||
|
const auto request = world.RequestLoad(cell.SceneGuid);
|
||||||
|
const auto duplicateRequest = world.RequestLoad(cell.SceneGuid);
|
||||||
|
Expect(duplicateRequest == request, "duplicate asynchronous scene requests are coalesced");
|
||||||
|
Expect(world.CancelLoad(request), "pending asynchronous load cancels");
|
||||||
|
TickUntilSettled(world, request);
|
||||||
|
Expect(world.GetLoadStatus(request)->State == MetaCoreSceneLoadState::Cancelled, "cancelled request remains cancelled");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestSceneDependencyValidation() {
|
||||||
|
auto root = MakeScene("DependencyRoot");
|
||||||
|
auto child = MakeScene("DependencyChild");
|
||||||
|
root.SubScenes.push_back({child.SceneGuid});
|
||||||
|
child.SubScenes.push_back({root.SceneGuid});
|
||||||
|
std::unordered_map<MetaCoreAssetGuid, MetaCoreSceneDocument, MetaCoreAssetGuidHasher> documents{
|
||||||
|
{root.SceneGuid, root}, {child.SceneGuid, child}
|
||||||
|
};
|
||||||
|
MetaCoreSceneWorld world([&](const MetaCoreAssetGuid& guid) -> std::optional<MetaCoreSceneDocument> {
|
||||||
|
const auto iterator = documents.find(guid);
|
||||||
|
return iterator == documents.end() ? std::nullopt : std::optional(iterator->second);
|
||||||
|
});
|
||||||
|
const auto issues = world.ValidateDependencies(root);
|
||||||
|
Expect(std::any_of(issues.begin(), issues.end(), [](const std::string& issue) {
|
||||||
|
return issue.find("cycle") != std::string::npos;
|
||||||
|
}), "recursive SubScene cycle is rejected");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestPrefabVariantNestedAndOverrides() {
|
||||||
|
MetaCorePrefabDocument base;
|
||||||
|
base.PrefabGuid = MetaCoreAssetGuid::Generate();
|
||||||
|
base.Name = "Base";
|
||||||
|
MetaCoreGameObjectData root;
|
||||||
|
root.Id = 1;
|
||||||
|
root.StableObjectGuid = MetaCoreBuildDeterministicGuid(base.PrefabGuid, 1);
|
||||||
|
root.Name = "BaseRoot";
|
||||||
|
base.GameObjects.push_back(root);
|
||||||
|
|
||||||
|
MetaCorePrefabDocument nested;
|
||||||
|
nested.PrefabGuid = MetaCoreAssetGuid::Generate();
|
||||||
|
nested.Name = "Nested";
|
||||||
|
MetaCoreGameObjectData nestedRoot;
|
||||||
|
nestedRoot.Id = 1;
|
||||||
|
nestedRoot.StableObjectGuid = MetaCoreBuildDeterministicGuid(nested.PrefabGuid, 1);
|
||||||
|
nestedRoot.Name = "NestedRoot";
|
||||||
|
nested.GameObjects.push_back(nestedRoot);
|
||||||
|
|
||||||
|
MetaCorePrefabDocument variant;
|
||||||
|
variant.PrefabGuid = MetaCoreAssetGuid::Generate();
|
||||||
|
variant.BasePrefabGuid = base.PrefabGuid;
|
||||||
|
variant.Name = "Variant";
|
||||||
|
variant.Overrides.push_back({
|
||||||
|
root.StableObjectGuid, "Transform", "Position", MetaCorePrefabOverrideOperation::SetProperty, "[1,2,3]"
|
||||||
|
});
|
||||||
|
MetaCoreNestedPrefabDocument nestedInstance;
|
||||||
|
nestedInstance.MountNodeGuid = root.StableObjectGuid;
|
||||||
|
nestedInstance.PrefabAssetGuid = nested.PrefabGuid;
|
||||||
|
nestedInstance.InstanceGuid = MetaCoreAssetGuid::Generate();
|
||||||
|
variant.NestedPrefabs.push_back(nestedInstance);
|
||||||
|
|
||||||
|
std::unordered_map<MetaCoreAssetGuid, MetaCorePrefabDocument, MetaCoreAssetGuidHasher> prefabs{
|
||||||
|
{base.PrefabGuid, base}, {nested.PrefabGuid, nested}, {variant.PrefabGuid, variant}
|
||||||
|
};
|
||||||
|
MetaCorePrefabResolver resolver([&](const MetaCoreAssetGuid& guid) -> std::optional<MetaCorePrefabDocument> {
|
||||||
|
const auto iterator = prefabs.find(guid);
|
||||||
|
return iterator == prefabs.end() ? std::nullopt : std::optional(iterator->second);
|
||||||
|
});
|
||||||
|
|
||||||
|
const auto resolved = resolver.Resolve(variant.PrefabGuid);
|
||||||
|
Expect(resolved && resolved->Document.GameObjects.size() == 2, "variant resolves base and nested prefab");
|
||||||
|
Expect(resolved->Document.GameObjects[0].Transform.Position == glm::vec3(1.0F, 2.0F, 3.0F), "variant property override");
|
||||||
|
Expect(resolved->Document.GameObjects[1].ParentId == resolved->Document.GameObjects[0].Id, "nested prefab mounts below target node");
|
||||||
|
|
||||||
|
const auto instanceGuid = MetaCoreAssetGuid::Generate();
|
||||||
|
const auto firstInstance = resolver.Instantiate(variant.PrefabGuid, instanceGuid);
|
||||||
|
const auto secondInstance = resolver.Instantiate(variant.PrefabGuid, instanceGuid);
|
||||||
|
Expect(firstInstance && secondInstance && firstInstance->size() == secondInstance->size(), "prefab instantiate succeeds");
|
||||||
|
for (std::size_t index = 0; index < firstInstance->size(); ++index)
|
||||||
|
Expect((*firstInstance)[index].StableObjectGuid == (*secondInstance)[index].StableObjectGuid,
|
||||||
|
"same instance GUID yields stable prefab object GUIDs");
|
||||||
|
|
||||||
|
const std::vector<MetaCorePrefabOverrideDocument> structuralOverrides{
|
||||||
|
{root.StableObjectGuid, "Camera", "", MetaCorePrefabOverrideOperation::AddComponent, "{}"},
|
||||||
|
{root.StableObjectGuid, "", "", MetaCorePrefabOverrideOperation::AddChild,
|
||||||
|
R"({"Name":"InstanceChild","Position":[4,5,6]})"}
|
||||||
|
};
|
||||||
|
const auto structuralInstance = resolver.Instantiate(
|
||||||
|
variant.PrefabGuid, MetaCoreAssetGuid::Generate(), structuralOverrides
|
||||||
|
);
|
||||||
|
Expect(structuralInstance && structuralInstance->size() == 3,
|
||||||
|
"instance structural override adds a child");
|
||||||
|
Expect((*structuralInstance)[0].Camera.has_value(), "instance component override adds a component");
|
||||||
|
|
||||||
|
prefabs[base.PrefabGuid].BasePrefabGuid = variant.PrefabGuid;
|
||||||
|
Expect(!resolver.Resolve(variant.PrefabGuid), "prefab inheritance cycle rejected");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
TestSceneJsonAndPackageVersions();
|
||||||
|
TestAdditiveWorldAndQualifiedReferences();
|
||||||
|
TestAsynchronousLoadCancelAndStreaming();
|
||||||
|
TestSceneDependencyValidation();
|
||||||
|
TestPrefabVariantNestedAndOverrides();
|
||||||
|
std::cout << "MetaCoreSceneTests passed\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@ -25,6 +25,7 @@
|
|||||||
#include "MetaCoreScene/MetaCoreTransformUtils.h"
|
#include "MetaCoreScene/MetaCoreTransformUtils.h"
|
||||||
#include "MetaCoreFoundation/MetaCoreHash.h"
|
#include "MetaCoreFoundation/MetaCoreHash.h"
|
||||||
#include "../Source/MetaCoreEditor/Private/MetaCoreGltfImporter.h"
|
#include "../Source/MetaCoreEditor/Private/MetaCoreGltfImporter.h"
|
||||||
|
#include "../Source/MetaCoreEditor/Private/MetaCoreEditorCameraController.h"
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
@ -1429,6 +1430,10 @@ void MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot() {
|
|||||||
for (const auto& renderable : snapshot.Renderables) {
|
for (const auto& renderable : snapshot.Renderables) {
|
||||||
if (renderable.ObjectId == root.GetId()) {
|
if (renderable.ObjectId == root.GetId()) {
|
||||||
MetaCoreExpect(renderable.Visible == true, "RenderSync 根对象 Visible 应为 true");
|
MetaCoreExpect(renderable.Visible == true, "RenderSync 根对象 Visible 应为 true");
|
||||||
|
MetaCoreExpect(
|
||||||
|
MetaCore::MetaCoreUsesNativeGltfVisibility(renderable),
|
||||||
|
"导入 glTF 应跳过 ECS 单位代理包围盒,交由 gltfio 使用真实几何包围盒剔除"
|
||||||
|
);
|
||||||
MetaCoreExpectVec3Near(renderable.BaseColor, glm::vec3(0.5F, 0.6F, 0.7F), "RenderSync 根对象 BaseColor 应正确");
|
MetaCoreExpectVec3Near(renderable.BaseColor, glm::vec3(0.5F, 0.6F, 0.7F), "RenderSync 根对象 BaseColor 应正确");
|
||||||
MetaCoreExpect(std::abs(renderable.Metallic - 0.35F) < 0.0001F, "RenderSync 根对象 Metallic 应正确");
|
MetaCoreExpect(std::abs(renderable.Metallic - 0.35F) < 0.0001F, "RenderSync 根对象 Metallic 应正确");
|
||||||
MetaCoreExpect(std::abs(renderable.Roughness - 0.45F) < 0.0001F, "RenderSync 根对象 Roughness 应正确");
|
MetaCoreExpect(std::abs(renderable.Roughness - 0.45F) < 0.0001F, "RenderSync 根对象 Roughness 应正确");
|
||||||
@ -1442,6 +1447,10 @@ void MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot() {
|
|||||||
MetaCoreExpect(renderable.EmissiveTexturePath == "Assets/Textures/SyncEmissive.png", "RenderSync 根对象自发光贴图路径应正确");
|
MetaCoreExpect(renderable.EmissiveTexturePath == "Assets/Textures/SyncEmissive.png", "RenderSync 根对象自发光贴图路径应正确");
|
||||||
MetaCoreExpect(renderable.AoTexturePath == "Assets/Textures/SyncAo.png", "RenderSync 根对象 AO 贴图路径应正确");
|
MetaCoreExpect(renderable.AoTexturePath == "Assets/Textures/SyncAo.png", "RenderSync 根对象 AO 贴图路径应正确");
|
||||||
} else if (renderable.ObjectId == child.GetId()) {
|
} else if (renderable.ObjectId == child.GetId()) {
|
||||||
|
MetaCoreExpect(
|
||||||
|
!MetaCore::MetaCoreUsesNativeGltfVisibility(renderable),
|
||||||
|
"普通内置网格仍应使用引擎可见性系统"
|
||||||
|
);
|
||||||
MetaCoreExpect(renderable.Visible == false, "RenderSync 子对象 Visible 应为 false");
|
MetaCoreExpect(renderable.Visible == false, "RenderSync 子对象 Visible 应为 false");
|
||||||
MetaCoreExpectVec3Near(renderable.BaseColor, glm::vec3(0.1F, 0.2F, 0.3F), "RenderSync 子对象 BaseColor 应正确");
|
MetaCoreExpectVec3Near(renderable.BaseColor, glm::vec3(0.1F, 0.2F, 0.3F), "RenderSync 子对象 BaseColor 应正确");
|
||||||
}
|
}
|
||||||
@ -1770,8 +1779,8 @@ void MetaCoreTestViewportPickingUsesMeshTrianglesBeforeHierarchyDepth() {
|
|||||||
shellMesh.IndexCount = 6;
|
shellMesh.IndexCount = 6;
|
||||||
shellMesh.PayloadIndex = 1;
|
shellMesh.PayloadIndex = 1;
|
||||||
shellMesh.PayloadSize = makePlanePayload(2.0F).size();
|
shellMesh.PayloadSize = makePlanePayload(2.0F).size();
|
||||||
shellMesh.BoundingBoxMin = glm::vec3(-2.0F, -2.0F, 0.0F);
|
shellMesh.BoundingBoxMin = glm::vec3(-2000.0F, -100.0F, -1000.0F);
|
||||||
shellMesh.BoundingBoxMax = glm::vec3(2.0F, 2.0F, 0.0F);
|
shellMesh.BoundingBoxMax = glm::vec3(2000.0F, 100.0F, 1000.0F);
|
||||||
document.GeneratedMeshAssets.push_back(shellMesh);
|
document.GeneratedMeshAssets.push_back(shellMesh);
|
||||||
|
|
||||||
MetaCore::MetaCoreMeshAssetDocument interiorMesh;
|
MetaCore::MetaCoreMeshAssetDocument interiorMesh;
|
||||||
@ -1841,6 +1850,12 @@ void MetaCoreTestViewportPickingUsesMeshTrianglesBeforeHierarchyDepth() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
MetaCoreExpect(pickedObjectId == shellObject.GetId(), "三角面拾取应选择可见的外壳,而不是层级更深的内部部件");
|
MetaCoreExpect(pickedObjectId == shellObject.GetId(), "三角面拾取应选择可见的外壳,而不是层级更深的内部部件");
|
||||||
|
editorContext.SetSelectedObjectId(shellObject.GetId());
|
||||||
|
interactionService.FocusSelectedObject(editorContext);
|
||||||
|
const auto focusedView = editorContext.GetCameraController().BuildSceneView();
|
||||||
|
const float focusedDistance = glm::length(focusedView.CameraPosition - focusedView.CameraTarget);
|
||||||
|
MetaCoreExpect(focusedDistance > 5000.0F, "模型聚焦应采用资源真实包围盒,而不是根节点单位 Scale");
|
||||||
|
MetaCoreExpect(focusedView.FarClip > focusedDistance + 2000.0F, "模型聚焦应扩展 Far Clip 覆盖完整资源包围盒");
|
||||||
|
|
||||||
coreServicesModule->Shutdown(moduleRegistry);
|
coreServicesModule->Shutdown(moduleRegistry);
|
||||||
moduleRegistry.ShutdownServices();
|
moduleRegistry.ShutdownServices();
|
||||||
@ -5493,6 +5508,16 @@ void MetaCoreTestParticleRenderableGrowthAndRemoval() {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
void MetaCoreTestLargeBoundsEditorCameraFraming() {
|
||||||
|
MetaCore::MetaCoreEditorCameraController camera;
|
||||||
|
camera.FocusBounds(glm::vec3(625.0F, -510.0F, 46.0F), 1600.0F);
|
||||||
|
const MetaCore::MetaCoreSceneView view = camera.BuildSceneView();
|
||||||
|
const float distance = glm::length(view.CameraPosition - view.CameraTarget);
|
||||||
|
MetaCoreExpect(distance > 5000.0F, "大模型聚焦距离不应被 100 单位上限截断");
|
||||||
|
MetaCoreExpect(view.FarClip > distance + 1600.0F, "大模型应完整位于动态 Far Clip 内");
|
||||||
|
MetaCoreExpect(view.NearClip >= 0.01F && view.NearClip <= 1.0F, "动态 Near Clip 应保持有效范围");
|
||||||
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
std::setvbuf(stdout, nullptr, _IONBF, 0);
|
std::setvbuf(stdout, nullptr, _IONBF, 0);
|
||||||
std::setvbuf(stderr, nullptr, _IONBF, 0);
|
std::setvbuf(stderr, nullptr, _IONBF, 0);
|
||||||
@ -5587,6 +5612,8 @@ int main() {
|
|||||||
MetaCoreTestCameraComponentJsonRoundTripAndLegacyDefaults();
|
MetaCoreTestCameraComponentJsonRoundTripAndLegacyDefaults();
|
||||||
std::cout << "[RUN] MetaCoreTestCameraSelectionAndProjectionUtilities..." << std::endl;
|
std::cout << "[RUN] MetaCoreTestCameraSelectionAndProjectionUtilities..." << std::endl;
|
||||||
MetaCoreTestCameraSelectionAndProjectionUtilities();
|
MetaCoreTestCameraSelectionAndProjectionUtilities();
|
||||||
|
std::cout << "[RUN] MetaCoreTestLargeBoundsEditorCameraFraming..." << std::endl;
|
||||||
|
MetaCoreTestLargeBoundsEditorCameraFraming();
|
||||||
std::cout << "[RUN] MetaCoreTestViewportPickingPrefersModelChildNode..." << std::endl;
|
std::cout << "[RUN] MetaCoreTestViewportPickingPrefersModelChildNode..." << std::endl;
|
||||||
MetaCoreTestViewportPickingPrefersModelChildNode();
|
MetaCoreTestViewportPickingPrefersModelChildNode();
|
||||||
std::cout << "[RUN] MetaCoreTestViewportPickingUsesMeshTrianglesBeforeHierarchyDepth..." << std::endl;
|
std::cout << "[RUN] MetaCoreTestViewportPickingUsesMeshTrianglesBeforeHierarchyDepth..." << std::endl;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user