milestone: remove Panda3D backend and stabilize JSON asset pipeline

This commit is contained in:
ayuan9957 2026-05-21 11:32:37 +08:00
parent 28919737fc
commit 7d5887bbbb
106 changed files with 2019 additions and 4389 deletions

5
.gitignore vendored
View File

@ -5,6 +5,11 @@ downloads/
.vs/
CMakeUserPresets.json
imgui.ini
SandboxProject/Library/
TestProject/Library/
**/*.mccooked
*.mcasset.mcmeta
*.mcmeta.mcmeta
*.obj
*.pdb
*.ilk

View File

@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.26)
project(MetaCore
VERSION 1.0.0
DESCRIPTION "MetaCore Panda3D-based Unity-like editor prototype"
DESCRIPTION "MetaCore Filament-based Unity-like editor prototype"
LANGUAGES CXX
)
@ -16,7 +16,7 @@ if(MSVC)
endif()
option(METACORE_BUILD_TESTS "Build MetaCore tests" ON)
option(METACORE_AUTO_PREPARE_PANDA3D "Automatically prepare Panda3D SDK during configure" ON)
list(PREPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
@ -24,8 +24,7 @@ if(EXISTS "${CMAKE_SOURCE_DIR}/vcpkg_installed/x64-windows/share/imgui/imgui-con
list(PREPEND CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/vcpkg_installed/x64-windows")
endif()
include(MetaCorePanda3D)
metacore_prepare_panda3d()
include(MetaCoreFilament)
@ -160,7 +159,6 @@ target_include_directories(MetaCorePlatform
target_link_libraries(MetaCorePlatform
PUBLIC
MetaCoreFoundation
MetaCorePanda3D::SDK
glm::glm
)
@ -243,7 +241,6 @@ target_link_libraries(MetaCoreRender
MetaCoreFoundation
MetaCorePlatform
MetaCoreScene
MetaCorePanda3D::SDK
glm::glm
imgui::imgui
)
@ -387,8 +384,7 @@ target_link_libraries(MetaCoreEditorApp
MetaCoreEditor
)
metacore_stage_panda3d_runtime(MetaCoreEditorApp)
metacore_stage_simplepbr_runtime(MetaCoreEditorApp)
add_executable(MetaCorePlayer
Apps/MetaCorePlayer/main.cpp
@ -401,11 +397,9 @@ target_link_libraries(MetaCorePlayer
MetaCoreRender
MetaCoreRuntimeData
MetaCoreScene
MetaCorePanda3D::SDK
)
metacore_stage_panda3d_runtime(MetaCorePlayer)
metacore_stage_simplepbr_runtime(MetaCorePlayer)
if(METACORE_BUILD_TESTS)
enable_testing()
@ -421,8 +415,7 @@ if(METACORE_BUILD_TESTS)
)
target_compile_options(MetaCoreSmokeTests PRIVATE ${METACORE_COMMON_WARNINGS})
metacore_stage_panda3d_runtime(MetaCoreSmokeTests)
metacore_stage_simplepbr_runtime(MetaCoreSmokeTests)
add_test(NAME MetaCoreSmokeTests COMMAND MetaCoreSmokeTests)
# Filament + ImGui Demo

124
README.md
View File

@ -1,70 +1,98 @@
# MetaCore
`MetaCore` 是一个基于 `Panda3D` 进行二次开发的 Unity-like 编辑器与引擎框架原型
MetaCore 是一个使用 `C++20` 开发的自主 3D 引擎与编辑器原型。项目以 **EnTT ECS** 为底层架构,使用 **Google Filament** 作为现代渲染核心,使用 **Dear ImGui** 构建编辑器界面,并规划使用 **RmlUi** 承载运行时 UI。目标是在轻量、可控、可国产化适配的基础上形成类似 Unity / Unreal Engine 的可视化 3D 应用开发工作流
## 当前目标
当前阶段的核心策略是:**编辑期优先 JSON 与可读元数据,快速迭代、便于调试和版本管理;发布期再通过 Cook / Package 转换为二进制运行时资产,保证交付性能和封装性。**
- 以 `Panda3D` 作为底层引擎与运行时基础
- 以 `Dear ImGui Docking` 搭建 Unity-like 编辑器工作流
- 使用 `GameObject + Component` 模型组织编辑器语义层
- 先把 `Hierarchy / Scene / Inspector / Project / Console` 跑通
- 在不破坏主架构的前提下,以模块化方式持续叠加功能
## 当前定位
- **底层架构**:基于 `EnTT` 的 ECS外层提供 `GameObject + Component` 的编辑器友好语义。
- **编辑器界面**:使用 `Dear ImGui Docking` 构建 Hierarchy、Scene、Inspector、Project、Console 等编辑器面板。
- **渲染核心**:以 `Google Filament` 为主渲染后端,面向 PBR、glTF/GLB、材质、灯光和现代多图形 API 能力。
- **运行时 UI**:规划使用 `RmlUi` 作为正式项目 UI 系统,定位类似 Unity UGUIImGui 只作为编辑器和工具 UI。
- **模型导入**:当前优先围绕 `glTF/GLB``gltfio` 建立模型、材质、贴图、节点层级导入闭环。
- **资产路线**:编辑器阶段使用 JSON / `.mcmeta` / 可读项目文件;打包阶段生成 cooked 二进制资产与运行时配置。
- **交付方向**:编辑器面向 Windows、Linux、统信 UOS、麒麟等桌面系统运行时面向 C/S 桌面交付,并为 B/S / WebGPU 嵌入预留边界。
- **行业对标**:工作流对标 Unity渲染和工程化目标参考 Unreal Engine但第一阶段更聚焦工业仿真、数字孪生和实时可视化交付。
## 技术栈
- `C++20`
- `CMake`
- `vcpkg`
- `Panda3D 1.10.16`
- `glm`
- `Dear ImGui Docking`
- **核心语言**`C++20`
- **构建系统**`CMake`
- **包管理**`vcpkg`
- **ECS**`EnTT`
- **渲染核心**`Google Filament`
- **模型导入**`gltfio` / `glTF` / `GLB`
- **编辑器 UI**`Dear ImGui (Docking)`
- **运行时 UI**`RmlUi`(规划主线)
- **3D 操控**`ImGuizmo`
- **数学库**`glm`
- **数据通信**`Winsock2`、文件回放、运行时数据绑定
## 渲染目标
MetaCore 当前不从零自研完整 RHI而是借助 Filament 的成熟后端能力逐步覆盖多平台渲染:
- `Vulkan`:首选高性能现代图形 API。
- `OpenGL`:用于兼容、过渡和更广泛设备适配。
- `DirectX 12`:面向 Windows 高性能运行时。
- `WebGPU`:面向未来 B/S 架构、浏览器嵌入和 Web 交付。
## 目录结构
- `Source/MetaCoreFoundation`:基础服务
- `Source/MetaCorePlatform`Panda3D 宿主窗口与输入采集
- `Source/MetaCoreScene`:场景与对象模型
- `Source/MetaCoreRender`Panda3D 场景桥与显示区域驱动
- `Source/MetaCoreEditor`:编辑器模块与 Unity-like 界面
- `Apps/MetaCoreEditor`:编辑器程序入口
- `Apps/MetaCorePlayer`:运行时壳层入口
- `tests`:基础烟雾测试
- [Source/MetaCoreFoundation](D:/MetaCore/Source/MetaCoreFoundation)GUID、项目文件、包、反射、日志等基础服务。
- [Source/MetaCorePlatform](D:/MetaCore/Source/MetaCorePlatform):平台窗口、输入与宿主环境抽象。
- [Source/MetaCoreScene](D:/MetaCore/Source/MetaCoreScene)EnTT ECS、GameObject、Component、场景树、快照和序列化。
- [Source/MetaCoreRender](D:/MetaCore/Source/MetaCoreRender)Filament 渲染桥、编辑器视口渲染、ImGui/渲染纹理衔接。
- [Source/MetaCoreRuntimeData](D:/MetaCore/Source/MetaCoreRuntimeData)TCP、文件回放、数据点、绑定、诊断与实时数据驱动。
- [Source/MetaCoreEditor](D:/MetaCore/Source/MetaCoreEditor)编辑器上下文、模块系统、面板、资产导入、Inspector、Prefab、Undo/Redo。
- [Apps/MetaCoreEditor](D:/MetaCore/Apps/MetaCoreEditor):编辑器独立程序入口。
- [Apps/MetaCorePlayer](D:/MetaCore/Apps/MetaCorePlayer):运行时 Player 程序入口。
- [docs/designs](D:/MetaCore/docs/designs)产品定位、阶段路线、引擎能力、资产、渲染、UI 和打包设计文档。
- [tests](D:/MetaCore/tests)Smoke Tests 与渲染 Demo。
## 构建
## 构建与验证
前提:
### 前提条件
- 已安装 `vcpkg`
- 已设置环境变量 `VCPKG_ROOT`
- Windows 环境可联网时,`cmake configure` 会自动准备 `Panda3D 1.10.16` SDK
- 如果已经本地安装 Panda3D可设置环境变量 `PANDA3D_ROOT`
1. 已安装 Visual Studio 2022 / MSVC 工具链。
2. 已安装 `vcpkg` 并正确设置 `VCPKG_ROOT`
3. `third_party/filament_installed` 下存在已编译好的 Google Filament SDK或根据 [cmake/MetaCoreFilament.cmake](D:/MetaCore/cmake/MetaCoreFilament.cmake) 配置对应路径。
### 构建步骤
```powershell
cmake --preset vs2022-debug
cmake --build --preset build-debug
ctest --preset test-debug
cmake -B build -G "Visual Studio 17 2022" -A x64
cmake --build build --config RelWithDebInfo --target MetaCoreEditorApp
cmake --build build --config RelWithDebInfo --target MetaCoreSmokeTests
.\build\RelWithDebInfo\MetaCoreSmokeTests.exe
```
如果希望生成更适合分发的可执行文件,可使用:
## 当前能力
```powershell
cmake --preset vs2022-release
cmake --build --preset build-release
```
- 原生窗口、输入和主循环。
- 基于 EnTT 的场景对象、组件、层级、快照和序列化。
- Dear ImGui Docking 编辑器框架。
- Filament 离屏视口桥接与 PBR 场景渲染。
- glTF/GLB 模型导入、节点层级同步和模型实例化。
- ImGuizmo 移动、旋转、缩放工具链。
- 资产 GUID、`.mcmeta`、AssetDatabase、导入与重导入基础链路。
- 编辑期 JSON 场景/资产元数据方向,运行期 Cook / Package 二进制方向。
- TCP / 文件回放 runtime data 驱动,支持绑定到场景组件。
- Smoke Tests 覆盖场景编辑、选择、Undo/Redo、模块组合、资产导入、运行时数据、UI 文档序列化等基础行为。
## 当前完成内容
## 开发原则
- Panda3D 单窗口宿主
- ImGui Docking 编辑器外壳
- Panda3D 中央场景 DisplayRegion
- 网格、坐标轴、立方体绘制
- `Hierarchy` 选择
- `Inspector` 中编辑 `Transform`
- `Alt+左键 / 中键 / 滚轮 / F` 相机交互
- 模块注册接口与内置编辑器模块
- **敏捷开发,快速迭代**:先打通可运行闭环,再逐步工程化和性能化。
- **编辑期可读优先**JSON、元数据、项目文件必须便于 diff、调试、人工修复和团队协作。
- **运行期性能优先**:发布包通过 Cook 生成二进制资产与运行时配置。
- **模块边界清晰**ECS、渲染桥、编辑器 UI、运行时 UI、资产导入、数据源都应保持可替换。
- **国产化适配优先**Windows 先行,同时面向 Linux、统信、麒麟等环境保留平台抽象。
## 技术支持连接
## 参考
https://github.com/CedricGuillemet/ImGuizmo
https://docs.panda3d.org/1.9/cpp/programming/using-cpp/index
- [Google Filament](https://github.com/google/filament)
- [EnTT](https://github.com/skypjack/entt)
- [Dear ImGui](https://github.com/ocornut/imgui)
- [ImGuizmo](https://github.com/CedricGuillemet/ImGuizmo)
- [RmlUi](https://github.com/mikke89/RmlUi)

View File

@ -1,631 +0,0 @@
{
"GameObjects": [
{
"Camera": {
"FarClip": 100.0,
"FieldOfViewDegrees": 60.0,
"IsPrimary": true,
"NearClip": 0.10000000149011612
},
"Id": 1,
"Name": "Main Camera",
"ParentId": 0,
"Transform": {
"Position": [
0.0,
2.200000047683716,
6.5
],
"RotationEulerDegrees": [
-15.0,
0.0,
0.0
],
"Scale": [
1.0,
1.0,
1.0
]
}
},
{
"Id": 2,
"Light": {
"Color": [
1.0,
1.0,
1.0
],
"Intensity": 1.5
},
"Name": "Directional Light",
"ParentId": 0,
"Transform": {
"Position": [
0.0,
0.0,
0.0
],
"RotationEulerDegrees": [
-45.0,
30.0,
0.0
],
"Scale": [
1.0,
1.0,
1.0
]
}
},
{
"Id": 3,
"MeshRenderer": {
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTextureGuid": "",
"AoTexturePath": "",
"BaseColor": [
0.75,
0.7799999713897705,
0.8399999737739563
],
"BaseColorTextureGuid": "",
"BaseColorTexturePath": "",
"BuiltinMesh": 0,
"DoubleSided": true,
"EmissiveColor": [
0.0,
0.0,
0.0
],
"EmissiveTextureGuid": "",
"EmissiveTexturePath": "",
"MaterialAssetGuids": [],
"MeshAssetGuid": "",
"MeshSource": 1,
"Metallic": 0.0,
"MetallicRoughnessTextureGuid": "",
"MetallicRoughnessTexturePath": "",
"ModelNodeIndex": -1,
"NormalTextureGuid": "",
"NormalTexturePath": "",
"Roughness": 1.0,
"SourceModelAssetGuid": "",
"SourceModelPath": "Assets/Models/box1.glb",
"SourceNodePath": "",
"SubMeshIndex": -1,
"Visible": true
},
"Name": "box1.glb",
"ParentId": 0,
"Transform": {
"Position": [
0.0,
0.5,
0.0
],
"RotationEulerDegrees": [
0.0,
0.0,
0.0
],
"Scale": [
1.0,
1.0,
1.0
]
}
},
{
"Id": 4,
"Light": {
"Color": [
1.0,
0.15000000596046448,
0.10000000149011612
],
"Intensity": 0.0
},
"Name": "Alarm Beacon",
"ParentId": 0,
"Transform": {
"Position": [
0.0,
2.5,
0.0
],
"RotationEulerDegrees": [
0.0,
0.0,
0.0
],
"Scale": [
1.0,
1.0,
1.0
]
}
},
{
"Id": 5,
"MeshRenderer": {
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTextureGuid": "",
"AoTexturePath": "",
"BaseColor": [
0.75,
0.7799999713897705,
0.8399999737739563
],
"BaseColorTextureGuid": "",
"BaseColorTexturePath": "",
"BuiltinMesh": 0,
"DoubleSided": true,
"EmissiveColor": [
0.0,
0.0,
0.0
],
"EmissiveTextureGuid": "",
"EmissiveTexturePath": "",
"MaterialAssetGuids": [],
"MeshAssetGuid": "",
"MeshSource": 1,
"Metallic": 0.0,
"MetallicRoughnessTextureGuid": "",
"MetallicRoughnessTexturePath": "",
"ModelNodeIndex": 0,
"NormalTextureGuid": "",
"NormalTexturePath": "",
"Roughness": 1.0,
"SourceModelAssetGuid": "",
"SourceModelPath": "",
"SourceNodePath": "",
"SubMeshIndex": -1,
"Visible": true
},
"Name": "立方体",
"ParentId": 3,
"Transform": {
"Position": [
-1.6927926540374756,
-4.459673881530762,
1.949385364241607e-07
],
"RotationEulerDegrees": [
0.0,
-0.0,
0.0
],
"Scale": [
0.9999998807907104,
1.0,
1.0
]
}
},
{
"Id": 6,
"MeshRenderer": {
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTextureGuid": "",
"AoTexturePath": "",
"BaseColor": [
0.75,
0.7799999713897705,
0.8399999737739563
],
"BaseColorTextureGuid": "",
"BaseColorTexturePath": "",
"BuiltinMesh": 0,
"DoubleSided": true,
"EmissiveColor": [
0.0,
0.0,
0.0
],
"EmissiveTextureGuid": "",
"EmissiveTexturePath": "",
"MaterialAssetGuids": [],
"MeshAssetGuid": "",
"MeshSource": 1,
"Metallic": 0.0,
"MetallicRoughnessTextureGuid": "",
"MetallicRoughnessTexturePath": "",
"ModelNodeIndex": 1,
"NormalTextureGuid": "",
"NormalTexturePath": "",
"Roughness": 1.0,
"SourceModelAssetGuid": "",
"SourceModelPath": "",
"SourceNodePath": "",
"SubMeshIndex": -1,
"Visible": true
},
"Name": "立方体.001",
"ParentId": 5,
"Transform": {
"Position": [
0.0,
-3.55507755279541,
1.553973731915903e-07
],
"RotationEulerDegrees": [
0.0,
-0.0,
0.0
],
"Scale": [
0.9999998807907104,
1.0,
1.0
]
}
},
{
"Id": 7,
"MeshRenderer": {
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTextureGuid": "",
"AoTexturePath": "",
"BaseColor": [
0.75,
0.7799999713897705,
0.8399999737739563
],
"BaseColorTextureGuid": "",
"BaseColorTexturePath": "",
"BuiltinMesh": 0,
"DoubleSided": true,
"EmissiveColor": [
0.0,
0.0,
0.0
],
"EmissiveTextureGuid": "",
"EmissiveTexturePath": "",
"MaterialAssetGuids": [],
"MeshAssetGuid": "",
"MeshSource": 1,
"Metallic": 0.0,
"MetallicRoughnessTextureGuid": "",
"MetallicRoughnessTexturePath": "",
"ModelNodeIndex": 2,
"NormalTextureGuid": "",
"NormalTexturePath": "",
"Roughness": 1.0,
"SourceModelAssetGuid": "",
"SourceModelPath": "",
"SourceNodePath": "",
"SubMeshIndex": -1,
"Visible": true
},
"Name": "立方体.007",
"ParentId": 6,
"Transform": {
"Position": [
4.175572872161865,
0.0,
0.0
],
"RotationEulerDegrees": [
0.0,
-0.0,
0.0
],
"Scale": [
0.9999998807907104,
1.0,
1.0
]
}
},
{
"Id": 8,
"MeshRenderer": {
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTextureGuid": "",
"AoTexturePath": "",
"BaseColor": [
0.75,
0.7799999713897705,
0.8399999737739563
],
"BaseColorTextureGuid": "",
"BaseColorTexturePath": "",
"BuiltinMesh": 0,
"DoubleSided": true,
"EmissiveColor": [
0.0,
0.0,
0.0
],
"EmissiveTextureGuid": "",
"EmissiveTexturePath": "",
"MaterialAssetGuids": [],
"MeshAssetGuid": "",
"MeshSource": 1,
"Metallic": 0.0,
"MetallicRoughnessTextureGuid": "",
"MetallicRoughnessTexturePath": "",
"ModelNodeIndex": 3,
"NormalTextureGuid": "",
"NormalTexturePath": "",
"Roughness": 1.0,
"SourceModelAssetGuid": "",
"SourceModelPath": "",
"SourceNodePath": "",
"SubMeshIndex": -1,
"Visible": true
},
"Name": "立方体.003",
"ParentId": 3,
"Transform": {
"Position": [
0.0,
0.0,
0.0
],
"RotationEulerDegrees": [
0.0,
-0.0,
0.0
],
"Scale": [
0.9999998807907104,
1.0,
1.0
]
}
},
{
"Id": 9,
"MeshRenderer": {
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTextureGuid": "",
"AoTexturePath": "",
"BaseColor": [
0.75,
0.7799999713897705,
0.8399999737739563
],
"BaseColorTextureGuid": "",
"BaseColorTexturePath": "",
"BuiltinMesh": 0,
"DoubleSided": true,
"EmissiveColor": [
0.0,
0.0,
0.0
],
"EmissiveTextureGuid": "",
"EmissiveTexturePath": "",
"MaterialAssetGuids": [],
"MeshAssetGuid": "",
"MeshSource": 1,
"Metallic": 0.0,
"MetallicRoughnessTextureGuid": "",
"MetallicRoughnessTexturePath": "",
"ModelNodeIndex": 4,
"NormalTextureGuid": "",
"NormalTexturePath": "",
"Roughness": 1.0,
"SourceModelAssetGuid": "",
"SourceModelPath": "",
"SourceNodePath": "",
"SubMeshIndex": -1,
"Visible": true
},
"Name": "立方体.002",
"ParentId": 8,
"Transform": {
"Position": [
0.0,
-1.4481612709005276e-07,
-3.31300687789917
],
"RotationEulerDegrees": [
0.0,
-0.0,
0.0
],
"Scale": [
0.9999998807907104,
1.0,
1.0
]
}
},
{
"Id": 10,
"MeshRenderer": {
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTextureGuid": "",
"AoTexturePath": "",
"BaseColor": [
0.75,
0.7799999713897705,
0.8399999737739563
],
"BaseColorTextureGuid": "",
"BaseColorTexturePath": "",
"BuiltinMesh": 0,
"DoubleSided": true,
"EmissiveColor": [
0.0,
0.0,
0.0
],
"EmissiveTextureGuid": "",
"EmissiveTexturePath": "",
"MaterialAssetGuids": [],
"MeshAssetGuid": "",
"MeshSource": 1,
"Metallic": 0.0,
"MetallicRoughnessTextureGuid": "",
"MetallicRoughnessTexturePath": "",
"ModelNodeIndex": 5,
"NormalTextureGuid": "",
"NormalTexturePath": "",
"Roughness": 1.0,
"SourceModelAssetGuid": "",
"SourceModelPath": "",
"SourceNodePath": "",
"SubMeshIndex": -1,
"Visible": true
},
"Name": "立方体.004",
"ParentId": 8,
"Transform": {
"Position": [
3.776623010635376,
0.0,
0.0
],
"RotationEulerDegrees": [
0.0,
-0.0,
0.0
],
"Scale": [
0.9999998807907104,
1.0,
1.0
]
}
},
{
"Id": 11,
"MeshRenderer": {
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTextureGuid": "",
"AoTexturePath": "",
"BaseColor": [
0.75,
0.7799999713897705,
0.8399999737739563
],
"BaseColorTextureGuid": "",
"BaseColorTexturePath": "",
"BuiltinMesh": 0,
"DoubleSided": true,
"EmissiveColor": [
0.0,
0.0,
0.0
],
"EmissiveTextureGuid": "",
"EmissiveTexturePath": "",
"MaterialAssetGuids": [],
"MeshAssetGuid": "",
"MeshSource": 1,
"Metallic": 0.0,
"MetallicRoughnessTextureGuid": "",
"MetallicRoughnessTexturePath": "",
"ModelNodeIndex": 6,
"NormalTextureGuid": "",
"NormalTexturePath": "",
"Roughness": 1.0,
"SourceModelAssetGuid": "",
"SourceModelPath": "",
"SourceNodePath": "",
"SubMeshIndex": -1,
"Visible": true
},
"Name": "立方体.005",
"ParentId": 8,
"Transform": {
"Position": [
0.0,
4.31797981262207,
-1.8874489171594178e-07
],
"RotationEulerDegrees": [
0.0,
-0.0,
0.0
],
"Scale": [
0.9999998807907104,
1.0,
1.0
]
}
},
{
"Id": 12,
"MeshRenderer": {
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTextureGuid": "",
"AoTexturePath": "",
"BaseColor": [
0.75,
0.7799999713897705,
0.8399999737739563
],
"BaseColorTextureGuid": "",
"BaseColorTexturePath": "",
"BuiltinMesh": 0,
"DoubleSided": true,
"EmissiveColor": [
0.0,
0.0,
0.0
],
"EmissiveTextureGuid": "",
"EmissiveTexturePath": "",
"MaterialAssetGuids": [],
"MeshAssetGuid": "",
"MeshSource": 1,
"Metallic": 0.0,
"MetallicRoughnessTextureGuid": "",
"MetallicRoughnessTexturePath": "",
"ModelNodeIndex": 7,
"NormalTextureGuid": "",
"NormalTexturePath": "",
"Roughness": 1.0,
"SourceModelAssetGuid": "",
"SourceModelPath": "",
"SourceNodePath": "",
"SubMeshIndex": -1,
"Visible": true
},
"Name": "立方体.006",
"ParentId": 8,
"Transform": {
"Position": [
-12.448348045349121,
0.0,
0.0
],
"RotationEulerDegrees": [
0.0,
0.0,
-0.0
],
"Scale": [
0.9999998807907104,
1.0,
1.0
]
}
}
],
"SceneName": "Untitled",
"Selection": {
"ActiveObjectId": 12,
"SelectedObjectIds": [
12
],
"SelectionAnchorId": 12
}
}

View File

@ -1,629 +0,0 @@
{
"GameObjects": [
{
"Camera": {
"FarClip": 100.0,
"FieldOfViewDegrees": 60.0,
"IsPrimary": true,
"NearClip": 0.10000000149011612
},
"Id": 1,
"Name": "Main Camera",
"ParentId": 0,
"Transform": {
"Position": [
0.0,
2.200000047683716,
6.5
],
"RotationEulerDegrees": [
-15.0,
0.0,
0.0
],
"Scale": [
1.0,
1.0,
1.0
]
}
},
{
"Id": 2,
"Light": {
"Color": [
1.0,
1.0,
1.0
],
"Intensity": 1.5
},
"Name": "Directional Light",
"ParentId": 0,
"Transform": {
"Position": [
0.0,
0.0,
0.0
],
"RotationEulerDegrees": [
-45.0,
30.0,
0.0
],
"Scale": [
1.0,
1.0,
1.0
]
}
},
{
"Id": 3,
"MeshRenderer": {
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTextureGuid": "",
"AoTexturePath": "",
"BaseColor": [
0.75,
0.7799999713897705,
0.8399999737739563
],
"BaseColorTextureGuid": "",
"BaseColorTexturePath": "",
"BuiltinMesh": 0,
"DoubleSided": true,
"EmissiveColor": [
0.0,
0.0,
0.0
],
"EmissiveTextureGuid": "",
"EmissiveTexturePath": "",
"MaterialAssetGuids": [],
"MeshAssetGuid": "",
"MeshSource": 1,
"Metallic": 0.0,
"MetallicRoughnessTextureGuid": "",
"MetallicRoughnessTexturePath": "",
"ModelNodeIndex": -1,
"NormalTextureGuid": "",
"NormalTexturePath": "",
"Roughness": 1.0,
"SourceModelAssetGuid": "",
"SourceModelPath": "Assets/Models/box1.glb",
"SourceNodePath": "",
"SubMeshIndex": -1,
"Visible": true
},
"Name": "box1.glb",
"ParentId": 0,
"Transform": {
"Position": [
0.0,
0.5,
0.0
],
"RotationEulerDegrees": [
0.0,
0.0,
0.0
],
"Scale": [
1.0,
1.0,
1.0
]
}
},
{
"Id": 4,
"Light": {
"Color": [
1.0,
0.15000000596046448,
0.10000000149011612
],
"Intensity": 0.0
},
"Name": "Alarm Beacon",
"ParentId": 0,
"Transform": {
"Position": [
0.0,
2.5,
0.0
],
"RotationEulerDegrees": [
0.0,
0.0,
0.0
],
"Scale": [
1.0,
1.0,
1.0
]
}
},
{
"Id": 5,
"MeshRenderer": {
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTextureGuid": "",
"AoTexturePath": "",
"BaseColor": [
0.75,
0.7799999713897705,
0.8399999737739563
],
"BaseColorTextureGuid": "",
"BaseColorTexturePath": "",
"BuiltinMesh": 0,
"DoubleSided": true,
"EmissiveColor": [
0.0,
0.0,
0.0
],
"EmissiveTextureGuid": "",
"EmissiveTexturePath": "",
"MaterialAssetGuids": [],
"MeshAssetGuid": "",
"MeshSource": 1,
"Metallic": 0.0,
"MetallicRoughnessTextureGuid": "",
"MetallicRoughnessTexturePath": "",
"ModelNodeIndex": 0,
"NormalTextureGuid": "",
"NormalTexturePath": "",
"Roughness": 1.0,
"SourceModelAssetGuid": "",
"SourceModelPath": "",
"SourceNodePath": "",
"SubMeshIndex": -1,
"Visible": true
},
"Name": "立方体",
"ParentId": 3,
"Transform": {
"Position": [
-1.6927926540374756,
-4.459673881530762,
1.949385364241607e-07
],
"RotationEulerDegrees": [
0.0,
-0.0,
0.0
],
"Scale": [
0.9999998807907104,
1.0,
1.0
]
}
},
{
"Id": 6,
"MeshRenderer": {
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTextureGuid": "",
"AoTexturePath": "",
"BaseColor": [
0.75,
0.7799999713897705,
0.8399999737739563
],
"BaseColorTextureGuid": "",
"BaseColorTexturePath": "",
"BuiltinMesh": 0,
"DoubleSided": true,
"EmissiveColor": [
0.0,
0.0,
0.0
],
"EmissiveTextureGuid": "",
"EmissiveTexturePath": "",
"MaterialAssetGuids": [],
"MeshAssetGuid": "",
"MeshSource": 1,
"Metallic": 0.0,
"MetallicRoughnessTextureGuid": "",
"MetallicRoughnessTexturePath": "",
"ModelNodeIndex": 1,
"NormalTextureGuid": "",
"NormalTexturePath": "",
"Roughness": 1.0,
"SourceModelAssetGuid": "",
"SourceModelPath": "",
"SourceNodePath": "",
"SubMeshIndex": -1,
"Visible": true
},
"Name": "立方体.001",
"ParentId": 5,
"Transform": {
"Position": [
0.0,
-3.55507755279541,
1.553973731915903e-07
],
"RotationEulerDegrees": [
0.0,
-0.0,
0.0
],
"Scale": [
0.9999998807907104,
1.0,
1.0
]
}
},
{
"Id": 7,
"MeshRenderer": {
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTextureGuid": "",
"AoTexturePath": "",
"BaseColor": [
0.75,
0.7799999713897705,
0.8399999737739563
],
"BaseColorTextureGuid": "",
"BaseColorTexturePath": "",
"BuiltinMesh": 0,
"DoubleSided": true,
"EmissiveColor": [
0.0,
0.0,
0.0
],
"EmissiveTextureGuid": "",
"EmissiveTexturePath": "",
"MaterialAssetGuids": [],
"MeshAssetGuid": "",
"MeshSource": 1,
"Metallic": 0.0,
"MetallicRoughnessTextureGuid": "",
"MetallicRoughnessTexturePath": "",
"ModelNodeIndex": 2,
"NormalTextureGuid": "",
"NormalTexturePath": "",
"Roughness": 1.0,
"SourceModelAssetGuid": "",
"SourceModelPath": "",
"SourceNodePath": "",
"SubMeshIndex": -1,
"Visible": true
},
"Name": "立方体.007",
"ParentId": 6,
"Transform": {
"Position": [
4.175572872161865,
0.0,
0.0
],
"RotationEulerDegrees": [
0.0,
-0.0,
0.0
],
"Scale": [
0.9999998807907104,
1.0,
1.0
]
}
},
{
"Id": 8,
"MeshRenderer": {
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTextureGuid": "",
"AoTexturePath": "",
"BaseColor": [
0.75,
0.7799999713897705,
0.8399999737739563
],
"BaseColorTextureGuid": "",
"BaseColorTexturePath": "",
"BuiltinMesh": 0,
"DoubleSided": true,
"EmissiveColor": [
0.0,
0.0,
0.0
],
"EmissiveTextureGuid": "",
"EmissiveTexturePath": "",
"MaterialAssetGuids": [],
"MeshAssetGuid": "",
"MeshSource": 1,
"Metallic": 0.0,
"MetallicRoughnessTextureGuid": "",
"MetallicRoughnessTexturePath": "",
"ModelNodeIndex": 3,
"NormalTextureGuid": "",
"NormalTexturePath": "",
"Roughness": 1.0,
"SourceModelAssetGuid": "",
"SourceModelPath": "",
"SourceNodePath": "",
"SubMeshIndex": -1,
"Visible": true
},
"Name": "立方体.003",
"ParentId": 3,
"Transform": {
"Position": [
0.0,
0.0,
0.0
],
"RotationEulerDegrees": [
0.0,
-0.0,
0.0
],
"Scale": [
0.9999998807907104,
1.0,
1.0
]
}
},
{
"Id": 9,
"MeshRenderer": {
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTextureGuid": "",
"AoTexturePath": "",
"BaseColor": [
0.75,
0.7799999713897705,
0.8399999737739563
],
"BaseColorTextureGuid": "",
"BaseColorTexturePath": "",
"BuiltinMesh": 0,
"DoubleSided": true,
"EmissiveColor": [
0.0,
0.0,
0.0
],
"EmissiveTextureGuid": "",
"EmissiveTexturePath": "",
"MaterialAssetGuids": [],
"MeshAssetGuid": "",
"MeshSource": 1,
"Metallic": 0.0,
"MetallicRoughnessTextureGuid": "",
"MetallicRoughnessTexturePath": "",
"ModelNodeIndex": 4,
"NormalTextureGuid": "",
"NormalTexturePath": "",
"Roughness": 1.0,
"SourceModelAssetGuid": "",
"SourceModelPath": "",
"SourceNodePath": "",
"SubMeshIndex": -1,
"Visible": true
},
"Name": "立方体.002",
"ParentId": 8,
"Transform": {
"Position": [
0.0,
-1.4481612709005276e-07,
-3.31300687789917
],
"RotationEulerDegrees": [
0.0,
-0.0,
0.0
],
"Scale": [
0.9999998807907104,
1.0,
1.0
]
}
},
{
"Id": 10,
"MeshRenderer": {
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTextureGuid": "",
"AoTexturePath": "",
"BaseColor": [
0.75,
0.7799999713897705,
0.8399999737739563
],
"BaseColorTextureGuid": "",
"BaseColorTexturePath": "",
"BuiltinMesh": 0,
"DoubleSided": true,
"EmissiveColor": [
0.0,
0.0,
0.0
],
"EmissiveTextureGuid": "",
"EmissiveTexturePath": "",
"MaterialAssetGuids": [],
"MeshAssetGuid": "",
"MeshSource": 1,
"Metallic": 0.0,
"MetallicRoughnessTextureGuid": "",
"MetallicRoughnessTexturePath": "",
"ModelNodeIndex": 5,
"NormalTextureGuid": "",
"NormalTexturePath": "",
"Roughness": 1.0,
"SourceModelAssetGuid": "",
"SourceModelPath": "",
"SourceNodePath": "",
"SubMeshIndex": -1,
"Visible": true
},
"Name": "立方体.004",
"ParentId": 8,
"Transform": {
"Position": [
3.776623010635376,
0.0,
0.0
],
"RotationEulerDegrees": [
0.0,
-0.0,
0.0
],
"Scale": [
0.9999998807907104,
1.0,
1.0
]
}
},
{
"Id": 11,
"MeshRenderer": {
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTextureGuid": "",
"AoTexturePath": "",
"BaseColor": [
0.75,
0.7799999713897705,
0.8399999737739563
],
"BaseColorTextureGuid": "",
"BaseColorTexturePath": "",
"BuiltinMesh": 0,
"DoubleSided": true,
"EmissiveColor": [
0.0,
0.0,
0.0
],
"EmissiveTextureGuid": "",
"EmissiveTexturePath": "",
"MaterialAssetGuids": [],
"MeshAssetGuid": "",
"MeshSource": 1,
"Metallic": 0.0,
"MetallicRoughnessTextureGuid": "",
"MetallicRoughnessTexturePath": "",
"ModelNodeIndex": 6,
"NormalTextureGuid": "",
"NormalTexturePath": "",
"Roughness": 1.0,
"SourceModelAssetGuid": "",
"SourceModelPath": "",
"SourceNodePath": "",
"SubMeshIndex": -1,
"Visible": true
},
"Name": "立方体.005",
"ParentId": 8,
"Transform": {
"Position": [
0.0,
4.31797981262207,
-1.8874489171594178e-07
],
"RotationEulerDegrees": [
0.0,
-0.0,
0.0
],
"Scale": [
0.9999998807907104,
1.0,
1.0
]
}
},
{
"Id": 12,
"MeshRenderer": {
"AlphaCutoff": 0.5,
"AlphaMode": 0,
"AoTextureGuid": "",
"AoTexturePath": "",
"BaseColor": [
0.75,
0.7799999713897705,
0.8399999737739563
],
"BaseColorTextureGuid": "",
"BaseColorTexturePath": "",
"BuiltinMesh": 0,
"DoubleSided": true,
"EmissiveColor": [
0.0,
0.0,
0.0
],
"EmissiveTextureGuid": "",
"EmissiveTexturePath": "",
"MaterialAssetGuids": [],
"MeshAssetGuid": "",
"MeshSource": 1,
"Metallic": 0.0,
"MetallicRoughnessTextureGuid": "",
"MetallicRoughnessTexturePath": "",
"ModelNodeIndex": 7,
"NormalTextureGuid": "",
"NormalTexturePath": "",
"Roughness": 1.0,
"SourceModelAssetGuid": "",
"SourceModelPath": "",
"SourceNodePath": "",
"SubMeshIndex": -1,
"Visible": true
},
"Name": "立方体.006",
"ParentId": 8,
"Transform": {
"Position": [
-4.638434886932373,
0.0,
0.0
],
"RotationEulerDegrees": [
0.0,
-0.0,
0.0
],
"Scale": [
0.9999998807907104,
1.0,
1.0
]
}
}
],
"SceneName": "Untitled",
"Selection": {
"ActiveObjectId": 0,
"SelectedObjectIds": [],
"SelectionAnchorId": 0
}
}

View File

@ -31,6 +31,11 @@
namespace MetaCore {
[[nodiscard]] bool MetaCoreHandleProjectAssetDrop(
MetaCoreEditorContext& editorContext,
std::optional<MetaCoreId> forcedParentId
);
namespace {
static std::string GeneratedResourceFilter_{};
@ -122,10 +127,6 @@ template <typename T>
std::optional<MetaCoreId> forcedParentId
);
[[nodiscard]] bool MetaCoreHandleProjectAssetDrop(
MetaCoreEditorContext& editorContext,
std::optional<MetaCoreId> forcedParentId
);
[[nodiscard]] std::optional<MetaCoreAssetRecord> FindSourceModelAssetForGameObject(
MetaCoreEditorContext& editorContext,
@ -603,7 +604,7 @@ void MetaCoreEnsureProjectStartupScene(MetaCoreEditorContext& editorContext) {
editorContext.GetScene().RestoreSnapshot(MetaCoreCreateDefaultScene().CaptureSnapshot());
editorContext.ClearSelection();
const std::filesystem::path bootstrapScenePath = std::filesystem::path("Scenes") / "Main.mcscene";
const std::filesystem::path bootstrapScenePath = std::filesystem::path("Scenes") / "Main.mcscene.json";
if (scenePersistenceService->SaveSceneAs(editorContext, bootstrapScenePath)) {
(void)assetDatabaseService->SetStartupScenePath(bootstrapScenePath);
editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Project", "已为新项目创建默认启动场景");
@ -3292,6 +3293,8 @@ void MetaCoreBeginProjectAssetDragDropSource(const MetaCoreAssetRecord& assetRec
ImGui::EndDragDropSource();
}
} // namespace
[[nodiscard]] bool MetaCoreHandleProjectAssetDrop(
MetaCoreEditorContext& editorContext,
std::optional<MetaCoreId> forcedParentId
@ -3326,6 +3329,8 @@ void MetaCoreBeginProjectAssetDragDropSource(const MetaCoreAssetRecord& assetRec
return false;
}
namespace {
void DrawPrefabDetails(MetaCoreEditorContext& editorContext, MetaCoreIAssetDatabaseService& assetDatabaseService, MetaCoreIPackageService& packageService, MetaCoreIReflectionRegistry& reflectionRegistry) {
const MetaCoreSelectedAssetState& selectedAsset = editorContext.GetSelectedAsset();
const auto assetRecord = assetDatabaseService.FindAssetByGuid(selectedAsset.Guid);

View File

@ -1,4 +1,4 @@
#include "MetaCoreEditor/MetaCoreEditorApp.h"
#include "MetaCoreEditor/MetaCoreEditorApp.h"
#include "MetaCoreEditor/MetaCoreBuiltinModules.h"
#include "MetaCoreEditor/MetaCoreEditorServices.h"
@ -37,6 +37,12 @@ extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg
namespace MetaCore {
// 声明在外部定义的拖拽资产放置处理函数,用于在此处处理视口拖放
[[nodiscard]] bool MetaCoreHandleProjectAssetDrop(
MetaCoreEditorContext& editorContext,
std::optional<MetaCoreId> forcedParentId
);
namespace {
#if defined(_DEBUG)
@ -76,7 +82,8 @@ constexpr bool GMetaCoreEnableImGuizmo = true;
if (sceneEditingService == nullptr) {
return false;
}
const auto instantiatedObjectId = sceneEditingService->InstantiateModelAsset(editorContext, payload.AssetGuid, std::nullopt);
// 拖入场景视口时默认实例化到根级,即父节点为 0防止错误继承其它选中物体的变换
const auto instantiatedObjectId = sceneEditingService->InstantiateModelAsset(editorContext, payload.AssetGuid, 0);
if (!instantiatedObjectId.has_value()) {
return false;
}
@ -89,7 +96,8 @@ constexpr bool GMetaCoreEnableImGuizmo = true;
if (prefabService == nullptr || !prefabService->SupportsPrefabWorkflows()) {
return false;
}
const auto instantiatedObjectId = prefabService->InstantiatePrefab(editorContext, payload.AssetGuid, std::nullopt);
// 拖入场景视口时同样默认实例化到根级,即父节点为 0
const auto instantiatedObjectId = prefabService->InstantiatePrefab(editorContext, payload.AssetGuid, 0);
if (!instantiatedObjectId.has_value()) {
return false;
}
@ -519,6 +527,13 @@ bool MetaCoreEditorApp::Initialize() {
MetaCoreTraceStartup("metacore.app: initialize message handler");
DragAcceptFiles(static_cast<HWND>(Window_.GetNativeWindowHandle()), TRUE);
// 允许低权限的 Windows 资源管理器 (Explorer) 向高权限的编辑器进程发送拖放消息,彻底解决拖拽禁止图标的系统底层限制
#ifndef MSGFLT_ADD
#define MSGFLT_ADD 1
#endif
ChangeWindowMessageFilter(WM_DROPFILES, MSGFLT_ADD);
ChangeWindowMessageFilter(0x0049, MSGFLT_ADD); // WM_COPYGLOBALMEM
Window_.SetNativeWindowMessageHandler([this](void* nativeWindowHandle, unsigned int message, std::uintptr_t wparam, std::intptr_t lparam) {
if (message == WM_DROPFILES) {
HDROP hDrop = reinterpret_cast<HDROP>(wparam);
@ -527,17 +542,29 @@ bool MetaCoreEditorApp::Initialize() {
const auto importPipelineService = ModuleRegistry_.ResolveService<MetaCoreIImportPipelineService>();
if (assetDatabaseService != nullptr && assetDatabaseService->HasProject() && importPipelineService != nullptr) {
const std::filesystem::path assetsPath = assetDatabaseService->GetProjectDescriptor().AssetsPath;
// 【修复外部拖入目录问题】:如果当前编辑器选中了特定子文件夹目录(例如 Assets/Models则应当拷贝入该子目录中而不是根目录
std::filesystem::path targetFolder = assetsPath;
if (EditorContext_ != nullptr) {
const std::filesystem::path selectedDir = EditorContext_->GetSelectedProjectDirectory();
if (!selectedDir.empty()) {
const std::filesystem::path candidate = assetDatabaseService->GetProjectDescriptor().RootPath / selectedDir;
if (std::filesystem::exists(candidate)) {
targetFolder = candidate;
}
}
}
bool importedAny = false;
for (UINT i = 0; i < fileCount; ++i) {
wchar_t filePath[MAX_PATH];
if (DragQueryFileW(hDrop, i, filePath, MAX_PATH)) {
std::filesystem::path sourcePath(filePath);
std::filesystem::path targetPath = assetsPath / sourcePath.filename();
std::filesystem::path targetPath = targetFolder / sourcePath.filename();
try {
if (!std::filesystem::exists(targetPath)) {
std::filesystem::copy_file(sourcePath, targetPath);
importedAny = true;
}
// 使用 overwrite_existing 允许外部重构模型直接拖入刷新,触发引擎自动重导入并更新引用
std::filesystem::copy_file(sourcePath, targetPath, std::filesystem::copy_options::overwrite_existing);
importedAny = true;
} catch (...) { }
}
}
@ -601,7 +628,7 @@ bool MetaCoreEditorApp::Initialize() {
assetDatabaseService != nullptr &&
assetDatabaseService->HasProject() &&
assetDatabaseService->GetProjectDescriptor().ScenePaths.empty()) {
const std::filesystem::path bootstrapScenePath = std::filesystem::path("Scenes") / "Main.mcscene";
const std::filesystem::path bootstrapScenePath = std::filesystem::path("Scenes") / "Main.mcscene.json";
if (scenePersistenceService->SaveSceneAs(*EditorContext_, bootstrapScenePath)) {
(void)assetDatabaseService->SetStartupScenePath(bootstrapScenePath);
loadedStartupScene = true;
@ -823,7 +850,7 @@ void MetaCoreEditorApp::DrawEditorFrame() {
ImGui::SetNextWindowPos(centralNode->Pos);
ImGui::SetNextWindowSize(ImVec2(centralNode->Size.x, 30.0F));
ImGui::Begin("ViewportTabs", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav);
static int activeTab = 0; // 0: Scene, 1: Game
if (ImGui::Selectable(" 场景 ", activeTab == 0, 0, ImVec2(60, 0))) activeTab = 0;
ImGui::SameLine();
@ -847,7 +874,7 @@ void MetaCoreEditorApp::DrawEditorFrame() {
}
// --- 补丁三重构开始 ---
// 1. 准备 GizmoCanvas 窗口属性
ImGui::SetNextWindowPos(ImVec2(centralNode->Pos.x, centralNode->Pos.y + sceneToolbarHeight + tabHeaderHeight));
ImGui::SetNextWindowSize(ImVec2(centralNode->Size.x, centralNode->Size.y - sceneToolbarHeight - tabHeaderHeight));
@ -856,11 +883,17 @@ void MetaCoreEditorApp::DrawEditorFrame() {
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0F);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0F, 0.0F));
constexpr ImGuiWindowFlags gizmoCanvasFlags =
// 【问题二修复】:如果当前没有处于资产拖拽过程中,则屏蔽输入以允许相机漫游操作;
// 如果正在拖放资产(如模型或预制体),则恢复输入以便 BeginDragDropTarget 能够正确接收放置。
ImGuiWindowFlags gizmoCanvasFlags =
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoDocking |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus |
ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs;
ImGuiWindowFlags_NoBackground;
if (ImGui::GetDragDropPayload() == nullptr) {
gizmoCanvasFlags |= ImGuiWindowFlags_NoInputs;
}
bool gizmoCanvasOpen = true;
bool gizmoUsing = false;
@ -872,7 +905,6 @@ void MetaCoreEditorApp::DrawEditorFrame() {
ImVec2 viewportSize = ImGui::GetContentRegionAvail();
// 3. 全局统一更新 viewportState
MetaCoreSceneViewportState& viewportState = EditorContext_->GetSceneViewportState();
viewportState.Left = viewportPos.x;
viewportState.Top = viewportPos.y;
viewportState.Width = viewportSize.x;
@ -916,6 +948,12 @@ void MetaCoreEditorApp::DrawEditorFrame() {
ImGui::Image(texPtr, viewportSize, ImVec2(0, 1), ImVec2(1, 0));
}
// 7.5. 视口拖拽接收器:从项目面板拖拽资产进入 3D 视口时,在此处响应放置并完成实例化
if (ImGui::BeginDragDropTarget()) {
(void)MetaCoreHandleProjectAssetDrop(*EditorContext_, std::nullopt);
ImGui::EndDragDropTarget();
}
// 8. 绘制 Gizmo (此时坐标和矩阵已完全对齐)
if (GMetaCoreEnableImGuizmo) {
SceneInteractionService_.HandleGizmoManipulation(*EditorContext_);
@ -928,7 +966,7 @@ void MetaCoreEditorApp::DrawEditorFrame() {
viewportState.Left + viewportState.Width - 110.0F,
viewportState.Top + 20.0F
);
glm::mat4 cubeViewMatrix = glm::lookAt(sceneView.CameraPosition, sceneView.CameraTarget, sceneView.CameraUp);
const glm::mat4 originalCubeViewMatrix = cubeViewMatrix;
@ -955,8 +993,11 @@ void MetaCoreEditorApp::DrawEditorFrame() {
MetaCoreDrawSelectionBoundsOverlay(*EditorContext_, Scene_, sceneView, viewportState);
MetaCoreDrawSelectionHierarchyOverlay(*EditorContext_, Scene_, sceneView, viewportState);
// 启用资产拖拽到 3D 场景视口的高亮响应遮罩,并显示操作提示
MetaCoreDrawSceneViewportDropTarget(*EditorContext_, viewportPos, viewportSize);
// 处理拾取
if (viewportState.Hovered && !gizmoHovering && !gizmoUsing &&
if (viewportState.Hovered && !gizmoHovering && !gizmoUsing &&
!ImGui::GetIO().WantCaptureMouse && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
const MetaCoreId pickedObjectId = SceneInteractionService_.PickGameObjectFromViewport(
Scene_,
@ -969,7 +1010,7 @@ void MetaCoreEditorApp::DrawEditorFrame() {
}
ImGui::End();
ImGui::PopStyleVar(3);
SceneInteractionService_.HandleGizmoEndUse(*EditorContext_, gizmoUsing);
SceneInteractionService_.DrawViewportToolbar(*EditorContext_);
// --- 补丁三重构结束 ---

View File

@ -161,7 +161,7 @@ private:
[[nodiscard]] MetaCoreRuntimeProjectDocument MetaCoreBuildDefaultRuntimeProjectDocument() {
MetaCoreRuntimeProjectDocument document;
document.StartupScenePath = std::filesystem::path("Scenes") / "Main.mcscene";
document.StartupScenePath = std::filesystem::path("Scenes") / "Main.mcscene.json";
document.DataSourcesPath = std::filesystem::path("Runtime") / "DataSources.mcruntime";
document.BindingsPath = std::filesystem::path("Runtime") / "Bindings.mcruntime";
document.DiagnosticsPath = std::filesystem::path("Runtime") / "Diagnostics.mcruntimestate";

View File

@ -14,32 +14,6 @@
namespace MetaCore {
MC_ENUM()
MC_ENUM()
enum class MetaCoreUiNodeType {
Panel = 0,
Text,
Image,
Button
};
MC_ENUM()
enum class MetaCoreUiHorizontalAlignment {
Left = 0,
Center,
Right,
Stretch
};
MC_ENUM()
enum class MetaCoreUiVerticalAlignment {
Top = 0,
Center,
Bottom,
Stretch
};
MC_STRUCT()
struct MetaCoreSubAssetMetadata {
MC_GENERATED_BODY()
@ -101,17 +75,6 @@ struct MetaCoreImportedAssetDocument {
};
MC_STRUCT()
struct MetaCorePrefabDocument {
MC_GENERATED_BODY()
MC_PROPERTY()
std::string Name{};
MC_PROPERTY()
std::vector<MetaCoreGameObjectData> GameObjects{};
};
MC_STRUCT()
struct MetaCoreCookManifestEntry {
MC_GENERATED_BODY()
@ -140,111 +103,4 @@ struct MetaCoreCookManifestDocument {
std::vector<MetaCoreCookManifestEntry> Entries{};
};
MC_STRUCT()
struct MetaCoreUiRectTransformDocument {
MC_GENERATED_BODY()
MC_PROPERTY()
glm::vec3 AnchorMin{0.0F, 0.0F, 0.0F};
MC_PROPERTY()
glm::vec3 AnchorMax{1.0F, 1.0F, 0.0F};
MC_PROPERTY()
glm::vec3 Pivot{0.5F, 0.5F, 0.0F};
MC_PROPERTY()
glm::vec3 Position{0.0F, 0.0F, 0.0F};
MC_PROPERTY()
glm::vec3 Size{100.0F, 100.0F, 0.0F};
};
MC_STRUCT()
struct MetaCoreUiStyleDocument {
MC_GENERATED_BODY()
MC_PROPERTY()
glm::vec3 BackgroundColor{0.15F, 0.15F, 0.15F};
MC_PROPERTY()
glm::vec3 TextColor{1.0F, 1.0F, 1.0F};
MC_PROPERTY()
glm::vec3 TintColor{1.0F, 1.0F, 1.0F};
MC_PROPERTY()
float FontSize = 16.0F;
MC_PROPERTY()
glm::vec3 Padding{8.0F, 8.0F, 0.0F};
MC_PROPERTY()
MetaCoreUiHorizontalAlignment HorizontalAlignment = MetaCoreUiHorizontalAlignment::Left;
MC_PROPERTY()
MetaCoreUiVerticalAlignment VerticalAlignment = MetaCoreUiVerticalAlignment::Top;
MC_PROPERTY()
MetaCoreAssetGuid ImageAssetGuid{};
MC_PROPERTY()
bool PreserveAspect = false;
};
MC_STRUCT()
struct MetaCoreUiNodeDocument {
MC_GENERATED_BODY()
MC_PROPERTY()
std::string Id{};
MC_PROPERTY()
std::string Name{};
MC_PROPERTY()
MetaCoreUiNodeType Type = MetaCoreUiNodeType::Panel;
MC_PROPERTY()
std::string ParentId{};
MC_PROPERTY()
std::vector<std::string> Children{};
MC_PROPERTY()
bool Visible = true;
MC_PROPERTY()
MetaCoreUiRectTransformDocument RectTransform{};
MC_PROPERTY()
MetaCoreUiStyleDocument Style{};
MC_PROPERTY()
std::string Text{};
MC_PROPERTY()
bool Interactable = false;
};
MC_STRUCT()
struct MetaCoreUiDocument {
MC_GENERATED_BODY()
MC_PROPERTY()
std::string Name{};
MC_PROPERTY()
std::int32_t ReferenceWidth = 1920;
MC_PROPERTY()
std::int32_t ReferenceHeight = 1080;
MC_PROPERTY()
std::vector<std::string> RootNodeIds{};
MC_PROPERTY()
std::vector<MetaCoreUiNodeDocument> Nodes{};
};
} // namespace MetaCore

View File

@ -44,7 +44,7 @@ bool MetaCoreEditorViewportRenderer::Initialize(MetaCoreRenderDevice& renderDevi
Shutdown();
MetaCoreTrace("metacore.render: viewport scene runtime bind begin");
// 彻底剔除 Panda3D不再初始化 Panda3D 场景桥接器
// Filament is the only scene bridge initialized by the editor viewport.
/*
if (!SceneBridge_.Initialize(renderDevice)) {
MetaCoreTrace("metacore.render: viewport scene runtime bind failed");

View File

@ -228,6 +228,16 @@ public:
// 【超级核心】:绑定顶级父 GameObject 标识到 Filament Asset 根 Entity彻底打通顶级 Gizmo 的同步!
ObjectToFilamentEntity_[parentObject.GetId()] = { asset, asset->getRoot() };
// 预先收集并缓存 parentObject 子树下的所有已有 GameObject用于高精度的三步复用匹配
std::vector<MetaCoreGameObject> subtreeObjects;
std::vector<MetaCoreId> subtreeIds = scene.GetSubtreeObjectIds(parentObject.GetId());
for (MetaCoreId subId : subtreeIds) {
auto subObj = scene.FindGameObject(subId);
if (subObj && subObj.GetId() != parentObject.GetId()) {
subtreeObjects.push_back(subObj);
}
}
// 第一遍:创建或复用所有对象
for (size_t i = 0; i < entityCount; i++) {
utils::Entity entity = entities[i];
@ -236,21 +246,44 @@ public:
const char* nodeName = asset->getName(entity);
std::string name = nodeName ? nodeName : ("Node_" + std::to_string(i));
// 如果场景中在 parentObject 下已经展开好了对应的子 GameObject直接复用以建立映射
MetaCoreGameObject childObj;
bool isExisting = false;
for (auto& sceneObj : scene.GetGameObjects()) {
if (sceneObj.GetParentId() == parentObject.GetId() &&
sceneObj.HasComponent<MetaCoreMeshRendererComponent>() &&
sceneObj.GetComponent<MetaCoreMeshRendererComponent>().ModelNodeIndex == static_cast<std::int32_t>(i)) {
childObj = sceneObj;
isExisting = true;
break;
// 1. 优先通过 ModelNodeIndex 进行精确匹配(针对带有网格的节点)
for (auto& sceneObj : subtreeObjects) {
if (sceneObj.HasComponent<MetaCoreMeshRendererComponent>()) {
auto& mesh = sceneObj.GetComponent<MetaCoreMeshRendererComponent>();
if (mesh.ModelNodeIndex == static_cast<std::int32_t>(i)) {
childObj = sceneObj;
isExisting = true;
break;
}
}
}
// 2. 如果没找到(针对没有网格的空节点),通过名字进行匹配复用
if (!isExisting) {
for (auto& sceneObj : subtreeObjects) {
if (sceneObj.GetName() == name) {
// 且该节点不能已经被其他实体映射占用
bool alreadyMapped = false;
for (auto& [ent, obj] : entityToObj) {
if (obj.GetId() == sceneObj.GetId()) {
alreadyMapped = true;
break;
}
}
if (!alreadyMapped) {
childObj = sceneObj;
isExisting = true;
break;
}
}
}
}
// 3. 实在没有找到,才进行动态创建(仅作为降级兜底)
if (!isExisting) {
// 如果没找到才动态创建它(用于默认场景的未展开根节点)
childObj = scene.CreateGameObject(name, parentObject.GetId());
auto& meshRenderer = childObj.AddComponent<MetaCoreMeshRendererComponent>();
meshRenderer.MeshSource = MetaCoreMeshSourceKind::Asset;
@ -304,9 +337,32 @@ public:
}
}
static std::string NormalizePath(std::string path) {
std::replace(path.begin(), path.end(), '\\', '/');
return path;
}
void SyncScene(MetaCoreScene& scene, bool compatibilityMeshOnly) {
if (!AssetLoader_) return;
// 预处理:构建子树模型网格节点计数表,以支撑在没有 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;
@ -338,11 +394,44 @@ public:
// 【黄金算法】:溯源确定唯一的模型根加载主体
MetaCoreGameObject hostRoot = gameObject;
if (meshRenderer.ModelNodeIndex >= 0) {
// 如果是子节点,沿着场景树向上回溯至最顶层父节点作为该模型的宿主加载实体
while (hostRoot.GetParentId() != 0) {
auto parentObj = scene.FindGameObject(hostRoot.GetParentId());
const std::string& currentModelPath = meshRenderer.SourceModelPath;
std::string normCurrentPath = NormalizePath(currentModelPath);
MetaCoreGameObject current = gameObject;
MetaCoreGameObject foundRoot = {};
// 1. 优先通过运行时 MetaCoreModelRootTag 快速寻找顶级根
while (current.GetParentId() != 0) {
auto parentObj = scene.FindGameObject(current.GetParentId());
if (!parentObj) break;
hostRoot = parentObj;
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;
}
}
@ -385,6 +474,11 @@ public:
// 同步层级结构到场景树并自动进行展开子节点的复用映射
MetaCoreGameObject nonConstHostRoot = hostRoot;
// 现场补挂 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();

File diff suppressed because it is too large Load Diff

View File

@ -2,48 +2,13 @@
#include "MetaCorePlatform/MetaCoreWindow.h"
#include "camera.h"
#include "displayRegion.h"
#include "graphicsEngine.h"
#include "graphicsWindow.h"
#include "pandaFramework.h"
#include "pandaNode.h"
#include "perspectiveLens.h"
#include "windowFramework.h"
#include <glm/mat4x4.hpp>
#include <algorithm>
#include <memory>
namespace MetaCore {
namespace {
glm::mat4 MetaCoreConvertPandaMatrixToGlm(const LMatrix4f& matrix) {
glm::mat4 result(1.0F);
for (int col = 0; col < 4; ++col) {
for (int row = 0; row < 4; ++row) {
// Transpose mathematical matrix: result[col][row] is GLM(row, col).
// We want GLM(row, col) = Panda(col, row) to switch v*M to M*v.
result[col][row] = matrix.get_cell(col, row);
}
}
return result;
}
} // namespace
class MetaCoreRenderDevice::MetaCoreRenderDeviceImpl {
public:
MetaCoreWindow* Window = nullptr;
PandaFramework* Framework = nullptr;
WindowFramework* WindowFrameworkHandle = nullptr;
GraphicsWindow* GraphicsWindowHandle = nullptr;
GraphicsEngine* GraphicsEngineHandle = nullptr;
DisplayRegion* SceneDisplayRegion = nullptr;
NodePath SceneRoot{};
NodePath EditorCamera{};
bool Initialized = false;
};
@ -68,40 +33,18 @@ void MetaCoreRenderDevice::Shutdown() {
if (!Impl_->Initialized) {
return;
}
if (!Impl_->EditorCamera.is_empty()) {
Impl_->EditorCamera.remove_node();
Impl_->EditorCamera = NodePath();
}
if (!Impl_->SceneRoot.is_empty()) {
Impl_->SceneRoot.remove_node();
Impl_->SceneRoot = NodePath();
}
Impl_->SceneDisplayRegion = nullptr;
Impl_->GraphicsEngineHandle = nullptr;
Impl_->GraphicsWindowHandle = nullptr;
Impl_->WindowFrameworkHandle = nullptr;
Impl_->Framework = nullptr;
Impl_->Window = nullptr;
Impl_->Initialized = false;
}
void MetaCoreRenderDevice::RenderFrame() const {
if (Impl_->GraphicsEngineHandle != nullptr) {
Impl_->GraphicsEngineHandle->render_frame();
}
}
void MetaCoreRenderDevice::PresentFrame() const {
if (Impl_->GraphicsEngineHandle != nullptr) {
Impl_->GraphicsEngineHandle->flip_frame();
}
}
void MetaCoreRenderDevice::SetSceneViewportRect(const MetaCoreViewportRect& viewportRect) {
// 已废弃 Panda3D 实现,视口管理已移至 Filament 链
(void)viewportRect;
}
} // namespace MetaCore

View File

@ -1,37 +0,0 @@
#pragma once
#include "MetaCoreFoundation/MetaCoreId.h"
#include <glm/mat4x4.hpp>
#include <filesystem>
#include <memory>
#include <string>
namespace MetaCore {
class MetaCoreRenderDevice;
class MetaCoreScene;
struct MetaCoreSceneView;
class MetaCorePandaSceneBridge {
public:
MetaCorePandaSceneBridge();
~MetaCorePandaSceneBridge();
bool Initialize(MetaCoreRenderDevice& renderDevice);
void Shutdown();
void SetProjectRootPath(const std::filesystem::path& projectRootPath);
void SyncScene(const MetaCoreScene& scene, bool compatibilityMeshOnly = false);
void ApplySceneView(const MetaCoreSceneView& sceneView);
[[nodiscard]] bool TryGetObjectWorldMatrix(MetaCoreId objectId, glm::mat4& worldMatrix) const;
[[nodiscard]] bool HasRuntimeSyncFailure() const;
[[nodiscard]] const std::string& GetLastRuntimeSyncFailure() const;
private:
class MetaCorePandaSceneBridgeImpl;
std::unique_ptr<MetaCorePandaSceneBridgeImpl> Impl_{};
};
} // namespace MetaCore

View File

@ -9,7 +9,6 @@
namespace MetaCore {
class MetaCoreEditorViewportRenderer;
class MetaCorePandaSceneBridge;
class MetaCoreWindow;
class MetaCoreRenderDevice {

View File

@ -403,6 +403,8 @@ MetaCoreSceneSnapshot MetaCoreScene::CaptureSnapshot() const {
data.Light = obj.GetComponent<MetaCoreLightComponent>();
if (obj.HasComponent<MetaCorePrefabInstanceMetadata>())
data.PrefabInstance = obj.GetComponent<MetaCorePrefabInstanceMetadata>();
if (obj.HasComponent<MetaCoreModelRootTag>())
data.ModelRootTag = obj.GetComponent<MetaCoreModelRootTag>();
snapshot.GameObjects.push_back(std::move(data));
}
@ -427,6 +429,8 @@ void MetaCoreScene::RestoreSnapshot(const MetaCoreSceneSnapshot& snapshot) {
obj.AddComponent<MetaCoreLightComponent>(data.Light.value());
if (data.PrefabInstance.has_value())
obj.AddComponent<MetaCorePrefabInstanceMetadata>(data.PrefabInstance.value());
if (data.ModelRootTag.has_value())
obj.AddComponent<MetaCoreModelRootTag>(data.ModelRootTag.value());
}
MetaCoreIdGenerator::EnsureAbove(maxId);
}
@ -448,6 +452,7 @@ MetaCoreScene MetaCoreCreateDefaultScene() {
meshRenderer.MeshSource = MetaCoreMeshSourceKind::Asset;
meshRenderer.SourceModelPath = "Assets/Models/box1.glb";
meshRenderer.SourceModelAssetGuid = MetaCoreAssetRegistry::Get().ResolvePathToGuid("Assets/Models/box1.glb");
cube.AddComponent<MetaCoreModelRootTag>(MetaCoreModelRootTag{ "Assets/Models/box1.glb" });
cube.GetComponent<MetaCoreTransformComponent>().Position = glm::vec3(0.0F, 0.5F, 0.0F);
MetaCoreGameObject alarmBeacon = scene.CreateGameObject("Alarm Beacon");

View File

@ -1,4 +1,4 @@
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
#include "MetaCoreFoundation/MetaCoreReflection.h"
#include <nlohmann/json.hpp>
#include <fstream>
@ -32,6 +32,218 @@ static MetaCoreAssetGuid StringToGuid(const std::string& str) {
return str.empty() ? MetaCoreAssetGuid{} : MetaCoreAssetGuid::Parse(str).value_or(MetaCoreAssetGuid{});
}
// 辅助方法:将单个 MetaCoreGameObjectData 转换为 json 格式
static json GameObjectDataToJson(const MetaCoreGameObjectData& obj, const MetaCoreTypeRegistry& registry) {
json objJson = json::object();
const MetaCoreStructDescriptor* objDesc = registry.FindStruct<MetaCoreGameObjectData>();
if (!objDesc) return objJson;
for (const auto& objField : objDesc->Fields) {
if (objField.Name == "Id") {
objJson["Id"] = obj.Id;
} else if (objField.Name == "ParentId") {
objJson["ParentId"] = obj.ParentId;
} else if (objField.Name == "Name") {
objJson["Name"] = obj.Name;
} else if (objField.Name == "Transform") {
const MetaCoreStructDescriptor* transDesc = registry.FindStruct<MetaCoreTransformComponent>();
if (transDesc) {
json transJson = json::object();
for (const auto& transField : transDesc->Fields) {
if (transField.Name == "Position") transJson["Position"] = Vec3ToJson(obj.Transform.Position);
else if (transField.Name == "RotationEulerDegrees") transJson["RotationEulerDegrees"] = Vec3ToJson(obj.Transform.RotationEulerDegrees);
else if (transField.Name == "Scale") transJson["Scale"] = Vec3ToJson(obj.Transform.Scale);
}
objJson["Transform"] = transJson;
}
} else if (objField.Name == "MeshRenderer" && obj.MeshRenderer.has_value()) {
const MetaCoreStructDescriptor* meshDesc = registry.FindStruct<MetaCoreMeshRendererComponent>();
if (meshDesc) {
json meshJson = json::object();
const auto& mesh = obj.MeshRenderer.value();
for (const auto& meshField : meshDesc->Fields) {
if (meshField.Name == "MeshSource") meshJson["MeshSource"] = static_cast<int>(mesh.MeshSource);
else if (meshField.Name == "BuiltinMesh") meshJson["BuiltinMesh"] = static_cast<int>(mesh.BuiltinMesh);
else if (meshField.Name == "MeshAssetGuid") meshJson["MeshAssetGuid"] = GuidToString(mesh.MeshAssetGuid);
else if (meshField.Name == "MaterialAssetGuids") {
json matGuids = json::array();
for (const auto& guid : mesh.MaterialAssetGuids) {
matGuids.push_back(GuidToString(guid));
}
meshJson["MaterialAssetGuids"] = matGuids;
}
else if (meshField.Name == "SourceModelAssetGuid") meshJson["SourceModelAssetGuid"] = GuidToString(mesh.SourceModelAssetGuid);
else if (meshField.Name == "SourceModelPath") meshJson["SourceModelPath"] = mesh.SourceModelPath;
else if (meshField.Name == "SourceNodePath") meshJson["SourceNodePath"] = mesh.SourceNodePath;
else if (meshField.Name == "BaseColorTextureGuid") meshJson["BaseColorTextureGuid"] = GuidToString(mesh.BaseColorTextureGuid);
else if (meshField.Name == "MetallicRoughnessTextureGuid") meshJson["MetallicRoughnessTextureGuid"] = GuidToString(mesh.MetallicRoughnessTextureGuid);
else if (meshField.Name == "NormalTextureGuid") meshJson["NormalTextureGuid"] = GuidToString(mesh.NormalTextureGuid);
else if (meshField.Name == "EmissiveTextureGuid") meshJson["EmissiveTextureGuid"] = GuidToString(mesh.EmissiveTextureGuid);
else if (meshField.Name == "AoTextureGuid") meshJson["AoTextureGuid"] = GuidToString(mesh.AoTextureGuid);
else if (meshField.Name == "BaseColorTexturePath") meshJson["BaseColorTexturePath"] = mesh.BaseColorTexturePath;
else if (meshField.Name == "MetallicRoughnessTexturePath") meshJson["MetallicRoughnessTexturePath"] = mesh.MetallicRoughnessTexturePath;
else if (meshField.Name == "NormalTexturePath") meshJson["NormalTexturePath"] = mesh.NormalTexturePath;
else if (meshField.Name == "EmissiveTexturePath") meshJson["EmissiveTexturePath"] = mesh.EmissiveTexturePath;
else if (meshField.Name == "AoTexturePath") meshJson["AoTexturePath"] = mesh.AoTexturePath;
else if (meshField.Name == "DoubleSided") meshJson["DoubleSided"] = mesh.DoubleSided;
else if (meshField.Name == "Metallic") meshJson["Metallic"] = mesh.Metallic;
else if (meshField.Name == "Roughness") meshJson["Roughness"] = mesh.Roughness;
else if (meshField.Name == "AlphaMode") meshJson["AlphaMode"] = static_cast<int>(mesh.AlphaMode);
else if (meshField.Name == "AlphaCutoff") meshJson["AlphaCutoff"] = mesh.AlphaCutoff;
else if (meshField.Name == "EmissiveColor") meshJson["EmissiveColor"] = Vec3ToJson(mesh.EmissiveColor);
else if (meshField.Name == "BaseColor") meshJson["BaseColor"] = Vec3ToJson(mesh.BaseColor);
else if (meshField.Name == "ModelNodeIndex") meshJson["ModelNodeIndex"] = mesh.ModelNodeIndex;
else if (meshField.Name == "SubMeshIndex") meshJson["SubMeshIndex"] = mesh.SubMeshIndex;
else if (meshField.Name == "Visible") meshJson["Visible"] = mesh.Visible;
}
objJson["MeshRenderer"] = meshJson;
}
} else if (objField.Name == "Light" && obj.Light.has_value()) {
const MetaCoreStructDescriptor* lightDesc = registry.FindStruct<MetaCoreLightComponent>();
if (lightDesc) {
json lightJson = json::object();
for (const auto& lightField : lightDesc->Fields) {
if (lightField.Name == "Color") lightJson["Color"] = Vec3ToJson(obj.Light->Color);
else if (lightField.Name == "Intensity") lightJson["Intensity"] = obj.Light->Intensity;
}
objJson["Light"] = lightJson;
}
} else if (objField.Name == "Camera" && obj.Camera.has_value()) {
const MetaCoreStructDescriptor* camDesc = registry.FindStruct<MetaCoreCameraComponent>();
if (camDesc) {
json camJson = json::object();
for (const auto& camField : camDesc->Fields) {
if (camField.Name == "FieldOfViewDegrees") camJson["FieldOfViewDegrees"] = obj.Camera->FieldOfViewDegrees;
else if (camField.Name == "NearClip") camJson["NearClip"] = obj.Camera->NearClip;
else if (camField.Name == "FarClip") camJson["FarClip"] = obj.Camera->FarClip;
else if (camField.Name == "IsPrimary") camJson["IsPrimary"] = obj.Camera->IsPrimary;
}
objJson["Camera"] = camJson;
}
} else if (objField.Name == "PrefabInstance" && obj.PrefabInstance.has_value()) {
const MetaCoreStructDescriptor* prefabDesc = registry.FindStruct<MetaCorePrefabInstanceMetadata>();
if (prefabDesc) {
json prefabJson = json::object();
for (const auto& prefabField : prefabDesc->Fields) {
if (prefabField.Name == "PrefabAssetGuid") prefabJson["PrefabAssetGuid"] = GuidToString(obj.PrefabInstance->PrefabAssetGuid);
else if (prefabField.Name == "PrefabObjectId") prefabJson["PrefabObjectId"] = obj.PrefabInstance->PrefabObjectId;
else if (prefabField.Name == "PrefabInstanceRootId") prefabJson["PrefabInstanceRootId"] = obj.PrefabInstance->PrefabInstanceRootId;
}
objJson["PrefabInstance"] = prefabJson;
}
}
}
return objJson;
}
// 辅助方法:从 json 格式中恢复单个 MetaCoreGameObjectData
static MetaCoreGameObjectData JsonToGameObjectData(const json& objJson, const MetaCoreTypeRegistry& registry) {
MetaCoreGameObjectData obj;
const MetaCoreStructDescriptor* objDesc = registry.FindStruct<MetaCoreGameObjectData>();
if (!objDesc) return obj;
for (const auto& objField : objDesc->Fields) {
if (objField.Name == "Id" && objJson.contains("Id")) {
obj.Id = objJson["Id"].get<MetaCoreId>();
} else if (objField.Name == "ParentId" && objJson.contains("ParentId")) {
obj.ParentId = objJson["ParentId"].get<MetaCoreId>();
} else if (objField.Name == "Name" && objJson.contains("Name")) {
obj.Name = objJson["Name"].get<std::string>();
} else if (objField.Name == "Transform" && objJson.contains("Transform")) {
const auto& transJson = objJson["Transform"];
const MetaCoreStructDescriptor* transDesc = registry.FindStruct<MetaCoreTransformComponent>();
if (transDesc) {
for (const auto& transField : transDesc->Fields) {
if (transField.Name == "Position" && transJson.contains("Position")) {
obj.Transform.Position = JsonToVec3(transJson["Position"]);
} else if (transField.Name == "RotationEulerDegrees" && transJson.contains("RotationEulerDegrees")) {
obj.Transform.RotationEulerDegrees = JsonToVec3(transJson["RotationEulerDegrees"]);
} else if (transField.Name == "Scale" && transJson.contains("Scale")) {
obj.Transform.Scale = JsonToVec3(transJson["Scale"]);
}
}
}
} else if (objField.Name == "MeshRenderer" && objJson.contains("MeshRenderer")) {
const auto& meshJson = objJson["MeshRenderer"];
const MetaCoreStructDescriptor* meshDesc = registry.FindStruct<MetaCoreMeshRendererComponent>();
if (meshDesc) {
MetaCoreMeshRendererComponent mesh;
for (const auto& meshField : meshDesc->Fields) {
if (meshField.Name == "MeshSource" && meshJson.contains("MeshSource")) mesh.MeshSource = static_cast<MetaCoreMeshSourceKind>(meshJson["MeshSource"].get<int>());
else if (meshField.Name == "BuiltinMesh" && meshJson.contains("BuiltinMesh")) mesh.BuiltinMesh = static_cast<MetaCoreBuiltinMeshType>(meshJson["BuiltinMesh"].get<int>());
else if (meshField.Name == "MeshAssetGuid" && meshJson.contains("MeshAssetGuid")) mesh.MeshAssetGuid = StringToGuid(meshJson["MeshAssetGuid"].get<std::string>());
else if (meshField.Name == "MaterialAssetGuids" && meshJson.contains("MaterialAssetGuids") && meshJson["MaterialAssetGuids"].is_array()) {
for (const auto& mGuidJson : meshJson["MaterialAssetGuids"]) {
mesh.MaterialAssetGuids.push_back(StringToGuid(mGuidJson.get<std::string>()));
}
}
else if (meshField.Name == "SourceModelAssetGuid" && meshJson.contains("SourceModelAssetGuid")) mesh.SourceModelAssetGuid = StringToGuid(meshJson["SourceModelAssetGuid"].get<std::string>());
else if (meshField.Name == "SourceModelPath" && meshJson.contains("SourceModelPath")) mesh.SourceModelPath = meshJson["SourceModelPath"].get<std::string>();
else if (meshField.Name == "SourceNodePath" && meshJson.contains("SourceNodePath")) mesh.SourceNodePath = meshJson["SourceNodePath"].get<std::string>();
else if (meshField.Name == "BaseColorTextureGuid" && meshJson.contains("BaseColorTextureGuid")) mesh.BaseColorTextureGuid = StringToGuid(meshJson["BaseColorTextureGuid"].get<std::string>());
else if (meshField.Name == "MetallicRoughnessTextureGuid" && meshJson.contains("MetallicRoughnessTextureGuid")) mesh.MetallicRoughnessTextureGuid = StringToGuid(meshJson["MetallicRoughnessTextureGuid"].get<std::string>());
else if (meshField.Name == "NormalTextureGuid" && meshJson.contains("NormalTextureGuid")) mesh.NormalTextureGuid = StringToGuid(meshJson["NormalTextureGuid"].get<std::string>());
else if (meshField.Name == "EmissiveTextureGuid" && meshJson.contains("EmissiveTextureGuid")) mesh.EmissiveTextureGuid = StringToGuid(meshJson["EmissiveTextureGuid"].get<std::string>());
else if (meshField.Name == "AoTextureGuid" && meshJson.contains("AoTextureGuid")) mesh.AoTextureGuid = StringToGuid(meshJson["AoTextureGuid"].get<std::string>());
else if (meshField.Name == "BaseColorTexturePath" && meshJson.contains("BaseColorTexturePath")) mesh.BaseColorTexturePath = meshJson["BaseColorTexturePath"].get<std::string>();
else if (meshField.Name == "MetallicRoughnessTexturePath" && meshJson.contains("MetallicRoughnessTexturePath")) mesh.MetallicRoughnessTexturePath = meshJson["MetallicRoughnessTexturePath"].get<std::string>();
else if (meshField.Name == "NormalTexturePath" && meshJson.contains("NormalTexturePath")) mesh.NormalTexturePath = meshJson["NormalTexturePath"].get<std::string>();
else if (meshField.Name == "EmissiveTexturePath" && meshJson.contains("EmissiveTexturePath")) mesh.EmissiveTexturePath = meshJson["EmissiveTexturePath"].get<std::string>();
else if (meshField.Name == "AoTexturePath" && meshJson.contains("AoTexturePath")) mesh.AoTexturePath = meshJson["AoTexturePath"].get<std::string>();
else if (meshField.Name == "DoubleSided" && meshJson.contains("DoubleSided")) mesh.DoubleSided = meshJson["DoubleSided"].get<bool>();
else if (meshField.Name == "Metallic" && meshJson.contains("Metallic")) mesh.Metallic = meshJson["Metallic"].get<float>();
else if (meshField.Name == "Roughness" && meshJson.contains("Roughness")) mesh.Roughness = meshJson["Roughness"].get<float>();
else if (meshField.Name == "AlphaMode" && meshJson.contains("AlphaMode")) mesh.AlphaMode = static_cast<MetaCoreMeshAlphaMode>(meshJson["AlphaMode"].get<int>());
else if (meshField.Name == "AlphaCutoff" && meshJson.contains("AlphaCutoff")) mesh.AlphaCutoff = meshJson["AlphaCutoff"].get<float>();
else if (meshField.Name == "EmissiveColor" && meshJson.contains("EmissiveColor")) mesh.EmissiveColor = JsonToVec3(meshJson["EmissiveColor"]);
else if (meshField.Name == "BaseColor" && meshJson.contains("BaseColor")) mesh.BaseColor = JsonToVec3(meshJson["BaseColor"]);
else if (meshField.Name == "ModelNodeIndex" && meshJson.contains("ModelNodeIndex")) mesh.ModelNodeIndex = meshJson["ModelNodeIndex"].get<std::int32_t>();
else if (meshField.Name == "SubMeshIndex" && meshJson.contains("SubMeshIndex")) mesh.SubMeshIndex = meshJson["SubMeshIndex"].get<std::int32_t>();
else if (meshField.Name == "Visible" && meshJson.contains("Visible")) mesh.Visible = meshJson["Visible"].get<bool>();
}
obj.MeshRenderer = mesh;
}
} else if (objField.Name == "Light" && objJson.contains("Light")) {
const auto& lightJson = objJson["Light"];
const MetaCoreStructDescriptor* lightDesc = registry.FindStruct<MetaCoreLightComponent>();
if (lightDesc) {
MetaCoreLightComponent light;
for (const auto& lightField : lightDesc->Fields) {
if (lightField.Name == "Color" && lightJson.contains("Color")) light.Color = JsonToVec3(lightJson["Color"]);
else if (lightField.Name == "Intensity" && lightJson.contains("Intensity")) light.Intensity = lightJson["Intensity"].get<float>();
}
obj.Light = light;
}
} else if (objField.Name == "Camera" && objJson.contains("Camera")) {
const auto& camJson = objJson["Camera"];
const MetaCoreStructDescriptor* camDesc = registry.FindStruct<MetaCoreCameraComponent>();
if (camDesc) {
MetaCoreCameraComponent cam;
for (const auto& camField : camDesc->Fields) {
if (camField.Name == "FieldOfViewDegrees" && camJson.contains("FieldOfViewDegrees")) cam.FieldOfViewDegrees = camJson["FieldOfViewDegrees"].get<float>();
else if (camField.Name == "NearClip" && camJson.contains("NearClip")) cam.NearClip = camJson["NearClip"].get<float>();
else if (camField.Name == "FarClip" && camJson.contains("FarClip")) cam.FarClip = camJson["FarClip"].get<float>();
else if (camField.Name == "IsPrimary" && camJson.contains("IsPrimary")) cam.IsPrimary = camJson["IsPrimary"].get<bool>();
}
obj.Camera = cam;
}
} else if (objField.Name == "PrefabInstance" && objJson.contains("PrefabInstance")) {
const auto& prefabJson = objJson["PrefabInstance"];
const MetaCoreStructDescriptor* prefabDesc = registry.FindStruct<MetaCorePrefabInstanceMetadata>();
if (prefabDesc) {
MetaCorePrefabInstanceMetadata prefab;
for (const auto& prefabField : prefabDesc->Fields) {
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 == "PrefabInstanceRootId" && prefabJson.contains("PrefabInstanceRootId")) prefab.PrefabInstanceRootId = prefabJson["PrefabInstanceRootId"].get<MetaCoreId>();
}
obj.PrefabInstance = prefab;
}
}
}
return obj;
}
bool MetaCoreSceneSerializer::SaveSceneToJson(
const std::filesystem::path& absolutePath,
const MetaCoreSceneDocument& sceneDocument,
@ -39,7 +251,7 @@ bool MetaCoreSceneSerializer::SaveSceneToJson(
) {
try {
json sceneJson;
// 验证 MetaCoreSceneDocument 反射注册
const MetaCoreStructDescriptor* sceneDesc = registry.FindStruct<MetaCoreSceneDocument>();
if (!sceneDesc) {
@ -63,108 +275,8 @@ bool MetaCoreSceneSerializer::SaveSceneToJson(
}
} else if (field.Name == "GameObjects") {
json gameObjectsArray = json::array();
const MetaCoreStructDescriptor* objDesc = registry.FindStruct<MetaCoreGameObjectData>();
if (objDesc) {
for (const auto& obj : sceneDocument.GameObjects) {
json objJson = json::object();
for (const auto& objField : objDesc->Fields) {
if (objField.Name == "Id") {
objJson["Id"] = obj.Id;
} else if (objField.Name == "ParentId") {
objJson["ParentId"] = obj.ParentId;
} else if (objField.Name == "Name") {
objJson["Name"] = obj.Name;
} else if (objField.Name == "Transform") {
const MetaCoreStructDescriptor* transDesc = registry.FindStruct<MetaCoreTransformComponent>();
if (transDesc) {
json transJson = json::object();
for (const auto& transField : transDesc->Fields) {
if (transField.Name == "Position") transJson["Position"] = Vec3ToJson(obj.Transform.Position);
else if (transField.Name == "RotationEulerDegrees") transJson["RotationEulerDegrees"] = Vec3ToJson(obj.Transform.RotationEulerDegrees);
else if (transField.Name == "Scale") transJson["Scale"] = Vec3ToJson(obj.Transform.Scale);
}
objJson["Transform"] = transJson;
}
} else if (objField.Name == "MeshRenderer" && obj.MeshRenderer.has_value()) {
const MetaCoreStructDescriptor* meshDesc = registry.FindStruct<MetaCoreMeshRendererComponent>();
if (meshDesc) {
json meshJson = json::object();
const auto& mesh = obj.MeshRenderer.value();
for (const auto& meshField : meshDesc->Fields) {
if (meshField.Name == "MeshSource") meshJson["MeshSource"] = static_cast<int>(mesh.MeshSource);
else if (meshField.Name == "BuiltinMesh") meshJson["BuiltinMesh"] = static_cast<int>(mesh.BuiltinMesh);
else if (meshField.Name == "MeshAssetGuid") meshJson["MeshAssetGuid"] = GuidToString(mesh.MeshAssetGuid);
else if (meshField.Name == "MaterialAssetGuids") {
json matGuids = json::array();
for (const auto& guid : mesh.MaterialAssetGuids) {
matGuids.push_back(GuidToString(guid));
}
meshJson["MaterialAssetGuids"] = matGuids;
}
else if (meshField.Name == "SourceModelAssetGuid") meshJson["SourceModelAssetGuid"] = GuidToString(mesh.SourceModelAssetGuid);
else if (meshField.Name == "SourceModelPath") meshJson["SourceModelPath"] = mesh.SourceModelPath;
else if (meshField.Name == "SourceNodePath") meshJson["SourceNodePath"] = mesh.SourceNodePath;
else if (meshField.Name == "BaseColorTextureGuid") meshJson["BaseColorTextureGuid"] = GuidToString(mesh.BaseColorTextureGuid);
else if (meshField.Name == "MetallicRoughnessTextureGuid") meshJson["MetallicRoughnessTextureGuid"] = GuidToString(mesh.MetallicRoughnessTextureGuid);
else if (meshField.Name == "NormalTextureGuid") meshJson["NormalTextureGuid"] = GuidToString(mesh.NormalTextureGuid);
else if (meshField.Name == "EmissiveTextureGuid") meshJson["EmissiveTextureGuid"] = GuidToString(mesh.EmissiveTextureGuid);
else if (meshField.Name == "AoTextureGuid") meshJson["AoTextureGuid"] = GuidToString(mesh.AoTextureGuid);
else if (meshField.Name == "BaseColorTexturePath") meshJson["BaseColorTexturePath"] = mesh.BaseColorTexturePath;
else if (meshField.Name == "MetallicRoughnessTexturePath") meshJson["MetallicRoughnessTexturePath"] = mesh.MetallicRoughnessTexturePath;
else if (meshField.Name == "NormalTexturePath") meshJson["NormalTexturePath"] = mesh.NormalTexturePath;
else if (meshField.Name == "EmissiveTexturePath") meshJson["EmissiveTexturePath"] = mesh.EmissiveTexturePath;
else if (meshField.Name == "AoTexturePath") meshJson["AoTexturePath"] = mesh.AoTexturePath;
else if (meshField.Name == "DoubleSided") meshJson["DoubleSided"] = mesh.DoubleSided;
else if (meshField.Name == "Metallic") meshJson["Metallic"] = mesh.Metallic;
else if (meshField.Name == "Roughness") meshJson["Roughness"] = mesh.Roughness;
else if (meshField.Name == "AlphaMode") meshJson["AlphaMode"] = static_cast<int>(mesh.AlphaMode);
else if (meshField.Name == "AlphaCutoff") meshJson["AlphaCutoff"] = mesh.AlphaCutoff;
else if (meshField.Name == "EmissiveColor") meshJson["EmissiveColor"] = Vec3ToJson(mesh.EmissiveColor);
else if (meshField.Name == "BaseColor") meshJson["BaseColor"] = Vec3ToJson(mesh.BaseColor);
else if (meshField.Name == "ModelNodeIndex") meshJson["ModelNodeIndex"] = mesh.ModelNodeIndex;
else if (meshField.Name == "SubMeshIndex") meshJson["SubMeshIndex"] = mesh.SubMeshIndex;
else if (meshField.Name == "Visible") meshJson["Visible"] = mesh.Visible;
}
objJson["MeshRenderer"] = meshJson;
}
} else if (objField.Name == "Light" && obj.Light.has_value()) {
const MetaCoreStructDescriptor* lightDesc = registry.FindStruct<MetaCoreLightComponent>();
if (lightDesc) {
json lightJson = json::object();
for (const auto& lightField : lightDesc->Fields) {
if (lightField.Name == "Color") lightJson["Color"] = Vec3ToJson(obj.Light->Color);
else if (lightField.Name == "Intensity") lightJson["Intensity"] = obj.Light->Intensity;
}
objJson["Light"] = lightJson;
}
} else if (objField.Name == "Camera" && obj.Camera.has_value()) {
const MetaCoreStructDescriptor* camDesc = registry.FindStruct<MetaCoreCameraComponent>();
if (camDesc) {
json camJson = json::object();
for (const auto& camField : camDesc->Fields) {
if (camField.Name == "FieldOfViewDegrees") camJson["FieldOfViewDegrees"] = obj.Camera->FieldOfViewDegrees;
else if (camField.Name == "NearClip") camJson["NearClip"] = obj.Camera->NearClip;
else if (camField.Name == "FarClip") camJson["FarClip"] = obj.Camera->FarClip;
else if (camField.Name == "IsPrimary") camJson["IsPrimary"] = obj.Camera->IsPrimary;
}
objJson["Camera"] = camJson;
}
} else if (objField.Name == "PrefabInstance" && obj.PrefabInstance.has_value()) {
const MetaCoreStructDescriptor* prefabDesc = registry.FindStruct<MetaCorePrefabInstanceMetadata>();
if (prefabDesc) {
json prefabJson = json::object();
for (const auto& prefabField : prefabDesc->Fields) {
if (prefabField.Name == "PrefabAssetGuid") prefabJson["PrefabAssetGuid"] = GuidToString(obj.PrefabInstance->PrefabAssetGuid);
else if (prefabField.Name == "PrefabObjectId") prefabJson["PrefabObjectId"] = obj.PrefabInstance->PrefabObjectId;
else if (prefabField.Name == "PrefabInstanceRootId") prefabJson["PrefabInstanceRootId"] = obj.PrefabInstance->PrefabInstanceRootId;
}
objJson["PrefabInstance"] = prefabJson;
}
}
}
gameObjectsArray.push_back(objJson);
}
for (const auto& obj : sceneDocument.GameObjects) {
gameObjectsArray.push_back(GameObjectDataToJson(obj, registry));
}
sceneJson["GameObjects"] = gameObjectsArray;
}
@ -199,7 +311,7 @@ std::optional<MetaCoreSceneDocument> MetaCoreSceneSerializer::LoadSceneFromJson(
file >> sceneJson;
MetaCoreSceneDocument sceneDocument;
// 验证 MetaCoreSceneDocument 反射注册
const MetaCoreStructDescriptor* sceneDesc = registry.FindStruct<MetaCoreSceneDocument>();
if (!sceneDesc) {
@ -225,112 +337,8 @@ std::optional<MetaCoreSceneDocument> MetaCoreSceneSerializer::LoadSceneFromJson(
}
}
} else if (field.Name == "GameObjects" && sceneJson.contains("GameObjects") && sceneJson["GameObjects"].is_array()) {
const MetaCoreStructDescriptor* objDesc = registry.FindStruct<MetaCoreGameObjectData>();
if (objDesc) {
for (const auto& objJson : sceneJson["GameObjects"]) {
MetaCoreGameObjectData obj;
for (const auto& objField : objDesc->Fields) {
if (objField.Name == "Id" && objJson.contains("Id")) {
obj.Id = objJson["Id"].get<MetaCoreId>();
} else if (objField.Name == "ParentId" && objJson.contains("ParentId")) {
obj.ParentId = objJson["ParentId"].get<MetaCoreId>();
} else if (objField.Name == "Name" && objJson.contains("Name")) {
obj.Name = objJson["Name"].get<std::string>();
} else if (objField.Name == "Transform" && objJson.contains("Transform")) {
const auto& transJson = objJson["Transform"];
const MetaCoreStructDescriptor* transDesc = registry.FindStruct<MetaCoreTransformComponent>();
if (transDesc) {
for (const auto& transField : transDesc->Fields) {
if (transField.Name == "Position" && transJson.contains("Position")) {
obj.Transform.Position = JsonToVec3(transJson["Position"]);
} else if (transField.Name == "RotationEulerDegrees" && transJson.contains("RotationEulerDegrees")) {
obj.Transform.RotationEulerDegrees = JsonToVec3(transJson["RotationEulerDegrees"]);
} else if (transField.Name == "Scale" && transJson.contains("Scale")) {
obj.Transform.Scale = JsonToVec3(transJson["Scale"]);
}
}
}
} else if (objField.Name == "MeshRenderer" && objJson.contains("MeshRenderer")) {
const auto& meshJson = objJson["MeshRenderer"];
const MetaCoreStructDescriptor* meshDesc = registry.FindStruct<MetaCoreMeshRendererComponent>();
if (meshDesc) {
MetaCoreMeshRendererComponent mesh;
for (const auto& meshField : meshDesc->Fields) {
if (meshField.Name == "MeshSource" && meshJson.contains("MeshSource")) mesh.MeshSource = static_cast<MetaCoreMeshSourceKind>(meshJson["MeshSource"].get<int>());
else if (meshField.Name == "BuiltinMesh" && meshJson.contains("BuiltinMesh")) mesh.BuiltinMesh = static_cast<MetaCoreBuiltinMeshType>(meshJson["BuiltinMesh"].get<int>());
else if (meshField.Name == "MeshAssetGuid" && meshJson.contains("MeshAssetGuid")) mesh.MeshAssetGuid = StringToGuid(meshJson["MeshAssetGuid"].get<std::string>());
else if (meshField.Name == "MaterialAssetGuids" && meshJson.contains("MaterialAssetGuids") && meshJson["MaterialAssetGuids"].is_array()) {
for (const auto& mGuidJson : meshJson["MaterialAssetGuids"]) {
mesh.MaterialAssetGuids.push_back(StringToGuid(mGuidJson.get<std::string>()));
}
}
else if (meshField.Name == "SourceModelAssetGuid" && meshJson.contains("SourceModelAssetGuid")) mesh.SourceModelAssetGuid = StringToGuid(meshJson["SourceModelAssetGuid"].get<std::string>());
else if (meshField.Name == "SourceModelPath" && meshJson.contains("SourceModelPath")) mesh.SourceModelPath = meshJson["SourceModelPath"].get<std::string>();
else if (meshField.Name == "SourceNodePath" && meshJson.contains("SourceNodePath")) mesh.SourceNodePath = meshJson["SourceNodePath"].get<std::string>();
else if (meshField.Name == "BaseColorTextureGuid" && meshJson.contains("BaseColorTextureGuid")) mesh.BaseColorTextureGuid = StringToGuid(meshJson["BaseColorTextureGuid"].get<std::string>());
else if (meshField.Name == "MetallicRoughnessTextureGuid" && meshJson.contains("MetallicRoughnessTextureGuid")) mesh.MetallicRoughnessTextureGuid = StringToGuid(meshJson["MetallicRoughnessTextureGuid"].get<std::string>());
else if (meshField.Name == "NormalTextureGuid" && meshJson.contains("NormalTextureGuid")) mesh.NormalTextureGuid = StringToGuid(meshJson["NormalTextureGuid"].get<std::string>());
else if (meshField.Name == "EmissiveTextureGuid" && meshJson.contains("EmissiveTextureGuid")) mesh.EmissiveTextureGuid = StringToGuid(meshJson["EmissiveTextureGuid"].get<std::string>());
else if (meshField.Name == "AoTextureGuid" && meshJson.contains("AoTextureGuid")) mesh.AoTextureGuid = StringToGuid(meshJson["AoTextureGuid"].get<std::string>());
else if (meshField.Name == "BaseColorTexturePath" && meshJson.contains("BaseColorTexturePath")) mesh.BaseColorTexturePath = meshJson["BaseColorTexturePath"].get<std::string>();
else if (meshField.Name == "MetallicRoughnessTexturePath" && meshJson.contains("MetallicRoughnessTexturePath")) mesh.MetallicRoughnessTexturePath = meshJson["MetallicRoughnessTexturePath"].get<std::string>();
else if (meshField.Name == "NormalTexturePath" && meshJson.contains("NormalTexturePath")) mesh.NormalTexturePath = meshJson["NormalTexturePath"].get<std::string>();
else if (meshField.Name == "EmissiveTexturePath" && meshJson.contains("EmissiveTexturePath")) mesh.EmissiveTexturePath = meshJson["EmissiveTexturePath"].get<std::string>();
else if (meshField.Name == "AoTexturePath" && meshJson.contains("AoTexturePath")) mesh.AoTexturePath = meshJson["AoTexturePath"].get<std::string>();
else if (meshField.Name == "DoubleSided" && meshJson.contains("DoubleSided")) mesh.DoubleSided = meshJson["DoubleSided"].get<bool>();
else if (meshField.Name == "Metallic" && meshJson.contains("Metallic")) mesh.Metallic = meshJson["Metallic"].get<float>();
else if (meshField.Name == "Roughness" && meshJson.contains("Roughness")) mesh.Roughness = meshJson["Roughness"].get<float>();
else if (meshField.Name == "AlphaMode" && meshJson.contains("AlphaMode")) mesh.AlphaMode = static_cast<MetaCoreMeshAlphaMode>(meshJson["AlphaMode"].get<int>());
else if (meshField.Name == "AlphaCutoff" && meshJson.contains("AlphaCutoff")) mesh.AlphaCutoff = meshJson["AlphaCutoff"].get<float>();
else if (meshField.Name == "EmissiveColor" && meshJson.contains("EmissiveColor")) mesh.EmissiveColor = JsonToVec3(meshJson["EmissiveColor"]);
else if (meshField.Name == "BaseColor" && meshJson.contains("BaseColor")) mesh.BaseColor = JsonToVec3(meshJson["BaseColor"]);
else if (meshField.Name == "ModelNodeIndex" && meshJson.contains("ModelNodeIndex")) mesh.ModelNodeIndex = meshJson["ModelNodeIndex"].get<std::int32_t>();
else if (meshField.Name == "SubMeshIndex" && meshJson.contains("SubMeshIndex")) mesh.SubMeshIndex = meshJson["SubMeshIndex"].get<std::int32_t>();
else if (meshField.Name == "Visible" && meshJson.contains("Visible")) mesh.Visible = meshJson["Visible"].get<bool>();
}
obj.MeshRenderer = mesh;
}
} else if (objField.Name == "Light" && objJson.contains("Light")) {
const auto& lightJson = objJson["Light"];
const MetaCoreStructDescriptor* lightDesc = registry.FindStruct<MetaCoreLightComponent>();
if (lightDesc) {
MetaCoreLightComponent light;
for (const auto& lightField : lightDesc->Fields) {
if (lightField.Name == "Color" && lightJson.contains("Color")) light.Color = JsonToVec3(lightJson["Color"]);
else if (lightField.Name == "Intensity" && lightJson.contains("Intensity")) light.Intensity = lightJson["Intensity"].get<float>();
}
obj.Light = light;
}
} else if (objField.Name == "Camera" && objJson.contains("Camera")) {
const auto& camJson = objJson["Camera"];
const MetaCoreStructDescriptor* camDesc = registry.FindStruct<MetaCoreCameraComponent>();
if (camDesc) {
MetaCoreCameraComponent cam;
for (const auto& camField : camDesc->Fields) {
if (camField.Name == "FieldOfViewDegrees" && camJson.contains("FieldOfViewDegrees")) cam.FieldOfViewDegrees = camJson["FieldOfViewDegrees"].get<float>();
else if (camField.Name == "NearClip" && camJson.contains("NearClip")) cam.NearClip = camJson["NearClip"].get<float>();
else if (camField.Name == "FarClip" && camJson.contains("FarClip")) cam.FarClip = camJson["FarClip"].get<float>();
else if (camField.Name == "IsPrimary" && camJson.contains("IsPrimary")) cam.IsPrimary = camJson["IsPrimary"].get<bool>();
}
obj.Camera = cam;
}
} else if (objField.Name == "PrefabInstance" && objJson.contains("PrefabInstance")) {
const auto& prefabJson = objJson["PrefabInstance"];
const MetaCoreStructDescriptor* prefabDesc = registry.FindStruct<MetaCorePrefabInstanceMetadata>();
if (prefabDesc) {
MetaCorePrefabInstanceMetadata prefab;
for (const auto& prefabField : prefabDesc->Fields) {
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 == "PrefabInstanceRootId" && prefabJson.contains("PrefabInstanceRootId")) prefab.PrefabInstanceRootId = prefabJson["PrefabInstanceRootId"].get<MetaCoreId>();
}
obj.PrefabInstance = prefab;
}
}
}
sceneDocument.GameObjects.push_back(obj);
}
for (const auto& objJson : sceneJson["GameObjects"]) {
sceneDocument.GameObjects.push_back(JsonToGameObjectData(objJson, registry));
}
}
}
@ -342,4 +350,270 @@ std::optional<MetaCoreSceneDocument> MetaCoreSceneSerializer::LoadSceneFromJson(
}
}
// Prefab
bool MetaCoreSceneSerializer::SavePrefabToJson(
const std::filesystem::path& absolutePath,
const MetaCorePrefabDocument& prefabDocument,
const MetaCoreTypeRegistry& registry
) {
try {
json prefabJson;
prefabJson["PrefabName"] = prefabDocument.Name;
json gameObjectsArray = json::array();
for (const auto& obj : prefabDocument.GameObjects) {
gameObjectsArray.push_back(GameObjectDataToJson(obj, registry));
}
prefabJson["GameObjects"] = gameObjectsArray;
std::ofstream file(absolutePath);
if (!file.is_open()) {
return false;
}
file << prefabJson.dump(4);
return true;
} catch (...) {
return false;
}
}
std::optional<MetaCorePrefabDocument> MetaCoreSceneSerializer::LoadPrefabFromJson(
const std::filesystem::path& absolutePath,
const MetaCoreTypeRegistry& registry
) {
try {
std::ifstream file(absolutePath);
if (!file.is_open()) {
return std::nullopt;
}
json prefabJson;
file >> prefabJson;
MetaCorePrefabDocument doc;
if (prefabJson.contains("PrefabName")) {
doc.Name = prefabJson["PrefabName"].get<std::string>();
}
if (prefabJson.contains("GameObjects") && prefabJson["GameObjects"].is_array()) {
for (const auto& objJson : prefabJson["GameObjects"]) {
doc.GameObjects.push_back(JsonToGameObjectData(objJson, registry));
}
}
return doc;
} catch (...) {
return std::nullopt;
}
}
// Material
bool MetaCoreSceneSerializer::SaveMaterialToJson(
const std::filesystem::path& absolutePath,
const MetaCoreMaterialAssetDocument& doc,
const MetaCoreTypeRegistry& registry
) {
(void)registry;
try {
json matJson;
matJson["AssetGuid"] = GuidToString(doc.AssetGuid);
matJson["Name"] = doc.Name;
matJson["StableImportKey"] = doc.StableImportKey;
matJson["ShaderModel"] = static_cast<int>(doc.ShaderModel);
matJson["BaseColor"] = Vec3ToJson(doc.BaseColor);
matJson["BaseColorTexture"] = GuidToString(doc.BaseColorTexture);
matJson["NormalTexture"] = GuidToString(doc.NormalTexture);
matJson["Metallic"] = doc.Metallic;
matJson["Roughness"] = doc.Roughness;
matJson["MetallicRoughnessTexture"] = GuidToString(doc.MetallicRoughnessTexture);
matJson["AoTexture"] = GuidToString(doc.AoTexture);
matJson["EmissiveColor"] = Vec3ToJson(doc.EmissiveColor);
matJson["EmissiveTexture"] = GuidToString(doc.EmissiveTexture);
matJson["AlphaMode"] = static_cast<int>(doc.AlphaMode);
matJson["AlphaCutoff"] = doc.AlphaCutoff;
matJson["DoubleSided"] = doc.DoubleSided;
std::ofstream file(absolutePath);
if (!file.is_open()) {
return false;
}
file << matJson.dump(4);
return true;
} catch (...) {
return false;
}
}
std::optional<MetaCoreMaterialAssetDocument> MetaCoreSceneSerializer::LoadMaterialFromJson(
const std::filesystem::path& absolutePath,
const MetaCoreTypeRegistry& registry
) {
(void)registry;
try {
std::ifstream file(absolutePath);
if (!file.is_open()) {
return std::nullopt;
}
json matJson;
file >> matJson;
MetaCoreMaterialAssetDocument doc;
if (matJson.contains("AssetGuid")) doc.AssetGuid = StringToGuid(matJson["AssetGuid"].get<std::string>());
if (matJson.contains("Name")) doc.Name = matJson["Name"].get<std::string>();
if (matJson.contains("StableImportKey")) doc.StableImportKey = matJson["StableImportKey"].get<std::string>();
if (matJson.contains("ShaderModel")) doc.ShaderModel = static_cast<MetaCoreMaterialShaderModel>(matJson["ShaderModel"].get<int>());
if (matJson.contains("BaseColor")) doc.BaseColor = JsonToVec3(matJson["BaseColor"]);
if (matJson.contains("BaseColorTexture")) doc.BaseColorTexture = StringToGuid(matJson["BaseColorTexture"].get<std::string>());
if (matJson.contains("NormalTexture")) doc.NormalTexture = StringToGuid(matJson["NormalTexture"].get<std::string>());
if (matJson.contains("Metallic")) doc.Metallic = matJson["Metallic"].get<float>();
if (matJson.contains("Roughness")) doc.Roughness = matJson["Roughness"].get<float>();
if (matJson.contains("MetallicRoughnessTexture")) doc.MetallicRoughnessTexture = StringToGuid(matJson["MetallicRoughnessTexture"].get<std::string>());
if (matJson.contains("AoTexture")) doc.AoTexture = StringToGuid(matJson["AoTexture"].get<std::string>());
if (matJson.contains("EmissiveColor")) doc.EmissiveColor = JsonToVec3(matJson["EmissiveColor"]);
if (matJson.contains("EmissiveTexture")) doc.EmissiveTexture = StringToGuid(matJson["EmissiveTexture"].get<std::string>());
if (matJson.contains("AlphaMode")) doc.AlphaMode = static_cast<MetaCoreMaterialAlphaMode>(matJson["AlphaMode"].get<int>());
if (matJson.contains("AlphaCutoff")) doc.AlphaCutoff = matJson["AlphaCutoff"].get<float>();
if (matJson.contains("DoubleSided")) doc.DoubleSided = matJson["DoubleSided"].get<bool>();
return doc;
} catch (...) {
return std::nullopt;
}
}
// UI 序列化辅助方法
static json RectTransformToJson(const MetaCoreUiRectTransformDocument& trans) {
json j = json::object();
j["AnchorMin"] = Vec3ToJson(trans.AnchorMin);
j["AnchorMax"] = Vec3ToJson(trans.AnchorMax);
j["Pivot"] = Vec3ToJson(trans.Pivot);
j["Position"] = Vec3ToJson(trans.Position);
j["Size"] = Vec3ToJson(trans.Size);
return j;
}
static MetaCoreUiRectTransformDocument JsonToRectTransform(const json& j) {
MetaCoreUiRectTransformDocument trans;
if (j.contains("AnchorMin")) trans.AnchorMin = JsonToVec3(j["AnchorMin"]);
if (j.contains("AnchorMax")) trans.AnchorMax = JsonToVec3(j["AnchorMax"]);
if (j.contains("Pivot")) trans.Pivot = JsonToVec3(j["Pivot"]);
if (j.contains("Position")) trans.Position = JsonToVec3(j["Position"]);
if (j.contains("Size")) trans.Size = JsonToVec3(j["Size"]);
return trans;
}
static json UiStyleToJson(const MetaCoreUiStyleDocument& style) {
json j = json::object();
j["BackgroundColor"] = Vec3ToJson(style.BackgroundColor);
j["TextColor"] = Vec3ToJson(style.TextColor);
j["TintColor"] = Vec3ToJson(style.TintColor);
j["FontSize"] = style.FontSize;
j["Padding"] = Vec3ToJson(style.Padding);
j["HorizontalAlignment"] = static_cast<int>(style.HorizontalAlignment);
j["VerticalAlignment"] = static_cast<int>(style.VerticalAlignment);
j["ImageAssetGuid"] = GuidToString(style.ImageAssetGuid);
j["PreserveAspect"] = style.PreserveAspect;
return j;
}
static MetaCoreUiStyleDocument JsonToUiStyle(const json& j) {
MetaCoreUiStyleDocument style;
if (j.contains("BackgroundColor")) style.BackgroundColor = JsonToVec3(j["BackgroundColor"]);
if (j.contains("TextColor")) style.TextColor = JsonToVec3(j["TextColor"]);
if (j.contains("TintColor")) style.TintColor = JsonToVec3(j["TintColor"]);
if (j.contains("FontSize")) style.FontSize = j["FontSize"].get<float>();
if (j.contains("Padding")) style.Padding = JsonToVec3(j["Padding"]);
if (j.contains("HorizontalAlignment")) style.HorizontalAlignment = static_cast<MetaCoreUiHorizontalAlignment>(j["HorizontalAlignment"].get<int>());
if (j.contains("VerticalAlignment")) style.VerticalAlignment = static_cast<MetaCoreUiVerticalAlignment>(j["VerticalAlignment"].get<int>());
if (j.contains("ImageAssetGuid")) style.ImageAssetGuid = StringToGuid(j["ImageAssetGuid"].get<std::string>());
if (j.contains("PreserveAspect")) style.PreserveAspect = j["PreserveAspect"].get<bool>();
return style;
}
static json UiNodeToJson(const MetaCoreUiNodeDocument& node) {
json j = json::object();
j["Id"] = node.Id;
j["Name"] = node.Name;
j["Type"] = static_cast<int>(node.Type);
j["ParentId"] = node.ParentId;
j["Children"] = node.Children;
j["Visible"] = node.Visible;
j["RectTransform"] = RectTransformToJson(node.RectTransform);
j["Style"] = UiStyleToJson(node.Style);
j["Text"] = node.Text;
j["Interactable"] = node.Interactable;
return j;
}
static MetaCoreUiNodeDocument JsonToUiNode(const json& j) {
MetaCoreUiNodeDocument node;
if (j.contains("Id")) node.Id = j["Id"].get<std::string>();
if (j.contains("Name")) node.Name = j["Name"].get<std::string>();
if (j.contains("Type")) node.Type = static_cast<MetaCoreUiNodeType>(j["Type"].get<int>());
if (j.contains("ParentId")) node.ParentId = j["ParentId"].get<std::string>();
if (j.contains("Children")) node.Children = j["Children"].get<std::vector<std::string>>();
if (j.contains("Visible")) node.Visible = j["Visible"].get<bool>();
if (j.contains("RectTransform")) node.RectTransform = JsonToRectTransform(j["RectTransform"]);
if (j.contains("Style")) node.Style = JsonToUiStyle(j["Style"]);
if (j.contains("Text")) node.Text = j["Text"].get<std::string>();
if (j.contains("Interactable")) node.Interactable = j["Interactable"].get<bool>();
return node;
}
bool MetaCoreSceneSerializer::SaveUiToJson(
const std::filesystem::path& absolutePath,
const MetaCoreUiDocument& uiDocument,
const MetaCoreTypeRegistry& registry
) {
(void)registry;
try {
json uiJson;
uiJson["Name"] = uiDocument.Name;
uiJson["ReferenceWidth"] = uiDocument.ReferenceWidth;
uiJson["ReferenceHeight"] = uiDocument.ReferenceHeight;
uiJson["RootNodeIds"] = uiDocument.RootNodeIds;
json nodesArray = json::array();
for (const auto& node : uiDocument.Nodes) {
nodesArray.push_back(UiNodeToJson(node));
}
uiJson["Nodes"] = nodesArray;
std::ofstream file(absolutePath);
if (!file.is_open()) {
return false;
}
file << uiJson.dump(4);
return true;
} catch (...) {
return false;
}
}
std::optional<MetaCoreUiDocument> MetaCoreSceneSerializer::LoadUiFromJson(
const std::filesystem::path& absolutePath,
const MetaCoreTypeRegistry& registry
) {
(void)registry;
try {
std::ifstream file(absolutePath);
if (!file.is_open()) {
return std::nullopt;
}
json uiJson;
file >> uiJson;
MetaCoreUiDocument doc;
if (uiJson.contains("Name")) doc.Name = uiJson["Name"].get<std::string>();
if (uiJson.contains("ReferenceWidth")) doc.ReferenceWidth = uiJson["ReferenceWidth"].get<std::int32_t>();
if (uiJson.contains("ReferenceHeight")) doc.ReferenceHeight = uiJson["ReferenceHeight"].get<std::int32_t>();
if (uiJson.contains("RootNodeIds")) doc.RootNodeIds = uiJson["RootNodeIds"].get<std::vector<std::string>>();
if (uiJson.contains("Nodes") && uiJson["Nodes"].is_array()) {
for (const auto& nodeJson : uiJson["Nodes"]) {
doc.Nodes.push_back(JsonToUiNode(nodeJson));
}
}
return doc;
} catch (...) {
return std::nullopt;
}
}
} // namespace MetaCore

View File

@ -127,4 +127,12 @@ struct MetaCoreLightComponent {
float Intensity = 1.5F;
};
MC_STRUCT()
struct MetaCoreModelRootTag {
MC_GENERATED_BODY()
MC_PROPERTY()
std::string SourceModelPath;
};
} // namespace MetaCore

View File

@ -127,6 +127,8 @@ struct MetaCoreGameObjectData {
std::optional<MetaCoreLightComponent> Light;
MC_PROPERTY()
std::optional<MetaCorePrefabInstanceMetadata> PrefabInstance;
MC_PROPERTY()
std::optional<MetaCoreModelRootTag> ModelRootTag;
};
} // namespace MetaCore

View File

@ -2,7 +2,10 @@
#include "MetaCoreFoundation/MetaCoreId.h"
#include "MetaCoreFoundation/MetaCoreReflection.h"
#include "MetaCoreFoundation/MetaCoreAssetGuid.h"
#include "MetaCoreFoundation/MetaCoreAssetTypes.h"
#include "MetaCoreScene/MetaCoreGameObject.h"
#include <glm/vec3.hpp>
#include <string>
#include <vector>
@ -37,4 +40,146 @@ struct MetaCoreSceneDocument {
MetaCoreEditorSelectionSnapshot Selection{};
};
MC_STRUCT()
struct MetaCorePrefabDocument {
MC_GENERATED_BODY()
MC_PROPERTY()
std::string Name{};
MC_PROPERTY()
std::vector<MetaCoreGameObjectData> GameObjects{};
};
MC_ENUM()
enum class MetaCoreUiNodeType {
Panel = 0,
Text,
Image,
Button
};
MC_ENUM()
enum class MetaCoreUiHorizontalAlignment {
Left = 0,
Center,
Right,
Stretch
};
MC_ENUM()
enum class MetaCoreUiVerticalAlignment {
Top = 0,
Center,
Bottom,
Stretch
};
MC_STRUCT()
struct MetaCoreUiRectTransformDocument {
MC_GENERATED_BODY()
MC_PROPERTY()
glm::vec3 AnchorMin{0.0F, 0.0F, 0.0F};
MC_PROPERTY()
glm::vec3 AnchorMax{1.0F, 1.0F, 0.0F};
MC_PROPERTY()
glm::vec3 Pivot{0.5F, 0.5F, 0.0F};
MC_PROPERTY()
glm::vec3 Position{0.0F, 0.0F, 0.0F};
MC_PROPERTY()
glm::vec3 Size{100.0F, 100.0F, 0.0F};
};
MC_STRUCT()
struct MetaCoreUiStyleDocument {
MC_GENERATED_BODY()
MC_PROPERTY()
glm::vec3 BackgroundColor{0.15F, 0.15F, 0.15F};
MC_PROPERTY()
glm::vec3 TextColor{1.0F, 1.0F, 1.0F};
MC_PROPERTY()
glm::vec3 TintColor{1.0F, 1.0F, 1.0F};
MC_PROPERTY()
float FontSize = 16.0F;
MC_PROPERTY()
glm::vec3 Padding{8.0F, 8.0F, 0.0F};
MC_PROPERTY()
MetaCoreUiHorizontalAlignment HorizontalAlignment = MetaCoreUiHorizontalAlignment::Left;
MC_PROPERTY()
MetaCoreUiVerticalAlignment VerticalAlignment = MetaCoreUiVerticalAlignment::Top;
MC_PROPERTY()
MetaCoreAssetGuid ImageAssetGuid{};
MC_PROPERTY()
bool PreserveAspect = false;
};
MC_STRUCT()
struct MetaCoreUiNodeDocument {
MC_GENERATED_BODY()
MC_PROPERTY()
std::string Id{};
MC_PROPERTY()
std::string Name{};
MC_PROPERTY()
MetaCoreUiNodeType Type = MetaCoreUiNodeType::Panel;
MC_PROPERTY()
std::string ParentId{};
MC_PROPERTY()
std::vector<std::string> Children{};
MC_PROPERTY()
bool Visible = true;
MC_PROPERTY()
MetaCoreUiRectTransformDocument RectTransform{};
MC_PROPERTY()
MetaCoreUiStyleDocument Style{};
MC_PROPERTY()
std::string Text{};
MC_PROPERTY()
bool Interactable = false;
};
MC_STRUCT()
struct MetaCoreUiDocument {
MC_GENERATED_BODY()
MC_PROPERTY()
std::string Name{};
MC_PROPERTY()
std::int32_t ReferenceWidth = 1920;
MC_PROPERTY()
std::int32_t ReferenceHeight = 1080;
MC_PROPERTY()
std::vector<std::string> RootNodeIds{};
MC_PROPERTY()
std::vector<MetaCoreUiNodeDocument> Nodes{};
};
} // namespace MetaCore

View File

@ -1,6 +1,8 @@
#pragma once
#include "MetaCoreScene/MetaCoreScene.h"
#include "MetaCoreScene/MetaCoreSceneDocument.h"
#include "MetaCoreFoundation/MetaCoreAssetTypes.h"
#include <filesystem>
#include <optional>
@ -22,6 +24,39 @@ public:
const std::filesystem::path& absolutePath,
const MetaCoreTypeRegistry& registry
);
// Prefab
static bool SavePrefabToJson(
const std::filesystem::path& absolutePath,
const MetaCorePrefabDocument& prefabDocument,
const MetaCoreTypeRegistry& registry
);
static std::optional<MetaCorePrefabDocument> LoadPrefabFromJson(
const std::filesystem::path& absolutePath,
const MetaCoreTypeRegistry& registry
);
// Material
static bool SaveMaterialToJson(
const std::filesystem::path& absolutePath,
const MetaCoreMaterialAssetDocument& materialDocument,
const MetaCoreTypeRegistry& registry
);
static std::optional<MetaCoreMaterialAssetDocument> LoadMaterialFromJson(
const std::filesystem::path& absolutePath,
const MetaCoreTypeRegistry& registry
);
// UI
static bool SaveUiToJson(
const std::filesystem::path& absolutePath,
const MetaCoreUiDocument& uiDocument,
const MetaCoreTypeRegistry& registry
);
static std::optional<MetaCoreUiDocument> LoadUiFromJson(
const std::filesystem::path& absolutePath,
const MetaCoreTypeRegistry& registry
);
};
} // namespace MetaCore

View File

@ -1,138 +0,0 @@
set(METACORE_PANDA3D_VERSION "1.10.16" CACHE STRING "Pinned Panda3D SDK version used by MetaCore")
set(METACORE_PANDA3D_ARCH "x64" CACHE STRING "Pinned Panda3D Windows architecture used by MetaCore")
set(METACORE_PANDA3D_LOCAL_ROOT "${CMAKE_SOURCE_DIR}/.metacore/deps/panda3d/${METACORE_PANDA3D_VERSION}-${METACORE_PANDA3D_ARCH}" CACHE PATH "Local Panda3D SDK cache directory")
function(metacore_prepare_panda3d)
if(WIN32)
if(DEFINED ENV{PANDA3D_ROOT} AND NOT "$ENV{PANDA3D_ROOT}" STREQUAL "")
set(_metacore_panda3d_root "$ENV{PANDA3D_ROOT}")
elseif(DEFINED PANDA3D_ROOT AND NOT "${PANDA3D_ROOT}" STREQUAL "")
set(_metacore_panda3d_root "${PANDA3D_ROOT}")
else()
set(_metacore_panda3d_root "${METACORE_PANDA3D_LOCAL_ROOT}")
endif()
file(TO_CMAKE_PATH "${_metacore_panda3d_root}" _metacore_panda3d_root)
if(NOT EXISTS "${_metacore_panda3d_root}/include/pandaFramework.h")
if(METACORE_AUTO_PREPARE_PANDA3D)
message(STATUS "MetaCore will prepare Panda3D SDK into ${_metacore_panda3d_root}")
execute_process(
COMMAND
powershell
-NoProfile
-ExecutionPolicy Bypass
-File "${CMAKE_SOURCE_DIR}/scripts/PrepareMetaCorePanda3D.ps1"
-Version "${METACORE_PANDA3D_VERSION}"
-Architecture "${METACORE_PANDA3D_ARCH}"
-InstallDir "${_metacore_panda3d_root}"
RESULT_VARIABLE _metacore_prepare_result
)
if(NOT _metacore_prepare_result EQUAL 0)
message(FATAL_ERROR "MetaCore failed to prepare Panda3D SDK. Set PANDA3D_ROOT manually or inspect scripts/PrepareMetaCorePanda3D.ps1 output.")
endif()
else()
message(FATAL_ERROR "Panda3D SDK was not found. Set PANDA3D_ROOT or enable METACORE_AUTO_PREPARE_PANDA3D.")
endif()
endif()
set(METACORE_PANDA3D_ROOT "${_metacore_panda3d_root}" CACHE PATH "Resolved Panda3D SDK root used by MetaCore" FORCE)
set(METACORE_PANDA3D_INCLUDE_DIR "${METACORE_PANDA3D_ROOT}/include" CACHE PATH "Resolved Panda3D include directory" FORCE)
set(METACORE_PANDA3D_LIB_DIR "${METACORE_PANDA3D_ROOT}/lib" CACHE PATH "Resolved Panda3D library directory" FORCE)
set(METACORE_PANDA3D_BIN_DIR "${METACORE_PANDA3D_ROOT}/bin" CACHE PATH "Resolved Panda3D runtime directory" FORCE)
set(METACORE_PANDA3D_ETC_DIR "${METACORE_PANDA3D_ROOT}/etc" CACHE PATH "Resolved Panda3D config directory" FORCE)
set(METACORE_PANDA3D_MODELS_DIR "${METACORE_PANDA3D_ROOT}/models" CACHE PATH "Resolved Panda3D models directory" FORCE)
set(METACORE_PANDA3D_PLUGINS_DIR "${METACORE_PANDA3D_ROOT}/plugins" CACHE PATH "Resolved Panda3D plugins directory" FORCE)
set(_metacore_panda3d_libraries
"${METACORE_PANDA3D_LIB_DIR}/libp3framework.lib"
"${METACORE_PANDA3D_LIB_DIR}/libpanda.lib"
"${METACORE_PANDA3D_LIB_DIR}/libpandafx.lib"
"${METACORE_PANDA3D_LIB_DIR}/libpandaexpress.lib"
"${METACORE_PANDA3D_LIB_DIR}/libpandagl.lib"
"${METACORE_PANDA3D_LIB_DIR}/libp3windisplay.lib"
"${METACORE_PANDA3D_LIB_DIR}/libp3dtool.lib"
"${METACORE_PANDA3D_LIB_DIR}/libp3dtoolconfig.lib"
opengl32
gdi32
user32
shell32
advapi32
ws2_32
winmm
)
add_library(MetaCorePanda3D::SDK INTERFACE IMPORTED GLOBAL)
set_target_properties(MetaCorePanda3D::SDK PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${METACORE_PANDA3D_INCLUDE_DIR}"
INTERFACE_LINK_LIBRARIES "${_metacore_panda3d_libraries}"
)
file(GLOB METACORE_PANDA3D_RUNTIME_DLLS "${METACORE_PANDA3D_BIN_DIR}/*.dll")
set(METACORE_PANDA3D_RUNTIME_DLLS "${METACORE_PANDA3D_RUNTIME_DLLS}" CACHE INTERNAL "Resolved Panda3D runtime DLL list")
else()
message(FATAL_ERROR "MetaCore Panda3D V1 currently supports Windows only.")
endif()
endfunction()
function(metacore_stage_panda3d_runtime target_name)
if(NOT TARGET ${target_name})
message(FATAL_ERROR "Target ${target_name} does not exist.")
endif()
foreach(_metacore_runtime_dll IN LISTS METACORE_PANDA3D_RUNTIME_DLLS)
add_custom_command(TARGET ${target_name} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${_metacore_runtime_dll}"
"$<TARGET_FILE_DIR:${target_name}>"
)
endforeach()
if(EXISTS "${METACORE_PANDA3D_ETC_DIR}")
add_custom_command(TARGET ${target_name} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${METACORE_PANDA3D_ETC_DIR}"
"$<TARGET_FILE_DIR:${target_name}>/etc"
)
endif()
if(EXISTS "${METACORE_PANDA3D_MODELS_DIR}")
add_custom_command(TARGET ${target_name} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${METACORE_PANDA3D_MODELS_DIR}"
"$<TARGET_FILE_DIR:${target_name}>/models"
)
endif()
if(EXISTS "${METACORE_PANDA3D_PLUGINS_DIR}")
add_custom_command(TARGET ${target_name} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${METACORE_PANDA3D_PLUGINS_DIR}"
"$<TARGET_FILE_DIR:${target_name}>/plugins"
)
endif()
endfunction()
function(metacore_stage_simplepbr_runtime target_name)
if(NOT TARGET ${target_name})
message(FATAL_ERROR "Target ${target_name} does not exist.")
endif()
set(_metacore_simplepbr_root "${CMAKE_SOURCE_DIR}/third_party/simplepbr-shaders")
if(EXISTS "${_metacore_simplepbr_root}/shaders")
add_custom_command(TARGET ${target_name} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${_metacore_simplepbr_root}/shaders"
"$<TARGET_FILE_DIR:${target_name}>/simplepbr/shaders"
)
endif()
if(EXISTS "${_metacore_simplepbr_root}/textures")
add_custom_command(TARGET ${target_name} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${_metacore_simplepbr_root}/textures"
"$<TARGET_FILE_DIR:${target_name}>/simplepbr/textures"
)
endif()
endfunction()

View File

@ -2,9 +2,27 @@
这份索引用于帮助快速找到第一阶段执行所需的核心文档。
## 当前主线
当前 MetaCore 的技术路线已经收敛为:
- 底层架构:`EnTT` ECS外层提供 `GameObject + Component` 编辑器语义。
- 编辑器界面:`Dear ImGui`。
- 渲染核心:`Google Filament`Panda3D 仅作为历史兼容或待清理上下文。
- 运行时 UI`RmlUi`,定位类似 Unity UGUI。
- 模型导入:当前优先 `glTF / GLB / gltfio`
- 资产策略:编辑期 JSON / 可读元数据,发布期 Cook / 二进制资产。
- 交付目标:编辑器面向 Windows、Linux、统信、麒麟运行时面向 C/S并为 B/S / WebGPU 预留。
建议先阅读:
- [metacore-current-architecture.md](D:/MetaCore/docs/designs/metacore-current-architecture.md)
- [metacore-product-plan.md](D:/MetaCore/docs/designs/metacore-product-plan.md)
## 推荐阅读顺序
1. 产品与范围
- [metacore-current-architecture.md](D:/MetaCore/docs/designs/metacore-current-architecture.md)
- [metacore-product-plan.md](D:/MetaCore/docs/designs/metacore-product-plan.md)
- [metacore-phase1-scope.md](D:/MetaCore/docs/designs/metacore-phase1-scope.md)

View File

@ -0,0 +1,185 @@
# MetaCore 当前架构与技术路线
更新时间2026-05-20
状态:当前主线说明
读者:产品、架构、引擎、编辑器、渲染、工具链、交付
## 一句话定位
MetaCore 是一个以 EnTT ECS 为底层架构、Filament 为现代渲染核心、ImGui 为编辑器界面、RmlUi 为运行时 UI 的轻量级 Unity / Unreal Engine 对标型 3D 引擎与编辑器,采用编辑期 JSON、发布期二进制 Cook 的敏捷迭代路线,面向工业仿真、数字孪生、实时可视化和跨平台运行时交付。
## 当前技术选型
| 能力域 | 当前选择 | 定位 |
| --- | --- | --- |
| 底层架构 | EnTT | ECS 数据层,承载实体、组件和场景状态 |
| 编辑器对象语义 | GameObject + Component | 面向编辑器和用户工作流的 ECS 封装 |
| 编辑器 UI | Dear ImGui Docking | 快速构建工具面板、Inspector、Hierarchy、Project、Console |
| 3D 操控 | ImGuizmo | 编辑器视口中的移动、旋转、缩放工具 |
| 渲染核心 | Google Filament | PBR、材质、灯光、glTF、跨图形 API 渲染后端 |
| 模型导入 | glTF / GLB / gltfio | 当前模型导入主线,后续扩展其他格式 |
| 运行时 UI | RmlUi | 项目正式 UI定位类似 Unity UGUI |
| 编辑期资产 | JSON / mcmeta / 可读项目文件 | 快速迭代、可 diff、可调试、可人工修复 |
| 发布期资产 | Cooked binary / package | 面向运行时性能、封装和交付稳定性 |
| RuntimeData | TCP / 文件回放 / 数据绑定 | 外部仿真、遥测、业务数据驱动场景组件 |
## 渲染路线
MetaCore 已经从 Panda3D 迁移到 Filament 主线。Panda3D 相关代码只应被视为历史兼容层或待清理实现,不再作为新增能力的扩展方向。
Filament 作为主渲染核心,承担:
- PBR 材质与光照。
- glTF / GLB 加载与资源解释。
- 编辑器视口离屏渲染。
- 运行时 Player 场景渲染。
- Vulkan、OpenGL、DirectX 12、WebGPU 等后端能力的长期承接。
优先级建议:
1. Vulkan首选高性能现代图形 API。
2. OpenGL兼容和过渡。
3. DirectX 12Windows 高性能运行时。
4. WebGPUB/S 架构、浏览器嵌入和 Web 交付。
## UI 路线
MetaCore 明确区分两套 UI
### 编辑器 UI
编辑器 UI 使用 Dear ImGui。它服务于工具工作流
- Hierarchy
- Scene
- Inspector
- Project
- Console
- RuntimeData 面板
- 导入、Cook、诊断等工具窗口
ImGui 不承担最终项目运行时 UI 的长期职责。
### 运行时 UI
运行时 UI 使用 RmlUi。它服务于项目交付
- 标题栏、侧边栏、设备信息面板。
- 状态文本、告警提示、按钮、图片和图标。
- 与场景对象、RuntimeData 和业务状态绑定。
- 后续在编辑器中提供可视化编辑,再由 RmlUi 在 Player 中渲染。
这条路线类似 Unity 中“编辑器工具 UI”和“UGUI 项目 UI”的分工。
## 资产与序列化路线
当前阶段采用双阶段资产策略:
### 编辑期
编辑器阶段优先使用 JSON 和可读元数据:
- `MetaCore.project.json`
- `.mcscene.json` 或可读场景文档
- `.mcmeta`
- JSON 化材质、UI、导入描述和资源记录
目标是:
- 快速迭代。
- 便于 diff 和代码评审。
- 便于调试和人工修复。
- 降低早期功能演进的成本。
### 发布期
打包阶段再生成二进制运行时资产:
- cooked scene
- cooked mesh / material / texture
- runtime config
- CookManifest
- 可交付 Player 包
目标是:
- 提高加载性能。
- 减少运行时解析成本。
- 封装交付内容。
- 支持 C/S 和未来 B/S 发布形态。
## 平台与交付目标
### 编辑器平台
编辑器优先面向:
- Windows
- Linux
- 统信 UOS
- 麒麟 OS
第一阶段可以 Windows 先行,但平台抽象必须为国产化桌面环境保留边界。
### 运行时交付
运行时面向两种架构:
- C/S第一阶段主要承诺适合桌面端工业仿真、数字孪生、大屏和本地部署。
- B/S后续产品化方向适合 WebGPU、浏览器嵌入、第三方业务系统集成。
## 行业对标
MetaCore 对标 Unity 和 Unreal Engine但不是第一阶段复制它们的全部功能广度。
对标 Unity
- GameObject / Component 工作流。
- Scene / Hierarchy / Inspector / Project。
- Prefab。
- UGUI 类运行时 UI。
- 资产导入与可视化编辑体验。
对标 Unreal Engine
- 高质量渲染目标。
- 材质和资源工程化。
- 大型项目交付、Cook、Package、运行时边界。
MetaCore 的差异化方向:
- 更轻量。
- 更容易国产化适配。
- 更适合工业仿真、数字孪生和业务系统嵌入。
- 更强调编辑期可读、运行期可交付的资产闭环。
## 开发方法
MetaCore 当前采用敏捷开发、快速迭代的方式:
- 先打通主链路,再逐步工程化。
- 先让编辑器可用,再逐步完善运行时性能。
- 先建立 JSON 可读资产闭环,再收敛 Cook 二进制发布链路。
- 先服务真实交付场景,再扩展通用引擎能力。
第一阶段的核心不是一次性做完整引擎,而是快速形成:
```text
创建项目
-> 导入模型
-> 编辑场景
-> 配置材质 / UI / 数据绑定
-> 保存 JSON 编辑期资产
-> Cook 运行时资产
-> Player 运行
-> 交付 C/S 应用
```
## 当前约束
- 新渲染能力默认走 Filament不再沿 Panda3D 扩展。
- 编辑器工具 UI 默认走 ImGui。
- 项目运行时 UI 默认走 RmlUi。
- 模型导入优先完善 glTF / GLB / gltfio 闭环。
- 资产格式优先保证编辑期 JSON 可读性,再进入发布期二进制优化。
- 测试应区分“编辑期 JSON 资产链路”和“发布期 Cook 二进制链路”,避免用二进制产物是否存在来阻塞早期编辑器迭代。

View File

@ -14,6 +14,19 @@ MetaCore 的定位是:
- 第一阶段先完成可支撑数字孪生项目开发的能力闭环
- 但所有优先建设的能力都应尽量沉淀为通用引擎能力,而不是行业特化逻辑
## 当前架构基线
当前仓库的引擎基线应按下列主线理解:
- `EnTT` 是底层 ECS 数据架构。
- `GameObject + Component` 是面向编辑器和用户工作流的对象语义层。
- `Dear ImGui` 是编辑器 UI 技术栈。
- `Google Filament` 是主渲染后端Panda3D 相关实现只作为历史上下文或待清理兼容层。
- `RmlUi` 是运行时 UI 的规划主线,负责正式项目 UI 渲染。
- `glTF / GLB / gltfio` 是当前模型导入主线。
- 编辑期资产以 JSON / `.mcmeta` / 可读项目文件为优先,发布期通过 Cook 生成二进制运行时资产。
- 第一阶段以 C/S 交付为主,同时为 B/S / WebGPU 交付保留架构边界。
这份文档回答三个问题:
1. MetaCore 作为引擎,功能清单应该怎么分层
@ -380,7 +393,7 @@ MetaCore 当前应按下列 12 个引擎能力域来规划:
`已具备`
- Panda3D 宿主和渲染桥
- Filament 渲染桥和编辑器离屏视口链路
- 编辑器视口渲染
- 基础场景显示
- 摄像机、网格、灯光、调试叠加
@ -389,12 +402,13 @@ MetaCore 当前应按下列 12 个引擎能力域来规划:
`部分具备`
- 渲染抽象已经存在
- glTF / GLB 场景节点与 Filament 渲染对象同步已经形成基础链路
- 视口交互体验已经能支持基本编辑工作
`薄弱或缺失`
- 完整材质系统
- Shader 资源与管线
- Filament 材质资源与 MetaCore 材质资源之间的稳定映射
- 稳定 PBR 工作流
- 阴影质量和配置
- 环境光与天空盒体系

View File

@ -227,7 +227,7 @@
- 重新打开
- 启动场景加载
对这个仓库来说,二进制 package 持久化是正确方向
对这个仓库来说,编辑期 JSON 持久化与发布期二进制 Cook 应同时存在:编辑器优先保证场景可读、可 diff、可快速修复运行时通过 Cook 生成稳定二进制 package。
### 8. UI 工作流

View File

@ -37,7 +37,8 @@ MetaCore 第一阶段首批真正应做成生产级闭环的模型导入格式
第一阶段 MetaCore 已经确定:
- 材质层先走基础 PBR
- shader 后端先接 `panda3d-simplepbr`
- 渲染后端主线先接 `Google Filament`
- 模型加载和 glTF 语义优先使用 `gltfio`
在这个前提下,`glTF/.glb` 的材质语义更接近第一阶段目标:

View File

@ -11,7 +11,7 @@
- 模型导入后,材质信息如何进入 MetaCore
- MetaCore 内部材质资源应该长什么样
- 场景对象如何引用材质
- 第一阶段如何接入 `panda3d-simplepbr`
- 第一阶段如何接入 `Google Filament`
如果这条链不先设计清楚后面模型导入、Inspector 编辑、场景保存、Player 渲染都会各做各的,最终一定返工。
@ -19,14 +19,14 @@
MetaCore 第一阶段应采用下面这条链路:
**源模型文件 -> 导入文档 -> 网格资源 / 材质资源 -> 场景对象 MeshRenderer -> simplepbr 参数映射 -> Player 渲染**
**源模型文件 -> 导入文档 -> 网格资源 / 材质资源 -> 场景对象 MeshRenderer -> Filament 材质映射 -> Player 渲染**
这里必须明确两件事:
1. 模型导入负责“生成 MetaCore 自己的资源”
2. 渲染后端只负责“消费 MetaCore 材质资源”
不能让模型文件直接决定运行时材质结构,也不能让 `simplepbr` 反向决定 MetaCore 材质资源长什么样。
不能让模型文件直接决定运行时材质结构,也不能让 Filament 反向决定 MetaCore 材质资源长什么样。
## 当前代码库现状
@ -46,7 +46,7 @@ MetaCore 第一阶段应采用下面这条链路:
- 模型导入后的材质槽映射定义
- 场景对象对材质资源的引用方式
- 材质 Inspector 与资源编辑流程
- 材质到 `simplepbr` 的统一映射层
- 材质到 Filament `MaterialInstance` 的统一映射层
因此当前最合理的做法不是直接继续加渲染效果,而是先把资源和组件关系定清楚。
@ -61,7 +61,7 @@ flowchart LR
D --> F["场景对象 MeshRenderer"]
E --> F
F --> G["MetaCore 渲染抽象层"]
G --> H["simplepbr 参数映射"]
G --> H["Filament 材质映射"]
H --> I["Player 渲染输出"]
```
@ -142,7 +142,7 @@ flowchart LR
- AlphaCutoff
- DoubleSided
第一阶段不要求复杂 shader graph但必须保证这个资源结构已经是 MetaCore 自己的,不是 `simplepbr` 参数字典。
第一阶段不要求复杂 shader graph但必须保证这个资源结构已经是 MetaCore 自己的,不是 Filament 参数字典。
### Texture Asset
@ -268,15 +268,15 @@ flowchart LR
不要把“对象引用”和“材质内容”全部混在同一个对象 Inspector 里。
## 与 simplepbr 的衔接方式
## 与 Filament 的衔接方式
第一阶段 `simplepbr` 应只出现在渲染映射层。
第一阶段 Filament 应只出现在渲染映射层和运行时渲染层。
也就是:
- `MetaCoreMaterialAsset`
-> `MetaCoreRenderMaterialParams`
-> Panda3D / `simplepbr`
-> Filament `MaterialInstance`
### 第一阶段最小映射
@ -293,7 +293,7 @@ flowchart LR
### 明确不要做
第一阶段不要把 `simplepbr` 的内部参数名、调用方式、配置细节直接暴露到:
第一阶段不要把 Filament 的内部参数名、调用方式、配置细节直接暴露到:
- 材质资源格式
- Editor Inspector
@ -381,7 +381,7 @@ flowchart LR
4. 先打通 `glTF/.glb -> Mesh/Material Asset`
5. 打通对象级 Mesh/Material 指派
6. 打通材质资源 Inspector
7. 建立 `Material -> simplepbr` 映射层
7. 建立 `Material -> Filament` 映射层
8. 验证 Editor 与 Player 一致性
9. 再考虑 `FBX` 与重导入增强
@ -410,6 +410,6 @@ flowchart LR
而是:
**先建立 MetaCore 自己的 Mesh/Material 资源体系,再把模型导入和 `simplepbr` 渲染后端挂到这套资源体系两端。**
**先建立 MetaCore 自己的 Mesh/Material 资源体系,再把模型导入和 Filament 渲染后端挂到这套资源体系两端。**
这是让 MetaCore 真正形成“模型导入 -> 材质编辑 -> 场景搭建 -> Player 渲染”闭环的关键一步。

View File

@ -54,7 +54,7 @@
- 能在 Scene 中稳定保存引用
- 能被 Editor Inspector 编辑
- 能被 Cook 和 Package 依赖分析
- 能接入 `simplepbr`
- 能接入 Filament
- 不锁死未来类 URP 的演进空间
## 资源与组件的关系
@ -261,7 +261,7 @@ AO 贴图资源引用。
- 导入校验
- Inspector 提示
- simplepbr 映射
- Filament 映射
## MeshRenderer 组件演进设计
@ -446,7 +446,7 @@ Cook 阶段应能够从 Scene / Prefab 中追踪依赖:
5. 更新 Scene / Prefab 序列化
6. 更新 Inspector
7. 再接模型导入与材质映射
8. 最后接 `simplepbr` 参数映射
8. 最后接 Filament 材质映射
## 第一阶段 Definition of Done
@ -466,7 +466,7 @@ Cook 阶段应能够从 Scene / Prefab 中追踪依赖:
所以最合理的方向是:
**把 Mesh、Material、Texture 正式变成 MetaCore 资源,把 MeshRenderer 正式变成资源引用组件再让导入、Inspector、Cook 和 simplepbr 全部围绕这套结构协作。**
**把 Mesh、Material、Texture 正式变成 MetaCore 资源,把 MeshRenderer 正式变成资源引用组件再让导入、Inspector、Cook 和 Filament 全部围绕这套结构协作。**
这一步一旦定稳,后续:

View File

@ -1,84 +1,88 @@
# MetaCore 材质与渲染管线选型说明
# MetaCore 材质与渲染管线选型说明
生成时间2026-03-28
状态:草案
范围M3 场景编辑、材质与光照工作流
生成时间2026-03-28
更新时间2026-05-20
状态:当前主线更新
范围材质、模型导入、Filament 渲染与发布管线
## 目的
这份文档用于明确 MetaCore 第一阶段材质与渲染管线的选型策略
这份文档用于明确 MetaCore 当前材质与渲染管线的选型策略。早期文档曾以 Panda3D / simplepbr 作为第一阶段 PBR 起点;当前工程主线已经迁移到 Google Filament因此后续渲染、材质、glTF 和运行时视觉能力应围绕 Filament 展开
目标不是一步到位做出完整的 Unity URP 等价物,而是:
目标不是一步到位做出完整 Unity URP 或 Unreal 渲染管线,而是:
- 第一阶段先把基础 PBR 材质工作流跑通
- 让材质、灯光、模型和场景搭建形成稳定闭环
- 在不锁死未来架构的前提下,借力现有可行方案
- 先打通 glTF / GLB -> MetaCore 资产 -> ECS 场景 -> Filament 渲染的主链路。
- 让模型、材质、贴图、灯光和场景保存形成稳定闭环。
- 保持 MetaCore 自己的材质资源模型,不让 Filament 反向决定编辑器资产结构。
- 为 Vulkan、OpenGL、DirectX 12、WebGPU 等后端能力预留空间。
## 结论先说
**第一阶段使用 `panda3d-simplepbr` 作为基础 PBR Shader 实现,是正确且务实的选择。**
**MetaCore 当前第一渲染主线是 Google Filament。**
但这必须满足一个前提:
**MetaCore 自己定义材质系统,`simplepbr` 只是第一版渲染后端实现,不是最终渲染架构。**
**MetaCore 自己定义材质系统,Filament 只是当前主渲染后端和材质落地目标,不是编辑期资产结构本身。**
## 为什么这个选型合理
换句话说:
对于第一阶段来说,你当前最需要的不是一整套宏大的渲染管线,而是:
```text
MetaCore Material Asset
-> MetaCore Render Material Description
-> Filament Material / MaterialInstance
-> Vulkan / OpenGL / DirectX 12 / WebGPU 后端
```
- 模型导入后能有像样的材质表现
- 场景中灯光表现稳定
- 编辑器和运行时对材质的理解尽量一致
- 工程团队能尽快把材质工作流做起来
## 为什么选择 Filament
`panda3d-simplepbr` 非常适合作为这个阶段的起点,因为它解决的是
Filament 比早期 Panda3D/simplepbr 路线更适合作为 MetaCore 的长期渲染核心:
- 基础 PBR 材质
- 基础灯光
- 贴图和阴影的第一版工程问题
- 原生面向现代 PBR。
- glTF / GLB 与材质语义更贴近当前资产导入主线。
- 支持成熟的光照、IBL、色彩管理和材质系统。
- 具备多后端能力,可承接 Vulkan、OpenGL、DirectX 12、WebGPU 等方向。
- 更适合后续做工业仿真、数字孪生和高质量实时可视化。
这正好对应 MetaCore 当前的 M3 目标。
## 分层原则
## 为什么它不能作为最终方案
MetaCore 的长期目标不是“接一个现成 shader 包就结束”,而是:
- 拥有自己的材质资源模型
- 拥有自己的渲染配置抽象
- 逐步演进为类 Unity URP 的可控渲染管线
因此必须把两层分清楚:
### 第一层MetaCore 材质系统
### 1. MetaCore 材质系统
由 MetaCore 自己定义:
- 材质资源结构
- 参数命名与参数槽位
- 贴图槽定义
- 默认材质类型
- 材质序列化与资源管理
- 材质资源结构。
- 参数命名与参数槽位。
- 贴图槽定义。
- 默认材质类型。
- 材质序列化、版本兼容和资源管理。
- 编辑器 Inspector 和资产引用方式。
### 第二层:第一版 shader / render backend
这层解决“MetaCore 材质是什么”。
当前先借助:
### 2. Filament 映射层
- `panda3d-simplepbr`
由渲染桥负责:
以后可以逐步替换为:
- 把 MetaCore 材质参数映射到 Filament `MaterialInstance`
- 把 glTF / gltfio 解析出的材质关系落到 MetaCore 材质资源。
- 处理贴图加载、采样器、颜色空间、粗糙度金属度、法线、透明模式等细节。
- 处理编辑器视口和 Player 运行时的一致性。
- MetaCore 自己的 render pipeline
- 更像 Unity URP 的 pass / feature / renderer 组织
这层解决“MetaCore 材质如何在 Filament 中显示”。
## 第一阶段推荐落地方式
### 3. 图形 API 后端
### 1. 先定义 MetaCore 自己的材质资源模型
具体后端由 Filament 承接:
不要先把材质资源直接设计成 `simplepbr` 参数表。
- Vulkan首选高性能目标。
- OpenGL兼容和过渡。
- DirectX 12Windows 高性能目标。
- WebGPU未来 B/S 和浏览器嵌入目标。
第一阶段就应当有 MetaCore 自己的材质抽象层。
MetaCore 不应在第一阶段直接把业务逻辑写死到某个底层图形 API
最低应包含:
## 第一阶段材质资源模型
第一阶段最低应包含:
- `MaterialType`
- `BaseColor`
@ -92,134 +96,105 @@ MetaCore 的长期目标不是“接一个现成 shader 包就结束”,而是
- `EmissiveTexture`
- `DoubleSided`
- `AlphaMode`
- `AlphaCutoff`
层定义的是“MetaCore 材质是什么”不是“simplepbr 怎么用”
些字段属于 MetaCore 材质语义,不属于 Filament 内部实现细节
### 2. simplepbr 只做第一版 shader 映射
## 与 glTF / gltfio 的关系
也就是
当前模型导入主线是 glTF / GLB / gltfio。导入链路应尽量保持
- MetaCore 材质参数
-> 映射到 Panda3D / simplepbr 需要的参数
```text
glTF / GLB
-> gltfio / importer
-> MetaCore Model Import Document
-> Mesh / Material / Texture 资源
-> Scene MeshRenderer 引用
-> Filament 渲染
```
这样后面如果你替换渲染后端:
导入器可以读取 glTF 的 PBR 语义,但最终应写入 MetaCore 自己的材质资源,而不是让场景对象直接绑定 gltfio 临时对象。
- 资源不需要全改
- 编辑器也不需要推翻
## 编辑期与发布期
### 3. 优先保证编辑器和运行时一致性
### 编辑期
第一阶段不要追求特效广度,先保证
编辑器阶段优先使用 JSON 和可读元数据
- 编辑器里看到的材质结果
- Player 里看到的材质结果
- 材质资源可读。
- 贴图引用可检查。
- MeshRenderer 的资源引用可 diff。
- 导入结果可调试、可重导入、可人工修复。
尽量一致。
### 发布期
对工业项目来说,这比“先上很多高级效果”更重要。
Cook 阶段再生成运行时二进制内容:
## 第一阶段材质系统必须支持的能力
- cooked material
- cooked texture
- cooked mesh
- cooked scene
- CookManifest
Cook 结果服务运行时加载性能,不应阻塞编辑器阶段的快速迭代。
## 第一阶段必须支持
### P0
- 基础 PBR 材质资源
- BaseColor
- BaseColorTexture
- NormalTexture
- Metallic / Roughness
- Emissive
- 材质资源保存与加载
- 材质复用
- 材质在场景对象上的指派
- 基础 PBR 材质资源。
- BaseColor / Metallic / Roughness / Normal / Emissive。
- glTF / GLB 材质导入。
- MeshRenderer 到 Mesh / Material / Texture 的资源引用。
- Filament 视口与 Player 运行时材质表现一致。
- 编辑期 JSON 资源保存与加载。
- Cook 阶段能追踪材质和贴图依赖。
### P1
- AO 贴图
- Alpha 模式
- 双面材质
- 基础材质实例工作流
- AO 贴图。
- Alpha Mask / Blend。
- 双面材质。
- IBL / 环境光配置。
- 材质预览。
- 材质实例与覆盖参数。
### P2
- 更高级的渲染 feature
- 后处理
- 特效扩展
- 更完整的 shadow / transparency 组织
- Shader Graph 或节点材质。
- 自定义 Filament 材质模板。
- 后处理资源化。
- 平台化材质裁剪和质量档位。
## 第一阶段光照建议
## 明确不做
基于当前阶段,先把这几个做稳
第一阶段不做
- Directional Light
- Point Light
- Spot Light
- 基础阴影
- 基础环境光 / IBL
- 完整 URP 等价物。
- 完整 Unreal 材质编辑器等价物。
- 大量自定义 shader feature。
- 复杂后处理栈。
- 多渲染管线并行维护。
重点不是参数数量,而是:
第一阶段的目标是让模型和材质在编辑器、保存加载、Player 和 Cook 中形成可信闭环。
- 稳定
- 一致
- 可编辑
- 可序列化
## 成功标准
## 与 M3 的关系
第一阶段材质与渲染链路成立的标准:
这个选型属于:
1. 导入一个 glTF / GLB 模型后Mesh、Material、Texture 关系能进入 MetaCore 资产系统。
2. 拖入场景后Scene 中的 MeshRenderer 通过资源引用表达模型和材质。
3. 保存、关闭、重新打开项目后,材质表现保持一致。
4. 编辑器视口和 Player 中的 Filament 渲染结果一致到可接受程度。
5. Cook 能追踪材质与贴图依赖,并生成运行时可消费的二进制资产。
6. 材质资源本身不绑定 Filament 内部对象生命周期,可以在未来演进映射层。
**M3 场景编辑、材质与光照工作流**
## 设计底线
它的作用是让 MetaCore 在这一阶段具备真正的材质生产力,而不是继续停留在“对象能显示颜色”的水平。
MetaCore 自己拥有材质系统Filament 是当前主渲染后端
所以这条线的优先级
不要把关系写反
- 明显高于继续扩 RuntimeData
- 明显高于继续加更多行业 adapter
- 应该和模型导入、层级编辑并列为引擎基础主线
## 推荐实现顺序
建议按下面顺序推进:
1. 定义 MetaCore 材质资源结构
2. 建立材质资源持久化
3. 建立材质到 simplepbr 参数的映射
4. 打通编辑器材质编辑入口
5. 打通场景对象材质指派
6. 验证编辑器与 Player 一致性
7. 再考虑材质实例、更多贴图槽和更丰富 feature
## 第一阶段明确不要做的事
这些都属于以后,不应在当前阶段过早展开:
- 完整的类 URP 自定义 render feature 体系
- 大而全的后处理系统
- 复杂透明与特效管线
- 完整 shader graph
- 面向所有项目类型的通用渲染框架
## 长期方向
长期方向应该明确为:
**MetaCore 第一阶段借助 `panda3d-simplepbr` 跑通基础 PBR 材质工作流,后续逐步演进为 MetaCore 自己的类 URP 渲染管线。**
这句话同时保证了:
- 当前可执行
- 中期可演进
- 长期不被第三方实现细节绑死
## 最终建议
当前最好的策略不是:
- 直接做 URP
- 也不是完全依附 `simplepbr`
而是:
**MetaCore 自己拥有材质系统,第一版渲染后端借力 `panda3d-simplepbr`。**
这是第一阶段最合理、风险最低、推进速度最快的选型。
- 不是“Filament 材质参数直接等于 MetaCore 材质资产”。
- 也不是“glTF 临时解析对象直接等于场景材质”。
- 而是“MetaCore 资产系统吸收导入结果,再由 Filament 映射层负责显示”。

View File

@ -40,7 +40,8 @@ MetaCore 第一阶段应明确采用下面这条交付链:
当前仓库里已经有较好的基础:
- `MetaCore.project.json`
- Scene 二进制 package 方向
- 编辑期 JSON 场景 / 资产元数据方向
- 发布期 cooked 二进制 package 方向
- Package 服务
- Cook 服务
- `CookManifest`
@ -49,6 +50,8 @@ MetaCore 第一阶段应明确采用下面这条交付链:
这说明第一阶段不是没有打包基础,而是还缺一套清晰的“交付工作流定义”。
当前原则是:编辑器工作目录优先保留可读 JSON 和元数据Cook 输出目录再生成运行时二进制内容。不要用“开发期是否已经物理生成二进制包”作为编辑器功能是否成立的唯一判断。
当前最需要解决的是:
- 什么内容属于交付包

Some files were not shown because too many files have changed in this diff Show More