From 04cff527e39f3ffab9261a18785e0cd4b97dc7e2 Mon Sep 17 00:00:00 2001 From: ayuan9957 <107920784+ayuan9957@users.noreply.github.com> Date: Thu, 21 May 2026 16:13:31 +0800 Subject: [PATCH] feat(render): add SceneRenderSync snapshot and route Filament bridge through it --- CMakeLists.txt | 34 ++ .../Private/MetaCoreFilamentSceneBridge.cpp | 308 +++++++++++------- .../Private/MetaCoreSceneRenderSync.cpp | 96 +++++- .../MetaCoreFilamentSceneBridge.h | 7 + .../MetaCoreRender/MetaCoreSceneRenderSync.h | 12 + .../MetaCoreScene/Private/MetaCoreScene.cpp | 10 + .../Public/MetaCoreScene/MetaCoreComponents.h | 4 + .../Public/MetaCoreScene/MetaCoreScene.h | 4 + 8 files changed, 359 insertions(+), 116 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a808837..2ec622b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,37 @@ else() set(METACORE_COMMON_WARNINGS -Wall -Wextra -Wpedantic) endif() +set(METACORE_UI_BLIT_MATERIAL_SOURCE "${CMAKE_SOURCE_DIR}/third_party/filament/libs/filagui/src/materials/uiBlit.mat") +set(METACORE_UI_BLIT_MATERIAL_OUTPUT "${CMAKE_BINARY_DIR}/uiBlit.filamat") +set(METACORE_FILAMENT_MATC "${METACORE_FILAMENT_ROOT}/bin/matc.exe") + +add_custom_command( + OUTPUT "${METACORE_UI_BLIT_MATERIAL_OUTPUT}" + COMMAND "${METACORE_FILAMENT_MATC}" + --platform desktop + --api opengl + --output "${METACORE_UI_BLIT_MATERIAL_OUTPUT}" + "${METACORE_UI_BLIT_MATERIAL_SOURCE}" + DEPENDS + "${METACORE_UI_BLIT_MATERIAL_SOURCE}" + "${METACORE_FILAMENT_MATC}" + VERBATIM +) + +add_custom_target(MetaCoreUiBlitMaterial + DEPENDS "${METACORE_UI_BLIT_MATERIAL_OUTPUT}" +) + +function(metacore_stage_ui_blit_material target_name) + add_dependencies(${target_name} MetaCoreUiBlitMaterial) + add_custom_command(TARGET ${target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${METACORE_UI_BLIT_MATERIAL_OUTPUT}" + "$/uiBlit.filamat" + VERBATIM + ) +endfunction() + add_executable(MetaCoreHeaderTool Tools/MetaCoreHeaderTool/main.cpp ) @@ -385,6 +416,7 @@ target_link_libraries(MetaCoreEditorApp PRIVATE MetaCoreEditor ) +metacore_stage_ui_blit_material(MetaCoreEditorApp) @@ -400,6 +432,7 @@ target_link_libraries(MetaCorePlayer MetaCoreRuntimeData MetaCoreScene ) +metacore_stage_ui_blit_material(MetaCorePlayer) @@ -432,4 +465,5 @@ if(METACORE_BUILD_TESTS) target_compile_options(FilamentImGuiDemo PRIVATE ${METACORE_COMMON_WARNINGS}) target_compile_definitions(FilamentImGuiDemo PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX) metacore_use_filament(FilamentImGuiDemo) + metacore_stage_ui_blit_material(FilamentImGuiDemo) endif() diff --git a/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp b/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp index 826ebc6..f8dc24a 100644 --- a/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp +++ b/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp @@ -60,6 +60,11 @@ public: Shutdown(); } + // 提供公有接口用于构建渲染同步快照 + MetaCoreSceneRenderSyncSnapshot BuildSnapshot(const MetaCoreScene& scene) const { + return RenderSync_.BuildSnapshot(scene); + } + bool Initialize(MetaCoreWindow& window) { std::cout << "FilamentSceneBridge: Initializing..." << std::endl; @@ -357,11 +362,11 @@ public: return path; } - void SyncScene(MetaCoreScene& scene, bool compatibilityMeshOnly, bool useScenePrimaryCamera) { + void SyncScene(const MetaCoreSceneRenderSyncSnapshot& snapshot, MetaCoreScene* scene, bool compatibilityMeshOnly, bool useScenePrimaryCamera) { if (!AssetLoader_) return; (void)compatibilityMeshOnly; - LastSyncSnapshot_ = RenderSync_.BuildSnapshot(scene); + LastSyncSnapshot_ = snapshot; ObjectWorldMatrices_ = LastSyncSnapshot_.WorldMatrices; SyncSceneLights(LastSyncSnapshot_); if (useScenePrimaryCamera) { @@ -371,44 +376,20 @@ public: } } - // 预处理:构建子树模型网格节点计数表,以支撑在没有 Tag 时的极速、完美 Fallback 回溯 - std::unordered_map> subtreeModelCounts; - for (const auto& obj : scene.GetGameObjects()) { - if (obj.HasComponent()) { - auto& mesh = obj.GetComponent(); - if (mesh.ModelNodeIndex >= 0 && !mesh.SourceModelPath.empty()) { - std::string normPath = NormalizePath(mesh.SourceModelPath); - MetaCoreId currentId = obj.GetId(); - while (currentId != 0) { - auto currentObj = scene.FindGameObject(currentId); - if (!currentObj) break; - subtreeModelCounts[currentId][normPath]++; - currentId = currentObj.GetParentId(); - } - } - } - } - - for (const auto& gameObject : scene.GetGameObjects()) { - if (!gameObject.HasComponent()) { - continue; - } - - const auto& meshRenderer = gameObject.GetComponent(); - - if (meshRenderer.MeshSource != MetaCoreMeshSourceKind::Asset && - meshRenderer.MeshSource != MetaCoreMeshSourceKind::Builtin) { + for (const auto& renderable : snapshot.Renderables) { + if (renderable.MeshSource != MetaCoreMeshSourceKind::Asset && + renderable.MeshSource != MetaCoreMeshSourceKind::Builtin) { continue; } std::string modelPath; - if (meshRenderer.SourceModelAssetGuid.IsValid()) { - modelPath = MetaCoreAssetRegistry::Get().ResolveGuidToPath(meshRenderer.SourceModelAssetGuid).generic_string(); + if (renderable.SourceModelAssetGuid.IsValid()) { + modelPath = MetaCoreAssetRegistry::Get().ResolveGuidToPath(renderable.SourceModelAssetGuid).generic_string(); } - if (modelPath.empty() && !meshRenderer.SourceModelPath.empty()) { - modelPath = meshRenderer.SourceModelPath; + if (modelPath.empty() && !renderable.SourceModelPath.empty()) { + modelPath = renderable.SourceModelPath; } - if (modelPath.empty() && meshRenderer.MeshSource == MetaCoreMeshSourceKind::Builtin && meshRenderer.BuiltinMesh == MetaCoreBuiltinMeshType::Cube) { + if (modelPath.empty() && renderable.MeshSource == MetaCoreMeshSourceKind::Builtin && renderable.BuiltinMesh == MetaCoreBuiltinMeshType::Cube) { modelPath = "Assets/Models/box1.glb"; } if (modelPath.empty()) { @@ -417,58 +398,16 @@ public: std::filesystem::path absolutePath = ProjectRootPath_ / modelPath; - // 【黄金算法】:溯源确定唯一的模型根加载主体 - MetaCoreGameObject hostRoot = gameObject; - if (meshRenderer.ModelNodeIndex >= 0) { - const std::string& currentModelPath = meshRenderer.SourceModelPath; - std::string normCurrentPath = NormalizePath(currentModelPath); - MetaCoreGameObject current = gameObject; - MetaCoreGameObject foundRoot = {}; + MetaCoreId hostRootId = renderable.HostRootId; + std::string hostRootName = renderable.HostRootName; - // 1. 优先通过运行时 MetaCoreModelRootTag 快速寻找顶级根 - while (current.GetParentId() != 0) { - auto parentObj = scene.FindGameObject(current.GetParentId()); - if (!parentObj) break; - - if (parentObj.HasComponent()) { - auto& tag = parentObj.GetComponent(); - if (NormalizePath(tag.SourceModelPath) == normCurrentPath) { - foundRoot = parentObj; - break; - } - } - current = parentObj; - } - - if (foundRoot) { - hostRoot = foundRoot; - } else { - // 2. Fallback:若无明确 Tag(例如反序列化后),使用转折点判定算法(匹配包含模型网格数最大的、最深的祖先) - current = gameObject; - MetaCoreGameObject bestCandidate = gameObject; - int maxCount = subtreeModelCounts[gameObject.GetId()][normCurrentPath]; - - while (current) { - int count = subtreeModelCounts[current.GetId()][normCurrentPath]; - if (count > maxCount) { - maxCount = count; - bestCandidate = current; - } - if (current.GetParentId() == 0) break; - current = scene.FindGameObject(current.GetParentId()); - } - hostRoot = bestCandidate; - } - } - - // 如果该模型的顶级根宿主已经被加载过,我们只需更新当前游戏对象的位置即可 - if (LoadedAssets_.contains(hostRoot.GetId())) { - UpdateTransform(gameObject); + // 如果该模型的顶级根宿主已经被加载过,则不需要重复加载,直接更新变换 + if (LoadedAssets_.contains(hostRootId)) { continue; } - // 加载新模型,以顶级根宿主 hostRoot 的 ID 进行注册存储 - std::cout << "FilamentSceneBridge: Loading GLTF: " << absolutePath << " (Host: " << hostRoot.GetName() << ")" << std::endl; + // 加载新模型,以顶级根宿主 hostRootId 进行注册存储 + std::cout << "FilamentSceneBridge: Loading GLTF: " << absolutePath << " (Host: " << hostRootName << ")" << std::endl; std::ifstream file(absolutePath, std::ios::binary); if (!file.is_open()) { @@ -495,23 +434,91 @@ public: // 添加到场景 Scene_->addEntities(asset->getEntities(), asset->getEntityCount()); - LoadedAssets_[hostRoot.GetId()] = asset; + LoadedAssets_[hostRootId] = asset; - // 同步层级结构到场景树并自动进行展开子节点的复用映射 - MetaCoreGameObject nonConstHostRoot = hostRoot; - - // 现场补挂 Tag,确保后续帧和拖拽时 100% 命中 Tag 快速通道,绝不走 Fallback - if (!nonConstHostRoot.HasComponent()) { - nonConstHostRoot.AddComponent(MetaCoreModelRootTag{ modelPath }); - } + if (scene != nullptr) { + // 有 ECS 实例模式下同步层级结构并补挂 Tag + auto hostRootObj = scene->FindGameObject(hostRootId); + if (hostRootObj) { + MetaCoreGameObject nonConstHostRoot = hostRootObj; + + // 现场补挂 Tag,确保后续帧和拖拽时 100% 命中 Tag 快速通道,绝不走 Fallback + if (!nonConstHostRoot.HasComponent()) { + nonConstHostRoot.AddComponent(MetaCoreModelRootTag{ modelPath }); + } - // 如果名字是默认的 "Cube",自动改为模型文件名 - if (nonConstHostRoot.GetName() == "Cube") { - std::string filename = std::filesystem::path(modelPath).filename().string(); - nonConstHostRoot.GetName() = filename; - } + // 如果名字是默认的 "Cube",自动改为模型文件名 + if (nonConstHostRoot.GetName() == "Cube") { + std::string filename = std::filesystem::path(modelPath).filename().string(); + nonConstHostRoot.GetName() = filename; + } - ParseModelHierarchyToEcs(scene, nonConstHostRoot, asset); + ParseModelHierarchyToEcs(*scene, nonConstHostRoot, asset); + } + } else { + // 如果 scene 为 nullptr,代表纯快照渲染模式。我们需要直接在 ObjectToFilamentEntity_ 中建立子实体的映射。 + // 顶级父 ID 对应 asset->getRoot() + ObjectToFilamentEntity_[hostRootId] = { asset, asset->getRoot() }; + + // 遍历新资产中的 entities 映射到 snapshot 中的 renderable 节点 + const utils::Entity* entities = asset->getEntities(); + size_t entityCount = asset->getEntityCount(); + + std::vector candidates; + for (const auto& r : snapshot.Renderables) { + if (r.HostRootId == hostRootId && r.ObjectId != hostRootId) { + candidates.push_back(r); + } + } + + std::map entityToId; + entityToId[asset->getRoot()] = hostRootId; + + for (size_t i = 0; i < entityCount; i++) { + utils::Entity entity = entities[i]; + if (entity == asset->getRoot()) continue; + + const char* nodeName = asset->getName(entity); + std::string name = nodeName ? nodeName : ("Node_" + std::to_string(i)); + + MetaCoreId childObjectId = 0; + bool isExisting = false; + + // 1. 优先通过 ModelNodeIndex 进行匹配 + for (const auto& cand : candidates) { + if (cand.ModelNodeIndex == static_cast(i)) { + childObjectId = cand.ObjectId; + isExisting = true; + break; + } + } + + // 2. 其次通过名字匹配 + if (!isExisting) { + for (const auto& cand : candidates) { + if (cand.Name == name) { + bool alreadyMapped = false; + for (auto& [ent, id] : entityToId) { + if (id == cand.ObjectId) { + alreadyMapped = true; + break; + } + } + if (!alreadyMapped) { + childObjectId = cand.ObjectId; + isExisting = true; + break; + } + } + } + } + + if (isExisting && childObjectId != 0) { + entityToId[entity] = childObjectId; + ObjectToFilamentEntity_[childObjectId] = { asset, entity }; + } + } + } // 创建中间 Pivot 实体用于坐标系转换 (glTF Y-up -> MetaCore Z-up) utils::Entity pivotEntity = utils::EntityManager::get().create(); @@ -532,19 +539,25 @@ public: tm.setTransform(tm.getInstance(assetRoot), *reinterpret_cast(glm::value_ptr(finalRootMatrix))); - // 【关键一步】:注册 hostRoot(顶级根宿主)的 ID 与 pivotEntity,彻底打通顶级宿主的 Gizmo 坐标变换! - ObjectToFilamentEntity_[hostRoot.GetId()] = { asset, pivotEntity }; + // 【关键一步】:注册 hostRootId(顶级根宿主)的 ID 与 pivotEntity,彻底打通顶级宿主的 Gizmo 坐标变换! + ObjectToFilamentEntity_[hostRootId] = { asset, pivotEntity }; Scene_->addEntity(pivotEntity); // 更新初始位置 - UpdateTransform(gameObject); + glm::mat4 initLocalMat{1.0f}; + auto itMat = snapshot.LocalMatrices.find(renderable.ObjectId); + if (itMat != snapshot.LocalMatrices.end()) { + initLocalMat = itMat->second; + } + UpdateTransform(renderable.ObjectId, initLocalMat); } // 全量同步所有已映射对象的 Transform - for (const auto& gameObject : scene.GetGameObjects()) { - if (ObjectToFilamentEntity_.count(gameObject.GetId())) { - UpdateTransform(gameObject); + for (const auto& [objectId, assetEntity] : ObjectToFilamentEntity_) { + auto itMat = snapshot.LocalMatrices.find(objectId); + if (itMat != snapshot.LocalMatrices.end()) { + UpdateTransform(objectId, itMat->second); } } } @@ -670,6 +683,51 @@ public: bool HasRuntimeSyncFailure() const { return false; } const std::string& GetLastRuntimeSyncFailure() const { return EmptyString_; } + bool VerifyTransformForTesting(MetaCoreId objectId, const glm::mat4& expectedMatrix) const { + auto it = ObjectToFilamentEntity_.find(objectId); + if (it == ObjectToFilamentEntity_.end()) return false; + + utils::Entity entity = it->second.second; + auto& tm = Engine_->getTransformManager(); + auto instance = tm.getInstance(entity); + if (!instance) return false; + + filament::math::mat4f currentMat = tm.getTransform(instance); + glm::mat4 actualMatrix; + std::memcpy(glm::value_ptr(actualMatrix), ¤tMat[0][0], sizeof(float) * 16); + + glm::mat4 targetMatrix = expectedMatrix; + if (LoadedAssets_.count(objectId) == 0) { + glm::mat4 R_plus90X = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), {1, 0, 0}); + glm::mat4 R_minus90X = glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), {1, 0, 0}); + targetMatrix = R_plus90X * targetMatrix * R_minus90X; + } + + // 浮点数比对,精度 0.0001 + for (int c = 0; c < 4; ++c) { + for (int r = 0; r < 4; ++r) { + if (std::abs(actualMatrix[c][r] - targetMatrix[c][r]) > 0.0001f) { + return false; + } + } + } + return true; + } + + bool VerifyLightExistsForTesting(MetaCoreId objectId) const { + return SceneLightEntities_.contains(objectId); + } + + float GetDefaultLightIntensityForTesting() const { + if (!Engine_) return 0.0F; + auto& lightManager = Engine_->getLightManager(); + const auto defaultLightInstance = lightManager.getInstance(Light_); + if (defaultLightInstance) { + return lightManager.getIntensity(defaultLightInstance); + } + return 0.0F; + } + private: static filament::math::float3 ToFilamentFloat3(const glm::vec3& value) { return filament::math::float3{ value.x, value.y, value.z }; @@ -700,11 +758,25 @@ private: std::unordered_set activeLightIds; auto& lightManager = Engine_->getLightManager(); const auto defaultLightInstance = lightManager.getInstance(Light_); + + // 统计当前是否有处于启用状态的光源 + bool hasActiveLight = false; + for (const auto& light : snapshot.Lights) { + if (light.Enabled) { + hasActiveLight = true; + break; + } + } + if (defaultLightInstance) { - lightManager.setIntensity(defaultLightInstance, snapshot.Lights.empty() ? 100000.0F : 0.0F); + // 如果场景中没有任何起作用的灯光,则自动亮起默认灯光作为兜底 + lightManager.setIntensity(defaultLightInstance, hasActiveLight ? 0.0F : 100000.0F); } for (const MetaCoreRenderSyncLight& light : snapshot.Lights) { + if (!light.Enabled) { + continue; + } activeLightIds.insert(light.ObjectId); auto lightIt = SceneLightEntities_.find(light.ObjectId); if (lightIt == SceneLightEntities_.end()) { @@ -740,8 +812,8 @@ private: } } - void UpdateTransform(const MetaCoreGameObject& gameObject) { - auto it = ObjectToFilamentEntity_.find(gameObject.GetId()); + void UpdateTransform(MetaCoreId objectId, const glm::mat4& localMatrix) { + auto it = ObjectToFilamentEntity_.find(objectId); if (it == ObjectToFilamentEntity_.end()) return; utils::Entity entity = it->second.second; @@ -749,15 +821,12 @@ private: auto& tm = Engine_->getTransformManager(); auto instance = tm.getInstance(entity); if (instance) { - if (!gameObject.HasComponent()) return; - - const auto& transform = gameObject.GetComponent(); - glm::mat4 matrix = MetaCoreBuildTransformMatrix(transform); + glm::mat4 matrix = localMatrix; // 【关键修复】:如果当前 entity 是子节点(即不是顶级映射的 pivotEntity) // 由于它的父级(assetRoot)带有了 -90X 旋转,我们必须对子节点应用基变换来抵消这个旋转。 // 公式:L_filament = R(90X) * L_ecs * R(-90X) - if (LoadedAssets_.count(gameObject.GetId()) == 0) { + if (LoadedAssets_.count(objectId) == 0) { glm::mat4 R_plus90X = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), {1, 0, 0}); glm::mat4 R_minus90X = glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), {1, 0, 0}); matrix = R_plus90X * matrix * R_minus90X; @@ -817,7 +886,12 @@ void MetaCoreFilamentSceneBridge::SetProjectRootPath(const std::filesystem::path } void MetaCoreFilamentSceneBridge::SyncScene(MetaCoreScene& scene, bool compatibilityMeshOnly, bool useScenePrimaryCamera) { - Impl_->SyncScene(scene, compatibilityMeshOnly, useScenePrimaryCamera); + auto snapshot = Impl_->BuildSnapshot(scene); + Impl_->SyncScene(snapshot, &scene, compatibilityMeshOnly, useScenePrimaryCamera); +} + +void MetaCoreFilamentSceneBridge::SyncScene(const MetaCoreSceneRenderSyncSnapshot& snapshot, MetaCoreScene* scene, bool compatibilityMeshOnly, bool useScenePrimaryCamera) { + Impl_->SyncScene(snapshot, scene, compatibilityMeshOnly, useScenePrimaryCamera); } void MetaCoreFilamentSceneBridge::ApplySceneView(const MetaCoreSceneView& sceneView) { @@ -852,4 +926,16 @@ const std::string& MetaCoreFilamentSceneBridge::GetLastRuntimeSyncFailure() cons return Impl_->GetLastRuntimeSyncFailure(); } +bool MetaCoreFilamentSceneBridge::VerifyTransformForTesting(MetaCoreId objectId, const glm::mat4& expectedMatrix) const { + return Impl_->VerifyTransformForTesting(objectId, expectedMatrix); +} + +bool MetaCoreFilamentSceneBridge::VerifyLightExistsForTesting(MetaCoreId objectId) const { + return Impl_->VerifyLightExistsForTesting(objectId); +} + +float MetaCoreFilamentSceneBridge::GetDefaultLightIntensityForTesting() const { + return Impl_->GetDefaultLightIntensityForTesting(); +} + } // namespace MetaCore diff --git a/Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp b/Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp index 3291334..7d58b3e 100644 --- a/Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp +++ b/Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp @@ -6,6 +6,7 @@ #include +#include #include namespace MetaCore { @@ -23,10 +24,36 @@ namespace { return glm::vec3(matrix[3]); } +[[nodiscard]] std::string MetaCoreNormalizeSyncPath(std::string path) { + std::replace(path.begin(), path.end(), '\\', '/'); + return path; +} + } // namespace MetaCoreSceneRenderSyncSnapshot MetaCoreSceneRenderSync::BuildSnapshot(const MetaCoreScene& scene) const { MetaCoreSceneRenderSyncSnapshot snapshot; + snapshot.FrameId = ++FrameId_; + snapshot.SceneRevision = scene.GetRevision(); + + // 预处理:构建子树模型网格节点计数表,支撑在没有 Tag 时的 Fallback 回溯 + std::unordered_map> subtreeModelCounts; + for (MetaCoreId objectId : scene.BuildHierarchyPreorder()) { + const MetaCoreGameObject gameObject = scene.FindGameObject(objectId); + if (gameObject && gameObject.HasComponent()) { + const auto& mesh = gameObject.GetComponent(); + if (mesh.ModelNodeIndex >= 0 && !mesh.SourceModelPath.empty()) { + std::string normPath = MetaCoreNormalizeSyncPath(mesh.SourceModelPath); + MetaCoreId currentId = objectId; + while (currentId != 0) { + auto currentObj = scene.FindGameObject(currentId); + if (!currentObj) break; + subtreeModelCounts[currentId][normPath]++; + currentId = currentObj.GetParentId(); + } + } + } + } for (MetaCoreId objectId : scene.BuildHierarchyPreorder()) { const MetaCoreGameObject gameObject = scene.FindGameObject(objectId); @@ -48,9 +75,55 @@ MetaCoreSceneRenderSyncSnapshot MetaCoreSceneRenderSync::BuildSnapshot(const Met } } snapshot.WorldMatrices[objectId] = worldMatrix; + snapshot.LocalMatrices[objectId] = localMatrix; if (gameObject.HasComponent()) { const auto& meshRenderer = gameObject.GetComponent(); + + // 黄金算法:溯源确定唯一的模型根加载主体 + MetaCoreGameObject hostRoot = gameObject; + if (meshRenderer.ModelNodeIndex >= 0) { + const std::string& currentModelPath = meshRenderer.SourceModelPath; + std::string normCurrentPath = MetaCoreNormalizeSyncPath(currentModelPath); + MetaCoreGameObject current = gameObject; + MetaCoreGameObject foundRoot = {}; + + // 1. 优先通过运行时 MetaCoreModelRootTag 快速寻找顶级根 + while (current.GetParentId() != 0) { + auto parentObj = scene.FindGameObject(current.GetParentId()); + if (!parentObj) break; + + if (parentObj.HasComponent()) { + auto& tag = parentObj.GetComponent(); + if (MetaCoreNormalizeSyncPath(tag.SourceModelPath) == normCurrentPath) { + foundRoot = parentObj; + break; + } + } + current = parentObj; + } + + if (foundRoot) { + hostRoot = foundRoot; + } else { + // 2. Fallback:若无明确 Tag,使用转折点判定算法 + current = gameObject; + MetaCoreGameObject bestCandidate = gameObject; + int maxCount = subtreeModelCounts[gameObject.GetId()][normCurrentPath]; + + while (current) { + int count = subtreeModelCounts[current.GetId()][normCurrentPath]; + if (count > maxCount) { + maxCount = count; + bestCandidate = current; + } + if (current.GetParentId() == 0) break; + current = scene.FindGameObject(current.GetParentId()); + } + hostRoot = bestCandidate; + } + } + snapshot.Renderables.push_back(MetaCoreRenderSyncRenderable{ objectId, parentId, @@ -63,7 +136,10 @@ MetaCoreSceneRenderSyncSnapshot MetaCoreSceneRenderSync::BuildSnapshot(const Met meshRenderer.MeshAssetGuid, meshRenderer.SourceModelAssetGuid, meshRenderer.SourceModelPath, - meshRenderer.ModelNodeIndex + meshRenderer.ModelNodeIndex, + hostRoot.GetId(), + hostRoot.GetName(), + hostRoot.HasComponent() }); } @@ -75,10 +151,11 @@ MetaCoreSceneRenderSyncSnapshot MetaCoreSceneRenderSync::BuildSnapshot(const Met camera.FieldOfViewDegrees, camera.NearClip, camera.FarClip, - worldMatrix + worldMatrix, + camera.Enabled }; snapshot.Cameras.push_back(syncCamera); - if (syncCamera.IsPrimary && !snapshot.PrimaryCamera.has_value()) { + if (syncCamera.IsPrimary && syncCamera.Enabled && !snapshot.PrimaryCamera.has_value()) { snapshot.PrimaryCamera = syncCamera; } } @@ -89,13 +166,22 @@ MetaCoreSceneRenderSyncSnapshot MetaCoreSceneRenderSync::BuildSnapshot(const Met objectId, light.Color, light.Intensity, - worldMatrix + worldMatrix, + light.Enabled }); } } if (!snapshot.PrimaryCamera.has_value() && !snapshot.Cameras.empty()) { - snapshot.PrimaryCamera = snapshot.Cameras.front(); + for (const auto& cam : snapshot.Cameras) { + if (cam.Enabled) { + snapshot.PrimaryCamera = cam; + break; + } + } + if (!snapshot.PrimaryCamera.has_value()) { + snapshot.PrimaryCamera = snapshot.Cameras.front(); + } } return snapshot; diff --git a/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreFilamentSceneBridge.h b/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreFilamentSceneBridge.h index b191890..2227123 100644 --- a/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreFilamentSceneBridge.h +++ b/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreFilamentSceneBridge.h @@ -13,6 +13,7 @@ namespace MetaCore { class MetaCoreWindow; class MetaCoreScene; struct MetaCoreSceneView; +struct MetaCoreSceneRenderSyncSnapshot; /** * @brief Filament 场景桥接器,负责将 MetaCore 的场景数据同步到 Filament 渲染引擎中 @@ -27,6 +28,7 @@ public: void Resize(int width, int height); void SetProjectRootPath(const std::filesystem::path& projectRootPath); void SyncScene(MetaCoreScene& scene, bool compatibilityMeshOnly = false, bool useScenePrimaryCamera = false); + void SyncScene(const MetaCoreSceneRenderSyncSnapshot& snapshot, MetaCoreScene* scene = nullptr, bool compatibilityMeshOnly = false, bool useScenePrimaryCamera = false); void ApplySceneView(const MetaCoreSceneView& sceneView); void RenderAll(); [[nodiscard]] uint32_t GetGLTextureId() const; @@ -36,6 +38,11 @@ public: [[nodiscard]] bool HasRuntimeSyncFailure() const; [[nodiscard]] const std::string& GetLastRuntimeSyncFailure() const; + // 测试专用的验证接口 + [[nodiscard]] bool VerifyTransformForTesting(MetaCoreId objectId, const glm::mat4& expectedMatrix) const; + [[nodiscard]] bool VerifyLightExistsForTesting(MetaCoreId objectId) const; + [[nodiscard]] float GetDefaultLightIntensityForTesting() const; + private: class MetaCoreFilamentSceneBridgeImpl; std::unique_ptr Impl_{}; diff --git a/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreSceneRenderSync.h b/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreSceneRenderSync.h index 4bb5bb9..47e9b73 100644 --- a/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreSceneRenderSync.h +++ b/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreSceneRenderSync.h @@ -30,6 +30,10 @@ struct MetaCoreRenderSyncRenderable { MetaCoreAssetGuid SourceModelAssetGuid{}; std::string SourceModelPath{}; std::int32_t ModelNodeIndex = -1; + + MetaCoreId HostRootId = 0; + std::string HostRootName{}; + bool HostRootHasModelRootTag = false; }; struct MetaCoreRenderSyncCamera { @@ -39,6 +43,7 @@ struct MetaCoreRenderSyncCamera { float NearClip = 0.1F; float FarClip = 100.0F; glm::mat4 WorldMatrix{1.0F}; + bool Enabled = true; }; struct MetaCoreRenderSyncLight { @@ -46,14 +51,18 @@ struct MetaCoreRenderSyncLight { glm::vec3 Color{1.0F, 1.0F, 1.0F}; float Intensity = 1.5F; glm::mat4 WorldMatrix{1.0F}; + bool Enabled = true; }; struct MetaCoreSceneRenderSyncSnapshot { std::unordered_map WorldMatrices{}; + std::unordered_map LocalMatrices{}; std::vector Renderables{}; std::vector Cameras{}; std::vector Lights{}; std::optional PrimaryCamera{}; + std::uint64_t FrameId = 0; + std::uint64_t SceneRevision = 0; }; class MetaCoreSceneRenderSync { @@ -63,6 +72,9 @@ public: const MetaCoreSceneRenderSyncSnapshot& snapshot, MetaCoreSceneView& sceneView ); + +private: + mutable std::uint64_t FrameId_ = 0; }; } // namespace MetaCore diff --git a/Source/MetaCoreScene/Private/MetaCoreScene.cpp b/Source/MetaCoreScene/Private/MetaCoreScene.cpp index c4b9fe7..054a340 100644 --- a/Source/MetaCoreScene/Private/MetaCoreScene.cpp +++ b/Source/MetaCoreScene/Private/MetaCoreScene.cpp @@ -39,6 +39,8 @@ MetaCoreGameObject MetaCoreScene::CreateGameObjectWithId(MetaCoreId id, const st obj.AddComponent().ParentId = parentId; obj.AddComponent(); + IncrementRevision(); + return obj; } @@ -145,6 +147,7 @@ bool MetaCoreScene::RenameGameObject(MetaCoreId objectId, const std::string& nam } object.GetName() = name; + IncrementRevision(); return true; } @@ -196,6 +199,10 @@ std::vector MetaCoreScene::DeleteGameObjects(const std::vector& objectIds } } + IncrementRevision(); + return true; } @@ -433,6 +442,7 @@ void MetaCoreScene::RestoreSnapshot(const MetaCoreSceneSnapshot& snapshot) { obj.AddComponent(data.ModelRootTag.value()); } MetaCoreIdGenerator::EnsureAbove(maxId); + IncrementRevision(); } MetaCoreScene MetaCoreCreateDefaultScene() { diff --git a/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h b/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h index f6df259..c715afd 100644 --- a/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h +++ b/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h @@ -54,6 +54,8 @@ struct MetaCoreCameraComponent { float FarClip = 100.0F; MC_PROPERTY() bool IsPrimary = false; + MC_PROPERTY() + bool Enabled = true; }; MC_STRUCT() @@ -125,6 +127,8 @@ struct MetaCoreLightComponent { glm::vec3 Color{1.0F, 1.0F, 1.0F}; MC_PROPERTY() float Intensity = 1.5F; + MC_PROPERTY() + bool Enabled = true; }; MC_STRUCT() diff --git a/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreScene.h b/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreScene.h index a84e428..04582c9 100644 --- a/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreScene.h +++ b/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreScene.h @@ -46,11 +46,15 @@ public: entt::registry& GetRegistry() { return Registry_; } const entt::registry& GetRegistry() const { return Registry_; } + [[nodiscard]] std::uint64_t GetRevision() const { return Revision_; } + void IncrementRevision() { ++Revision_; } + private: void CollectPreorder(MetaCoreId objectId, std::vector& output) const; entt::registry Registry_; std::unordered_map IdToEntity_; + std::uint64_t Revision_ = 0; }; MetaCoreScene MetaCoreCreateDefaultScene();