feat(render): add SceneRenderSync snapshot and route Filament bridge through it

This commit is contained in:
ayuan9957 2026-05-21 16:13:31 +08:00
parent 1a822288b7
commit 04cff527e3
8 changed files with 359 additions and 116 deletions

View File

@ -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}"
"$<TARGET_FILE_DIR:${target_name}>/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()

View File

@ -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<MetaCoreId, std::unordered_map<std::string, int>> subtreeModelCounts;
for (const auto& obj : scene.GetGameObjects()) {
if (obj.HasComponent<MetaCoreMeshRendererComponent>()) {
auto& mesh = obj.GetComponent<MetaCoreMeshRendererComponent>();
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<MetaCoreMeshRendererComponent>()) {
continue;
}
const auto& meshRenderer = gameObject.GetComponent<MetaCoreMeshRendererComponent>();
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<MetaCoreModelRootTag>()) {
auto& tag = parentObj.GetComponent<MetaCoreModelRootTag>();
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<MetaCoreModelRootTag>()) {
nonConstHostRoot.AddComponent<MetaCoreModelRootTag>(MetaCoreModelRootTag{ modelPath });
}
if (scene != nullptr) {
// 有 ECS 实例模式下同步层级结构并补挂 Tag
auto hostRootObj = scene->FindGameObject(hostRootId);
if (hostRootObj) {
MetaCoreGameObject nonConstHostRoot = hostRootObj;
// 现场补挂 Tag确保后续帧和拖拽时 100% 命中 Tag 快速通道,绝不走 Fallback
if (!nonConstHostRoot.HasComponent<MetaCoreModelRootTag>()) {
nonConstHostRoot.AddComponent<MetaCoreModelRootTag>(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<MetaCoreRenderSyncRenderable> candidates;
for (const auto& r : snapshot.Renderables) {
if (r.HostRootId == hostRootId && r.ObjectId != hostRootId) {
candidates.push_back(r);
}
}
std::map<utils::Entity, MetaCoreId> 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<std::int32_t>(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<const filament::math::mat4f*>(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), &currentMat[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<MetaCoreId> 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<MetaCoreTransformComponent>()) return;
const auto& transform = gameObject.GetComponent<MetaCoreTransformComponent>();
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

View File

@ -6,6 +6,7 @@
#include <glm/geometric.hpp>
#include <algorithm>
#include <cmath>
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<MetaCoreId, std::unordered_map<std::string, int>> subtreeModelCounts;
for (MetaCoreId objectId : scene.BuildHierarchyPreorder()) {
const MetaCoreGameObject gameObject = scene.FindGameObject(objectId);
if (gameObject && gameObject.HasComponent<MetaCoreMeshRendererComponent>()) {
const auto& mesh = gameObject.GetComponent<MetaCoreMeshRendererComponent>();
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<MetaCoreMeshRendererComponent>()) {
const auto& meshRenderer = gameObject.GetComponent<MetaCoreMeshRendererComponent>();
// 黄金算法:溯源确定唯一的模型根加载主体
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<MetaCoreModelRootTag>()) {
auto& tag = parentObj.GetComponent<MetaCoreModelRootTag>();
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<MetaCoreModelRootTag>()
});
}
@ -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;

View File

@ -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<MetaCoreFilamentSceneBridgeImpl> Impl_{};

View File

@ -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<MetaCoreId, glm::mat4> WorldMatrices{};
std::unordered_map<MetaCoreId, glm::mat4> LocalMatrices{};
std::vector<MetaCoreRenderSyncRenderable> Renderables{};
std::vector<MetaCoreRenderSyncCamera> Cameras{};
std::vector<MetaCoreRenderSyncLight> Lights{};
std::optional<MetaCoreRenderSyncCamera> 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

View File

@ -39,6 +39,8 @@ MetaCoreGameObject MetaCoreScene::CreateGameObjectWithId(MetaCoreId id, const st
obj.AddComponent<MetaCoreHierarchyComponent>().ParentId = parentId;
obj.AddComponent<MetaCoreTransformComponent>();
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<MetaCoreId> MetaCoreScene::DeleteGameObjects(const std::vector<MetaC
}
}
if (!deletedIds.empty()) {
IncrementRevision();
}
return deletedIds;
}
@ -381,6 +388,8 @@ bool MetaCoreScene::ReparentGameObjects(const std::vector<MetaCoreId>& objectIds
}
}
IncrementRevision();
return true;
}
@ -433,6 +442,7 @@ void MetaCoreScene::RestoreSnapshot(const MetaCoreSceneSnapshot& snapshot) {
obj.AddComponent<MetaCoreModelRootTag>(data.ModelRootTag.value());
}
MetaCoreIdGenerator::EnsureAbove(maxId);
IncrementRevision();
}
MetaCoreScene MetaCoreCreateDefaultScene() {

View File

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

View File

@ -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<MetaCoreId>& output) const;
entt::registry Registry_;
std::unordered_map<MetaCoreId, entt::entity> IdToEntity_;
std::uint64_t Revision_ = 0;
};
MetaCoreScene MetaCoreCreateDefaultScene();