Fix tree model depth rendering, ImGuizmo interactions, HDR IBL format, and link stb_image_impl

This commit is contained in:
ayuan9957 2026-06-10 17:24:45 +08:00
parent 27aa4547c7
commit 9e2c2a8954
38 changed files with 1051 additions and 473 deletions

View File

@ -9,6 +9,7 @@
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataDispatcher.h"
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataSource.h"
#include "MetaCoreScene/MetaCoreRuntimeLifecycle.h"
#include "MetaCoreScene/MetaCoreScenePackage.h"
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
#include "MetaCoreScene/MetaCoreScene.h"
@ -727,6 +728,21 @@ int main(int argc, char* argv[]) {
scene = MetaCore::MetaCoreCreateDefaultScene();
std::cout << "MetaCorePlayer: startup scene unavailable, using built-in default scene\n";
}
MetaCore::MetaCoreRuntimeLifecycleExecutor runtimeLifecycleExecutor;
const std::size_t runtimeComponentCount = runtimeLifecycleExecutor.RegisterDescriptors(
MetaCore::MetaCoreGetRuntimeLifecycleRegistry().GetDescriptors()
);
if (runtimeComponentCount > 0) {
std::cout << "MetaCorePlayer: registered " << runtimeComponentCount
<< " runtime lifecycle component(s)\n";
}
const auto runtimeLifecycleErrorSink = [](const MetaCore::MetaCoreRuntimeLifecycleError& error) {
std::cerr << "MetaCorePlayer: lifecycle " << error.Phase
<< " failed component=" << error.ComponentTypeId
<< " object=" << error.ObjectId
<< " error=" << error.Message << '\n';
};
runtimeLifecycleExecutor.EnterScene(scene, runtimeLifecycleErrorSink);
std::optional<MetaCore::MetaCoreUiDocument> startupUiDocument;
if (!runtimeProjectDocument.StartupUiPath.empty()) {
@ -892,6 +908,7 @@ int main(int argc, char* argv[]) {
});
runtimeUiRenderer.Resize(windowWidth, windowHeight);
runtimeUiRenderer.BeginFrame(1.0F / 60.0F);
runtimeLifecycleExecutor.TickScene(scene, 1.0F / 60.0F, runtimeLifecycleErrorSink);
if (!runtimeSources.empty()) {
std::uint64_t latestUpdateAt = 0;
for (MetaCoreRuntimeDataSourceInstance& sourceInstance : runtimeSources) {
@ -949,6 +966,7 @@ int main(int argc, char* argv[]) {
window.EndFrame();
}
runtimeLifecycleExecutor.ExitScene(scene, runtimeLifecycleErrorSink);
runtimeUiRenderer.Shutdown();
viewportRenderer.Shutdown();
renderDevice.Shutdown();

View File

@ -223,6 +223,7 @@ target_compile_definitions(MetaCorePlatform PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX
set(METACORE_SCENE_HEADERS
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreGameObject.h
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreRuntimeLifecycle.h
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreSceneDocument.h
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreScenePackage.h
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreScene.h
@ -232,6 +233,7 @@ set(METACORE_SCENE_HEADERS
set(METACORE_SCENE_SOURCES
Source/MetaCoreScene/Private/MetaCoreScenePackage.cpp
Source/MetaCoreScene/Private/MetaCoreRuntimeLifecycle.cpp
Source/MetaCoreScene/Private/MetaCoreScene.cpp
Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp
Source/MetaCoreScene/Private/MetaCoreUiRmlCompiler.cpp
@ -486,6 +488,7 @@ endif()
add_executable(MetaCorePlayer
Apps/MetaCorePlayer/main.cpp
third_party/stb/stb_image_impl.cpp
)
target_link_libraries(MetaCorePlayer

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,8 @@
{
"asset_type": "asset",
"guid": "305e95e7-4f41-426b-8ea5-8ba4fc1a8724",
"importer_id": "BinaryImporter",
"package_path": "Assets/111.glb.bak.mcasset",
"source_hash": 17894180331051193223,
"source_path": "Assets/111.glb.bak"
}

Binary file not shown.

View File

@ -0,0 +1,8 @@
{
"asset_type": "asset",
"guid": "b5c7953d-3806-4660-9596-043eaa9220a1",
"importer_id": "BinaryImporter",
"package_path": "Assets/111.glb.bak2.mcasset",
"source_hash": 16380358792779090604,
"source_path": "Assets/111.glb.bak2"
}

View File

@ -0,0 +1,22 @@
{
"asset_type": "model",
"guid": "ee0fec65-bcc9-4efb-a5b4-871385648c23",
"importer_id": "GltfModelImporter",
"package_path": "Assets/111.glb.mcasset",
"source_hash": 11760661239691667159,
"source_path": "Assets/111.glb",
"sub_assets": [
{
"guid": "ff7c19a0-ba4c-d02a-2111-835582811855",
"index": 0,
"name": "111",
"type": "mesh"
},
{
"guid": "030a04c3-630d-affd-1c58-fc3bc8aa9f91",
"index": 0,
"name": "111_Material",
"type": "material"
}
]
}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,22 @@
{
"asset_type": "model",
"guid": "7817e162-2dd7-4747-bdfb-93bb38a72ebb",
"importer_id": "GltfModelImporter",
"package_path": "Assets/Models/Floor4.glb.mcasset",
"source_hash": 17521808281966876099,
"source_path": "Assets/Models/Floor4.glb",
"sub_assets": [
{
"guid": "f0a46cf7-2e2c-d921-eb90-551705ce1b1e",
"index": 0,
"name": "Floor4",
"type": "mesh"
},
{
"guid": "7424828f-d8de-69d9-8850-1cc4e467ba88",
"index": 0,
"name": "Floor4_Material",
"type": "material"
}
]
}

Binary file not shown.

View File

@ -0,0 +1,22 @@
{
"asset_type": "model",
"guid": "f6a51ac3-fb03-42df-8836-9bc605b60b0c",
"importer_id": "GltfModelImporter",
"package_path": "Assets/Models/JXB.glb.mcasset",
"source_hash": 9095552786760188715,
"source_path": "Assets/Models/JXB.glb",
"sub_assets": [
{
"guid": "20593992-3c28-20dc-0734-2068a98c33dc",
"index": 0,
"name": "JXB",
"type": "mesh"
},
{
"guid": "24102470-e146-19aa-cccb-65dc97d3c0db",
"index": 0,
"name": "JXB_Material",
"type": "material"
}
]
}

Binary file not shown.

View File

@ -0,0 +1,22 @@
{
"asset_type": "model",
"guid": "0998aab9-9952-4b49-8723-7a1dd5903e9e",
"importer_id": "GltfModelImporter",
"package_path": "Assets/Models/RFF-37-3D.glb.mcasset",
"source_hash": 7543758070289905162,
"source_path": "Assets/Models/RFF-37-3D.glb",
"sub_assets": [
{
"guid": "e642444d-d75f-96ad-fedb-d226fc4669fc",
"index": 0,
"name": "RFF-37-3D",
"type": "mesh"
},
{
"guid": "22e350d5-7fa6-c46b-0533-914588f2d8a6",
"index": 0,
"name": "RFF-37-3D_Material",
"type": "material"
}
]
}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,22 @@
{
"asset_type": "model",
"guid": "719e2e9f-4445-48f5-bf2e-eb4bacf2cac5",
"importer_id": "GltfModelImporter",
"package_path": "Assets/Models/ch_desk.glb.mcasset",
"source_hash": 2230133667640719958,
"source_path": "Assets/Models/ch_desk.glb",
"sub_assets": [
{
"guid": "eefc1764-5c68-39f6-74f5-2d02ce94d07a",
"index": 0,
"name": "ch_desk",
"type": "mesh"
},
{
"guid": "a29b1867-cf52-f37c-e545-39802041e061",
"index": 0,
"name": "ch_desk_Material",
"type": "material"
}
]
}

Binary file not shown.

View File

@ -0,0 +1,22 @@
{
"asset_type": "model",
"guid": "ad0a19f9-393b-4ed8-899c-d4e44505396f",
"importer_id": "GltfModelImporter",
"package_path": "Assets/Models/cuite.glb.mcasset",
"source_hash": 16889646040474063424,
"source_path": "Assets/Models/cuite.glb",
"sub_assets": [
{
"guid": "e46dfa30-317d-d3a3-bc8e-f4e2cbcb067d",
"index": 0,
"name": "cuite",
"type": "mesh"
},
{
"guid": "e0590e30-51ba-e7cd-b784-c0a613b48da3",
"index": 0,
"name": "cuite_Material",
"type": "material"
}
]
}

Binary file not shown.

View File

@ -0,0 +1,22 @@
{
"asset_type": "model",
"guid": "9f9aa1f2-21ea-4b17-9da7-3713c32e11ee",
"importer_id": "GltfModelImporter",
"package_path": "Assets/Models/haqijingzhu.glb.mcasset",
"source_hash": 8705570643519871155,
"source_path": "Assets/Models/haqijingzhu.glb",
"sub_assets": [
{
"guid": "e0e7ad5c-3363-965c-37a7-7715359229a1",
"index": 0,
"name": "haqijingzhu",
"type": "mesh"
},
{
"guid": "7c3bb866-d2e3-de15-c287-49e822dbec2b",
"index": 0,
"name": "haqijingzhu_Material",
"type": "material"
}
]
}

Binary file not shown.

View File

@ -0,0 +1,22 @@
{
"asset_type": "model",
"guid": "54bd5826-8c6e-4b90-be92-d77180516bf6",
"importer_id": "GltfModelImporter",
"package_path": "Assets/RFF-37-3D.glb.mcasset",
"source_hash": 7543758070289905162,
"source_path": "Assets/RFF-37-3D.glb",
"sub_assets": [
{
"guid": "bbb65c83-5b02-4945-f61d-657c8260dd61",
"index": 0,
"name": "RFF-37-3D",
"type": "mesh"
},
{
"guid": "ef308213-3ea5-cb66-35a6-23bf086156eb",
"index": 0,
"name": "RFF-37-3D_Material",
"type": "material"
}
]
}

Binary file not shown.

View File

@ -0,0 +1,22 @@
{
"asset_type": "model",
"guid": "f9c69ec5-0d65-450d-80f2-636393b1d608",
"importer_id": "GltfModelImporter",
"package_path": "Assets/haqijingzhu.glb.mcasset",
"source_hash": 8705570643519871155,
"source_path": "Assets/haqijingzhu.glb",
"sub_assets": [
{
"guid": "61886497-7a91-4806-5681-54511c9ff052",
"index": 0,
"name": "haqijingzhu",
"type": "mesh"
},
{
"guid": "5d2f7f6a-fa58-6d4a-ab5e-dfebc1789e24",
"index": 0,
"name": "haqijingzhu_Material",
"type": "material"
}
]
}

View File

@ -2010,7 +2010,7 @@ void MetaCoreTrackInspectorEdit(MetaCoreEditorContext& editorContext, const char
}
const bool changed = editorContext.ExecuteSnapshotCommand(
addComponent ? "添加组件" : "移除组件",
addComponent ? "Add Component" : "移除组件",
[&]() {
bool anyChanged = false;
for (const MetaCoreId selectedId : editorContext.GetSelectedObjectIds()) {
@ -2408,7 +2408,8 @@ public:
const auto scenePersistenceService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIScenePersistenceService>();
const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
if (ImGui::BeginMenu("文件")) {
// 顶级菜单 1项目 (对应 Project)
if (ImGui::BeginMenu("项目")) {
if (ImGui::MenuItem("新建项目...")) {
if (assetDatabaseService != nullptr && assetDatabaseService->HasProject()) {
const auto& project = assetDatabaseService->GetProjectDescriptor();
@ -2437,16 +2438,41 @@ public:
if (ImGui::MenuItem("刷新 Asset 数据库", nullptr, false, assetDatabaseService != nullptr)) {
MetaCoreHandleReloadAssets(editorContext);
}
ImGui::Separator();
// 收纳 MetaCore 专属资源功能菜单项
if (ImGui::BeginMenu("资源管理")) {
ImGui::MenuItem("导入资源", nullptr, false, false);
if (ImGui::MenuItem("从选中对象创建 Prefab", nullptr, false, editorContext.GetActiveObjectId() != 0)) {
(void)MetaCoreCreatePrefabFromSelection(editorContext);
}
ImGui::EndMenu();
}
// 复制 Infernux 占位项,对齐界面一致度
ImGui::MenuItem("偏好设置", nullptr, false, false);
ImGui::MenuItem("构建设置", nullptr, false, false);
ImGui::MenuItem("物理层交互矩阵", nullptr, false, false);
ImGui::Separator();
if (ImGui::MenuItem("重置布局")) {
editorContext.SetDockLayoutBuilt(false);
}
// 收纳原有的 Help 菜单
if (ImGui::BeginMenu("帮助说明")) {
ImGui::MenuItem("MetaCore Editor", nullptr, false, false);
ImGui::MenuItem("当前运行模式MetaCore 编辑器", nullptr, false, false);
ImGui::EndMenu();
}
if (ImGui::MenuItem("退出")) {
editorContext.GetWindow().RequestClose();
}
ImGui::EndMenu();
}
// 新建项目弹窗中文化
if (ImGui::BeginPopupModal("MetaCoreCreateProjectPopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::InputText("项目目录", CreateProjectPathBuffer_, sizeof(CreateProjectPathBuffer_));
ImGui::InputText("项目名称", CreateProjectNameBuffer_, sizeof(CreateProjectNameBuffer_));
@ -2462,6 +2488,7 @@ public:
ImGui::EndPopup();
}
// 打开项目弹窗中文化
if (ImGui::BeginPopupModal("MetaCoreOpenProjectPopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::InputText("项目路径", OpenProjectPathBuffer_, sizeof(OpenProjectPathBuffer_));
if (ImGui::Button("打开")) {
@ -2475,6 +2502,7 @@ public:
ImGui::EndPopup();
}
// 顶级菜单 2编辑 (对应 Edit)
if (ImGui::BeginMenu("编辑")) {
if (ImGui::MenuItem("撤销", "Ctrl+Z", false, editorContext.GetCommandService().CanUndo())) {
MetaCoreHandleUndo(editorContext);
@ -2492,52 +2520,48 @@ public:
if (ImGui::MenuItem("重命名", "F2", false, editorContext.GetActiveObjectId() != 0)) {
editorContext.RequestRenameActiveObject();
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("资源")) {
ImGui::MenuItem("导入资源", nullptr, false, false);
if (ImGui::MenuItem("从选中对象创建 Prefab", nullptr, false, editorContext.GetActiveObjectId() != 0)) {
(void)MetaCoreCreatePrefabFromSelection(editorContext);
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("游戏对象")) {
if (ImGui::MenuItem("创建空对象")) {
(void)MetaCoreCreateObject(editorContext, "GameObject", false, std::nullopt);
}
if (ImGui::MenuItem("创建立方体")) {
(void)MetaCoreCreateObject(editorContext, "Cube", true, std::nullopt);
}
if (ImGui::MenuItem("创建平面")) {
(void)MetaCoreCreateObject(editorContext, "Plane", true, std::nullopt);
}
ImGui::Separator();
if (ImGui::MenuItem("应用当前 Prefab", nullptr, false, editorContext.GetActiveObjectId() != 0)) {
(void)MetaCoreApplySelectedPrefabInstance(editorContext);
// 收纳 MetaCore 专属游戏对象与组件创建逻辑
if (ImGui::BeginMenu("创建游戏对象")) {
if (ImGui::MenuItem("创建空对象")) {
(void)MetaCoreCreateObject(editorContext, "GameObject", false, std::nullopt);
}
if (ImGui::MenuItem("创建立方体")) {
(void)MetaCoreCreateObject(editorContext, "Cube", true, std::nullopt);
}
if (ImGui::MenuItem("创建平面")) {
(void)MetaCoreCreateObject(editorContext, "Plane", true, std::nullopt);
}
ImGui::Separator();
if (ImGui::MenuItem("应用当前 Prefab", nullptr, false, editorContext.GetActiveObjectId() != 0)) {
(void)MetaCoreApplySelectedPrefabInstance(editorContext);
}
if (ImGui::MenuItem("还原当前 Prefab", nullptr, false, editorContext.GetActiveObjectId() != 0)) {
(void)MetaCoreRevertSelectedPrefabInstance(editorContext);
}
if (ImGui::MenuItem("Break Prefab Instance", nullptr, false, editorContext.GetActiveObjectId() != 0)) {
(void)MetaCoreBreakSelectedPrefabInstance(editorContext);
}
ImGui::Separator();
if (ImGui::MenuItem("复制", "Ctrl+D", false, !editorContext.GetSelectedObjectIds().empty())) {
(void)MetaCoreDuplicateSelection(editorContext);
}
if (ImGui::MenuItem("删除", "Delete", false, !editorContext.GetSelectedObjectIds().empty())) {
(void)MetaCoreDeleteSelection(editorContext);
}
ImGui::EndMenu();
}
if (ImGui::MenuItem("还原当前 Prefab", nullptr, false, editorContext.GetActiveObjectId() != 0)) {
(void)MetaCoreRevertSelectedPrefabInstance(editorContext);
}
if (ImGui::MenuItem("Break Prefab Instance", nullptr, false, editorContext.GetActiveObjectId() != 0)) {
(void)MetaCoreBreakSelectedPrefabInstance(editorContext);
}
ImGui::Separator();
if (ImGui::MenuItem("复制", "Ctrl+D", false, !editorContext.GetSelectedObjectIds().empty())) {
(void)MetaCoreDuplicateSelection(editorContext);
}
if (ImGui::MenuItem("删除", "Delete", false, !editorContext.GetSelectedObjectIds().empty())) {
(void)MetaCoreDeleteSelection(editorContext);
if (ImGui::BeginMenu("添加组件")) {
ImGui::MenuItem("Add Component", nullptr, false, false);
ImGui::EndMenu();
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("组件")) {
ImGui::MenuItem("添加组件", nullptr, false, false);
ImGui::EndMenu();
}
// 顶级菜单 3窗口 (对应 Window)
if (ImGui::BeginMenu("窗口")) {
for (const auto& panelProvider : editorContext.GetModuleRegistry().GetPanelProviders()) {
bool& panelOpen = editorContext.GetModuleRegistry().AccessPanelOpenState(panelProvider->GetPanelId());
@ -2545,12 +2569,6 @@ public:
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("帮助")) {
ImGui::MenuItem("MetaCore Editor", nullptr, false, false);
ImGui::MenuItem("当前运行模式MetaCore 编辑器", nullptr, false, false);
ImGui::EndMenu();
}
}
private:
@ -2562,34 +2580,33 @@ private:
class MetaCoreHierarchyPanelProvider final : public MetaCoreIEditorPanelProvider {
public:
std::string GetPanelId() const override { return "Hierarchy"; }
std::string GetPanelTitle() const override { return "层级"; }
std::string GetPanelTitle() const override { return "Hierarchy"; }
bool IsOpenByDefault() const override { return true; }
void DrawPanel(MetaCoreEditorContext& editorContext) override {
// 全局资源选择状态
static std::filesystem::path GProjectSelectedDirectory{};
static MetaCoreAssetGuid GProjectSelectedAssetGuid{};
static std::filesystem::path GProjectSelectedAssetPath{};
static std::string GProjectSelectedAssetType{};
static MetaCoreAssetStorageKind GProjectSelectedAssetStorageKind = MetaCoreAssetStorageKind::SourcePackage;
static MetaCoreAssetGuid GProjectSelectedGeneratedResourceGuid{};
static std::string GProjectSelectedGeneratedResourceKind{};
static std::string GProjectGeneratedResourceFilter{};
static std::string GProjectGeneratedResourceKindFilter{};
static std::string GProjectSelectedUiNodeId{};
// Global Asset Selection State in BuiltinEditorModule
static std::filesystem::path GProjectSelectedDirectory{};
static MetaCoreAssetGuid GProjectSelectedAssetGuid{};
static std::filesystem::path GProjectSelectedAssetPath{};
static std::string GProjectSelectedAssetType{};
static MetaCoreAssetStorageKind GProjectSelectedAssetStorageKind = MetaCoreAssetStorageKind::SourcePackage;
static MetaCoreAssetGuid GProjectSelectedGeneratedResourceGuid{};
static std::string GProjectSelectedGeneratedResourceKind{};
static std::string GProjectGeneratedResourceFilter{};
static std::string GProjectGeneratedResourceKindFilter{};
static std::string GProjectSelectedUiNodeId{};
static std::filesystem::path GProjectPendingRenamePath{};
static std::array<char, 256> GProjectAssetFilterBuffer{};
static std::array<char, 128> GProjectFolderNameBuffer{};
static std::array<char, 256> GProjectRenamePathBuffer{};
static std::filesystem::path GProjectPendingRenamePath{};
static std::array<char, 256> GProjectAssetFilterBuffer{};
static std::array<char, 128> GProjectFolderNameBuffer{};
static std::array<char, 256> GProjectRenamePathBuffer{};
const auto scenePersistenceService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIScenePersistenceService>();
if (const MetaCoreId requestedRenameId = editorContext.ConsumeRenameRequestObjectId(); requestedRenameId != 0) {
BeginRename(editorContext, requestedRenameId);
}
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 8));
ImGui::TextColored(ImVec4(0.4F, 0.6F, 1.0F, 1.0F), " (S) ");
ImGui::TextColored(ImVec4(0.4F, 0.6F, 1.0F, 1.0F), "Scene: ");
ImGui::SameLine();
const std::string sceneLabel =
scenePersistenceService != nullptr && scenePersistenceService->HasOpenScene()
@ -2609,7 +2626,6 @@ static std::array<char, 256> GProjectRenamePathBuffer{};
DrawRootDropTarget(editorContext);
DrawWindowContextMenu(editorContext);
DrawRenamePopup(editorContext);
}
private:
@ -2620,6 +2636,34 @@ private:
return;
}
// 就地重命名交互逻辑
if (RenameObjectId_ == objectId) {
if (OpenRenamePopup_) {
ImGui::SetKeyboardFocusHere();
OpenRenamePopup_ = false;
}
ImGui::SetNextItemWidth(-1.0f);
const bool submittedByEnter = ImGui::InputText(
"##InlineRename",
RenameBuffer_.data(),
RenameBuffer_.size(),
ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue
);
if (ImGui::IsKeyPressed(ImGuiKey_Escape)) {
RenameObjectId_ = 0;
return;
}
if (submittedByEnter || ImGui::IsItemDeactivated()) {
std::string newName(RenameBuffer_.data());
if (!newName.empty()) {
(void)MetaCoreRenameObject(editorContext, objectId, newName);
}
RenameObjectId_ = 0;
return;
}
return;
}
const std::vector<MetaCoreId> childIds = scene.GetChildrenOf(objectId);
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanFullWidth;
if (childIds.empty()) {
@ -2635,19 +2679,12 @@ private:
gameObject.HasComponent<MetaCoreMeshRendererComponent>() &&
!gameObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelPath.empty();
// 1:1 对齐 Infernux 装饰样式:模型实例冠以 * 前缀 (使用 ASCII 规避缺失字体导致的问号)
// 对齐 Infernux 装饰样式:模型实例以星号前缀标示
std::string hierarchyLabel = gameObject.GetName();
if (isModelInstance) {
hierarchyLabel = "* " + hierarchyLabel;
}
// 1:1 对齐选中背景色与模型实例的文字红强调色
if (isSelected) {
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.173F, 0.365F, 0.529F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.173F, 0.365F, 0.529F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.173F, 0.365F, 0.529F, 1.0F));
}
bool pushedTextColor = false;
if (isModelInstance) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(235.0F / 255.0F, 87.0F / 255.0F, 87.0F / 255.0F, 1.0F));
@ -2665,10 +2702,6 @@ private:
ImGui::PopStyleColor(1);
}
if (isSelected) {
ImGui::PopStyleColor(3);
}
const bool leftClicked = ImGui::IsItemClicked(ImGuiMouseButton_Left);
const bool rightClicked = ImGui::IsItemClicked(ImGuiMouseButton_Right);
if ((leftClicked || rightClicked) && !ImGui::IsItemToggledOpen()) {
@ -2697,7 +2730,7 @@ private:
}
MetaCoreId payloadId = objectId;
ImGui::SetDragDropPayload("MC_HIERARCHY_OBJECT", &payloadId, sizeof(payloadId));
ImGui::Text("拖拽 %zu 个对象", editorContext.GetSelectedObjectIds().size());
ImGui::Text("Drag %zu objects", editorContext.GetSelectedObjectIds().size());
ImGui::EndDragDropSource();
}
@ -2729,13 +2762,13 @@ private:
void DrawNodeContextMenu(MetaCoreEditorContext& editorContext, MetaCoreId objectId) {
const MetaCoreGameObject gameObject = editorContext.GetScene().FindGameObject(objectId);
if (ImGui::MenuItem("创建空对象")) {
if (ImGui::MenuItem("Create Empty")) {
(void)MetaCoreCreateObject(editorContext, "GameObject", false, objectId);
}
if (ImGui::MenuItem("创建立方体")) {
if (ImGui::MenuItem("Create Cube")) {
(void)MetaCoreCreateObject(editorContext, "Cube", true, objectId);
}
if (ImGui::MenuItem("创建平面")) {
if (ImGui::MenuItem("Create Plane")) {
(void)MetaCoreCreateObject(editorContext, "Plane", true, objectId);
}
ImGui::Separator();
@ -2746,35 +2779,35 @@ private:
const auto sameSourceModelObjects = FindSceneObjectsUsingSameSourceModel(editorContext, gameObject);
const auto sameSourceNodeObjects = FindSceneObjectsUsingSameSourceNode(editorContext, gameObject);
if (sourceAsset.has_value()) {
ImGui::TextDisabled("模型: %s", sourceAsset->RelativePath.filename().string().c_str());
ImGui::TextDisabled("Model: %s", sourceAsset->RelativePath.filename().string().c_str());
} else {
ImGui::TextDisabled("模型: %s", gameObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelPath.c_str());
ImGui::TextDisabled("Model: %s", gameObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelPath.c_str());
}
ImGui::TextDisabled("节点: %d", gameObject.GetComponent<MetaCoreMeshRendererComponent>().ModelNodeIndex);
ImGui::TextDisabled("同源模型实例: %d", static_cast<int>(sameSourceModelObjects.size()));
ImGui::TextDisabled("同源节点实例: %d", static_cast<int>(sameSourceNodeObjects.size()));
ImGui::TextDisabled("Node: %d", gameObject.GetComponent<MetaCoreMeshRendererComponent>().ModelNodeIndex);
ImGui::TextDisabled("Same Source Model Instances: %d", static_cast<int>(sameSourceModelObjects.size()));
ImGui::TextDisabled("Same Source Node Instances: %d", static_cast<int>(sameSourceNodeObjects.size()));
ImGui::Separator();
if (ImGui::MenuItem("打开源模型")) {
if (ImGui::MenuItem("Open Source Model")) {
OpenSourceModelForGameObject(editorContext, gameObject);
}
if (ImGui::MenuItem("选择源节点", nullptr, false, gameObject.GetComponent<MetaCoreMeshRendererComponent>().ModelNodeIndex >= 0)) {
if (ImGui::MenuItem("Select Source Node", nullptr, false, gameObject.GetComponent<MetaCoreMeshRendererComponent>().ModelNodeIndex >= 0)) {
OpenSourceModelForGameObject(editorContext, gameObject);
}
if (ImGui::MenuItem("选择同源模型实例", nullptr, false, !sameSourceModelObjects.empty())) {
if (ImGui::MenuItem("Select Same Source Model Instances", nullptr, false, !sameSourceModelObjects.empty())) {
editorContext.SetSelection(sameSourceModelObjects, sameSourceModelObjects.front());
}
if (ImGui::MenuItem("选择同源节点实例", nullptr, false, !sameSourceNodeObjects.empty())) {
if (ImGui::MenuItem("Select Same Source Node Instances", nullptr, false, !sameSourceNodeObjects.empty())) {
editorContext.SetSelection(sameSourceNodeObjects, sameSourceNodeObjects.front());
}
ImGui::Separator();
}
if (ImGui::MenuItem("重命名", "F2", false, objectId != 0)) {
if (ImGui::MenuItem("Rename", "F2", false, objectId != 0)) {
BeginRename(editorContext, objectId);
}
if (ImGui::MenuItem("复制", "Ctrl+D", false, !editorContext.GetSelectedObjectIds().empty())) {
if (ImGui::MenuItem("Duplicate", "Ctrl+D", false, !editorContext.GetSelectedObjectIds().empty())) {
(void)MetaCoreDuplicateSelection(editorContext);
}
if (ImGui::MenuItem("删除", "Delete", false, !editorContext.GetSelectedObjectIds().empty())) {
if (ImGui::MenuItem("Delete", "Delete", false, !editorContext.GetSelectedObjectIds().empty())) {
(void)MetaCoreDeleteSelection(editorContext);
}
}
@ -2784,23 +2817,23 @@ private:
return;
}
if (ImGui::MenuItem("创建空对象")) {
if (ImGui::MenuItem("Create Empty")) {
(void)MetaCoreCreateObject(editorContext, "GameObject", false, 0);
}
if (ImGui::MenuItem("创建立方体")) {
if (ImGui::MenuItem("Create Cube")) {
(void)MetaCoreCreateObject(editorContext, "Cube", true, 0);
}
if (ImGui::MenuItem("创建平面")) {
if (ImGui::MenuItem("Create Plane")) {
(void)MetaCoreCreateObject(editorContext, "Plane", true, 0);
}
ImGui::Separator();
if (ImGui::MenuItem("重命名", "F2", false, editorContext.GetActiveObjectId() != 0)) {
if (ImGui::MenuItem("Rename", "F2", false, editorContext.GetActiveObjectId() != 0)) {
BeginRename(editorContext, editorContext.GetActiveObjectId());
}
if (ImGui::MenuItem("复制", "Ctrl+D", false, !editorContext.GetSelectedObjectIds().empty())) {
if (ImGui::MenuItem("Duplicate", "Ctrl+D", false, !editorContext.GetSelectedObjectIds().empty())) {
(void)MetaCoreDuplicateSelection(editorContext);
}
if (ImGui::MenuItem("删除", "Delete", false, !editorContext.GetSelectedObjectIds().empty())) {
if (ImGui::MenuItem("Delete", "Delete", false, !editorContext.GetSelectedObjectIds().empty())) {
(void)MetaCoreDeleteSelection(editorContext);
}
@ -2809,16 +2842,20 @@ private:
void DrawRootDropTarget(MetaCoreEditorContext& editorContext) {
ImGui::Separator();
ImGui::TextDisabled("拖拽到下方设为根节点");
ImGui::TextDisabled("Drag here to reparent to root");
const ImVec2 dropAreaMin = ImGui::GetCursorScreenPos();
const ImVec2 dropAreaSize = ImVec2(ImGui::GetContentRegionAvail().x, 24.0F);
ImGui::InvisibleButton("MetaCoreHierarchyRootDrop", dropAreaSize);
const ImVec2 dropAreaMax = ImVec2(dropAreaMin.x + dropAreaSize.x, dropAreaMin.y + dropAreaSize.y);
// 绘制 Notion 虚线直角框
ImGui::GetWindowDrawList()->AddRect(
dropAreaMin,
dropAreaMax,
IM_COL32(96, 106, 122, 180),
2.0F
0.0F,
0,
1.0F
);
if (ImGui::BeginDragDropTarget()) {
@ -2843,53 +2880,11 @@ private:
}
RenameObjectId_ = objectId;
RenameBuffer_.fill('\0');
RenameBuffer_.fill(0);
std::snprintf(RenameBuffer_.data(), RenameBuffer_.size(), "%s", object.GetName().c_str());
OpenRenamePopup_ = true;
}
void DrawRenamePopup(MetaCoreEditorContext& editorContext) {
if (RenameObjectId_ == 0) {
return;
}
if (OpenRenamePopup_) {
ImGui::OpenPopup("重命名对象##MetaCoreHierarchy");
OpenRenamePopup_ = false;
}
if (!ImGui::BeginPopupModal("重命名对象##MetaCoreHierarchy", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
return;
}
if (ImGui::IsWindowAppearing()) {
ImGui::SetKeyboardFocusHere();
}
const bool submittedByEnter = ImGui::InputText(
"名称",
RenameBuffer_.data(),
RenameBuffer_.size(),
ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue
);
bool closePopup = false;
if (submittedByEnter || ImGui::Button("确定", ImVec2(90.0F, 0.0F))) {
closePopup = MetaCoreRenameObject(editorContext, RenameObjectId_, RenameBuffer_.data());
}
ImGui::SameLine();
if (ImGui::Button("取消", ImVec2(90.0F, 0.0F))) {
closePopup = true;
}
if (closePopup) {
RenameObjectId_ = 0;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
MetaCoreId RenameObjectId_ = 0;
std::array<char, 256> RenameBuffer_{};
bool OpenRenamePopup_ = false;
@ -2898,7 +2893,7 @@ private:
class MetaCoreScenePanelProvider final : public MetaCoreIEditorPanelProvider {
public:
std::string GetPanelId() const override { return "Scene"; }
std::string GetPanelTitle() const override { return "场景"; }
std::string GetPanelTitle() const override { return "Scene"; }
bool IsOpenByDefault() const override { return true; }
void DrawPanel(MetaCoreEditorContext&) override {
@ -4530,18 +4525,8 @@ void DrawDirectoryTree(
if (!MatchesAssetFilter(label) && !HasVisibleChildren(assetDatabaseService, dir)) return;
const bool isSelectedDirectory = editorContext.GetSelectedProjectDirectory() == dir;
if (isSelectedDirectory) {
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.173F, 0.365F, 0.529F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.173F, 0.365F, 0.529F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.173F, 0.365F, 0.529F, 1.0F));
}
const bool nodeOpened = ImGui::TreeNodeEx(dir.generic_string().c_str(), ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanFullWidth | (isSelectedDirectory ? ImGuiTreeNodeFlags_Selected : 0), "%s", label.c_str());
if (isSelectedDirectory) {
ImGui::PopStyleColor(3);
}
if (nodeOpened) {
if (ImGui::IsItemClicked()) editorContext.SetSelectedProjectDirectory(dir);
for (const auto& d : assetDatabaseService.GetDirectoriesUnder(dir)) {
@ -4556,7 +4541,7 @@ void DrawDirectoryTree(
class MetaCoreProjectPanelProvider final : public MetaCoreIEditorPanelProvider {
public:
std::string GetPanelId() const override { return "Project"; }
std::string GetPanelTitle() const override { return "项目"; }
std::string GetPanelTitle() const override { return "Project"; }
bool IsOpenByDefault() const override { return true; }
void DrawPanel(MetaCoreEditorContext& editorContext) override {
@ -4569,8 +4554,8 @@ public:
bool openRenamePopup = false;
ImGui::TextDisabled("%s | %s", assetDatabaseService->GetProjectDescriptor().Name.c_str(), assetDatabaseService->GetProjectDescriptor().RootPath.string().c_str());
if (ImGui::Button("刷新")) { MetaCoreHandleReloadAssets(editorContext); assetDatabaseService->Refresh(); }
ImGui::InputTextWithHint("##Filter", "过滤资源...", AssetFilterBuffer_.data(), AssetFilterBuffer_.size());
ImGui::InputTextWithHint("##Filter", "Filter Assets...", AssetFilterBuffer_.data(), AssetFilterBuffer_.size());
ImGui::Columns(2, "ProjectCols", true);
DrawDirectoryTree(editorContext, *assetDatabaseService, "Assets", "Assets");
@ -4584,7 +4569,7 @@ public:
// 1. 绘制面包屑导航 (Breadcrumbs)
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4, 2));
ImGui::AlignTextToFramePadding();
ImGui::TextDisabled("路径:");
ImGui::TextDisabled("Path:");
ImGui::SameLine();
std::vector<std::filesystem::path> pathSegments;
@ -4622,7 +4607,7 @@ public:
ImGui::PushItemWidth(100.0f);
ImGui::SliderFloat("##IconSize", &IconSize_, 16.0f, 128.0f, "");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("图标大小: %.0f px", IconSize_);
ImGui::SetTooltip("Icon Size: %.0f px", IconSize_);
}
ImGui::PopItemWidth();
ImGui::PopStyleVar();
@ -4684,17 +4669,8 @@ public:
isSelected = (editorContext.GetSelectedAsset().Guid == item.Guid);
}
if (isSelected) {
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.173F, 0.365F, 0.529F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.173F, 0.365F, 0.529F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.173F, 0.365F, 0.529F, 1.0F));
}
ImGui::PushID(item.IsDirectory ? item.Path.string().c_str() : item.Guid.ToString().c_str());
const bool clicked = ImGui::Selectable(labelText.c_str(), isSelected);
if (isSelected) {
ImGui::PopStyleColor(3);
}
// 2. 用 ImDrawList 在该行的左侧空白处绘制 10px 精美矢量微型图标 (避免缺失显示为问号)
ImDrawList* drawList = ImGui::GetWindowDrawList();
@ -4707,8 +4683,9 @@ public:
float h = miniSize * 0.75f;
ImVec2 topLeft(miniIconCenter.x - w * 0.5f, miniIconCenter.y - h * 0.5f + h * 0.1f);
ImVec2 bottomRight(miniIconCenter.x + w * 0.5f, miniIconCenter.y + h * 0.5f);
drawList->AddRectFilled(topLeft, bottomRight, IM_COL32(230, 180, 45, 255), 1.0f);
drawList->AddRectFilled(ImVec2(topLeft.x, topLeft.y - h * 0.2f), ImVec2(topLeft.x + w * 0.4f, topLeft.y + h * 0.1f), IM_COL32(190, 145, 30, 255), 0.5f);
// 替换亮黄色为 Notion 冷灰色
drawList->AddRectFilled(topLeft, bottomRight, IM_COL32(110, 110, 110, 255), 1.0f);
drawList->AddRectFilled(ImVec2(topLeft.x, topLeft.y - h * 0.2f), ImVec2(topLeft.x + w * 0.4f, topLeft.y + h * 0.1f), IM_COL32(80, 80, 80, 255), 0.5f);
} else if (item.Type == "material") {
drawList->AddCircleFilled(miniIconCenter, miniSize * 0.45f, IM_COL32(140, 80, 210, 255), 12);
ImVec2 highlightCenter(miniIconCenter.x - miniSize * 0.15f, miniIconCenter.y - miniSize * 0.15f);
@ -4847,15 +4824,7 @@ public:
const std::string subId = MetaCoreBuildGeneratedResourceSubId(generatedEntry.Kind, generatedEntry.AssetGuid);
const bool generatedSelected = editorContext.GetSelectedAsset().Guid == a.Guid && editorContext.GetSelectedAssetSubId() == subId;
const std::string generatedLabel = "" + generatedEntry.Name + " [" + generatedEntry.SecondaryText + "]";
if (generatedSelected) {
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.173F, 0.365F, 0.529F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.173F, 0.365F, 0.529F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.173F, 0.365F, 0.529F, 1.0F));
}
const bool generatedClicked = ImGui::Selectable(generatedLabel.c_str(), generatedSelected);
if (generatedSelected) {
ImGui::PopStyleColor(3);
}
if (generatedClicked) {
editorContext.SelectAsset(MetaCoreSelectedAssetState{
a.Guid,
@ -4903,9 +4872,9 @@ public:
ImGui::SetCursorPos(startPos);
if (isSelected) {
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.173F, 0.365F, 0.529F, 0.40F));
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.173F, 0.365F, 0.529F, 0.60F));
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.173F, 0.365F, 0.529F, 0.80F));
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.200F, 0.200F, 0.200F, 0.50F));
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.220F, 0.165F, 0.165F, 0.60F));
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.270F, 0.180F, 0.180F, 0.80F));
} else {
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.08f));
@ -4922,20 +4891,20 @@ public:
float largeSize = IconSize_;
if (item.IsDirectory) {
// 绘制带有浅黄色前盖和深黄色后盖层叠效果的双矩形立体文件夹
// 绘制带有浅灰色前盖和深灰色后盖层叠效果的双矩形立体文件夹
float w = largeSize * 0.85f;
float h = largeSize * 0.65f;
ImVec2 topLeft(gridIconCenter.x - w * 0.5f, gridIconCenter.y - h * 0.5f + h * 0.1f);
ImVec2 bottomRight(gridIconCenter.x + w * 0.5f, gridIconCenter.y + h * 0.5f);
// 绘制文件夹后背板 (略暗)
drawList->AddRectFilled(topLeft, bottomRight, IM_COL32(190, 145, 30, 255), 3.0f);
// 绘制文件夹后背板 (略暗,改冷灰)
drawList->AddRectFilled(topLeft, bottomRight, IM_COL32(80, 80, 80, 255), 3.0f);
// 绘制文件夹上方标签页
ImVec2 tabTopLeft(topLeft.x, topLeft.y - h * 0.2f);
ImVec2 tabBottomRight(topLeft.x + w * 0.4f, topLeft.y + h * 0.1f);
drawList->AddRectFilled(tabTopLeft, tabBottomRight, IM_COL32(190, 145, 30, 255), 2.0f);
// 绘制文件夹前挡板 (略亮)
drawList->AddRectFilled(tabTopLeft, tabBottomRight, IM_COL32(80, 80, 80, 255), 2.0f);
// 绘制文件夹前挡板 (略亮,改冷灰)
ImVec2 frontTopLeft(topLeft.x, topLeft.y + h * 0.15f);
drawList->AddRectFilled(frontTopLeft, bottomRight, IM_COL32(235, 185, 45, 255), 3.0f);
drawList->AddRectFilled(frontTopLeft, bottomRight, IM_COL32(110, 110, 110, 255), 3.0f);
} else if (item.Type == "material") {
// 绘制具有左上角高光球体感的紫色材质圆球
float radius = largeSize * 0.4f;
@ -5145,19 +5114,19 @@ public:
ImGui::Columns(1);
if (openRenamePopup) {
ImGui::OpenPopup("重命名资源");
ImGui::OpenPopup("Rename Asset");
}
if (ImGui::BeginPopupContextWindow("ProjectBlankContextMenu", ImGuiPopupFlags_MouseButtonRight)) {
if (ImGui::MenuItem("刷新")) {
if (ImGui::MenuItem("Refresh")) {
MetaCoreHandleReloadAssets(editorContext);
assetDatabaseService->Refresh();
}
if (ImGui::MenuItem("新建文件夹")) {
if (ImGui::MenuItem("New Folder")) {
std::snprintf(NewFolderBuffer_.data(), NewFolderBuffer_.size(), "NewFolder");
ImGui::OpenPopup("新建文件夹");
ImGui::OpenPopup("New Folder");
}
if (ImGui::MenuItem("新建材质")) {
if (ImGui::MenuItem("New Material")) {
CreateNewMaterial(editorContext, *assetDatabaseService);
}
if (ImGui::MenuItem("New UI Document")) {
@ -5185,10 +5154,10 @@ public:
ImGui::EndPopup();
}
if (ImGui::BeginPopupModal("重命名资源", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("请输入新名称:");
if (ImGui::BeginPopupModal("Rename Asset", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Enter new name:");
ImGui::InputText("##NewName", RenameBuffer_.data(), RenameBuffer_.size());
if (ImGui::Button("确定", ImVec2(120, 0))) {
if (ImGui::Button("OK", ImVec2(120, 0))) {
std::string newName(RenameBuffer_.data());
if (!newName.empty()) {
(void)assetDatabaseService->RenamePath(PendingRenamePath_, newName);
@ -5196,16 +5165,16 @@ public:
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("取消", ImVec2(120, 0))) {
if (ImGui::Button("Cancel", ImVec2(120, 0))) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
if (ImGui::BeginPopupModal("新建文件夹", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("请输入文件夹名称:");
if (ImGui::BeginPopupModal("New Folder", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Enter folder name:");
ImGui::InputText("##FolderName", NewFolderBuffer_.data(), NewFolderBuffer_.size());
if (ImGui::Button("确定", ImVec2(120, 0))) {
if (ImGui::Button("OK", ImVec2(120, 0))) {
std::string folderName(NewFolderBuffer_.data());
if (!folderName.empty()) {
std::filesystem::path newDirPath = assetDatabaseService->GetProjectDescriptor().RootPath / selectedDirectory / folderName;
@ -5216,7 +5185,7 @@ public:
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("取消", ImVec2(120, 0))) {
if (ImGui::Button("Cancel", ImVec2(120, 0))) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
@ -5227,7 +5196,7 @@ public:
class MetaCoreConsolePanelProvider final : public MetaCoreIEditorPanelProvider {
public:
std::string GetPanelId() const override { return "Console"; }
std::string GetPanelTitle() const override { return "控制台"; }
std::string GetPanelTitle() const override { return "Console"; }
bool IsOpenByDefault() const override { return true; }
void DrawPanel(MetaCoreEditorContext& editorContext) override {
@ -5290,13 +5259,15 @@ public:
ImGui::Separator();
// 2. 日志列表区域
// 为下方详细文本保留 85px 高度
float availHeight = ImGui::GetContentRegionAvail().y - 85.0f;
if (availHeight < 50.0f) {
availHeight = 50.0f;
static float ConsoleDetailHeight = 100.0f;
float totalHeight = ImGui::GetContentRegionAvail().y;
float splitterHeight = 4.0f;
float listHeight = totalHeight - ConsoleDetailHeight - splitterHeight;
if (listHeight < 50.0f) {
listHeight = 50.0f;
}
if (ImGui::BeginChild("LogList", ImVec2(0, availHeight), true)) {
if (ImGui::BeginChild("LogList", ImVec2(0, listHeight), true)) {
struct DisplayEntry {
size_t OriginalIndex = 0;
MetaCoreLogLevel Level = MetaCoreLogLevel::Info;
@ -5394,9 +5365,22 @@ public:
}
ImGui::EndChild();
// 可拖动高度调节栏 Splitter
ImGui::Separator();
ImGui::InvisibleButton("##ConsoleSplitter", ImVec2(-1, splitterHeight));
if (ImGui::IsItemActive()) {
float dy = ImGui::GetMouseDragDelta(0).y;
if (std::abs(dy) > 0.5f) {
ConsoleDetailHeight = std::clamp(ConsoleDetailHeight - dy, 40.0f, totalHeight - 60.0f);
ImGui::ResetMouseDragDelta(0);
}
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);
} else if (ImGui::IsItemHovered()) {
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);
}
ImGui::Separator();
// 3. 详细展示区
// 3. 详细展示区 (只读 InputTextMultiline 支持 Ctrl+C 复制)
if (ImGui::BeginChild("LogDetail", ImVec2(0, 0), true)) {
if (ConsoleSelectedEntryIdx_ >= 0 && ConsoleSelectedEntryIdx_ < static_cast<int>(logService.GetEntries().size())) {
const auto& entry = logService.GetEntries()[ConsoleSelectedEntryIdx_];
@ -5407,12 +5391,24 @@ public:
txtColor = ImVec4(0.95f, 0.35f, 0.30f, 1.0f);
}
ImGui::TextColored(txtColor, "[%s]", entry.Category.c_str());
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.85f, 0.85f, 0.85f, 1.0f));
ImGui::TextWrapped("%s", entry.Message.c_str());
ImGui::PopStyleColor();
std::string logDetailText = "[" + entry.Category + "] " + entry.Message;
ImGui::PushStyleColor(ImGuiCol_Text, txtColor);
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
ImGui::InputTextMultiline(
"##ConsoleDetailText",
const_cast<char*>(logDetailText.c_str()),
logDetailText.size() + 1,
ImVec2(-1, -1),
ImGuiInputTextFlags_ReadOnly
);
ImGui::PopStyleVar();
ImGui::PopStyleColor(3);
} else {
ImGui::TextDisabled("选择任意日志项以查看详细信息。");
ImGui::TextDisabled("Select a log entry to view details.");
}
}
ImGui::EndChild();
@ -6131,10 +6127,7 @@ public:
ImGui::Text("Platform");
if (ImGui::BeginChild("PlatformList", ImVec2(0, 180.0f), true)) {
// Unity 风格的高亮单选列表
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.173f, 0.365f, 0.529f, 0.8f));
ImGui::Selectable("💻 PC, Mac & Linux", true);
ImGui::PopStyleColor();
ImGui::Selectable("📱 Android", false);
ImGui::Selectable("🍏 iOS", false);
@ -6644,7 +6637,7 @@ bool MetaCoreTransformInspectorDrawer::GScaleLocked = true;
class MetaCoreInspectorPanelProvider final : public MetaCoreIEditorPanelProvider {
public:
std::string GetPanelId() const override { return "Inspector"; }
std::string GetPanelTitle() const override { return "检查器"; }
std::string GetPanelTitle() const override { return "Inspector"; }
bool IsOpenByDefault() const override { return true; }
void DrawPanel(MetaCoreEditorContext& editorContext) override {
@ -6660,14 +6653,14 @@ public:
MetaCoreDrawAssetInspector(editorContext, *assetDatabaseService, *packageService, *reflectionRegistry);
}
} else {
ImGui::TextUnformatted("当前没有选中对象。");
ImGui::TextUnformatted("No object selected.");
}
return;
}
MetaCoreGameObject selectedObject = editorContext.GetSelectedGameObject();
if (!selectedObject) {
ImGui::TextUnformatted("当前没有可编辑的主对象。");
ImGui::TextUnformatted("No active object editable.");
return;
}
@ -6702,7 +6695,7 @@ public:
if (!hasActiveProp) {
defaultActiveState = activeState;
}
(void)editorContext.ExecuteSnapshotCommand("修改对象激活状态", [&]() {
(void)editorContext.ExecuteSnapshotCommand("Modify Active State", [&]() {
bool anyChanged = false;
for (const MetaCoreId selectedId : editorContext.GetSelectedObjectIds()) {
MetaCoreGameObject obj = editorContext.GetScene().FindGameObject(selectedId);
@ -6779,7 +6772,7 @@ public:
ImGui::SameLine();
// 扁平化名称输入框样式,移除突兀的蓝色边框,并与 Static 控件完美对齐,使用全局 Infernux Theme 配色
float staticControlWidth = 85.0F; // "静态的" 文字及 checkbox 的总估算宽度
float staticControlWidth = 85.0F; // "Static" 文字及 checkbox 的总估算宽度
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.165F, 0.165F, 0.165F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0F, 0.0F, 0.0F, 0.0F));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0F);
@ -6794,7 +6787,7 @@ public:
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - staticControlWidth);
static bool isStatic = false;
ImGui::TextDisabled("静态的"); ImGui::SameLine();
ImGui::TextDisabled("Static"); ImGui::SameLine();
ImGui::Checkbox("##Static", &isStatic);
// Tag and Layer
@ -6810,12 +6803,12 @@ public:
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0F);
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0F, 0.5F)); // 文本左对齐
ImGui::Text("标签"); ImGui::SameLine();
ImGui::Text("Tag"); ImGui::SameLine();
ImGui::SetNextItemWidth(-1);
if (ImGui::Button("Untagged ▾##Tag", ImVec2(-1, 0))) { /* Menu */ }
ImGui::NextColumn();
ImGui::Text("图层"); ImGui::SameLine();
ImGui::Text("Layer"); ImGui::SameLine();
ImGui::SetNextItemWidth(-1);
if (ImGui::Button("Default ▾##Layer", ImVec2(-1, 0))) { /* Menu */ }

View File

@ -478,66 +478,96 @@ void MetaCoreApplyEditorStyle() {
style.WindowBorderSize = 1.0F;
style.ChildBorderSize = 1.0F;
style.PopupBorderSize = 1.0F;
style.FrameBorderSize = 0.0F;
style.TabBorderSize = 1.0F;
style.FrameBorderSize = 1.0F;
style.TabBorderSize = 0.0F;
style.TabBarBorderSize = 1.0F;
style.WindowPadding = ImVec2(4.0F, 4.0F);
style.FramePadding = ImVec2(4.0F, 2.0F);
style.ItemSpacing = ImVec2(4.0F, 4.0F);
style.ItemInnerSpacing = ImVec2(4.0F, 4.0F);
style.WindowPadding = ImVec2(10.0F, 10.0F);
style.FramePadding = ImVec2(8.0F, 3.0F);
style.CellPadding = ImVec2(4.0F, 4.0F);
style.ItemSpacing = ImVec2(8.0F, 6.0F);
style.ItemInnerSpacing = ImVec2(6.0F, 4.0F);
style.IndentSpacing = 18.0F;
style.ScrollbarSize = 8.0F;
style.GrabMinSize = 6.0F;
// 1:1 对齐 Infernux Unity 风格暗色调与暗红高亮主题
style.Colors[ImGuiCol_Text] = ImVec4(0.84F, 0.84F, 0.84F, 1.0F); // 主文本色
style.Colors[ImGuiCol_TextDisabled] = ImVec4(0.40F, 0.40F, 0.40F, 1.0F); // 禁用文本色
style.Colors[ImGuiCol_WindowBg] = ImVec4(0.22F, 0.22F, 0.22F, 1.0F); // 窗口背景 (Unity #383838)
style.Colors[ImGuiCol_ChildBg] = ImVec4(0.00F, 0.00F, 0.00F, 0.0F); // 子窗口背景 (透明)
style.Colors[ImGuiCol_PopupBg] = ImVec4(0.24F, 0.24F, 0.24F, 0.96F); // 弹出菜单背景 (Unity #3E3E3E)
style.Colors[ImGuiCol_Border] = ImVec4(0.10F, 0.10F, 0.10F, 1.0F); // 边框颜色 (Unity #1A1A1A)
style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.00F, 0.00F, 0.00F, 0.0F); // 边框阴影 (无)
style.Colors[ImGuiCol_FrameBg] = ImVec4(0.165F, 0.165F, 0.165F, 1.0F); // 输入框/滑动条背景 (Unity #2A2A2A)
style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(0.20F, 0.20F, 0.20F, 1.0F); // 输入框悬停
style.Colors[ImGuiCol_FrameBgActive] = ImVec4(0.24F, 0.17F, 0.17F, 1.0F); // 输入框激活 (带微弱红调)
style.Colors[ImGuiCol_TitleBg] = ImVec4(0.16F, 0.16F, 0.16F, 1.0F); // 标题栏背景
style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.16F, 0.16F, 0.16F, 1.0F); // 活动标题栏背景
style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.16F, 0.16F, 0.16F, 0.5F); // 折叠标题栏背景
style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.16F, 0.16F, 0.16F, 1.0F); // 菜单栏背景
style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.13F, 0.13F, 0.13F, 1.0F); // 滚动条背景
style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.25F, 0.25F, 0.25F, 1.0F); // 滚动条滑块 (Unity #404040)
style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.30F, 0.25F, 0.25F, 1.0F); // 滚动条悬停 (微红)
style.Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.35F, 0.22F, 0.22F, 1.0F); // 滚动条激活
style.Colors[ImGuiCol_CheckMark] = ImVec4(0.922F, 0.341F, 0.341F, 1.0F); // 勾选标记 (强调红 #EB5757)
style.Colors[ImGuiCol_SliderGrab] = ImVec4(0.25F, 0.25F, 0.25F, 1.0F); // 滑动条滑块
style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.35F, 0.22F, 0.22F, 1.0F); // 滑动条激活
style.Colors[ImGuiCol_Button] = ImVec4(0.25F, 0.25F, 0.25F, 1.0F); // 普通按钮背景
style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.30F, 0.25F, 0.25F, 1.0F); // 按钮悬停 (微红调)
style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.35F, 0.22F, 0.22F, 1.0F); // 按钮激活 (深红调)
style.Colors[ImGuiCol_Header] = ImVec4(0.235F, 0.235F, 0.235F, 1.0F); // 折叠头背景 (Unity #3C3C3C)
style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.28F, 0.24F, 0.24F, 1.0F); // 悬停 (微红调)
style.Colors[ImGuiCol_HeaderActive] = ImVec4(0.32F, 0.25F, 0.25F, 1.0F); // 激活 (深红调)
style.Colors[ImGuiCol_Separator] = ImVec4(0.10F, 0.10F, 0.10F, 1.0F); // 分割线 (同边框)
style.Colors[ImGuiCol_SeparatorHovered] = ImVec4(0.35F, 0.25F, 0.25F, 0.6F); // 悬停分割线 (红暖色)
style.Colors[ImGuiCol_SeparatorActive] = ImVec4(0.40F, 0.28F, 0.28F, 0.8F); // 激活分割线 (深红)
style.Colors[ImGuiCol_ResizeGrip] = ImVec4(0.16F, 0.16F, 0.16F, 0.5F); // 调整手柄
style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.28F, 0.24F, 0.24F, 0.6F);
style.Colors[ImGuiCol_ResizeGripActive] = ImVec4(0.32F, 0.25F, 0.25F, 0.9F);
style.Colors[ImGuiCol_Tab] = ImVec4(0.18F, 0.18F, 0.18F, 1.0F); // 标签页未选中 (暗灰色)
style.Colors[ImGuiCol_TabHovered] = ImVec4(0.28F, 0.24F, 0.24F, 1.0F); // 标签悬停 (微红调)
style.Colors[ImGuiCol_TabActive] = ImVec4(0.22F, 0.22F, 0.22F, 1.0F); // 活动标签 (同窗口底色)
style.Colors[ImGuiCol_TabUnfocused] = ImVec4(0.18F, 0.18F, 0.18F, 1.0F);
style.Colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.22F, 0.22F, 0.22F, 1.0F);
style.Colors[ImGuiCol_DockingPreview] = ImVec4(0.173F, 0.365F, 0.529F, 0.7F); // 停靠预览区 (Unity 蓝色半透明)
style.Colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.18F, 0.18F, 0.18F, 1.0F);
style.Colors[ImGuiCol_TableHeaderBg] = ImVec4(0.16F, 0.16F, 0.16F, 1.0F); // 表格头背景
style.Colors[ImGuiCol_TableBorderStrong] = ImVec4(0.10F, 0.10F, 0.10F, 1.0F); // 强边框
style.Colors[ImGuiCol_TableBorderLight] = ImVec4(0.15F, 0.15F, 0.15F, 1.0F); // 弱边框
style.Colors[ImGuiCol_TableRowBg] = ImVec4(0.00F, 0.00F, 0.00F, 0.0F);
style.Colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00F, 1.00F, 1.00F, 0.03F); // 斑马纹交替底色
style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(0.173F, 0.365F, 0.529F, 1.0F); // 选中文本背景 (Unity 经典选色 #2C5D87)
style.Colors[ImGuiCol_DragDropTarget] = ImVec4(0.173F, 0.365F, 0.529F, 0.9F); // 拖放目标高亮 (带选蓝)
style.Colors[ImGuiCol_NavHighlight] = ImVec4(0.922F, 0.341F, 0.341F, 1.0F); // 导航高亮
style.Colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00F, 1.00F, 1.00F, 0.7F);
style.Colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80F, 0.80F, 0.80F, 0.2F);
style.Colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.00F, 0.00F, 0.00F, 0.6F); // 模态遮罩
// Anti-aliasing
style.AntiAliasedLines = true;
style.AntiAliasedFill = true;
// 1:1 对齐 Infernux Notion 风格暗色调与亮红高亮主题
constexpr float AR = 0.922F; // #EB5757 Accent color R
constexpr float AG = 0.341F; // #EB5757 Accent color G
constexpr float AB = 0.341F; // #EB5757 Accent color B
style.Colors[ImGuiCol_Text] = ImVec4(0.812F, 0.812F, 0.812F, 1.00F); // #CFCFCF
style.Colors[ImGuiCol_TextDisabled] = ImVec4(0.333F, 0.333F, 0.333F, 1.00F); // #555555
style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(0.200F, 0.200F, 0.200F, 0.60F); // #333333
style.Colors[ImGuiCol_WindowBg] = ImVec4(0.098F, 0.098F, 0.098F, 1.00F); // #191919
style.Colors[ImGuiCol_ChildBg] = ImVec4(0.125F, 0.125F, 0.125F, 1.00F); // #202020
style.Colors[ImGuiCol_PopupBg] = ImVec4(0.125F, 0.125F, 0.125F, 0.98F); // #202020
style.Colors[ImGuiCol_FrameBg] = ImVec4(0.125F, 0.125F, 0.125F, 1.00F); // #202020
style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(0.165F, 0.165F, 0.165F, 1.00F); // #2A2A2A
style.Colors[ImGuiCol_FrameBgActive] = ImVec4(0.240F, 0.170F, 0.170F, 1.00F); // 激活时偏红
style.Colors[ImGuiCol_TitleBg] = ImVec4(0.098F, 0.098F, 0.098F, 1.00F);
style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.098F, 0.098F, 0.098F, 1.00F);
style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.098F, 0.098F, 0.098F, 0.75F);
style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.098F, 0.098F, 0.098F, 1.00F);
style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.098F, 0.098F, 0.098F, 0.00F);
style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.184F, 0.184F, 0.184F, 1.00F);
style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.333F, 0.333F, 0.333F, 1.00F);
style.Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.439F, 0.439F, 0.439F, 1.00F);
style.Colors[ImGuiCol_CheckMark] = ImVec4(AR, AG, AB, 1.00F); // #EB5757
style.Colors[ImGuiCol_SliderGrab] = ImVec4(AR, AG, AB, 0.88F); // #EB5757
style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(AR, AG, AB, 1.00F); // #EB5757
style.Colors[ImGuiCol_NavHighlight] = ImVec4(0.00F, 0.00F, 0.00F, 0.00F);
style.Colors[ImGuiCol_Button] = ImVec4(0.165F, 0.165F, 0.165F, 1.00F); // #2A2A2A
style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.220F, 0.165F, 0.165F, 1.00F);
style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.270F, 0.180F, 0.180F, 1.00F);
style.Colors[ImGuiCol_Header] = ImVec4(0.200F, 0.200F, 0.200F, 1.00F); // #333333
style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.200F, 0.160F, 0.160F, 1.00F);
style.Colors[ImGuiCol_HeaderActive] = ImVec4(0.240F, 0.170F, 0.170F, 1.00F);
style.Colors[ImGuiCol_Border] = ImVec4(0.184F, 0.184F, 0.184F, 1.00F);
style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.00F, 0.00F, 0.00F, 0.00F);
style.Colors[ImGuiCol_Separator] = ImVec4(0.184F, 0.184F, 0.184F, 1.00F);
style.Colors[ImGuiCol_SeparatorHovered] = ImVec4(AR, AG, AB, 0.60F); // #EB5757
style.Colors[ImGuiCol_SeparatorActive] = ImVec4(AR, AG, AB, 0.80F); // #EB5757
style.Colors[ImGuiCol_ResizeGrip] = ImVec4(0.00F, 0.00F, 0.00F, 0.00F);
style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(AR, AG, AB, 0.30F);
style.Colors[ImGuiCol_ResizeGripActive] = ImVec4(AR, AG, AB, 0.50F);
style.Colors[ImGuiCol_Tab] = ImVec4(0.098F, 0.098F, 0.098F, 1.00F); // #191919
style.Colors[ImGuiCol_TabHovered] = ImVec4(0.165F, 0.165F, 0.165F, 1.00F); // #2A2A2A
style.Colors[ImGuiCol_TabActive] = ImVec4(0.125F, 0.125F, 0.125F, 1.00F); // #202020
style.Colors[ImGuiCol_TabUnfocused] = ImVec4(0.098F, 0.098F, 0.098F, 1.00F);
style.Colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.125F, 0.125F, 0.125F, 1.00F);
#ifdef ImGuiCol_TabSelectedOverline
style.Colors[ImGuiCol_TabSelectedOverline] = ImVec4(AR, AG, AB, 1.00F); // #EB5757
#endif
#ifdef ImGuiCol_TabDimmedSelectedOverline
style.Colors[ImGuiCol_TabDimmedSelectedOverline] = ImVec4(AR, AG, AB, 0.60F);
#endif
style.Colors[ImGuiCol_DockingPreview] = ImVec4(AR, AG, AB, 0.25F); // #EB5757
style.Colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.060F, 0.060F, 0.060F, 1.00F);
style.Colors[ImGuiCol_TableHeaderBg] = ImVec4(0.125F, 0.125F, 0.125F, 1.00F);
style.Colors[ImGuiCol_TableBorderStrong] = ImVec4(0.184F, 0.184F, 0.184F, 1.00F);
style.Colors[ImGuiCol_TableBorderLight] = ImVec4(0.149F, 0.149F, 0.149F, 1.00F);
style.Colors[ImGuiCol_TableRowBg] = ImVec4(0.00F, 0.00F, 0.00F, 0.00F);
style.Colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00F, 1.00F, 1.00F, 0.02F);
style.Colors[ImGuiCol_DragDropTarget] = ImVec4(1.00F, 1.00F, 1.00F, 1.00F);
style.Colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.00F, 0.00F, 0.00F, 0.56F);
// 优化 ImGuizmo 属性,使其看起来更高端并移除了舱口剖面线(虚线黑影)
ImGuizmo::Style& gizmoStyle = ImGuizmo::GetStyle();
@ -1009,9 +1039,9 @@ void MetaCoreEditorApp::DrawEditorFrame() {
DrawSceneViewWindow();
DrawGameViewWindow();
DrawPlaceholderDockWindow("UI Editor###ui_editor", "UI Editor");
DrawPlaceholderDockWindow("Anim Clip 2D###animclip2d_editor", "Animation Clip 2D");
DrawPlaceholderDockWindow("Anim FSM###animfsm_editor", "Animation FSM");
DrawPlaceholderDockWindow("UI编辑器###ui_editor", "UI编辑器");
DrawPlaceholderDockWindow("2D动画片段编辑器###animclip2d_editor", "2D动画片段编辑器");
DrawPlaceholderDockWindow("动画状态机编辑器###animfsm_editor", "动画状态机编辑器");
ApplyPendingDockTabSelections();
DrawEditorStatusBar();
}
@ -1024,31 +1054,66 @@ void MetaCoreEditorApp::DrawEditorToolbar() {
ImGuiWindowFlags_NoScrollWithMouse |
ImGuiWindowFlags_NoSavedSettings;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0F, 4.0F));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8.0F, 4.0F));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(4.0F, 4.0F)); // TOOLBAR_WIN_PAD
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0F, 4.0F)); // TOOLBAR_FRAME_PAD
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0F, 4.0F)); // TOOLBAR_ITEM_SPC
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0F); // TOOLBAR_FRAME_RND
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0F); // TOOLBAR_FRAME_BRD
if (ImGui::Begin("Toolbar###toolbar", nullptr, toolbarWindowFlags)) {
auto drawButton = [](const char* label, bool selected, bool enabled = true) {
if (selected) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.20F, 0.34F, 0.58F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.25F, 0.40F, 0.68F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.16F, 0.29F, 0.50F, 1.0F));
} else if (!enabled) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.16F, 0.16F, 0.16F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.16F, 0.16F, 0.16F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.16F, 0.16F, 0.16F, 1.0F));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.45F, 0.45F, 0.45F, 1.0F));
}
const bool clicked = enabled && ImGui::Button(label, ImVec2(0.0F, 24.0F));
if (selected) {
ImGui::PopStyleColor(3);
} else if (!enabled) {
ImGui::PopStyleColor(4);
enum ButtonStyle {
Idle = 0,
PlayActive,
PauseActive,
Disabled
};
auto drawButton = [](const char* label, ButtonStyle style) {
ImVec4 baseColor;
ImVec4 hoverColor;
ImVec4 activeColor;
ImVec4 textColor = ImVec4(0.812F, 0.812F, 0.812F, 1.00F);
bool enabled = true;
switch (style) {
case PlayActive:
baseColor = ImVec4(0.20F, 0.45F, 0.30F, 1.00F); // PLAY_ACTIVE
hoverColor = ImVec4(0.26F, 0.51F, 0.36F, 1.00F);
activeColor = ImVec4(0.32F, 0.57F, 0.42F, 1.00F);
break;
case PauseActive:
baseColor = ImVec4(0.50F, 0.40F, 0.15F, 1.00F); // PAUSE_ACTIVE
hoverColor = ImVec4(0.56F, 0.46F, 0.21F, 1.00F);
activeColor = ImVec4(0.62F, 0.52F, 0.27F, 1.00F);
break;
case Disabled:
baseColor = ImVec4(0.15F, 0.15F, 0.15F, 0.40F); // BTN_DISABLED
hoverColor = ImVec4(0.15F, 0.15F, 0.15F, 0.40F);
activeColor = ImVec4(0.15F, 0.15F, 0.15F, 0.40F);
textColor = ImVec4(0.40F, 0.40F, 0.40F, 1.00F);
enabled = false;
break;
case Idle:
default:
baseColor = ImVec4(0.18F, 0.18F, 0.18F, 1.00F); // BTN_IDLE
hoverColor = ImVec4(0.24F, 0.24F, 0.24F, 1.00F);
activeColor = ImVec4(0.30F, 0.30F, 0.30F, 1.00F);
break;
}
ImGui::PushStyleColor(ImGuiCol_Button, baseColor);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, hoverColor);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, activeColor);
ImGui::PushStyleColor(ImGuiCol_Text, textColor);
const bool clicked = enabled && ImGui::Button(label, ImVec2(52.0F, 24.0F));
ImGui::PopStyleColor(4);
return clicked;
};
const float windowWidth = ImGui::GetWindowWidth();
const float playControlsWidth = 300.0F;
const float playControlsWidth = 160.0F; // 52 * 3 + 2 * 2 = 160
ImGui::SetCursorPosX(std::max(8.0F, (windowWidth - playControlsWidth) * 0.5F));
if (const auto playModeService = ModuleRegistry_.ResolveService<MetaCoreIPlayModeService>();
@ -1056,109 +1121,170 @@ void MetaCoreEditorApp::DrawEditorToolbar() {
const MetaCorePlayModeState playState = playModeService->GetState();
const bool isPlaying = playState == MetaCorePlayModeState::Playing || playState == MetaCorePlayModeState::Paused;
const bool isPaused = playState == MetaCorePlayModeState::Paused;
if (playState == MetaCorePlayModeState::Edit) {
if (drawButton("Play", false)) {
(void)playModeService->EnterPlayMode(*EditorContext_);
}
} else {
if (drawButton("Stop", true)) {
// 1. Play / Stop
if (isPlaying) {
if (drawButton("停止", PlayActive)) {
(void)playModeService->ExitPlayMode(*EditorContext_);
}
} else {
if (drawButton("播放", Idle)) {
(void)playModeService->EnterPlayMode(*EditorContext_);
}
}
ImGui::SameLine(0.0F, 4.0F);
if (isPaused) {
if (drawButton("Resume", true)) {
ImGui::SameLine(0.0F, 2.0F);
// 2. Pause / Resume
if (!isPlaying) {
(void)drawButton("暂停", Disabled);
} else if (isPaused) {
if (drawButton("继续", PauseActive)) {
(void)playModeService->ResumePlayMode(*EditorContext_);
}
} else if (drawButton("Pause", false, isPlaying)) {
(void)playModeService->PausePlayMode(*EditorContext_);
} else {
if (drawButton("暂停", Idle)) {
(void)playModeService->PausePlayMode(*EditorContext_);
}
}
ImGui::SameLine(0.0F, 4.0F);
if (drawButton("Step", false, isPaused)) {
(void)playModeService->StepPlayMode(*EditorContext_, 1.0F / 60.0F);
ImGui::SameLine(0.0F, 2.0F);
// 3. Step
if (isPaused) {
if (drawButton("步进", Idle)) {
(void)playModeService->StepPlayMode(*EditorContext_, 1.0F / 60.0F);
}
} else {
(void)drawButton("步进", Disabled);
}
if (isPlaying) {
ImGui::SameLine(0.0F, 8.0F);
ImGui::Text("Time %.2fs", playModeService->GetElapsedPlayTimeSeconds());
ImGui::Text("已运行 %.2fs", playModeService->GetElapsedPlayTimeSeconds());
}
} else {
(void)drawButton("Play", false, false);
ImGui::SameLine(0.0F, 4.0F);
(void)drawButton("Pause", false, false);
ImGui::SameLine(0.0F, 4.0F);
(void)drawButton("Step", false, false);
(void)drawButton("播放", Disabled);
ImGui::SameLine(0.0F, 2.0F);
(void)drawButton("暂停", Disabled);
ImGui::SameLine(0.0F, 2.0F);
(void)drawButton("步进", Disabled);
}
constexpr float rightControlsWidth = 180.0F;
ImGui::SameLine(std::max(8.0F, windowWidth - rightControlsWidth));
if (ImGui::Button("Gizmos", ImVec2(78.0F, 24.0F))) {
// 右对齐下拉菜单
const float rightX = windowWidth - 160.0F;
if (rightX > 250.0F) {
ImGui::SameLine(rightX);
}
// Gizmos 下拉菜单 (对齐 Infernux "Gizmos v")
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0F, 0.0F, 0.0F, 0.0F)); // 幽灵按钮风格
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0F, 1.0F, 1.0F, 0.06F));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0F, 1.0F, 1.0F, 0.10F));
if (ImGui::Button("Gizmos v", ImVec2(72.0F, 24.0F))) {
ImGui::OpenPopup("MetaCoreToolbarGizmosPopup");
}
ImGui::SameLine(0.0F, 4.0F);
if (ImGui::Button("Camera", ImVec2(78.0F, 24.0F))) {
if (ImGui::Button("Camera v", ImVec2(72.0F, 24.0F))) {
ImGui::OpenPopup("MetaCoreToolbarCameraPopup");
}
ImGui::PopStyleColor(3);
if (ImGui::BeginPopup("MetaCoreToolbarGizmosPopup")) {
if (ImGui::MenuItem("View", nullptr, EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::None)) {
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0F, 8.0F)); // POPUP_WIN_PAD
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0F, 4.0F)); // POPUP_ITEM_SPC
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0F, 3.0F)); // POPUP_FRAME_PAD
ImGui::Dummy(ImVec2(180.0F, 0.0F));
ImGui::TextUnformatted("Gizmos 设置");
ImGui::Separator();
ImGui::Dummy(ImVec2(0.0F, 4.0F));
if (ImGui::MenuItem("视图", nullptr, EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::None)) {
EditorContext_->SetGizmoOperation(MetaCoreGizmoOperation::None);
}
if (ImGui::MenuItem("Move", nullptr, EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::Translate)) {
if (ImGui::MenuItem("位移", nullptr, EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::Translate)) {
EditorContext_->SetGizmoOperation(MetaCoreGizmoOperation::Translate);
}
if (ImGui::MenuItem("Rotate", nullptr, EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::Rotate)) {
if (ImGui::MenuItem("旋转", nullptr, EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::Rotate)) {
EditorContext_->SetGizmoOperation(MetaCoreGizmoOperation::Rotate);
}
if (ImGui::MenuItem("Scale", nullptr, EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::Scale)) {
if (ImGui::MenuItem("缩放", nullptr, EditorContext_->GetGizmoOperation() == MetaCoreGizmoOperation::Scale)) {
EditorContext_->SetGizmoOperation(MetaCoreGizmoOperation::Scale);
}
ImGui::Separator();
if (ImGui::MenuItem("Local", nullptr, EditorContext_->GetGizmoMode() == MetaCoreGizmoMode::Local)) {
if (ImGui::MenuItem("局部", nullptr, EditorContext_->GetGizmoMode() == MetaCoreGizmoMode::Local)) {
EditorContext_->SetGizmoMode(MetaCoreGizmoMode::Local);
}
if (ImGui::MenuItem("Global", nullptr, EditorContext_->GetGizmoMode() == MetaCoreGizmoMode::Global)) {
if (ImGui::MenuItem("全局", nullptr, EditorContext_->GetGizmoMode() == MetaCoreGizmoMode::Global)) {
EditorContext_->SetGizmoMode(MetaCoreGizmoMode::Global);
}
ImGui::Separator();
bool snapEnabled = EditorContext_->GetGizmoSnapSettings().Enabled;
if (ImGui::Checkbox("Snap", &snapEnabled)) {
if (ImGui::Checkbox("吸附", &snapEnabled)) {
EditorContext_->SetGizmoSnapEnabled(snapEnabled);
}
bool showGrid = EditorContext_->GetShowViewportGrid();
if (ImGui::Checkbox("Show Grid", &showGrid)) {
if (ImGui::Checkbox("显示网格", &showGrid)) {
EditorContext_->SetShowViewportGrid(showGrid);
}
ImGui::PopStyleVar(3);
ImGui::EndPopup();
}
if (ImGui::BeginPopup("MetaCoreToolbarCameraPopup")) {
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0F, 8.0F)); // POPUP_WIN_PAD
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0F, 4.0F)); // POPUP_ITEM_SPC
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0F, 3.0F)); // POPUP_FRAME_PAD
MetaCoreSceneView sceneView = EditorContext_->GetCameraController().BuildSceneView();
ImGui::TextUnformatted("Scene Camera");
ImGui::Dummy(ImVec2(340.0F, 0.0F));
ImGui::TextUnformatted("场景相机");
ImGui::Separator();
ImGui::Dummy(ImVec2(0.0F, 4.0F));
bool cameraChanged = false;
float fieldOfViewDegrees = sceneView.VerticalFieldOfViewDegrees;
ImGui::SetNextItemWidth(180.0F);
if (ImGui::SliderFloat("FOV", &fieldOfViewDegrees, 20.0F, 110.0F, "%.1f")) {
ImGui::TextUnformatted("视野角度");
ImGui::SameLine(130.0F);
ImGui::SetNextItemWidth(120.0F);
if (ImGui::SliderFloat("##fov_slider", &fieldOfViewDegrees, 10.0F, 120.0F, "%.1F")) {
sceneView.VerticalFieldOfViewDegrees = fieldOfViewDegrees;
cameraChanged = true;
}
ImGui::SetNextItemWidth(220.0F);
if (ImGui::DragFloat3("Position", glm::value_ptr(sceneView.CameraPosition), 0.05F)) {
ImGui::SameLine(0.0F, 6.0F);
ImGui::SetNextItemWidth(72.0F);
if (ImGui::InputFloat("##fov_input", &fieldOfViewDegrees, 1.0F, 10.0F, "%.1f")) {
sceneView.VerticalFieldOfViewDegrees = std::clamp(fieldOfViewDegrees, 10.0F, 120.0F);
cameraChanged = true;
}
ImGui::SetNextItemWidth(220.0F);
if (ImGui::DragFloat3("Target", glm::value_ptr(sceneView.CameraTarget), 0.05F)) {
ImGui::Dummy(ImVec2(0.0F, 4.0F));
ImGui::TextUnformatted("相机坐标");
ImGui::SetNextItemWidth(260.0F);
if (ImGui::DragFloat3("##pos_drag", glm::value_ptr(sceneView.CameraPosition), 0.05F)) {
cameraChanged = true;
}
ImGui::Dummy(ImVec2(0.0F, 4.0F));
ImGui::TextUnformatted("相机目标点");
ImGui::SetNextItemWidth(260.0F);
if (ImGui::DragFloat3("##target_drag", glm::value_ptr(sceneView.CameraTarget), 0.05F)) {
cameraChanged = true;
}
if (cameraChanged) {
EditorContext_->GetCameraController().ApplySceneView(sceneView);
}
if (ImGui::Button("Focus Selection", ImVec2(-1.0F, 0.0F))) {
ImGui::Dummy(ImVec2(0.0F, 4.0F));
if (ImGui::Button("聚焦选中物体", ImVec2(-1.0F, 24.0F))) {
if (MetaCoreGameObject selectedObject = EditorContext_->GetSelectedGameObject(); selectedObject) {
EditorContext_->GetCameraController().FocusGameObject(selectedObject);
}
}
if (ImGui::Button("Reset Camera", ImVec2(-1.0F, 0.0F))) {
if (ImGui::Button("重置相机设置", ImVec2(-1.0F, 24.0F))) {
MetaCoreSceneView defaultSceneView{};
defaultSceneView.CameraPosition = {0.0F, 2.5F, 6.5F};
defaultSceneView.CameraTarget = {0.0F, 0.7F, 0.0F};
@ -1166,11 +1292,13 @@ void MetaCoreEditorApp::DrawEditorToolbar() {
defaultSceneView.VerticalFieldOfViewDegrees = 60.0F;
EditorContext_->GetCameraController().ApplySceneView(defaultSceneView);
}
ImGui::PopStyleVar(3);
ImGui::EndPopup();
}
}
ImGui::End();
ImGui::PopStyleVar(2);
ImGui::PopStyleVar(5);
}
void MetaCoreEditorApp::DrawEditorStatusBar() {
@ -1188,25 +1316,118 @@ void MetaCoreEditorApp::DrawEditorStatusBar() {
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoSavedSettings;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0F, 3.0F));
ImGui::Begin("MetaCoreStatusBar", nullptr, statusWindowFlags);
ImGui::TextUnformatted("MetaCore");
ImGui::SameLine(0.0F, 16.0F);
if (const auto playModeService = ModuleRegistry_.ResolveService<MetaCoreIPlayModeService>();
playModeService != nullptr) {
const MetaCorePlayModeState playState = playModeService->GetState();
const char* stateText = playState == MetaCorePlayModeState::Edit ? "Edit" :
(playState == MetaCorePlayModeState::Playing ? "Playing" : "Paused");
ImGui::Text("Mode: %s", stateText);
}
if (const auto assetDatabaseService = ModuleRegistry_.ResolveService<MetaCoreIAssetDatabaseService>();
assetDatabaseService != nullptr && assetDatabaseService->HasProject()) {
ImGui::SameLine(0.0F, 16.0F);
const std::string projectRoot = assetDatabaseService->GetProjectDescriptor().RootPath.string();
ImGui::Text("Project: %s", projectRoot.c_str());
// Background color: STATUS_BAR_BG #131313
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.13F, 0.13F, 0.13F, 1.00F));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0F);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.0F, 4.0F)); // STATUS_BAR_WIN_PAD
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0F, 0.0F)); // STATUS_BAR_ITEM_SPC
if (ImGui::Begin("##MetaCoreStatusBar", nullptr, statusWindowFlags)) {
float dispW = ImGui::GetWindowWidth();
float leftZoneW = dispW - 160.0F;
// Retrieve log entries
const auto& entries = EditorContext_->GetLogService().GetEntries();
std::string latestMsg = "";
MetaCoreLogLevel latestLevel = MetaCoreLogLevel::Info;
int warnCount = 0;
int errorCount = 0;
for (const auto& entry : entries) {
if (entry.Level == MetaCoreLogLevel::Warning) {
warnCount++;
} else if (entry.Level == MetaCoreLogLevel::Error) {
errorCount++;
}
latestMsg = entry.Message;
latestLevel = entry.Level;
}
// 1. Clickable area for latest message -> opens/selects Console panel
float clickW = std::max(leftZoneW - 8.0F, 100.0F);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0F, 0.0F, 0.0F, 0.0F)); // Ghost
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.20F, 0.20F, 0.20F, 0.60F));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.25F, 0.25F, 0.25F, 0.80F));
if (ImGui::InvisibleButton("##StatusBarClick", ImVec2(clickW, 16.0F))) {
ModuleRegistry_.AccessPanelOpenState("Console") = true;
ImGui::SetWindowFocus("Console###console");
}
ImGui::PopStyleColor(3);
// Overlay text on the invisible button
ImGui::SameLine(6.0F);
// Truncate message
int maxChars = std::max(10, static_cast<int>((leftZoneW - 160.0F) / 8.0F));
std::string msgDisplay = latestMsg;
if (static_cast<int>(msgDisplay.size()) > maxChars) {
msgDisplay = msgDisplay.substr(0, maxChars - 1) + "...";
}
// Color and icon for level
ImVec4 levelClr = ImVec4(0.82F, 0.82F, 0.85F, 1.00F); // Info color
std::string icon = "";
if (latestLevel == MetaCoreLogLevel::Error) {
levelClr = ImVec4(0.922F, 0.341F, 0.341F, 1.00F); // Accent red
icon = "* "; // Character for error
} else if (latestLevel == MetaCoreLogLevel::Warning) {
levelClr = ImVec4(0.890F, 0.710F, 0.300F, 1.00F); // Yellow
icon = "! "; // Warning icon character
}
ImGui::PushStyleColor(ImGuiCol_Text, levelClr);
if (latestMsg.empty()) {
ImGui::TextUnformatted("MetaCore Ready");
} else {
ImGui::TextUnformatted((icon + msgDisplay).c_str());
}
ImGui::PopStyleColor();
// 2. Right counters: [W] N [E] N
float counterX = leftZoneW - 130.0F;
if (counterX > 0.0F) {
ImGui::SameLine(counterX);
// Warning count
if (warnCount > 0) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.890F, 0.710F, 0.300F, 1.00F));
} else {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.133F, 0.133F, 0.133F, 0.60F)); // Dimmed
}
ImGui::Text("! %d", warnCount);
ImGui::PopStyleColor();
ImGui::SameLine(0.0F, 12.0F);
// Error count
if (errorCount > 0) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.922F, 0.341F, 0.341F, 1.00F));
} else {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.133F, 0.133F, 0.133F, 0.60F)); // Dimmed
}
ImGui::Text("* %d", errorCount);
ImGui::PopStyleColor();
}
// 3. Engine status (right zone)
float statusX = leftZoneW + 8.0F;
ImGui::SameLine(statusX);
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.55F, 0.55F, 0.55F, 1.00F)); // Dim
if (const auto playModeService = ModuleRegistry_.ResolveService<MetaCoreIPlayModeService>();
playModeService != nullptr) {
const MetaCorePlayModeState playState = playModeService->GetState();
const char* stateText = playState == MetaCorePlayModeState::Edit ? "Edit" :
(playState == MetaCorePlayModeState::Playing ? "Playing" : "Paused");
ImGui::TextUnformatted(stateText);
} else {
ImGui::TextUnformatted("Edit");
}
ImGui::PopStyleColor();
}
ImGui::End();
ImGui::PopStyleVar();
ImGui::PopStyleVar(3);
ImGui::PopStyleColor();
}
void MetaCoreEditorApp::DrawSceneViewWindow() {
@ -1225,7 +1446,7 @@ void MetaCoreEditorApp::DrawSceneViewWindow() {
ImGuiWindowFlags_NoScrollWithMouse;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0F, 0.0F));
if (ImGui::Begin("\u573A\u666F###scene_view", &sceneOpen, sceneWindowFlags)) {
if (ImGui::Begin("场景###scene_view", &sceneOpen, sceneWindowFlags)) {
MetaCoreSceneViewportState& viewportState = EditorContext_->GetSceneViewportState();
ImVec2 viewportPos = ImGui::GetCursorScreenPos();
ImVec2 viewportSize = ImGui::GetContentRegionAvail();
@ -1276,22 +1497,22 @@ void MetaCoreEditorApp::DrawSceneViewWindow() {
ImGui::Dummy(viewportSize);
}
// 场景视口浮动快捷工具条 (Toolbar Overlay)
// 浮动在视口左上角采用半透明暗色背景1:1 复刻 Unity 视口功能控制体验
// 场景视口浮动快捷工具条 (ViewportToolbar)
ImGui::SetCursorScreenPos(ImVec2(viewportPos.x + 10.0f, viewportPos.y + 10.0f));
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.12f, 0.12f, 0.12f, 0.85f));
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.0f, 4.0f));
if (ImGui::BeginChild("ViewportToolbar", ImVec2(410.0f, 28.0f), false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
if (ImGui::BeginChild("ViewportToolbar", ImVec2(370.0f, 28.0f), false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
// A. Gizmo 轴操作切换 (T: 位移, R: 旋转, S: 缩放)
auto currentOp = EditorContext_->GetGizmoOperation();
auto drawToolbarButton = [](const char* label, bool active) -> bool {
if (active) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.173f, 0.365f, 0.529f, 1.0f)); // 选中亮蓝色
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.22f, 0.45f, 0.65f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.12f, 0.28f, 0.42f, 1.0f));
// 替换亮蓝色为 Notion 红灰风格高亮
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.270F, 0.180F, 0.180F, 1.00F));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.320f, 0.220f, 0.220f, 1.00F));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.200f, 0.140f, 0.140f, 1.00F));
} else {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); // 平时透明
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.12f));
@ -1323,10 +1544,10 @@ void MetaCoreEditorApp::DrawSceneViewWindow() {
ImGui::TextDisabled("|");
ImGui::SameLine(0, 6);
// B. 坐标系切换 (Local本地 / Global世界)
// B. 坐标系切换 (局部 / 全局)
auto currentMode = EditorContext_->GetGizmoMode();
const char* modeLabel = (currentMode == MetaCoreGizmoMode::Local) ? "Local" : "Global";
if (ImGui::Button(modeLabel, ImVec2(50.0f, 20.0f))) {
const char* modeLabel = (currentMode == MetaCoreGizmoMode::Local) ? "局部" : "全局";
if (ImGui::Button(modeLabel, ImVec2(40.0f, 20.0f))) {
EditorContext_->SetGizmoMode(currentMode == MetaCoreGizmoMode::Local ? MetaCoreGizmoMode::Global : MetaCoreGizmoMode::Local);
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("切换本地/全局坐标系");
@ -1335,10 +1556,10 @@ void MetaCoreEditorApp::DrawSceneViewWindow() {
ImGui::TextDisabled("|");
ImGui::SameLine(0, 6);
// C. 枢轴模式切换 (Pivot枢轴 / Center中心)
// C. 枢轴模式切换 (枢轴 / 中心)
auto currentPivot = EditorContext_->GetGizmoPivotMode();
const char* pivotLabel = (currentPivot == MetaCoreGizmoPivotMode::Pivot) ? "Pivot" : "Center";
if (ImGui::Button(pivotLabel, ImVec2(55.0f, 20.0f))) {
const char* pivotLabel = (currentPivot == MetaCoreGizmoPivotMode::Pivot) ? "枢轴" : "中心";
if (ImGui::Button(pivotLabel, ImVec2(40.0f, 20.0f))) {
EditorContext_->SetGizmoPivotMode(currentPivot == MetaCoreGizmoPivotMode::Pivot ? MetaCoreGizmoPivotMode::Center : MetaCoreGizmoPivotMode::Pivot);
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("切换 Pivot/Center 枢轴中心");
@ -1367,7 +1588,7 @@ void MetaCoreEditorApp::DrawSceneViewWindow() {
ImGui::SameLine(0, 6);
ImGui::TextDisabled("|");
ImGui::SameLine(0, 6);
if (ImGui::Button("Reset Cam", ImVec2(75.0f, 20.0f))) {
if (ImGui::Button("复位", ImVec2(40.0f, 20.0f))) {
EditorContext_->GetCameraController().FocusGameObject({}); // 传入空 GameObject 表示复位摄像机到初始中心位置
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("复位视口摄像机");
@ -1454,7 +1675,7 @@ void MetaCoreEditorApp::DrawGameViewWindow() {
ImGuiWindowFlags_NoScrollWithMouse;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0F, 0.0F));
if (ImGui::Begin("\u6E38\u620F###game_view", nullptr, gameWindowFlags)) {
if (ImGui::Begin("游戏###game_view", nullptr, gameWindowFlags)) {
const ImVec2 origin = ImGui::GetCursorScreenPos();
ImVec2 size = ImGui::GetContentRegionAvail();
if (size.x < 1.0F) {

View File

@ -18,35 +18,68 @@ void MetaCoreEditorCameraController::Update(const MetaCoreSceneViewportState& vi
}
ImGuiIO& io = ImGui::GetIO();
if (io.WantCaptureMouse) {
return;
}
// We remove the strict WantCaptureMouse check here because the entire Scene Viewport
// is an ImGui window, and WantCaptureMouse is always true.
// We rely on viewportState.Hovered instead to determine if we should process input.
const bool altDown = input.IsKeyDown(MetaCoreInputKey::LeftAlt) || input.IsKeyDown(MetaCoreInputKey::RightAlt);
// Zoom: Alt + Right Drag or Mouse Wheel
if (viewportState.Hovered && altDown && ImGui::IsMouseDragging(ImGuiMouseButton_Right)) {
Distance_ *= (1.0F - io.MouseDelta.y * 0.01F);
Distance_ = std::clamp(Distance_, 0.5F, 100.0F);
} else if (viewportState.Hovered && std::abs(io.MouseWheel) > 0.001F) {
Distance_ *= (io.MouseWheel > 0.0F) ? 0.88F : 1.12F;
Distance_ = std::clamp(Distance_, 0.5F, 100.0F);
Distance_ = std::max(0.01F, Distance_);
} else if (viewportState.Hovered && !ImGui::IsMouseDown(ImGuiMouseButton_Right) && std::abs(io.MouseWheel) > 0.001F) {
// [Unity Style] 滚轮直接带动整个相机坐标系朝正前方无限推进/后退
float scrollSpeed = std::max(1.0F, Distance_ * 0.1F) * CameraSpeed_ * 0.1F;
Target_ += GetForwardDirection() * io.MouseWheel * scrollSpeed;
}
// Orbit: Alt + Left Drag
if (viewportState.Hovered && altDown && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
YawDegrees_ -= io.MouseDelta.x * 0.35F; // 逆时针
YawDegrees_ += io.MouseDelta.x * 0.35F; // [Unity Style] 右拖逆时针
PitchDegrees_ -= io.MouseDelta.y * 0.35F;
PitchDegrees_ = std::clamp(PitchDegrees_, -85.0F, 85.0F);
}
// Look Around: Right Drag (without Alt)
else if (viewportState.Hovered && !altDown && ImGui::IsMouseDragging(ImGuiMouseButton_Right)) {
// Fly Mode / Look Around: Right Hold (without Alt)
else if (viewportState.Hovered && !altDown && ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
// --- 0. 滚轮动态调速 ---
if (std::abs(io.MouseWheel) > 0.001F) {
CameraSpeed_ *= (io.MouseWheel > 0.0F) ? 1.25F : 0.8F;
CameraSpeed_ = std::clamp(CameraSpeed_, 0.1F, 2000.0F);
}
// --- 1. 原地张望 (Free Look) ---
const glm::vec3 oldPosition = GetCameraPosition();
YawDegrees_ -= io.MouseDelta.x * 0.35F;
PitchDegrees_ -= io.MouseDelta.y * 0.35F;
// [Unity Style] 鼠标右移=视角右转(Yaw++),鼠标下移=视角下看(Pitch--)
YawDegrees_ += io.MouseDelta.x * 0.18F;
PitchDegrees_ -= io.MouseDelta.y * 0.18F;
PitchDegrees_ = std::clamp(PitchDegrees_, -85.0F, 85.0F);
Target_ = oldPosition + GetForwardDirection() * Distance_;
// --- 2. 自由漫游飞行 (FPS Flight) ---
glm::vec3 movement(0.0F);
if (ImGui::IsKeyDown(ImGuiKey_W)) movement += GetForwardDirection();
if (ImGui::IsKeyDown(ImGuiKey_S)) movement -= GetForwardDirection();
if (ImGui::IsKeyDown(ImGuiKey_D)) movement += GetRightDirection();
if (ImGui::IsKeyDown(ImGuiKey_A)) movement -= GetRightDirection();
if (ImGui::IsKeyDown(ImGuiKey_E)) movement += glm::vec3(0.0F, 0.0F, 1.0F);
if (ImGui::IsKeyDown(ImGuiKey_Q)) movement -= glm::vec3(0.0F, 0.0F, 1.0F);
if (glm::length(movement) > 0.001F) {
movement = glm::normalize(movement);
float speed = CameraSpeed_ * io.DeltaTime;
if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) {
speed *= 3.0F;
}
Target_ += movement * speed;
}
}
// Pan: Middle Drag

View File

@ -26,6 +26,7 @@ private:
glm::vec3 Target_{0.0F, 0.0F, 0.0F};
float Distance_ = 10.0F;
float CameraSpeed_ = 10.0F; // [MetaCore] Dynamic camera flight speed
float YawDegrees_ = 45.0F;
float PitchDegrees_ = -35.0F;
float FieldOfViewDegrees_ = 60.0F;

View File

@ -203,6 +203,7 @@ void MetaCoreExpandFocusBounds(
void MetaCoreSceneInteractionService::ResetFrameState() {
GizmoWasUsing_ = false;
HasGizmoBeforeSnapshot_ = false;
GizmoDragStarted_ = false;
}
MetaCoreId MetaCoreSceneInteractionService::PickGameObjectFromViewport(
@ -229,11 +230,11 @@ MetaCoreId MetaCoreSceneInteractionService::PickGameObjectFromViewport(
sceneView.CameraTarget,
sceneView.CameraUp
);
const glm::mat4 projectionMatrix = glm::perspective(
const glm::mat4 projectionMatrix = glm::perspectiveZO(
glm::radians(sceneView.VerticalFieldOfViewDegrees),
viewportState.Width / viewportState.Height,
0.05F,
500.0F
0.1F,
1000.0F
);
const float normalizedX = (localCursor.x / viewportState.Width) * 2.0F - 1.0F;
@ -509,13 +510,19 @@ void MetaCoreSceneInteractionService::HandleGizmoManipulation(MetaCoreEditorCont
const int h = static_cast<int>(viewportState.Height);
const float gizmoAspect = (h > 0) ? (static_cast<float>(w) / static_cast<float>(h)) : 1.0f;
const glm::mat4 gizmoViewMatrix = glm::lookAt(sceneView.CameraPosition, sceneView.CameraTarget, sceneView.CameraUp);
const glm::mat4 gizmoProjectionMatrix = glm::perspective(
const glm::mat4 gizmoProjectionMatrix = glm::perspectiveZO(
glm::radians(sceneView.VerticalFieldOfViewDegrees),
gizmoAspect, 0.1F, 1000.0F
);
// 获取当前物体的真实世界矩阵
glm::mat4 worldMatrixMetaCore = MetaCoreBuildSelectionGizmoWorldMatrix(editorContext, selectedObject.GetId());
const glm::mat4 originalWorldMatrixMetaCore = worldMatrixMetaCore;
// 若当前未进行拖拽,保持每一帧更新初始矩阵为物体的最新矩阵
if (!GizmoDragStarted_) {
GizmoStartMatrix_ = worldMatrixMetaCore;
GizmoCurrentMatrix_ = worldMatrixMetaCore;
}
ImGuizmo::SetOrthographic(false);
ImGuizmo::Enable(true);
@ -538,32 +545,29 @@ void MetaCoreSceneInteractionService::HandleGizmoManipulation(MetaCoreEditorCont
ImGuizmo::PushID(reinterpret_cast<void*>(selectedObject.GetId()));
glm::mat4 gizmoMatrix = worldMatrixMetaCore;
const MetaCoreGizmoSnapSettings& snapSettings = editorContext.GetGizmoSnapSettings();
const float snapStep = snapSettings.StepForOperation(currentOp);
const float snapValues[3] = {snapStep, snapStep, snapStep};
ImGuizmo::Manipulate(
glm::value_ptr(gizmoViewMatrix),
glm::value_ptr(gizmoProjectionMatrix),
MetaCoreToImGuizmoOperation(currentOp),
effectiveMode,
glm::value_ptr(gizmoMatrix),
nullptr,
snapSettings.Enabled ? snapValues : nullptr
glm::value_ptr(gizmoMatrix)
);
const bool gizmoUsing = ImGuizmo::IsUsing();
ImGuizmo::PopID();
HandleGizmoBeginUse(editorContext, gizmoUsing);
if (gizmoUsing && gizmoMatrix != originalWorldMatrixMetaCore) {
(void)ApplyWorldTransformDeltaToSelection(
editorContext,
selectedObject.GetId(),
originalWorldMatrixMetaCore,
gizmoMatrix,
editorContext.GetGizmoPivotMode()
);
if (gizmoUsing && gizmoMatrix != GizmoStartMatrix_) {
glm::mat4 newLocalMatrix = gizmoMatrix;
if (selectedObject.GetParentId() != 0) {
const glm::mat4 parentWorldMatrixMetaCore = MetaCoreBuildWorldTransformMatrix(editorContext.GetScene(), selectedObject.GetParentId());
newLocalMatrix = glm::inverse(parentWorldMatrixMetaCore) * gizmoMatrix;
}
MetaCoreApplyMatrixToTransform(newLocalMatrix, selectedObject.GetComponent<MetaCoreTransformComponent>());
}
HandleGizmoEndUse(editorContext, gizmoUsing);
}
@ -776,9 +780,10 @@ void MetaCoreSceneInteractionService::HandleShortcuts(MetaCoreEditorContext& edi
FocusSelectedObject(editorContext);
}
if (ImGui::IsKeyPressed(ImGuiKey_A)) {
FocusVisibleScene(editorContext);
}
// [MetaCore] Removed 'A' hotkey for FocusVisibleScene because it conflicts with WASD FPS flight
// if (ImGui::IsKeyPressed(ImGuiKey_A)) {
// FocusVisibleScene(editorContext);
// }
if (ImGui::IsKeyPressed(ImGuiKey_G)) {
editorContext.SetShowViewportGrid(!editorContext.GetShowViewportGrid());

View File

@ -46,6 +46,11 @@ private:
bool GizmoWasUsing_ = false;
bool HasGizmoBeforeSnapshot_ = false;
MetaCoreEditorStateSnapshot GizmoBeforeSnapshot_{};
// 记录 Gizmo 拖拽状态以防止单轴拖拽卡顿并保持视觉轴实时跟随
bool GizmoDragStarted_ = false;
glm::mat4 GizmoStartMatrix_{1.0F};
glm::mat4 GizmoCurrentMatrix_{1.0F};
};
} // namespace MetaCore

View File

@ -22,10 +22,12 @@
#include <filament/LightManager.h>
#include <filament/RenderableManager.h>
#include <filament/MaterialInstance.h>
#include <filament/IndirectLight.h>
#include <gltfio/AssetLoader.h>
#include <gltfio/FilamentAsset.h>
#include <gltfio/ResourceLoader.h>
#include <gltfio/TextureProvider.h>
#include <gltfio/MaterialProvider.h>
#include <utils/Entity.h>
@ -94,12 +96,18 @@ public:
Scene_ = Engine_->createScene();
// 创建默认灯光
filament::LightManager::ShadowOptions shadowOptions;
shadowOptions.mapSize = 2048;
shadowOptions.shadowCascades = 3;
// 主光 (Sun)
Light_ = utils::EntityManager::get().create();
filament::LightManager::Builder(filament::LightManager::Type::DIRECTIONAL)
.color(filament::Color::toLinear<filament::ACCURATE>({ 1.0f, 1.0f, 1.0f }))
.color(filament::Color::toLinear<filament::ACCURATE>({ 1.0f, 0.95f, 0.9f }))
.intensity(100000.0f)
.direction({ 0.5f, 0.5f, -1.0f }) // Z-up 下 Z 向下
.direction({ 0.5f, 0.5f, -1.0f })
.castShadows(true)
.shadowOptions(shadowOptions)
.build(*Engine_, Light_);
Scene_->addEntity(Light_);
EntitiesInScene_.insert(Light_);
@ -165,6 +173,63 @@ public:
MaterialProvider_ = filament::gltfio::createJitShaderProvider(Engine_);
NameManager_ = new utils::NameComponentManager(utils::EntityManager::get());
AssetLoader_ = filament::gltfio::AssetLoader::create({ Engine_, MaterialProvider_, NameManager_ });
ResourceLoader_ = new filament::gltfio::ResourceLoader({ Engine_ });
// 创建 STB 图像解码器以支持 JPG/PNG 等内部贴图
StbDecoder_ = filament::gltfio::createStbProvider(Engine_);
ResourceLoader_->addTextureProvider("image/png", StbDecoder_);
ResourceLoader_->addTextureProvider("image/jpeg", StbDecoder_);
// 构造一个 1x1 的浅灰色 Texture 充当 IBL 反射天空盒
// 生成标准的 256x256 全级 Mipmap IBL 反射贴图
// Filament 要求 IBL 至少 256 且具有完整的 Mipmap否则带有 Roughness 的 PBR 材质(尤其是金属)会因为采样不到而死黑!
// 生成标准的 256x256 全级 Mipmap HDR(RGBA16F) IBL 反射贴图
// Filament 的 IndirectLight::reflections 严格要求必须是 HDR 格式(如 RGBA16F如果给 LDR (RGBA8) 则会直接被丢弃!
// 必须使用 FLOAT 数据类型提交!
uint32_t texSize = 256;
IblTexture_ = filament::Texture::Builder()
.width(texSize).height(texSize).levels(9) // 256 共有 9 级 mipmap
.usage(filament::Texture::Usage::DEFAULT)
.format(filament::Texture::InternalFormat::RGBA16F)
.sampler(filament::Texture::Sampler::SAMPLER_CUBEMAP)
.build(*Engine_);
for (uint8_t level = 0; level < 9; ++level) {
uint32_t shifted = texSize >> level;
uint32_t levelSize = shifted > 0 ? shifted : 1;
size_t facePixels = levelSize * levelSize;
float* colors = new float[facePixels * 6 * 4];
for (size_t i = 0; i < facePixels * 6 * 4; ++i) colors[i] = 0.5f; // HDR 环境光强度 0.5
filament::Texture::PixelBufferDescriptor bufferDesc(
colors, facePixels * 6 * 4 * sizeof(float),
filament::Texture::Format::RGBA,
filament::Texture::Type::FLOAT,
[](void* buffer, size_t size, void* user) { delete[] static_cast<float*>(buffer); }
);
filament::Texture::FaceOffsets faceOffsets;
for (int i = 0; i < 6; ++i) faceOffsets.offsets[i] = i * facePixels * 4 * sizeof(float);
IblTexture_->setImage(*Engine_, level, std::move(bufferDesc), faceOffsets);
}
// 生成默认全局环境漫反射与镜面反射 (IBL)
filament::math::float3 sh[9] = {};
sh[0] = { 1.0f, 1.0f, 1.0f }; // 基础环境光亮度
IndirectLight_ = filament::IndirectLight::Builder()
.irradiance(3, sh)
.reflections(IblTexture_) // 恢复物理正确的镜面反射
.intensity(30000.0f) // 正常物理光强
.build(*Engine_);
Scene_->setIndirectLight(IndirectLight_);
// 开启高级后处理
View_->setAntiAliasing(filament::View::AntiAliasing::FXAA);
View_->setBloomOptions({ .strength = 0.15f, .resolution = 384, .levels = 6, .blendMode = filament::View::BloomOptions::BlendMode::ADD, .threshold = true });
std::cout << "FilamentSceneBridge: Initialized with Offscreen=" << (offscreen ? "true" : "false") << std::endl;
return true;
@ -184,6 +249,7 @@ public:
std::cout << "[DEBUG] Destroyed asset for ID: " << id << std::endl;
}
LoadedAssets_.clear();
AssetBuffers_.clear();
// 2. 销毁所有 pivotEntity 中间辅助坐标变换实体
for (auto& [objectId, assetEntity] : ObjectToFilamentEntity_) {
@ -211,16 +277,9 @@ public:
}
ObjectToFilamentEntity_.clear();
ObjectWorldMatrices_.clear();
AssetBuffers_.clear();
// 3. 销毁所有克隆的材质实例,由于引用的实体都已被销毁,此时可安全释放
for (auto& [id, mats] : AssetDuplicatedMaterials_) {
for (auto* mat : mats) {
if (mat) {
Engine_->destroy(mat);
}
}
}
AssetDuplicatedMaterials_.clear();
for (auto& [id, entity] : SceneLightEntities_) {
if (Scene_) {
@ -240,6 +299,22 @@ public:
filament::gltfio::AssetLoader::destroy(&AssetLoader_);
AssetLoader_ = nullptr;
}
if (ResourceLoader_) {
delete ResourceLoader_;
ResourceLoader_ = nullptr;
}
if (StbDecoder_) {
delete StbDecoder_;
StbDecoder_ = nullptr;
}
if (IndirectLight_) {
Engine_->destroy(IndirectLight_);
IndirectLight_ = nullptr;
}
if (IblTexture_) {
Engine_->destroy(IblTexture_);
IblTexture_ = nullptr;
}
if (MaterialProvider_) {
MaterialProvider_->destroyMaterials();
delete MaterialProvider_;
@ -525,18 +600,10 @@ public:
// 确保顶级父 ID 的变换缓存也被清除
ObjectWorldMatrices_.erase(hostRootId);
// 销毁克隆的材质
auto matIt = AssetDuplicatedMaterials_.find(hostRootId);
if (matIt != AssetDuplicatedMaterials_.end()) {
for (auto* mat : matIt->second) {
if (mat) {
Engine_->destroy(mat);
}
}
AssetDuplicatedMaterials_.erase(matIt);
}
// 从 LoadedAssets_ 中移除
AssetBuffers_.erase(hostRootId);
it = LoadedAssets_.erase(it);
} else {
++it;
@ -599,8 +666,10 @@ public:
}
// 加载贴图等资源
filament::gltfio::ResourceLoader resourceLoader({ Engine_ });
resourceLoader.loadResources(asset);
if (ResourceLoader_) {
ResourceLoader_->loadResources(asset);
ResourceLoader_->asyncUpdateLoad(); // 触发底层的纹理同步
}
// 添加到场景
Scene_->addEntities(asset->getEntities(), asset->getEntityCount());
@ -613,32 +682,9 @@ public:
}
LoadedAssets_[hostRootId] = asset;
AssetBuffers_[hostRootId] = std::move(buffer);
// 复制材质实例防止共享干扰,放在加载好资源后
{
auto& rm = Engine_->getRenderableManager();
std::vector<filament::MaterialInstance*>& dupList = AssetDuplicatedMaterials_[hostRootId];
const utils::Entity* assetEntities = asset->getEntities();
size_t assetEntityCount = asset->getEntityCount();
for (size_t i = 0; i < assetEntityCount; ++i) {
utils::Entity ent = assetEntities[i];
if (!ent) continue;
auto rmInst = rm.getInstance(ent);
if (rmInst) {
size_t primCount = rm.getPrimitiveCount(rmInst);
for (size_t primIndex = 0; primIndex < primCount; ++primIndex) {
filament::MaterialInstance* originalMat = rm.getMaterialInstanceAt(rmInst, primIndex);
if (originalMat) {
filament::MaterialInstance* dupMat = filament::MaterialInstance::duplicate(originalMat);
if (dupMat) {
rm.setMaterialInstanceAt(rmInst, primIndex, dupMat);
dupList.push_back(dupMat);
}
}
}
}
}
}
if (scene != nullptr) {
// 有 ECS 实例模式下同步层级结构并补挂 Tag
@ -928,6 +974,11 @@ public:
return;
}
// 持续推进后台的纹理解码任务!
if (ResourceLoader_) {
ResourceLoader_->asyncUpdateLoad();
}
bool renderRuntimeUi = false;
if (!RenderTarget_ && RuntimeUIView_ && RuntimeUiHelper_) {
const auto viewport = View_->getViewport();
@ -1263,12 +1314,17 @@ private:
activeLightIds.insert(light.ObjectId);
auto lightIt = SceneLightEntities_.find(light.ObjectId);
if (lightIt == SceneLightEntities_.end()) {
filament::LightManager::ShadowOptions shadowOptions;
shadowOptions.mapSize = 2048;
shadowOptions.shadowCascades = 3;
utils::Entity lightEntity = utils::EntityManager::get().create();
filament::LightManager::Builder(filament::LightManager::Type::DIRECTIONAL)
.color(BuildFilamentLightColor(light.Color))
.intensity(BuildFilamentLightIntensity(light.Intensity))
.direction(BuildFilamentLightDirection(light.WorldMatrix))
.castShadows(true)
.shadowOptions(shadowOptions)
.build(*Engine_, lightEntity);
Scene_->addEntity(lightEntity);
EntitiesInScene_.insert(lightEntity);
@ -1331,14 +1387,19 @@ private:
filament::gltfio::AssetLoader* AssetLoader_ = nullptr;
filament::gltfio::MaterialProvider* MaterialProvider_ = nullptr;
filament::gltfio::ResourceLoader* ResourceLoader_ = nullptr;
utils::NameComponentManager* NameManager_ = nullptr;
filament::IndirectLight* IndirectLight_ = nullptr;
filament::Texture* IblTexture_ = nullptr;
filament::gltfio::TextureProvider* StbDecoder_ = nullptr;
MetaCoreSceneRenderSync RenderSync_{};
MetaCoreSceneRenderSyncSnapshot LastSyncSnapshot_{};
std::unordered_map<MetaCoreId, glm::mat4> ObjectWorldMatrices_{};
std::unordered_map<MetaCoreId, std::pair<filament::gltfio::FilamentAsset*, utils::Entity>> ObjectToFilamentEntity_;
std::unordered_map<MetaCoreId, filament::gltfio::FilamentAsset*> LoadedAssets_;
std::unordered_map<MetaCoreId, std::vector<filament::MaterialInstance*>> AssetDuplicatedMaterials_;
std::unordered_map<MetaCoreId, std::vector<char>> AssetBuffers_;
std::unordered_map<MetaCoreId, utils::Entity> SceneLightEntities_;
std::unordered_set<utils::Entity, utils::Entity::Hasher> EntitiesInScene_;

View File

@ -89,7 +89,10 @@ MetaCoreImGuiHelper::MetaCoreImGuiHelper(Engine* engine, filament::View* view, c
mRenderable = em.create();
mScene->addEntity(mRenderable);
ImGui::StyleColorsDark();
if (mOwnsImGuiContext) {
// 只有在拥有私有 ImGui 上下文时才调用 StyleColorsDark()
ImGui::StyleColorsDark();
}
}
void MetaCoreImGuiHelper::createAtlasTexture(Engine* engine) {

View File

@ -1098,19 +1098,24 @@ namespace IMGUIZMO_NAMESPACE
// projection reverse
vec_t nearPos, farPos;
nearPos.Transform(makeVect(0, 0, 1.f, 1.f), gContext.mProjectionMat);
farPos.Transform(makeVect(0, 0, 2.f, 1.f), gContext.mProjectionMat);
nearPos.Transform(makeVect(0, 0, 1.f, 1.f), gContext.mProjectionMat);
farPos.Transform(makeVect(0, 0, 2.f, 1.f), gContext.mProjectionMat);
gContext.mReversed = (nearPos.z/nearPos.w) > (farPos.z / farPos.w);
// FIXED: Force mReversed to false. The automatic detection fails for Right-Handed ZO projection matrices,
// causing rays to be cast backwards from the far plane, which breaks horizon detection.
gContext.mReversed = false;
// compute scale from the size of camera right vector projected on screen at the matrix position
const float zNear = gContext.mReversed ? (1.f - FLT_EPSILON) : 0.f;// compute scale from the size of camera right vector projected on screen at the matrix position
vec_t pointRight = viewInverse.v.right;
pointRight.TransformPoint(gContext.mViewProjection);
vec_t rightViewInverse = viewInverse.v.right;
rightViewInverse.TransformVector(gContext.mModelInverse);
float rightLength = GetSegmentLengthClipSpace(makeVect(0.f, 0.f), rightViewInverse);
gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / rightLength;
if (!gContext.mbUsing)
{
gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / rightLength;
}
ImVec2 centerSSpace = worldToPos(makeVect(0.f, 0.f), gContext.mMVP);
gContext.mScreenSquareCenter = centerSSpace;
@ -2169,7 +2174,12 @@ namespace IMGUIZMO_NAMESPACE
ImGui::CaptureMouseFromApp();
#endif
const float signedLength = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan);
const float len = fabsf(signedLength); // near plan
if (signedLength < 0.0f) {
// FIXED: If intersection is behind the camera (mouse points at the sky above the horizon),
// ignore the movement to prevent the object from shooting to infinity.
return false;
}
const float len = signedLength; // FIXED: Removed fabsf to allow correct projection when ray diverges
const vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len;
// compute delta
@ -2258,8 +2268,15 @@ namespace IMGUIZMO_NAMESPACE
for (unsigned int i = 0; i < 3; i++)
{
vec_t orthoVector = Cross(movePlanNormal[i], cameraToModelNormalized);
movePlanNormal[i].Cross(orthoVector);
movePlanNormal[i].Normalize();
if (orthoVector.Length() < 0.1f) // FIXED: If axis is almost parallel to camera, use view plane to avoid singularity
{
movePlanNormal[i] = -gContext.mCameraDir;
}
else
{
movePlanNormal[i].Cross(orthoVector);
movePlanNormal[i].Normalize();
}
}
// pickup plan
gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MT_MOVE_X]);
@ -2323,6 +2340,10 @@ namespace IMGUIZMO_NAMESPACE
ImGui::CaptureMouseFromApp();
#endif
const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan);
if (len < 0.0f) {
// FIXED: Prevent sky/horizon explosion
return false;
}
vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len;
vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor;
vec_t delta = newOrigin - gContext.mModelLocal.v.position;