feat(render): add SceneRenderSync snapshot and route Filament bridge through it
This commit is contained in:
parent
1a822288b7
commit
04cff527e3
@ -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()
|
||||
|
||||
@ -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), ¤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<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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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_{};
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user