diff --git a/SandboxProject/Assets/Models/jyc.glb b/SandboxProject/Assets/Models/jyc.glb new file mode 100644 index 0000000..9326300 Binary files /dev/null and b/SandboxProject/Assets/Models/jyc.glb differ diff --git a/SandboxProject/Assets/Models/jyc.glb.mcmeta b/SandboxProject/Assets/Models/jyc.glb.mcmeta new file mode 100644 index 0000000..2fbe0a0 --- /dev/null +++ b/SandboxProject/Assets/Models/jyc.glb.mcmeta @@ -0,0 +1,22 @@ +{ + "asset_type": "model", + "guid": "fa8372eb-d4ea-4e8a-93ab-768df4f04c4a", + "importer_id": "GltfModelImporter", + "package_path": "Assets/Models/jyc.glb.mcasset", + "source_hash": 6864693034290467672, + "source_path": "Assets/Models/jyc.glb", + "sub_assets": [ + { + "guid": "50f3a009-11f7-c225-e32c-79f879faf21e", + "index": 0, + "name": "jyc", + "type": "mesh" + }, + { + "guid": "64cad172-c4ea-929e-1c10-8a6af41df213", + "index": 0, + "name": "jyc_Material", + "type": "material" + } + ] +} \ No newline at end of file diff --git a/SandboxProject/Scenes/Main.mcscene b/SandboxProject/Scenes/Main.mcscene index 1b9bd30..23f6c0a 100644 --- a/SandboxProject/Scenes/Main.mcscene +++ b/SandboxProject/Scenes/Main.mcscene @@ -47,12 +47,547 @@ 0.0 ], "RotationEulerDegrees": [ - -45.0, - 30.0, - 0.0 + -55.14899826049805, + -54.19499969482422, + -33.18817901611328 ], "Scale": [ 1.0, + 0.9999997615814209, + 0.9999995231628418 + ] + } + }, + { + "Id": 3, + "MeshRenderer": { + "AlphaCutoff": 0.5, + "AlphaMode": 0, + "AoTextureGuid": "", + "AoTexturePath": "", + "BaseColor": [ + 1.0, + 1.0, + 1.0 + ], + "BaseColorTextureGuid": "", + "BaseColorTexturePath": "", + "BuiltinMesh": 0, + "DoubleSided": false, + "EmissiveColor": [ + 0.0, + 0.0, + 0.0 + ], + "EmissiveTextureGuid": "", + "EmissiveTexturePath": "", + "MaterialAssetGuids": [ + "9833a707-d635-426e-5db2-4cb58fbb5d03" + ], + "MeshAssetGuid": "34a6ed73-d4a5-83a3-5863-742c204c4869", + "MeshSource": 1, + "Metallic": 0.0, + "MetallicRoughnessTextureGuid": "", + "MetallicRoughnessTexturePath": "", + "ModelNodeIndex": 0, + "NormalTextureGuid": "", + "NormalTexturePath": "", + "Roughness": 1.0, + "SourceModelAssetGuid": "7033a4a0-8f64-4826-8fec-22ce0fccb514", + "SourceModelPath": "Assets/Models/box1.glb", + "SourceNodePath": "box1", + "SubMeshIndex": -1, + "Visible": true + }, + "Name": "box1.glb", + "ParentId": 0, + "Transform": { + "Position": [ + 0.0, + 0.0, + 0.0 + ], + "RotationEulerDegrees": [ + 0.0, + 0.0, + 102.15394592285156 + ], + "Scale": [ + 1.0, + 1.0, + 1.0 + ] + } + }, + { + "Id": 4, + "MeshRenderer": { + "AlphaCutoff": 0.5, + "AlphaMode": 0, + "AoTextureGuid": "", + "AoTexturePath": "", + "BaseColor": [ + 0.0, + 0.33333349227905273, + 1.0 + ], + "BaseColorTextureGuid": "", + "BaseColorTexturePath": "", + "BuiltinMesh": 0, + "DoubleSided": true, + "EmissiveColor": [ + 0.0, + 0.0, + 0.0 + ], + "EmissiveTextureGuid": "", + "EmissiveTexturePath": "", + "MaterialAssetGuids": [], + "MeshAssetGuid": "", + "MeshSource": 1, + "Metallic": 0.0, + "MetallicRoughnessTextureGuid": "", + "MetallicRoughnessTexturePath": "", + "ModelNodeIndex": 0, + "NormalTextureGuid": "", + "NormalTexturePath": "", + "Roughness": 1.0, + "SourceModelAssetGuid": "", + "SourceModelPath": "", + "SourceNodePath": "", + "SubMeshIndex": -1, + "Visible": true + }, + "Name": "立方体", + "ParentId": 3, + "Transform": { + "Position": [ + -1.6927926540374756, + -4.459673881530762, + 1.949385364241607e-07 + ], + "RotationEulerDegrees": [ + 0.0, + -0.0, + 0.0 + ], + "Scale": [ + 0.9999998807907104, + 1.0, + 1.0 + ] + } + }, + { + "Id": 5, + "MeshRenderer": { + "AlphaCutoff": 0.5, + "AlphaMode": 0, + "AoTextureGuid": "", + "AoTexturePath": "", + "BaseColor": [ + 0.0, + 0.33333349227905273, + 1.0 + ], + "BaseColorTextureGuid": "", + "BaseColorTexturePath": "", + "BuiltinMesh": 0, + "DoubleSided": true, + "EmissiveColor": [ + 0.0, + 0.0, + 0.0 + ], + "EmissiveTextureGuid": "", + "EmissiveTexturePath": "", + "MaterialAssetGuids": [], + "MeshAssetGuid": "", + "MeshSource": 1, + "Metallic": 0.0, + "MetallicRoughnessTextureGuid": "", + "MetallicRoughnessTexturePath": "", + "ModelNodeIndex": 1, + "NormalTextureGuid": "", + "NormalTexturePath": "", + "Roughness": 1.0, + "SourceModelAssetGuid": "", + "SourceModelPath": "", + "SourceNodePath": "", + "SubMeshIndex": -1, + "Visible": true + }, + "Name": "立方体.001", + "ParentId": 4, + "Transform": { + "Position": [ + 0.0, + -3.55507755279541, + 1.553973731915903e-07 + ], + "RotationEulerDegrees": [ + 0.0, + -0.0, + 0.0 + ], + "Scale": [ + 0.9999998807907104, + 1.0, + 1.0 + ] + } + }, + { + "Id": 6, + "MeshRenderer": { + "AlphaCutoff": 0.5, + "AlphaMode": 0, + "AoTextureGuid": "", + "AoTexturePath": "", + "BaseColor": [ + 1.0, + 1.0, + 1.0 + ], + "BaseColorTextureGuid": "", + "BaseColorTexturePath": "", + "BuiltinMesh": 0, + "DoubleSided": true, + "EmissiveColor": [ + 0.0, + 0.0, + 0.0 + ], + "EmissiveTextureGuid": "", + "EmissiveTexturePath": "", + "MaterialAssetGuids": [], + "MeshAssetGuid": "", + "MeshSource": 1, + "Metallic": 0.0, + "MetallicRoughnessTextureGuid": "", + "MetallicRoughnessTexturePath": "", + "ModelNodeIndex": 2, + "NormalTextureGuid": "", + "NormalTexturePath": "", + "Roughness": 1.0, + "SourceModelAssetGuid": "", + "SourceModelPath": "", + "SourceNodePath": "", + "SubMeshIndex": -1, + "Visible": true + }, + "Name": "立方体.007", + "ParentId": 5, + "Transform": { + "Position": [ + 4.175572872161865, + 0.0, + 0.0 + ], + "RotationEulerDegrees": [ + 0.0, + -0.0, + 0.0 + ], + "Scale": [ + 0.9999998807907104, + 1.0, + 1.0 + ] + } + }, + { + "Id": 7, + "MeshRenderer": { + "AlphaCutoff": 0.5, + "AlphaMode": 0, + "AoTextureGuid": "", + "AoTexturePath": "", + "BaseColor": [ + 1.0, + 0.0407407283782959, + 0.0407407283782959 + ], + "BaseColorTextureGuid": "", + "BaseColorTexturePath": "", + "BuiltinMesh": 0, + "DoubleSided": true, + "EmissiveColor": [ + 0.0, + 0.0, + 0.0 + ], + "EmissiveTextureGuid": "", + "EmissiveTexturePath": "", + "MaterialAssetGuids": [ + "" + ], + "MeshAssetGuid": "", + "MeshSource": 1, + "Metallic": 0.0, + "MetallicRoughnessTextureGuid": "", + "MetallicRoughnessTexturePath": "", + "ModelNodeIndex": 3, + "NormalTextureGuid": "", + "NormalTexturePath": "", + "Roughness": 1.0, + "SourceModelAssetGuid": "", + "SourceModelPath": "", + "SourceNodePath": "", + "SubMeshIndex": -1, + "Visible": true + }, + "Name": "立方体.003", + "ParentId": 3, + "Transform": { + "Position": [ + 0.0, + 0.0, + 0.0 + ], + "RotationEulerDegrees": [ + 0.0, + -0.0, + 0.0 + ], + "Scale": [ + 0.9999998807907104, + 1.0, + 1.0 + ] + } + }, + { + "Id": 8, + "MeshRenderer": { + "AlphaCutoff": 0.5, + "AlphaMode": 0, + "AoTextureGuid": "", + "AoTexturePath": "", + "BaseColor": [ + 0.0, + 0.33333349227905273, + 1.0 + ], + "BaseColorTextureGuid": "", + "BaseColorTexturePath": "", + "BuiltinMesh": 0, + "DoubleSided": true, + "EmissiveColor": [ + 0.0, + 0.0, + 0.0 + ], + "EmissiveTextureGuid": "", + "EmissiveTexturePath": "", + "MaterialAssetGuids": [], + "MeshAssetGuid": "", + "MeshSource": 1, + "Metallic": 0.0, + "MetallicRoughnessTextureGuid": "", + "MetallicRoughnessTexturePath": "", + "ModelNodeIndex": 4, + "NormalTextureGuid": "", + "NormalTexturePath": "", + "Roughness": 1.0, + "SourceModelAssetGuid": "", + "SourceModelPath": "", + "SourceNodePath": "", + "SubMeshIndex": -1, + "Visible": true + }, + "Name": "立方体.002", + "ParentId": 7, + "Transform": { + "Position": [ + 0.0, + -1.4481612709005276e-07, + -3.31300687789917 + ], + "RotationEulerDegrees": [ + 0.0, + -0.0, + 0.0 + ], + "Scale": [ + 0.9999998807907104, + 1.0, + 1.0 + ] + } + }, + { + "Id": 9, + "MeshRenderer": { + "AlphaCutoff": 0.5, + "AlphaMode": 0, + "AoTextureGuid": "", + "AoTexturePath": "", + "BaseColor": [ + 0.75, + 0.7799999713897705, + 0.8399999737739563 + ], + "BaseColorTextureGuid": "", + "BaseColorTexturePath": "", + "BuiltinMesh": 0, + "DoubleSided": true, + "EmissiveColor": [ + 0.0, + 0.0, + 0.0 + ], + "EmissiveTextureGuid": "", + "EmissiveTexturePath": "", + "MaterialAssetGuids": [], + "MeshAssetGuid": "", + "MeshSource": 1, + "Metallic": 0.0, + "MetallicRoughnessTextureGuid": "", + "MetallicRoughnessTexturePath": "", + "ModelNodeIndex": 5, + "NormalTextureGuid": "", + "NormalTexturePath": "", + "Roughness": 1.0, + "SourceModelAssetGuid": "", + "SourceModelPath": "", + "SourceNodePath": "", + "SubMeshIndex": -1, + "Visible": true + }, + "Name": "立方体.004", + "ParentId": 7, + "Transform": { + "Position": [ + 3.776623010635376, + 0.0, + 0.0 + ], + "RotationEulerDegrees": [ + 0.0, + -0.0, + 0.0 + ], + "Scale": [ + 0.9999998807907104, + 1.0, + 1.0 + ] + } + }, + { + "Id": 10, + "MeshRenderer": { + "AlphaCutoff": 0.5, + "AlphaMode": 0, + "AoTextureGuid": "", + "AoTexturePath": "", + "BaseColor": [ + 0.75, + 0.7799999713897705, + 0.8399999737739563 + ], + "BaseColorTextureGuid": "", + "BaseColorTexturePath": "", + "BuiltinMesh": 0, + "DoubleSided": true, + "EmissiveColor": [ + 0.0, + 0.0, + 0.0 + ], + "EmissiveTextureGuid": "", + "EmissiveTexturePath": "", + "MaterialAssetGuids": [], + "MeshAssetGuid": "", + "MeshSource": 1, + "Metallic": 0.0, + "MetallicRoughnessTextureGuid": "", + "MetallicRoughnessTexturePath": "", + "ModelNodeIndex": 6, + "NormalTextureGuid": "", + "NormalTexturePath": "", + "Roughness": 1.0, + "SourceModelAssetGuid": "", + "SourceModelPath": "", + "SourceNodePath": "", + "SubMeshIndex": -1, + "Visible": true + }, + "Name": "立方体.005", + "ParentId": 7, + "Transform": { + "Position": [ + 0.0, + 4.31797981262207, + -1.8874489171594178e-07 + ], + "RotationEulerDegrees": [ + 0.0, + -0.0, + 0.0 + ], + "Scale": [ + 0.9999998807907104, + 1.0, + 1.0 + ] + } + }, + { + "Id": 11, + "MeshRenderer": { + "AlphaCutoff": 0.5, + "AlphaMode": 0, + "AoTextureGuid": "", + "AoTexturePath": "", + "BaseColor": [ + 0.75, + 0.7799999713897705, + 0.8399999737739563 + ], + "BaseColorTextureGuid": "", + "BaseColorTexturePath": "", + "BuiltinMesh": 0, + "DoubleSided": true, + "EmissiveColor": [ + 0.0, + 0.0, + 0.0 + ], + "EmissiveTextureGuid": "", + "EmissiveTexturePath": "", + "MaterialAssetGuids": [], + "MeshAssetGuid": "", + "MeshSource": 1, + "Metallic": 0.0, + "MetallicRoughnessTextureGuid": "", + "MetallicRoughnessTexturePath": "", + "ModelNodeIndex": 7, + "NormalTextureGuid": "", + "NormalTexturePath": "", + "Roughness": 1.0, + "SourceModelAssetGuid": "", + "SourceModelPath": "", + "SourceNodePath": "", + "SubMeshIndex": -1, + "Visible": true + }, + "Name": "立方体.006", + "ParentId": 7, + "Transform": { + "Position": [ + -4.638434886932373, + 0.0, + 0.0 + ], + "RotationEulerDegrees": [ + 0.0, + -0.0, + 0.0 + ], + "Scale": [ + 0.9999998807907104, 1.0, 1.0 ] diff --git a/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp b/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp index 342ce3b..66a2960 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp @@ -1636,9 +1636,13 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon const bool multiEdit = editorContext.GetSelectedObjectIds().size() > 1; if (multiEdit) { - ImGui::TextDisabled("Multi-edit %zu objects", editorContext.GetSelectedObjectIds().size()); + ImGui::TextDisabled("多对象编辑 (%zu 个对象)", editorContext.GetSelectedObjectIds().size()); } + // 扁平化无框样式设置 + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0F, 0.0F, 0.0F, 0.0F)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0F); + int meshSourceIndex = static_cast(meshRenderer.MeshSource); const char* meshSourceItems[] = {"Builtin", "Asset"}; if (ImGui::Combo("Mesh Source", &meshSourceIndex, meshSourceItems, IM_ARRAYSIZE(meshSourceItems))) { @@ -1672,58 +1676,28 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon if (hasSourceModel) { const auto sourceAsset = MetaCoreFindSourceModelAssetForMeshRenderer(editorContext, meshRenderer); - // 注释掉每帧遍历场景查找相同模型的逻辑,避免卡顿 - /* - std::vector sameSourceModelObjects; - std::vector sameSourceNodeObjects; - for (const auto& sceneObject : editorContext.GetScene().GetGameObjects()) { - if (!sceneObject.HasComponent()) { - continue; - } - if (sceneObject.GetComponent().SourceModelPath == meshRenderer.SourceModelPath) { - sameSourceModelObjects.push_back(sceneObject.GetId()); - if (sceneObject.GetComponent().ModelNodeIndex == meshRenderer.ModelNodeIndex) { - sameSourceNodeObjects.push_back(sceneObject.GetId()); - } - } - } - */ - - ImGui::Separator(); - ImGui::TextDisabled("Source"); + // 清爽显示模型资源,避免过于冗长 if (sourceAsset.has_value()) { ImGui::Text("Model: %s", sourceAsset->RelativePath.filename().string().c_str()); } else { ImGui::Text("Model: %s", meshRenderer.SourceModelPath.c_str()); } - ImGui::Text("Node Index: %d", meshRenderer.ModelNodeIndex); + ImGui::SameLine(); + ImGui::TextDisabled("(Node %d)", meshRenderer.ModelNodeIndex); - // 同样注释掉显示实例数量的文本 - // ImGui::Text("Same Model Instances: %d", static_cast(sameSourceModelObjects.size())); - // ImGui::Text("Same Node Instances: %d", static_cast(sameSourceNodeObjects.size())); - - if (ImGui::Button("Open Source Model")) { + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.18F, 0.18F, 0.18F, 1.0F)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.24F, 0.24F, 0.24F, 1.0F)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.20F, 0.20F, 0.20F, 1.0F)); + if (ImGui::Button("Open##SourceModel")) { MetaCoreOpenSourceModelFromMeshRenderer(editorContext, meshRenderer); } - /* - if (!sameSourceModelObjects.empty()) { - ImGui::SameLine(); - if (ImGui::Button("Select Same Model")) { - editorContext.SetSelection(sameSourceModelObjects, sameSourceModelObjects.front()); - } - } - if (!sameSourceNodeObjects.empty()) { - ImGui::SameLine(); - if (ImGui::Button("Select Same Node")) { - editorContext.SetSelection(sameSourceNodeObjects, sameSourceNodeObjects.front()); - } - } - */ - ImGui::Separator(); + ImGui::PopStyleColor(3); } const auto meshChoices = MetaCoreCollectMeshAssetChoices(editorContext); MetaCoreAssetGuid meshAssetGuid = meshRenderer.MeshAssetGuid; + // 渲染 Mesh 资源选择 Combo,隐藏底层的 Mesh Asset Guid 输入框 if (MetaCoreDrawGeneratedAssetCombo("Mesh Asset", meshChoices, meshAssetGuid)) { MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", false); MetaCoreApplyValueToSelectedObjects( @@ -1734,19 +1708,25 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon meshAssetGuid ); } - if (MetaCoreEditAssetGuidField(editorContext, "Mesh Asset Guid", meshAssetGuid)) { - MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", false); - MetaCoreApplyValueToSelectedObjects( - editorContext, - [&](MetaCoreGameObject& selectedObject) -> MetaCoreAssetGuid* { - return selectedObject.HasComponent() ? &selectedObject.GetComponent().MeshAssetGuid : nullptr; - }, - meshAssetGuid - ); - } + } + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + + // Materials 材质槽位折叠重构 + if (ImGui::TreeNodeEx("Materials", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_SpanAvailWidth)) { int materialSlotCount = static_cast(meshRenderer.MaterialAssetGuids.size()); - if (ImGui::InputInt("Material Slot Count", &materialSlotCount)) { + + // Size 槽位数量排版 + ImGui::Columns(2, "MaterialSizeCol", false); + ImGui::SetColumnWidth(0, 100.0F); + ImGui::Text("Size"); + ImGui::NextColumn(); + + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0F, 0.0F, 0.0F, 0.0F)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0F); + ImGui::SetNextItemWidth(-1); + if (ImGui::InputInt("##MaterialSlotCount", &materialSlotCount)) { materialSlotCount = std::max(materialSlotCount, 0); MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", false); MetaCoreForEachSelectedGameObject(editorContext, [&](MetaCoreGameObject& selectedObject) { @@ -1756,33 +1736,54 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon selectedObject.GetComponent().MaterialAssetGuids.resize(static_cast(materialSlotCount)); }); } + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + ImGui::Columns(1); + // 槽位列表循环 const auto materialChoices = MetaCoreCollectMaterialAssetChoices(editorContext); for (std::size_t materialIndex = 0; materialIndex < meshRenderer.MaterialAssetGuids.size(); ++materialIndex) { MetaCoreAssetGuid materialGuid = meshRenderer.MaterialAssetGuids[materialIndex]; - ImGui::Separator(); - MetaCoreDrawMeshRendererMaterialSlotSummary(editorContext, materialIndex, materialGuid); - const MetaCoreAssetGuid materialGuidBeforeEdit = materialGuid; - const std::string comboLabel = "Material Asset " + std::to_string(materialIndex); - if (MetaCoreDrawGeneratedAssetCombo(comboLabel.c_str(), materialChoices, materialGuid)) { - MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", false); - MetaCoreForEachSelectedGameObject(editorContext, [&](MetaCoreGameObject& selectedObject) { - if (!selectedObject.HasComponent()) { - return; - } - if (selectedObject.GetComponent().MaterialAssetGuids.size() <= materialIndex) { - selectedObject.GetComponent().MaterialAssetGuids.resize(materialIndex + 1); - } - selectedObject.GetComponent().MaterialAssetGuids[materialIndex] = materialGuid; - }); - } - if (materialGuid != materialGuidBeforeEdit) { - if (materialGuid.IsValid()) { - (void)MetaCoreApplyDraggedMaterialToSelectedObjects(editorContext, materialIndex, materialGuid); + const std::string elementLabel = "Element " + std::to_string(materialIndex); + + ImGui::Columns(2, ("MaterialSlotCol_" + std::to_string(materialIndex)).c_str(), false); + ImGui::SetColumnWidth(0, 100.0F); + ImGui::Text("%s", elementLabel.c_str()); + ImGui::NextColumn(); + + // 精简槽位扁平外观,右侧留出小圆球材质选择按钮空间 + float circleBtnWidth = 18.0F; + float comboWidth = ImGui::GetContentRegionAvail().x - circleBtnWidth - 4.0F; + + std::string materialName = "None (Material)"; + if (materialGuid.IsValid()) { + const auto assetEditingService = editorContext.GetModuleRegistry().ResolveService(); + const auto materialAsset = assetEditingService != nullptr + ? assetEditingService->LoadMaterialAsset(materialGuid) + : std::nullopt; + if (materialAsset.has_value() && !materialAsset->Name.empty()) { + materialName = materialAsset->Name + " (Material)"; } else { - MetaCoreClearMaterialSlotOnSelectedObjects(editorContext, materialIndex); + materialName = materialGuid.ToString() + " (Material)"; } } + + // 用扁平化 Button 模拟选择控件,避免原 Combo 的多余高亮蓝框 + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.12F, 0.12F, 0.12F, 1.0F)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.18F, 0.18F, 0.18F, 1.0F)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.15F, 0.15F, 0.15F, 1.0F)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0F, 0.0F, 0.0F, 0.0F)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0F); + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0F, 0.5F)); // 文本左对齐 + + ImGui::SetNextItemWidth(comboWidth); + bool openPopup = false; + if (ImGui::Button((materialName + "##Combo_" + std::to_string(materialIndex)).c_str(), ImVec2(comboWidth, 0.0F))) { + openPopup = true; + } + + // 材质拖拽接收 + const MetaCoreAssetGuid materialGuidBeforeEdit = materialGuid; if (ImGui::BeginDragDropTarget()) { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(MetaCoreGeneratedMaterialDragDropPayloadType); payload != nullptr && payload->DataSize == sizeof(MetaCoreGeneratedMaterialDragDropPayload)) { @@ -1802,172 +1803,211 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon } ImGui::EndDragDropTarget(); } - const std::string label = "Material Guid " + std::to_string(materialIndex); - if (MetaCoreEditAssetGuidField(editorContext, label.c_str(), materialGuid)) { - MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", false); - MetaCoreForEachSelectedGameObject(editorContext, [&](MetaCoreGameObject& selectedObject) { - if (!selectedObject.HasComponent()) { - return; - } - if (selectedObject.GetComponent().MaterialAssetGuids.size() <= materialIndex) { - selectedObject.GetComponent().MaterialAssetGuids.resize(materialIndex + 1); - } - selectedObject.GetComponent().MaterialAssetGuids[materialIndex] = materialGuid; - }); - } - ImGui::SameLine(); - ImGui::TextDisabled("拖拽材质到此槽位"); - if (ImGui::Button(("Clear Slot##" + std::to_string(materialIndex)).c_str())) { - MetaCoreClearMaterialSlotOnSelectedObjects(editorContext, materialIndex); - materialGuid = MetaCoreAssetGuid{}; - editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已清空材质槽位"); - } + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(4); + + // 右侧小圆点按钮,点击同样激活选择器 Popup ImGui::SameLine(); - if (ImGui::Button(("Restore Default##" + std::to_string(materialIndex)).c_str())) { - if (MetaCoreRestoreDefaultMaterialSlotOnSelectedObjects(editorContext, materialIndex)) { - editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已恢复模型默认材质槽位"); - if (gameObject.HasComponent() && - gameObject.GetComponent().MaterialAssetGuids.size() > materialIndex) { - materialGuid = gameObject.GetComponent().MaterialAssetGuids[materialIndex]; + ImVec2 circleBtnPos = ImGui::GetCursorScreenPos(); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0F, 0.0F, 0.0F, 0.0F)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.0F, 0.0F, 0.0F, 0.0F)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.0F, 0.0F, 0.0F, 0.0F)); + if (ImGui::Button(("##Circle_" + std::to_string(materialIndex)).c_str(), ImVec2(circleBtnWidth, 18.0F))) { + openPopup = true; + } + ImGui::PopStyleColor(3); + + // 手绘 Unity 精美圆形选择图标 + ImDrawList* drawList = ImGui::GetWindowDrawList(); + ImVec2 center(circleBtnPos.x + circleBtnWidth * 0.5F, circleBtnPos.y + 9.0F); + bool isCircleHovered = ImGui::IsItemHovered(); + ImU32 outlineColor = isCircleHovered ? IM_COL32(200, 200, 200, 255) : IM_COL32(120, 120, 120, 255); + drawList->AddCircle(center, 5.0F, outlineColor, 12, 1.0F); + drawList->AddCircleFilled(center, 1.5F, outlineColor); + + // 弹出选择材质资产的 Popup + std::string popupName = "SelectMaterialPopup##" + std::to_string(materialIndex); + if (openPopup) { + ImGui::OpenPopup(popupName.c_str()); + } + if (ImGui::BeginPopup(popupName.c_str())) { + if (ImGui::Selectable("", !materialGuid.IsValid())) { + materialGuid = MetaCoreAssetGuid{}; + } + for (const auto& choice : materialChoices) { + bool isSelected = (choice.AssetGuid == materialGuid); + if (ImGui::Selectable(choice.Label.c_str(), isSelected)) { + materialGuid = choice.AssetGuid; } + } + ImGui::EndPopup(); + } + + if (materialGuid != materialGuidBeforeEdit) { + if (materialGuid.IsValid()) { + (void)MetaCoreApplyDraggedMaterialToSelectedObjects(editorContext, materialIndex, materialGuid); } else { - editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Material", "当前槽位没有可恢复的模型默认材质"); + MetaCoreClearMaterialSlotOnSelectedObjects(editorContext, materialIndex); } } - if (materialGuid.IsValid()) { - ImGui::TextDisabled("资源槽位:引用材质资源;下面的 BaseColor / Visible 是当前实例。"); - if (ImGui::Button(("Open Material Resource##" + std::to_string(materialIndex)).c_str())) { - const auto assetEditingService = editorContext.GetModuleRegistry().ResolveService(); - if (const auto resolvedMaterial = assetEditingService != nullptr - ? assetEditingService->ResolveGeneratedAsset(materialGuid) - : std::nullopt; - resolvedMaterial.has_value() && resolvedMaterial->GeneratedKind == "material") { - editorContext.SelectAsset(MetaCoreSelectedAssetState{ - resolvedMaterial->SourceAsset.Guid, - resolvedMaterial->SourceAsset.RelativePath, - resolvedMaterial->SourceAsset.Type, - resolvedMaterial->SourceAsset.StorageKind - }); - editorContext.SetSelectedAssetSubId("material:" + materialGuid.ToString()); - editorContext.ClearSelection(); - } + // 右键快捷弹出菜单,收容所有高级/调试指令 + if (ImGui::BeginPopupContextItem(("MaterialSlotContext##" + std::to_string(materialIndex)).c_str())) { + if (ImGui::MenuItem("清空槽位 (Clear Slot)")) { + MetaCoreClearMaterialSlotOnSelectedObjects(editorContext, materialIndex); + materialGuid = MetaCoreAssetGuid{}; + editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已清空材质槽位"); } - ImGui::SameLine(); - if (ImGui::Button(("Reapply Resource To Instance##" + std::to_string(materialIndex)).c_str())) { - if (MetaCoreReapplyMaterialResourceToSelectedObjects(editorContext, materialIndex)) { - editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已将材质资源重新应用到实例"); + if (ImGui::MenuItem("恢复模型默认材质 (Restore Default)")) { + if (MetaCoreRestoreDefaultMaterialSlotOnSelectedObjects(editorContext, materialIndex)) { + editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已恢复模型默认材质槽位"); + if (gameObject.HasComponent() && + gameObject.GetComponent().MaterialAssetGuids.size() > materialIndex) { + materialGuid = gameObject.GetComponent().MaterialAssetGuids[materialIndex]; + } } else { - editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Material", "当前槽位没有可重新应用的材质资源"); + editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Material", "当前槽位没有可恢复的模型默认材质"); } } - if (editorContext.GetSelectedObjectIds().size() == 1) { - ImGui::SameLine(); - if (ImGui::Button(("Push Instance Preview To Resource##" + std::to_string(materialIndex)).c_str())) { - if (MetaCorePushSelectedMeshRendererPreviewToMaterialResource(editorContext, materialIndex)) { - editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已将当前实例预览写回材质资源"); + if (materialGuid.IsValid()) { + ImGui::Separator(); + if (ImGui::MenuItem("重新应用材质资源到实例 (Reapply to Instance)")) { + if (MetaCoreReapplyMaterialResourceToSelectedObjects(editorContext, materialIndex)) { + editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已将材质资源重新应用到实例"); } else { - editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Material", "实例预览写回材质资源失败"); + editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Material", "当前槽位没有可重新应用的材质资源"); } } - } - - const std::string treeLabel = "编辑材质资源 " + std::to_string(materialIndex) + "##MaterialInspector_" + std::to_string(materialIndex); - if (ImGui::TreeNode(treeLabel.c_str())) { - const auto assetEditingService = editorContext.GetModuleRegistry().ResolveService(); - const auto resolvedMaterial = assetEditingService != nullptr - ? assetEditingService->ResolveGeneratedAsset(materialGuid) - : std::nullopt; - const auto materialDocument = assetEditingService != nullptr - ? assetEditingService->LoadMaterialAsset(materialGuid) - : std::nullopt; - if (!resolvedMaterial.has_value() || - resolvedMaterial->GeneratedKind != "material" || - !materialDocument.has_value()) { - ImGui::TextDisabled("未找到对应材质资源"); - } else { - MetaCoreMaterialAssetDocument materialAsset = *materialDocument; - bool modified = false; - - ImGui::TextDisabled("材质: %s", materialAsset.Name.c_str()); - float baseColor[3] = {materialAsset.BaseColor.x, materialAsset.BaseColor.y, materialAsset.BaseColor.z}; - if (ImGui::ColorEdit3(("Base Color##Inline" + std::to_string(materialIndex)).c_str(), baseColor)) { - materialAsset.BaseColor = {baseColor[0], baseColor[1], baseColor[2]}; - modified = true; - } - if (ImGui::SliderFloat(("Metallic##Inline" + std::to_string(materialIndex)).c_str(), &materialAsset.Metallic, 0.0F, 1.0F)) { - modified = true; - } - if (ImGui::SliderFloat(("Roughness##Inline" + std::to_string(materialIndex)).c_str(), &materialAsset.Roughness, 0.0F, 1.0F)) { - modified = true; - } - if (ImGui::Checkbox(("Double Sided##Inline" + std::to_string(materialIndex)).c_str(), &materialAsset.DoubleSided)) { - modified = true; - } - - const char* alphaModeItems[] = {"Opaque", "Mask", "Blend"}; - int alphaModeIndex = static_cast(materialAsset.AlphaMode); - if (ImGui::Combo(("Alpha Mode##Inline" + std::to_string(materialIndex)).c_str(), &alphaModeIndex, alphaModeItems, IM_ARRAYSIZE(alphaModeItems))) { - materialAsset.AlphaMode = static_cast(alphaModeIndex); - modified = true; - } - if (materialAsset.AlphaMode == MetaCoreMaterialAlphaMode::Mask) { - if (ImGui::SliderFloat(("Alpha Cutoff##Inline" + std::to_string(materialIndex)).c_str(), &materialAsset.AlphaCutoff, 0.0F, 1.0F)) { - modified = true; - } - } - - const auto textureChoices = MetaCoreCollectTextureAssetChoices(editorContext); - MetaCoreAssetGuid baseColorTexture = materialAsset.BaseColorTexture; - if (MetaCoreDrawGeneratedAssetCombo(("Base Color Texture##Inline" + std::to_string(materialIndex)).c_str(), textureChoices, baseColorTexture)) { - materialAsset.BaseColorTexture = baseColorTexture; - modified = true; - } - MetaCoreAssetGuid metalRoughTexture = materialAsset.MetallicRoughnessTexture; - if (MetaCoreDrawGeneratedAssetCombo(("MetalRough Texture##Inline" + std::to_string(materialIndex)).c_str(), textureChoices, metalRoughTexture)) { - materialAsset.MetallicRoughnessTexture = metalRoughTexture; - modified = true; - } - MetaCoreAssetGuid normalTexture = materialAsset.NormalTexture; - if (MetaCoreDrawGeneratedAssetCombo(("Normal Texture##Inline" + std::to_string(materialIndex)).c_str(), textureChoices, normalTexture)) { - materialAsset.NormalTexture = normalTexture; - modified = true; - } - float emissiveColor[3] = {materialAsset.EmissiveColor.x, materialAsset.EmissiveColor.y, materialAsset.EmissiveColor.z}; - if (ImGui::ColorEdit3(("Emissive Color##Inline" + std::to_string(materialIndex)).c_str(), emissiveColor)) { - materialAsset.EmissiveColor = {emissiveColor[0], emissiveColor[1], emissiveColor[2]}; - modified = true; - } - MetaCoreAssetGuid emissiveTexture = materialAsset.EmissiveTexture; - if (MetaCoreDrawGeneratedAssetCombo(("Emissive Texture##Inline" + std::to_string(materialIndex)).c_str(), textureChoices, emissiveTexture)) { - materialAsset.EmissiveTexture = emissiveTexture; - modified = true; - } - MetaCoreAssetGuid aoTexture = materialAsset.AoTexture; - if (MetaCoreDrawGeneratedAssetCombo(("AO Texture##Inline" + std::to_string(materialIndex)).c_str(), textureChoices, aoTexture)) { - materialAsset.AoTexture = aoTexture; - modified = true; - } - - if (modified) { - if (assetEditingService->SaveMaterialAsset(materialGuid, materialAsset)) { - if (const auto modelDocument = assetEditingService->LoadModelAsset(resolvedMaterial->SourceAsset.Guid); - modelDocument.has_value()) { - MetaCoreApplyGeneratedMaterialPreviewToScene(editorContext, *modelDocument); - } - ImGui::TextColored(ImVec4(0.32F, 0.85F, 0.42F, 1.0F), "已保存材质修改"); + if (editorContext.GetSelectedObjectIds().size() == 1) { + if (ImGui::MenuItem("将实例预览写回材质资源 (Push to Resource)")) { + if (MetaCorePushSelectedMeshRendererPreviewToMaterialResource(editorContext, materialIndex)) { + editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已将当前实例预览写回材质资源"); } else { - ImGui::TextColored(ImVec4(0.92F, 0.32F, 0.28F, 1.0F), "材质修改保存失败"); + editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Material", "实例预览写回材质资源失败"); } } } - ImGui::TreePop(); + ImGui::Separator(); + if (ImGui::MenuItem("打开材质资源 (Open Material Resource)")) { + const auto assetEditingService = editorContext.GetModuleRegistry().ResolveService(); + if (const auto resolvedMaterial = assetEditingService != nullptr + ? assetEditingService->ResolveGeneratedAsset(materialGuid) + : std::nullopt; + resolvedMaterial.has_value() && resolvedMaterial->GeneratedKind == "material") { + editorContext.SelectAsset(MetaCoreSelectedAssetState{ + resolvedMaterial->SourceAsset.Guid, + resolvedMaterial->SourceAsset.RelativePath, + resolvedMaterial->SourceAsset.Type, + resolvedMaterial->SourceAsset.StorageKind + }); + editorContext.SetSelectedAssetSubId("material:" + materialGuid.ToString()); + editorContext.ClearSelection(); + } + } } + ImGui::EndPopup(); } + + ImGui::Columns(1); } + ImGui::TreePop(); } + // 二级折叠菜单:Lighting (照明设置占位,对标 Unity) + if (ImGui::CollapsingHeader("Lighting")) { + ImGui::Columns(2, "LightingCols", false); + ImGui::SetColumnWidth(0, 160.0F); + + ImGui::Text("Cast Shadows"); + ImGui::NextColumn(); + ImGui::BeginDisabled(); + const char* shadowItems[] = {"Off", "On", "Two Sided", "Shadows Only"}; + int shadowIndex = 1; // 默认 "On" + ImGui::SetNextItemWidth(-1); + ImGui::Combo("##CastShadows", &shadowIndex, shadowItems, IM_ARRAYSIZE(shadowItems)); + ImGui::EndDisabled(); + ImGui::NextColumn(); + + ImGui::Text("Receive Shadows"); + ImGui::NextColumn(); + ImGui::BeginDisabled(); + bool receiveShadows = true; + ImGui::Checkbox("##ReceiveShadows", &receiveShadows); + ImGui::EndDisabled(); + ImGui::NextColumn(); + + ImGui::Text("Contribute GI"); + ImGui::NextColumn(); + ImGui::BeginDisabled(); + bool contributeGI = false; + ImGui::Checkbox("##ContributeGI", &contributeGI); + ImGui::EndDisabled(); + + ImGui::Columns(1); + } + + // 二级折叠菜单:Probes (探测器设置占位,对标 Unity) + if (ImGui::CollapsingHeader("Probes")) { + ImGui::Columns(2, "ProbesCols", false); + ImGui::SetColumnWidth(0, 160.0F); + + ImGui::Text("Light Probes"); + ImGui::NextColumn(); + ImGui::BeginDisabled(); + const char* lightProbeItems[] = {"Off", "Blend Probes", "Use Proxy Volume"}; + int lpIndex = 1; + ImGui::SetNextItemWidth(-1); + ImGui::Combo("##LightProbes", &lpIndex, lightProbeItems, IM_ARRAYSIZE(lightProbeItems)); + ImGui::EndDisabled(); + ImGui::NextColumn(); + + ImGui::Text("Reflection Probes"); + ImGui::NextColumn(); + ImGui::BeginDisabled(); + const char* reflProbeItems[] = {"Off", "Blend Probes", "Blend Probes And Skybox"}; + int rpIndex = 1; + ImGui::SetNextItemWidth(-1); + ImGui::Combo("##ReflectionProbes", &rpIndex, reflProbeItems, IM_ARRAYSIZE(reflProbeItems)); + ImGui::EndDisabled(); + ImGui::NextColumn(); + + ImGui::Text("Anchor Override"); + ImGui::NextColumn(); + ImGui::BeginDisabled(); + ImGui::SetNextItemWidth(-1); + ImGui::Button("None (Transform)"); + ImGui::EndDisabled(); + + ImGui::Columns(1); + } + + // 二级折叠菜单:Additional Settings (额外渲染设置占位,对标 Unity) + if (ImGui::CollapsingHeader("Additional Settings")) { + ImGui::Columns(2, "AddSettingsCols", false); + ImGui::SetColumnWidth(0, 160.0F); + + ImGui::Text("Motion Vectors"); + ImGui::NextColumn(); + ImGui::BeginDisabled(); + const char* mvItems[] = {"Camera Motion Only", "Per Object Motion", "Force No Motion"}; + int mvIndex = 1; + ImGui::SetNextItemWidth(-1); + ImGui::Combo("##MotionVectors", &mvIndex, mvItems, IM_ARRAYSIZE(mvItems)); + ImGui::EndDisabled(); + ImGui::NextColumn(); + + ImGui::Text("Dynamic Occlusion"); + ImGui::NextColumn(); + ImGui::BeginDisabled(); + bool dynOcc = true; + ImGui::Checkbox("##DynamicOcclusion", &dynOcc); + ImGui::EndDisabled(); + + ImGui::Columns(1); + } + + // 实例属性修改区域 const auto sharedColor = MetaCoreGetSharedSelectedValue( editorContext, [](MetaCoreGameObject& selectedObject) -> std::optional { @@ -1976,35 +2016,7 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon [](const glm::vec3& lhs, const glm::vec3& rhs) { return MetaCoreNearlyEqualVec3(lhs, rhs); } ); glm::vec3 baseColor = sharedColor.value_or(gameObject.GetComponent().BaseColor); - if (multiEdit && !sharedColor.has_value()) { - ImGui::TextDisabled("BaseColor: Mixed"); - } - if (prefabObject != nullptr && prefabObject->MeshRenderer.has_value() && - !MetaCoreNearlyEqualVec3(gameObject.GetComponent().BaseColor, prefabObject->MeshRenderer->BaseColor)) { - ImGui::TextColored(ImVec4(0.93F, 0.72F, 0.18F, 1.0F), "BaseColor Overrides"); - ImGui::SameLine(); - if (ImGui::SmallButton("Apply##MeshBaseColor")) { - (void)MetaCoreApplySelectedPrefabField(editorContext, "MeshRenderer", "base_color"); - } - ImGui::SameLine(); - if (ImGui::SmallButton("Revert##MeshBaseColor")) { - (void)MetaCoreRevertSelectedPrefabField(editorContext, "MeshRenderer", "base_color"); - baseColor = prefabObject->MeshRenderer->BaseColor; - } - } - ImGui::TextDisabled("实例预览:仅影响当前场景对象,不会直接改动材质资源。"); - ImGui::ColorEdit3("BaseColor", glm::value_ptr(baseColor)); - MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", true); - if (ImGui::IsItemEdited()) { - MetaCoreApplyValueToSelectedObjects( - editorContext, - [](MetaCoreGameObject& selectedObject) -> glm::vec3* { - return selectedObject.HasComponent() ? &selectedObject.GetComponent().BaseColor : nullptr; - }, - baseColor - ); - } - + const auto sharedVisible = MetaCoreGetSharedSelectedValue( editorContext, [](MetaCoreGameObject& selectedObject) -> std::optional { @@ -2013,32 +2025,206 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon [](bool lhs, bool rhs) { return lhs == rhs; } ); bool visible = sharedVisible.value_or(gameObject.GetComponent().Visible); + + // Prefab 覆盖修改控制 + if (prefabObject != nullptr && prefabObject->MeshRenderer.has_value()) { + bool hasColorOverride = !MetaCoreNearlyEqualVec3(gameObject.GetComponent().BaseColor, prefabObject->MeshRenderer->BaseColor); + bool hasVisibleOverride = (gameObject.GetComponent().Visible != prefabObject->MeshRenderer->Visible); + + if (hasColorOverride || hasVisibleOverride) { + ImGui::Separator(); + ImGui::TextColored(ImVec4(0.93F, 0.72F, 0.18F, 1.0F), "预制件覆写属性已检测到"); + ImGui::SameLine(); + if (ImGui::SmallButton("全部应用##MeshPrefab")) { + if (hasColorOverride) (void)MetaCoreApplySelectedPrefabField(editorContext, "MeshRenderer", "base_color"); + if (hasVisibleOverride) (void)MetaCoreApplySelectedPrefabField(editorContext, "MeshRenderer", "visible"); + } + ImGui::SameLine(); + if (ImGui::SmallButton("全部放弃##MeshPrefab")) { + if (hasColorOverride) { + (void)MetaCoreRevertSelectedPrefabField(editorContext, "MeshRenderer", "base_color"); + baseColor = prefabObject->MeshRenderer->BaseColor; + } + if (hasVisibleOverride) { + (void)MetaCoreRevertSelectedPrefabField(editorContext, "MeshRenderer", "visible"); + visible = prefabObject->MeshRenderer->Visible; + } + } + } + } + + ImGui::Separator(); + + // 渲染实例属性(实例预览) + ImGui::Columns(2, "MeshInstanceProps", false); + ImGui::SetColumnWidth(0, 160.0F); + + ImGui::Text("BaseColor (Instance)"); + ImGui::NextColumn(); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0F, 0.0F, 0.0F, 0.0F)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0F); + ImGui::SetNextItemWidth(-1); + if (multiEdit && !sharedColor.has_value()) { + ImGui::TextDisabled("混合值"); + } else { + if (ImGui::ColorEdit3("##InstBaseColor", glm::value_ptr(baseColor))) { + MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", true); + MetaCoreApplyValueToSelectedObjects( + editorContext, + [](MetaCoreGameObject& selectedObject) -> glm::vec3* { + return selectedObject.HasComponent() ? &selectedObject.GetComponent().BaseColor : nullptr; + }, + baseColor + ); + } + } + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + ImGui::NextColumn(); + + ImGui::Text("Visible (Instance)"); + ImGui::NextColumn(); if (multiEdit && !sharedVisible.has_value()) { - ImGui::TextDisabled("Visible: Mixed"); - } - if (prefabObject != nullptr && prefabObject->MeshRenderer.has_value() && - gameObject.GetComponent().Visible != prefabObject->MeshRenderer->Visible) { - ImGui::TextColored(ImVec4(0.93F, 0.72F, 0.18F, 1.0F), "Visible Overrides"); - ImGui::SameLine(); - if (ImGui::SmallButton("Apply##MeshVisible")) { - (void)MetaCoreApplySelectedPrefabField(editorContext, "MeshRenderer", "visible"); - } - ImGui::SameLine(); - if (ImGui::SmallButton("Revert##MeshVisible")) { - (void)MetaCoreRevertSelectedPrefabField(editorContext, "MeshRenderer", "visible"); - visible = prefabObject->MeshRenderer->Visible; + ImGui::TextDisabled("混合值"); + } else { + if (ImGui::Checkbox("##InstVisible", &visible)) { + MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", false); + MetaCoreApplyValueToSelectedObjects( + editorContext, + [](MetaCoreGameObject& selectedObject) -> bool* { + return selectedObject.HasComponent() ? &selectedObject.GetComponent().Visible : nullptr; + }, + visible + ); } } - ImGui::Checkbox("Visible", &visible); - MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", false); - if (ImGui::IsItemEdited()) { - MetaCoreApplyValueToSelectedObjects( - editorContext, - [](MetaCoreGameObject& selectedObject) -> bool* { - return selectedObject.HasComponent() ? &selectedObject.GetComponent().Visible : nullptr; - }, - visible - ); + + ImGui::Columns(1); + + // 底部内联材质资源编辑器:带立体材质球图标的 CollapsingHeader + for (std::size_t materialIndex = 0; materialIndex < meshRenderer.MaterialAssetGuids.size(); ++materialIndex) { + MetaCoreAssetGuid materialGuid = meshRenderer.MaterialAssetGuids[materialIndex]; + if (!materialGuid.IsValid()) { + continue; + } + + std::string materialName = "Default-Material"; + const auto assetEditingService = editorContext.GetModuleRegistry().ResolveService(); + const auto resolvedMaterial = assetEditingService != nullptr + ? assetEditingService->ResolveGeneratedAsset(materialGuid) + : std::nullopt; + const auto materialDocument = assetEditingService != nullptr + ? assetEditingService->LoadMaterialAsset(materialGuid) + : std::nullopt; + if (materialDocument.has_value() && !materialDocument->Name.empty()) { + materialName = materialDocument->Name; + } + + ImVec2 headerPos = ImGui::GetCursorScreenPos(); + float frameHeight = ImGui::GetFrameHeight(); + + // 留出材质球图标的空间 + std::string headerLabel = " " + materialName + " (Material)##InlineMaterialPanel_" + std::to_string(materialIndex); + + bool isPanelOpen = ImGui::CollapsingHeader(headerLabel.c_str()); + + // 使用 ImDrawList 在折叠头上绘制三维立体感小材质球 + ImDrawList* drawList = ImGui::GetWindowDrawList(); + ImVec2 iconCenter(headerPos.x + 26.0F, headerPos.y + frameHeight * 0.5F); + float radius = 6.0F; + // 1. 球体底色 + drawList->AddCircleFilled(iconCenter, radius, IM_COL32(95, 105, 120, 255)); + // 2. 模拟三维光影暗部 + drawList->AddCircleFilled(ImVec2(iconCenter.x + 0.8F, iconCenter.y + 0.8F), radius * 0.8F, IM_COL32(65, 70, 85, 255)); + drawList->AddCircleFilled(ImVec2(iconCenter.x + 1.2F, iconCenter.y + 1.2F), radius * 0.6F, IM_COL32(45, 50, 60, 255)); + // 3. 高光斑点 + ImVec2 highlightCenter(iconCenter.x - 1.8F, iconCenter.y - 1.8F); + drawList->AddCircleFilled(highlightCenter, 1.8F, IM_COL32(255, 255, 255, 180)); + drawList->AddCircleFilled(highlightCenter, 0.8F, IM_COL32(255, 255, 255, 255)); + + if (isPanelOpen) { + if (!resolvedMaterial.has_value() || + resolvedMaterial->GeneratedKind != "material" || + !materialDocument.has_value()) { + ImGui::TextDisabled("未找到对应材质资源"); + } else { + MetaCoreMaterialAssetDocument materialAsset = *materialDocument; + bool modified = false; + + // 材质基本属性编辑 + float baseColorRes[3] = {materialAsset.BaseColor.x, materialAsset.BaseColor.y, materialAsset.BaseColor.z}; + if (ImGui::ColorEdit3(("Base Color##Res" + std::to_string(materialIndex)).c_str(), baseColorRes)) { + materialAsset.BaseColor = {baseColorRes[0], baseColorRes[1], baseColorRes[2]}; + modified = true; + } + if (ImGui::SliderFloat(("Metallic##Res" + std::to_string(materialIndex)).c_str(), &materialAsset.Metallic, 0.0F, 1.0F)) { + modified = true; + } + if (ImGui::SliderFloat(("Roughness##Res" + std::to_string(materialIndex)).c_str(), &materialAsset.Roughness, 0.0F, 1.0F)) { + modified = true; + } + if (ImGui::Checkbox(("Double Sided##Res" + std::to_string(materialIndex)).c_str(), &materialAsset.DoubleSided)) { + modified = true; + } + + const char* alphaModeItems[] = {"Opaque", "Mask", "Blend"}; + int alphaModeIndex = static_cast(materialAsset.AlphaMode); + if (ImGui::Combo(("Alpha Mode##Res" + std::to_string(materialIndex)).c_str(), &alphaModeIndex, alphaModeItems, IM_ARRAYSIZE(alphaModeItems))) { + materialAsset.AlphaMode = static_cast(alphaModeIndex); + modified = true; + } + if (materialAsset.AlphaMode == MetaCoreMaterialAlphaMode::Mask) { + if (ImGui::SliderFloat(("Alpha Cutoff##Res" + std::to_string(materialIndex)).c_str(), &materialAsset.AlphaCutoff, 0.0F, 1.0F)) { + modified = true; + } + } + + // 贴图通道配置 + const auto textureChoices = MetaCoreCollectTextureAssetChoices(editorContext); + MetaCoreAssetGuid baseColorTexture = materialAsset.BaseColorTexture; + if (MetaCoreDrawGeneratedAssetCombo(("Base Color Texture##Res" + std::to_string(materialIndex)).c_str(), textureChoices, baseColorTexture)) { + materialAsset.BaseColorTexture = baseColorTexture; + modified = true; + } + MetaCoreAssetGuid metalRoughTexture = materialAsset.MetallicRoughnessTexture; + if (MetaCoreDrawGeneratedAssetCombo(("MetalRough Texture##Res" + std::to_string(materialIndex)).c_str(), textureChoices, metalRoughTexture)) { + materialAsset.MetallicRoughnessTexture = metalRoughTexture; + modified = true; + } + MetaCoreAssetGuid normalTexture = materialAsset.NormalTexture; + if (MetaCoreDrawGeneratedAssetCombo(("Normal Texture##Res" + std::to_string(materialIndex)).c_str(), textureChoices, normalTexture)) { + materialAsset.NormalTexture = normalTexture; + modified = true; + } + float emissiveColor[3] = {materialAsset.EmissiveColor.x, materialAsset.EmissiveColor.y, materialAsset.EmissiveColor.z}; + if (ImGui::ColorEdit3(("Emissive Color##Res" + std::to_string(materialIndex)).c_str(), emissiveColor)) { + materialAsset.EmissiveColor = {emissiveColor[0], emissiveColor[1], emissiveColor[2]}; + modified = true; + } + MetaCoreAssetGuid emissiveTexture = materialAsset.EmissiveTexture; + if (MetaCoreDrawGeneratedAssetCombo(("Emissive Texture##Res" + std::to_string(materialIndex)).c_str(), textureChoices, emissiveTexture)) { + materialAsset.EmissiveTexture = emissiveTexture; + modified = true; + } + MetaCoreAssetGuid aoTexture = materialAsset.AoTexture; + if (MetaCoreDrawGeneratedAssetCombo(("AO Texture##Res" + std::to_string(materialIndex)).c_str(), textureChoices, aoTexture)) { + materialAsset.AoTexture = aoTexture; + modified = true; + } + + if (modified) { + if (assetEditingService->SaveMaterialAsset(materialGuid, materialAsset)) { + if (const auto modelDocument = assetEditingService->LoadModelAsset(resolvedMaterial->SourceAsset.Guid); + modelDocument.has_value()) { + MetaCoreApplyGeneratedMaterialPreviewToScene(editorContext, *modelDocument); + } + editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已保存材质资源修改"); + } else { + editorContext.AddConsoleMessage(MetaCoreLogLevel::Error, "Material", "材质修改保存失败"); + } + } + } + } } } diff --git a/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp b/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp index 3d44448..b21f7de 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp @@ -4058,6 +4058,13 @@ private: ) { ImGui::PushID(label.c_str()); + // 扁平化无边框输入框样式,消除粗蓝色高亮边框 + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0F, 0.0F, 0.0F, 0.0F)); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.12F, 0.12F, 0.12F, 1.0F)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.18F, 0.18F, 0.18F, 1.0F)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.15F, 0.15F, 0.15F, 1.0F)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0F); + glm::vec3 displayValues = values; const bool multiEdit = editorContext.GetSelectedObjectIds().size() > 1; auto getTargetValue = [&](MetaCoreGameObject& selectedObject) -> glm::vec3* { @@ -4086,11 +4093,31 @@ private: // Scale Lock Icon (Unity Style) if (isScale) { - ImGui::PushStyleColor(ImGuiCol_Text, GScaleLocked ? ImVec4(1, 1, 1, 1) : ImVec4(0.5F, 0.5F, 0.5F, 1)); - if (ImGui::Selectable(GScaleLocked ? " (L) " : " (U) ", &GScaleLocked, 0, ImVec2(24, 0))) { + // 使用不可见的热区按钮 + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.0F, 0.0F, 0.0F, 0.0F)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.2F, 0.2F, 0.2F, 0.4F)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.15F, 0.15F, 0.15F, 0.6F)); + if (ImGui::Selectable("##LockScale", &GScaleLocked, 0, ImVec2(18, 0))) { // Toggle locked state } - ImGui::PopStyleColor(); + ImGui::PopStyleColor(3); + + // 使用 ImDrawList 手绘精美锁链环 + ImVec2 r_min = ImGui::GetItemRectMin(); + ImVec2 r_max = ImGui::GetItemRectMax(); + ImVec2 center = ImVec2((r_min.x + r_max.x) * 0.5F, (r_min.y + r_max.y) * 0.5F); + ImDrawList* drawList = ImGui::GetWindowDrawList(); + + ImU32 lockColor = GScaleLocked ? IM_COL32(230, 230, 230, 255) : IM_COL32(120, 120, 120, 255); + if (GScaleLocked) { + // 锁定状态:两个咬合的环 + drawList->AddRect(ImVec2(center.x - 5.0F, center.y - 3.0F), ImVec2(center.x + 1.0F, center.y + 1.0F), lockColor, 1.5F, ImDrawFlags_None, 1.2F); + drawList->AddRect(ImVec2(center.x - 1.0F, center.y - 1.0F), ImVec2(center.x + 5.0F, center.y + 3.0F), lockColor, 1.5F, ImDrawFlags_None, 1.2F); + } else { + // 解锁状态:两个断开的环 + drawList->AddRect(ImVec2(center.x - 6.0F, center.y - 4.0F), ImVec2(center.x - 1.0F, center.y - 0.0F), lockColor, 1.5F, ImDrawFlags_None, 1.2F); + drawList->AddRect(ImVec2(center.x + 1.0F, center.y + 0.0F), ImVec2(center.x + 6.0F, center.y + 4.0F), lockColor, 1.5F, ImDrawFlags_None, 1.2F); + } ImGui::SameLine(); } @@ -4125,7 +4152,7 @@ private: ImGui::SetNextItemWidth(itemWidth); float oldVal = val; if (ImGui::DragFloat(("##" + std::string(compLabel)).c_str(), &val, speed)) { - if (isScale && GScaleLocked && oldVal != 0.0f) { + if (isScale && GScaleLocked && oldVal != 0.0F) { float ratio = val / oldVal; if (compLabel[0] == 'X') { displayValues.y *= ratio; displayValues.z *= ratio; } else if (compLabel[0] == 'Y') { displayValues.x *= ratio; displayValues.z *= ratio; } @@ -4166,7 +4193,10 @@ private: ImGui::EndPopup(); } - ImGui::PopStyleVar(); + ImGui::PopStyleVar(); // Pop FrameBorderSize + ImGui::PopStyleColor(4); // Pop Border, FrameBg, FrameBgHovered, FrameBgActive + + ImGui::PopStyleVar(); // Pop ItemSpacing ImGui::Columns(1); ImGui::PopID(); } @@ -4215,36 +4245,145 @@ public: ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 4)); // Active Checkbox + Cube Icon + Name - static bool active = true; - ImGui::Checkbox("##Active", &active); + bool active = true; + bool hasActiveProp = false; + if (selectedObject.HasComponent()) { + active = selectedObject.GetComponent().Visible; + hasActiveProp = true; + } else if (selectedObject.HasComponent()) { + active = selectedObject.GetComponent().Enabled; + hasActiveProp = true; + } else if (selectedObject.HasComponent()) { + active = selectedObject.GetComponent().Enabled; + hasActiveProp = true; + } + + static bool defaultActiveState = true; + bool activeState = hasActiveProp ? active : defaultActiveState; + + if (ImGui::Checkbox("##Active", &activeState)) { + if (!hasActiveProp) { + defaultActiveState = activeState; + } + (void)editorContext.ExecuteSnapshotCommand("修改对象激活状态", [&]() { + bool anyChanged = false; + for (const MetaCoreId selectedId : editorContext.GetSelectedObjectIds()) { + MetaCoreGameObject obj = editorContext.GetScene().FindGameObject(selectedId); + if (obj) { + if (obj.HasComponent()) { + if (obj.GetComponent().Visible != activeState) { + obj.GetComponent().Visible = activeState; + anyChanged = true; + } + } + if (obj.HasComponent()) { + if (obj.GetComponent().Enabled != activeState) { + obj.GetComponent().Enabled = activeState; + anyChanged = true; + } + } + if (obj.HasComponent()) { + if (obj.GetComponent().Enabled != activeState) { + obj.GetComponent().Enabled = activeState; + anyChanged = true; + } + } + } + } + return anyChanged; + }); + } ImGui::SameLine(); - // Cube icon (small) - ImGui::TextColored(ImVec4(0.4F, 0.7F, 1.0F, 1.0F), " (B) "); + // Cube icon (small) - 使用 ImDrawList 绘制等角 3D 蓝色立方体以对标 Unity + { + float size = 12.0F; + ImVec2 p = ImGui::GetCursorScreenPos(); + p.y += (ImGui::GetFrameHeight() - size) * 0.5F - 1.0F; + ImDrawList* drawList = ImGui::GetWindowDrawList(); + + float h = size * 0.5F; + float dx = h * 0.866F; // 30度角的 x 偏移 + float dy = h * 0.5F; // 30度角的 y 偏移 + + ImVec2 c = ImVec2(p.x + dx, p.y + h); + ImVec2 p_top = ImVec2(c.x, c.y - h); + ImVec2 p_bottom = ImVec2(c.x, c.y + h); + ImVec2 p_left_top = ImVec2(c.x - dx, c.y - dy); + ImVec2 p_left_bottom = ImVec2(c.x - dx, c.y + dy); + ImVec2 p_right_top = ImVec2(c.x + dx, c.y - dy); + ImVec2 p_right_bottom = ImVec2(c.x + dx, c.y + dy); + + // 填充三个面,使用渐变色营造 3D 立体感 + ImVec2 top_face[4] = { p_top, p_right_top, c, p_left_top }; + drawList->AddConvexPolyFilled(top_face, 4, IM_COL32(110, 160, 230, 255)); + + ImVec2 left_face[4] = { p_left_top, c, p_bottom, p_left_bottom }; + drawList->AddConvexPolyFilled(left_face, 4, IM_COL32(75, 120, 190, 255)); + + ImVec2 right_face[4] = { p_right_top, p_right_bottom, p_bottom, c }; + drawList->AddConvexPolyFilled(right_face, 4, IM_COL32(50, 95, 160, 255)); + + // 绘制棱线增加精致感 + ImU32 lineColor = IM_COL32(230, 240, 255, 200); + drawList->AddLine(c, p_top, lineColor, 1.0F); + drawList->AddLine(c, p_bottom, lineColor, 1.0F); + drawList->AddLine(c, p_left_top, lineColor, 1.0F); + drawList->AddLine(c, p_right_top, lineColor, 1.0F); + drawList->AddLine(p_top, p_left_top, lineColor, 1.0F); + drawList->AddLine(p_top, p_right_top, lineColor, 1.0F); + drawList->AddLine(p_left_top, p_left_bottom, lineColor, 1.0F); + drawList->AddLine(p_right_top, p_right_bottom, lineColor, 1.0F); + drawList->AddLine(p_left_bottom, p_bottom, lineColor, 1.0F); + drawList->AddLine(p_right_bottom, p_bottom, lineColor, 1.0F); + + ImGui::Dummy(ImVec2(dx * 2.0F, size)); // 占位 + } ImGui::SameLine(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 110.0F); + // 扁平化名称输入框样式,移除突兀的蓝色边框,并与 Static 控件完美对齐 + float staticControlWidth = 85.0F; // "静态的" 文字及 checkbox 的总估算宽度 + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.12F, 0.12F, 0.12F, 1.0F)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0F, 0.0F, 0.0F, 0.0F)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0F); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - staticControlWidth - 4.0F); if (ImGui::InputText("##Name", NameBuffer_.data(), NameBuffer_.size(), ImGuiInputTextFlags_EnterReturnsTrue)) { (void)MetaCoreRenameObject(editorContext, selectedObject.GetId(), NameBuffer_.data()); } + ImGui::PopStyleVar(); + ImGui::PopStyleColor(2); + ImGui::SameLine(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - staticControlWidth); static bool isStatic = false; - ImGui::TextDisabled("Static"); ImGui::SameLine(); + ImGui::TextDisabled("静态的"); ImGui::SameLine(); ImGui::Checkbox("##Static", &isStatic); // Tag and Layer - float colWidth = ImGui::GetContentRegionAvail().x * 0.5f; + float colWidth = ImGui::GetContentRegionAvail().x * 0.5F; ImGui::Columns(2, "TagLayerCols", false); ImGui::SetColumnWidth(0, colWidth); - ImGui::Text("Tag"); ImGui::SameLine(); + // 调整下拉选择框的按钮样式,使其看起来更像扁平的下拉框 + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.12F, 0.12F, 0.12F, 1.0F)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.18F, 0.18F, 0.18F, 1.0F)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.15F, 0.15F, 0.15F, 1.0F)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0F, 0.0F, 0.0F, 0.0F)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0F); + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0F, 0.5F)); // 文本左对齐 + + ImGui::Text("标签"); ImGui::SameLine(); ImGui::SetNextItemWidth(-1); - if (ImGui::Button("Untagged##Tag", ImVec2(-1, 0))) { /* Menu */ } + if (ImGui::Button("Untagged ▾##Tag", ImVec2(-1, 0))) { /* Menu */ } ImGui::NextColumn(); - ImGui::Text("Layer"); ImGui::SameLine(); + ImGui::Text("图层"); ImGui::SameLine(); ImGui::SetNextItemWidth(-1); - if (ImGui::Button("Default##Layer", ImVec2(-1, 0))) { /* Menu */ } + if (ImGui::Button("Default ▾##Layer", ImVec2(-1, 0))) { /* Menu */ } + + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(4); ImGui::Columns(1); ImGui::PopStyleVar(2); diff --git a/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp b/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp index 283a71e..db645e0 100644 --- a/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp +++ b/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include #include @@ -44,6 +46,7 @@ #include #include #include +#include // 引入 Windows 和 OpenGL 头文件以创建纹理 #define WIN32_LEAN_AND_MEAN @@ -165,7 +168,7 @@ public: if (Engine_) { std::cout << "FilamentSceneBridge: Shutting down..." << std::endl; - // 销毁所有加载的资产 + // 1. 销毁所有加载的资产,切断其内部 Renderable 对克隆材质实例的占用引用 std::cout << "[DEBUG] LoadedAssets_ size: " << LoadedAssets_.size() << std::endl; for (auto& [id, asset] : LoadedAssets_) { std::cout << "[DEBUG] Destroying asset for ID: " << id << ", asset pointer: " << asset << std::endl; @@ -175,9 +178,44 @@ public: std::cout << "[DEBUG] Destroyed asset for ID: " << id << std::endl; } LoadedAssets_.clear(); + + // 2. 销毁所有 pivotEntity 中间辅助坐标变换实体 + for (auto& [objectId, assetEntity] : ObjectToFilamentEntity_) { + utils::Entity entity = assetEntity.second; + if (!entity) continue; + bool isAssetEntity = false; + if (assetEntity.first) { + const utils::Entity* entities = assetEntity.first->getEntities(); + size_t entityCount = assetEntity.first->getEntityCount(); + for (size_t i = 0; i < entityCount; ++i) { + if (entities[i] == entity) { + isAssetEntity = true; + break; + } + } + } + if (!isAssetEntity) { + if (Scene_) { + Scene_->remove(entity); + EntitiesInScene_.erase(entity); + } + Engine_->destroy(entity); + utils::EntityManager::get().destroy(entity); + } + } ObjectToFilamentEntity_.clear(); ObjectWorldMatrices_.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_) { Scene_->remove(entity); @@ -257,125 +295,90 @@ public: void ParseModelHierarchyToEcs(MetaCoreScene& scene, MetaCoreGameObject& parentObject, filament::gltfio::FilamentAsset* asset) { if (!asset) return; - const utils::Entity* entities = asset->getEntities(); - size_t entityCount = asset->getEntityCount(); - auto& tm = Engine_->getTransformManager(); - // 建立 Filament Entity 到 MetaCore GameObject 的映射 - std::map entityToObj; - entityToObj[asset->getRoot()] = parentObject; - - // 【超级核心】:绑定顶级父 GameObject 标识到 Filament Asset 根 Entity,彻底打通顶级 Gizmo 的同步! + // 绑定顶级父 GameObject 标识到 Filament Asset 根 Entity,彻底打通顶级 Gizmo 的同步! ObjectToFilamentEntity_[parentObject.GetId()] = { asset, asset->getRoot() }; - // 预先收集并缓存 parentObject 子树下的所有已有 GameObject,用于高精度的三步复用匹配 - std::vector subtreeObjects; + // 1. 自底向上建立完整的 Filament 父子关系映射,解决隐藏过渡节点导致的层级断裂问题 + std::unordered_map, utils::Entity::Hasher> parentToChildren = + BuildFullParentToChildrenMap(asset); + + // 2. 建立 ECS 的父子关系映射 + std::unordered_map> ecsParentToChildren; std::vector subtreeIds = scene.GetSubtreeObjectIds(parentObject.GetId()); for (MetaCoreId subId : subtreeIds) { auto subObj = scene.FindGameObject(subId); - if (subObj && subObj.GetId() != parentObject.GetId()) { - subtreeObjects.push_back(subObj); + if (subObj) { + ecsParentToChildren[subObj.GetParentId()].push_back(subObj); } } - // 第一遍:创建或复用所有对象 - for (size_t i = 0; i < entityCount; i++) { - utils::Entity entity = entities[i]; - if (entity == asset->getRoot()) continue; + // 3. 递归 DFS 精确结构匹配并初始化 Transform 属性,穿透所有过渡节点 + std::function matchRecursive = + [&](MetaCoreGameObject currentObj, utils::Entity currentEntity) { + ObjectToFilamentEntity_[currentObj.GetId()] = { asset, currentEntity }; - const char* nodeName = asset->getName(entity); - std::string name = nodeName ? nodeName : ("Node_" + std::to_string(i)); + if (currentEntity != asset->getRoot()) { + auto instance = tm.getInstance(currentEntity); + if (instance) { + filament::math::mat4f localTransform = tm.getTransform(instance); + + glm::mat4 filamentMatrix; + std::memcpy(glm::value_ptr(filamentMatrix), &localTransform[0][0], sizeof(float) * 16); + + glm::mat4 R_minus90X = glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), {1, 0, 0}); + glm::mat4 R_plus90X = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), {1, 0, 0}); + glm::mat4 matrix = R_minus90X * filamentMatrix * R_plus90X; - MetaCoreGameObject childObj; - bool isExisting = false; + // 拆解矩阵 + glm::vec3 scale; + glm::quat rotation; + glm::vec3 translation; + glm::vec3 skew; + glm::vec4 perspective; + glm::decompose(matrix, scale, rotation, translation, skew, perspective); - // 1. 优先通过 ModelNodeIndex 进行精确匹配(针对带有网格的节点) - for (auto& sceneObj : subtreeObjects) { - if (sceneObj.HasComponent()) { - auto& mesh = sceneObj.GetComponent(); - if (mesh.ModelNodeIndex == static_cast(i)) { - childObj = sceneObj; - isExisting = true; - break; + auto& transform = currentObj.GetComponent(); + transform.Position = translation; + transform.RotationEulerDegrees = glm::degrees(glm::eulerAngles(rotation)); + transform.Scale = scale; } } - } - // 2. 如果没找到(针对没有网格的空节点),通过名字进行匹配复用 - if (!isExisting) { - for (auto& sceneObj : subtreeObjects) { - if (sceneObj.GetName() == name) { - // 且该节点不能已经被其他实体映射占用 - bool alreadyMapped = false; - for (auto& [ent, obj] : entityToObj) { - if (obj.GetId() == sceneObj.GetId()) { - alreadyMapped = true; - break; - } - } - if (!alreadyMapped) { - childObj = sceneObj; - isExisting = true; + // 穿透过渡节点获取当前实体的有效 Filament 子实体列表 + std::vector fChildren; + GetEffectiveFilamentChildren(currentEntity, asset, parentToChildren, fChildren); + + auto& eChildren = ecsParentToChildren[currentObj.GetId()]; + + for (utils::Entity fChild : fChildren) { + const char* nodeName = asset->getName(fChild); + std::string name = nodeName ? nodeName : ""; + + MetaCoreGameObject matchedEObj; + for (auto it = eChildren.begin(); it != eChildren.end(); ++it) { + if (it->GetName() == name) { + matchedEObj = *it; + eChildren.erase(it); break; } } + + // 模糊/降级匹配 + if (!matchedEObj && name.empty() && !eChildren.empty()) { + matchedEObj = eChildren.front(); + eChildren.erase(eChildren.begin()); + } + + if (matchedEObj) { + matchRecursive(matchedEObj, fChild); + } } - } + }; - // 3. 实在没有找到,才进行动态创建(仅作为降级兜底) - if (!isExisting) { - childObj = scene.CreateGameObject(name, parentObject.GetId()); - auto& meshRenderer = childObj.AddComponent(); - meshRenderer.MeshSource = MetaCoreMeshSourceKind::Asset; - meshRenderer.ModelNodeIndex = static_cast(i); - } - - entityToObj[entity] = childObj; - ObjectToFilamentEntity_[childObj.GetId()] = { asset, entity }; - - // 获取并设置 Transform - auto instance = tm.getInstance(entity); - if (instance) { - filament::math::mat4f localTransform = tm.getTransform(instance); - - glm::mat4 filamentMatrix; - std::memcpy(glm::value_ptr(filamentMatrix), &localTransform[0][0], sizeof(float) * 16); - - glm::mat4 R_minus90X = glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), {1, 0, 0}); - glm::mat4 R_plus90X = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), {1, 0, 0}); - glm::mat4 matrix = R_minus90X * filamentMatrix * R_plus90X; - - // 拆解矩阵 - glm::vec3 scale; - glm::quat rotation; - glm::vec3 translation; - glm::vec3 skew; - glm::vec4 perspective; - glm::decompose(matrix, scale, rotation, translation, skew, perspective); - - auto& transform = childObj.GetComponent(); - transform.Position = translation; - transform.RotationEulerDegrees = glm::degrees(glm::eulerAngles(rotation)); - transform.Scale = scale; - } - } - - // 第二遍:修正父子关系 - for (size_t i = 0; i < entityCount; i++) { - utils::Entity entity = entities[i]; - if (entity == asset->getRoot()) continue; - - auto instance = tm.getInstance(entity); - if (instance) { - utils::Entity parentEntity = tm.getParent(instance); - if (parentEntity && entityToObj.count(parentEntity)) { - MetaCoreGameObject currentObj = entityToObj[entity]; - MetaCoreGameObject parentObj = entityToObj[parentEntity]; - currentObj.SetParentId(parentObj.GetId()); - } - } - } + // 从根开始执行递归层级树匹配 + matchRecursive(parentObject, asset->getRoot()); } static std::string NormalizePath(std::string path) { @@ -389,6 +392,51 @@ public: LastSyncSnapshot_ = snapshot; ObjectWorldMatrices_ = LastSyncSnapshot_.WorldMatrices; + + // 构建物体ID到网格快照的快速映射,方便后续查询可见性及材质属性 + std::unordered_map renderableMap; + for (const auto& r : snapshot.Renderables) { + renderableMap[r.ObjectId] = &r; + } + + // 定义用于检测场景中某个对象及其所有祖先节点当前在快照里是否应为可见状态的辅助方法 + std::unordered_map visibilityCache; + std::function isObjectVisible = [&](MetaCoreId objectId) -> bool { + if (objectId == 0) { + return true; + } + auto cacheIt = visibilityCache.find(objectId); + if (cacheIt != visibilityCache.end()) { + return cacheIt->second; + } + + if (!snapshot.WorldMatrices.contains(objectId)) { + visibilityCache[objectId] = false; + return false; + } + + bool selfVisible = true; + MetaCoreId parentId = 0; + auto rIt = renderableMap.find(objectId); + if (rIt != renderableMap.end()) { + selfVisible = rIt->second->Visible; + parentId = rIt->second->ParentId; + } else if (scene != nullptr) { + auto obj = scene->FindGameObject(objectId); + if (obj) { + parentId = obj.GetParentId(); + } + } + + if (!selfVisible) { + visibilityCache[objectId] = false; + return false; + } + + bool parentVisible = isObjectVisible(parentId); + visibilityCache[objectId] = parentVisible; + return parentVisible; + }; SyncSceneLights(LastSyncSnapshot_); if (useScenePrimaryCamera) { MetaCoreSceneView primarySceneView; @@ -460,6 +508,17 @@ 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_ 中移除 it = LoadedAssets_.erase(it); } else { @@ -537,6 +596,32 @@ public: } LoadedAssets_[hostRootId] = asset; + + // 复制材质实例防止共享干扰,放在加载好资源后 + { + auto& rm = Engine_->getRenderableManager(); + std::vector& 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 @@ -562,64 +647,56 @@ public: // 顶级父 ID 对应 asset->getRoot() ObjectToFilamentEntity_[hostRootId] = { asset, asset->getRoot() }; - // 遍历新资产中的 entities 映射到 snapshot 中的 renderable 节点 - const utils::Entity* entities = asset->getEntities(); - size_t entityCount = asset->getEntityCount(); - - std::vector candidates; + // 1. 自底向上建立完整的 Filament 父子关系映射,解决隐藏过渡节点导致的层级断裂问题 + std::unordered_map, utils::Entity::Hasher> parentToChildren = + BuildFullParentToChildrenMap(asset); + + // 2. 建立快照中的父子关系映射 + std::unordered_map> snapParentToChildren; for (const auto& r : snapshot.Renderables) { if (r.HostRootId == hostRootId && r.ObjectId != hostRootId) { - candidates.push_back(r); + snapParentToChildren[r.ParentId].push_back(r); } } - std::map entityToId; - entityToId[asset->getRoot()] = hostRootId; + // 3. 递归 DFS 进行精确层级树结构匹配,穿透所有过渡节点 + std::function matchRecursive = + [&](MetaCoreId currentId, utils::Entity currentEntity) { + ObjectToFilamentEntity_[currentId] = { asset, currentEntity }; - for (size_t i = 0; i < entityCount; i++) { - utils::Entity entity = entities[i]; - if (entity == asset->getRoot()) continue; + // 穿透过渡节点获取当前实体的有效 Filament 子实体列表 + std::vector fChildren; + GetEffectiveFilamentChildren(currentEntity, asset, parentToChildren, fChildren); - const char* nodeName = asset->getName(entity); - std::string name = nodeName ? nodeName : ("Node_" + std::to_string(i)); + auto& sChildren = snapParentToChildren[currentId]; - MetaCoreId childObjectId = 0; - bool isExisting = false; + for (utils::Entity fChild : fChildren) { + const char* nodeName = asset->getName(fChild); + std::string name = nodeName ? nodeName : ""; - // 1. 优先通过 ModelNodeIndex 进行匹配 - for (const auto& cand : candidates) { - if (cand.ModelNodeIndex == static_cast(i)) { - childObjectId = cand.ObjectId; - isExisting = true; - break; - } - } - - // 2. 其次通过名字匹配 - if (!isExisting) { - for (const auto& cand : candidates) { - if (cand.Name == name) { - bool alreadyMapped = false; - for (auto& [ent, id] : entityToId) { - if (id == cand.ObjectId) { - alreadyMapped = true; - break; - } - } - if (!alreadyMapped) { - childObjectId = cand.ObjectId; - isExisting = true; + MetaCoreId matchedChildId = 0; + for (auto it = sChildren.begin(); it != sChildren.end(); ++it) { + if (it->Name == name) { + matchedChildId = it->ObjectId; + sChildren.erase(it); break; } } - } - } - if (isExisting && childObjectId != 0) { - entityToId[entity] = childObjectId; - ObjectToFilamentEntity_[childObjectId] = { asset, entity }; - } - } + // 兜底匹配 + if (matchedChildId == 0 && name.empty() && !sChildren.empty()) { + matchedChildId = sChildren.front().ObjectId; + sChildren.erase(sChildren.begin()); + } + + if (matchedChildId != 0) { + matchRecursive(matchedChildId, fChild); + } + } + }; + + // 从根开始执行递归层级树匹配 + matchRecursive(hostRootId, asset->getRoot()); } // 创建中间 Pivot 实体用于坐标系转换 (glTF Y-up -> MetaCore Z-up) @@ -681,7 +758,7 @@ public: auto it = entityToObjectId.find(entity); if (it != entityToObjectId.end()) { // 若是直接映射节点,其可见性由快照决定 - shouldBeInScene = snapshot.WorldMatrices.contains(it->second); + shouldBeInScene = isObjectVisible(it->second); } else { // 若是没有直接映射关系的内部渲染/辅助实体,上溯其在 Filament 层级树中的父节点链, // 找到最近的具有映射关系的祖先节点,并继承其可见性状态。 @@ -700,10 +777,10 @@ public: } if (mappedAncestor) { - shouldBeInScene = snapshot.WorldMatrices.contains(entityToObjectId[mappedAncestor]); + shouldBeInScene = isObjectVisible(entityToObjectId[mappedAncestor]); } else { // 回退保护:如果没有找到,则与该资产对应的顶级根宿主保持状态一致 - shouldBeInScene = snapshot.WorldMatrices.contains(hostRootId); + shouldBeInScene = isObjectVisible(hostRootId); } } @@ -741,7 +818,7 @@ public: } if (!isAssetEntity) { - bool shouldBeInScene = snapshot.WorldMatrices.contains(objectId); + bool shouldBeInScene = isObjectVisible(objectId); if (shouldBeInScene) { if (Scene_ && !EntitiesInScene_.contains(entity)) { Scene_->addEntity(entity); @@ -766,6 +843,40 @@ public: } } } + + // 5. 同步所有存活网格的材质属性,例如 BaseColor + auto& rm = Engine_->getRenderableManager(); + for (const auto& renderable : snapshot.Renderables) { + auto itEntity = ObjectToFilamentEntity_.find(renderable.ObjectId); + if (itEntity == ObjectToFilamentEntity_.end()) { + continue; + } + utils::Entity entity = itEntity->second.second; + if (!entity) { + continue; + } + + auto instance = rm.getInstance(entity); + if (!instance) { + continue; + } + + // 遍历所有 primitive,获取材质实例,并设置其 baseColorFactor 属性 + size_t primCount = rm.getPrimitiveCount(instance); + for (size_t primIndex = 0; primIndex < primCount; ++primIndex) { + filament::MaterialInstance* matInst = rm.getMaterialInstanceAt(instance, primIndex); + if (matInst) { + // BaseColor 包含 RGB,以 float4 传递,Alpha 设为 1.0 + filament::math::float4 baseColorVec{ + renderable.BaseColor.x, + renderable.BaseColor.y, + renderable.BaseColor.z, + 1.0f + }; + matInst->setParameter("baseColorFactor", baseColorVec); + } + } + } } void ApplySceneView(const MetaCoreSceneView& sceneView) { @@ -952,11 +1063,100 @@ public: bool VerifyEntityInSceneForTesting(MetaCoreId objectId) const { auto it = ObjectToFilamentEntity_.find(objectId); - if (it == ObjectToFilamentEntity_.end()) return false; - return EntitiesInScene_.contains(it->second.second); + if (it == ObjectToFilamentEntity_.end()) { + std::cout << "[DEBUG Verify] Cannot find objectId " << objectId << " in ObjectToFilamentEntity_" << std::endl; + std::cout << "[DEBUG Verify] ObjectToFilamentEntity_ size: " << ObjectToFilamentEntity_.size() << std::endl; + for (const auto& [oid, ent] : ObjectToFilamentEntity_) { + std::cout << " - OID: " << oid << ", entity: " << ent.second.getId() << std::endl; + } + return false; + } + bool inScene = EntitiesInScene_.contains(it->second.second); + if (!inScene) { + std::cout << "[DEBUG Verify] Object " << objectId << " maps to entity " + << it->second.second.getId() << ", but this entity is NOT in EntitiesInScene_" << std::endl; + std::cout << "[DEBUG Verify] EntitiesInScene_ contains: "; + for (auto ent : EntitiesInScene_) { + std::cout << ent.getId() << " "; + } + std::cout << std::endl; + } + return inScene; } private: + // 判断当前实体是否为过渡节点(即无名且无 Mesh 渲染组件的节点) + bool IsTransitionNode(utils::Entity entity, filament::gltfio::FilamentAsset* asset) const { + if (!entity) return false; + if (entity == asset->getRoot()) return false; + + // 1. 判断是否有名字 + const char* name = asset->getName(entity); + if (name && strlen(name) > 0) { + return false; + } + + // 2. 判断是否是渲染实体 + auto& rm = Engine_->getRenderableManager(); + if (rm.getInstance(entity)) { + return false; + } + + return true; + } + + // 穿透子节点链中的所有过渡节点,提取出所有具备有效名称或为渲染实体的第一代有效子节点 + void GetEffectiveFilamentChildren( + utils::Entity parent, + filament::gltfio::FilamentAsset* asset, + const std::unordered_map, utils::Entity::Hasher>& parentToChildren, + std::vector& outEffectiveChildren + ) const { + auto it = parentToChildren.find(parent); + if (it == parentToChildren.end()) return; + + for (utils::Entity child : it->second) { + if (IsTransitionNode(child, asset)) { + GetEffectiveFilamentChildren(child, asset, parentToChildren, outEffectiveChildren); + } else { + outEffectiveChildren.push_back(child); + } + } + } + + // 自底向上溯源生成覆盖所有中间隐藏过渡节点的完整父子映射表 + std::unordered_map, utils::Entity::Hasher> + BuildFullParentToChildrenMap(filament::gltfio::FilamentAsset* asset) const { + std::unordered_map, utils::Entity::Hasher> parentToChildren; + if (!asset) return parentToChildren; + + const utils::Entity* entities = asset->getEntities(); + size_t entityCount = asset->getEntityCount(); + auto& tm = Engine_->getTransformManager(); + + for (size_t i = 0; i < entityCount; ++i) { + utils::Entity current = entities[i]; + if (current == asset->getRoot()) continue; + + while (current && current != asset->getRoot()) { + auto instance = tm.getInstance(current); + if (!instance) break; + + utils::Entity parent = tm.getParent(instance); + if (parent.isNull()) break; + + auto& children = parentToChildren[parent]; + if (std::find(children.begin(), children.end(), current) == children.end()) { + children.push_back(current); + } + + current = parent; + } + } + + return parentToChildren; + } + static filament::math::float3 ToFilamentFloat3(const glm::vec3& value) { return filament::math::float3{ value.x, value.y, value.z }; } @@ -975,7 +1175,7 @@ private: } static float BuildFilamentLightIntensity(float intensity) { - return (intensity > 0.0F ? intensity : 0.0F) * 10000.0F; + return (intensity > 0.0F ? intensity : 0.0F) * 100000.0F; } void SyncSceneLights(const MetaCoreSceneRenderSyncSnapshot& snapshot) { @@ -1083,6 +1283,7 @@ private: std::unordered_map ObjectWorldMatrices_{}; std::unordered_map> ObjectToFilamentEntity_; std::unordered_map LoadedAssets_; + std::unordered_map> AssetDuplicatedMaterials_; std::unordered_map SceneLightEntities_; std::unordered_set EntitiesInScene_; diff --git a/Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp b/Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp index cf0f8b7..bd64c51 100644 --- a/Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp +++ b/Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp @@ -137,7 +137,8 @@ MetaCoreSceneRenderSyncSnapshot MetaCoreSceneRenderSync::BuildSnapshot(const Met meshRenderer.ModelNodeIndex, hostRoot.GetId(), hostRoot.GetName(), - hostRoot.HasComponent() + hostRoot.HasComponent(), + meshRenderer.BaseColor }); } diff --git a/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreSceneRenderSync.h b/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreSceneRenderSync.h index 47e9b73..61071c1 100644 --- a/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreSceneRenderSync.h +++ b/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreSceneRenderSync.h @@ -34,6 +34,7 @@ struct MetaCoreRenderSyncRenderable { MetaCoreId HostRootId = 0; std::string HostRootName{}; bool HostRootHasModelRootTag = false; + glm::vec3 BaseColor{1.0F, 1.0F, 1.0F}; }; struct MetaCoreRenderSyncCamera { diff --git a/tests/MetaCoreSmokeTests.cpp b/tests/MetaCoreSmokeTests.cpp index 7f5cc4d..4f7ad23 100644 --- a/tests/MetaCoreSmokeTests.cpp +++ b/tests/MetaCoreSmokeTests.cpp @@ -507,12 +507,14 @@ void MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot() { auto& rootMesh = root.AddComponent(); rootMesh.MeshSource = MetaCore::MetaCoreMeshSourceKind::Asset; rootMesh.SourceModelPath = "Assets/Models/SyncRoot.glb"; + rootMesh.BaseColor = glm::vec3(0.5F, 0.6F, 0.7F); MetaCore::MetaCoreGameObject child = scene.CreateGameObject("RenderChild", root.GetId()); child.GetComponent().Position = glm::vec3(4.0F, 5.0F, 6.0F); auto& childMesh = child.AddComponent(); childMesh.MeshSource = MetaCore::MetaCoreMeshSourceKind::Builtin; childMesh.Visible = false; + childMesh.BaseColor = glm::vec3(0.1F, 0.2F, 0.3F); MetaCore::MetaCoreGameObject cameraObject = scene.CreateGameObject("PrimaryCamera"); auto& camera = cameraObject.AddComponent(); @@ -533,6 +535,16 @@ void MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot() { MetaCoreExpect(snapshot.Renderables.size() == 2, "RenderSync 应收集 MeshRenderer 对象"); MetaCoreExpect(snapshot.Cameras.size() == 1, "RenderSync 应收集 Camera 对象"); MetaCoreExpect(snapshot.Lights.size() == 1, "RenderSync 应收集 Light 对象"); + + for (const auto& renderable : snapshot.Renderables) { + if (renderable.ObjectId == root.GetId()) { + MetaCoreExpect(renderable.Visible == true, "RenderSync 根对象 Visible 应为 true"); + MetaCoreExpectVec3Near(renderable.BaseColor, glm::vec3(0.5F, 0.6F, 0.7F), "RenderSync 根对象 BaseColor 应正确"); + } else if (renderable.ObjectId == child.GetId()) { + MetaCoreExpect(renderable.Visible == false, "RenderSync 子对象 Visible 应为 false"); + MetaCoreExpectVec3Near(renderable.BaseColor, glm::vec3(0.1F, 0.2F, 0.3F), "RenderSync 子对象 BaseColor 应正确"); + } + } MetaCoreExpect(snapshot.PrimaryCamera.has_value(), "RenderSync 应识别主相机"); MetaCoreExpect(snapshot.PrimaryCamera->ObjectId == cameraObject.GetId(), "RenderSync 主相机 ID 应正确"); @@ -2760,6 +2772,10 @@ void MetaCoreTestDecoupledSnapshotSync() { std::cout << "[DEBUG] transOk: " << transOk << std::endl; MetaCoreExpect(transOk, "快照中的自定义局部变换应正确同步到 Filament"); + bool cubeVisibleInScene = bridge.VerifyEntityInSceneForTesting(cubeId); + std::cout << "[DEBUG] cubeVisibleInScene: " << cubeVisibleInScene << std::endl; + MetaCoreExpect(cubeVisibleInScene, "默认可见的网格实体应该在 Filament 场景中"); + // 7. 验证 Enabled 状态为 false 的光源被过滤排除,且默认灯光起降机制生效 (自动亮起) bool hasLight = bridge.VerifyLightExistsForTesting(lightId); std::cout << "[DEBUG] hasLight: " << hasLight << std::endl; @@ -2775,6 +2791,12 @@ void MetaCoreTestDecoupledSnapshotSync() { light.Enabled = true; } } + // 同时我们将 cube 的 Visible 改为 false,测试动态隐藏 + for (auto& r : snapshot.Renderables) { + if (r.ObjectId == cubeId) { + r.Visible = false; + } + } std::cout << "[DEBUG] SyncScene 2 starting..." << std::endl; bridge.SyncScene(snapshot, nullptr, false, false); std::cout << "[DEBUG] SyncScene 2 completed." << std::endl; @@ -2787,6 +2809,10 @@ void MetaCoreTestDecoupledSnapshotSync() { std::cout << "[DEBUG] defaultLightIntensityNow: " << defaultLightIntensityNow << std::endl; MetaCoreExpect(defaultLightIntensityNow < 1.0F, "启用自定义光源时,默认灯光强度应自动降为 0"); + bool cubeVisibleInScene2 = bridge.VerifyEntityInSceneForTesting(cubeId); + std::cout << "[DEBUG] cubeVisibleInScene2: " << cubeVisibleInScene2 << std::endl; + MetaCoreExpect(!cubeVisibleInScene2, "禁用可见性后,网格实体应该从 Filament 场景中移除"); + // 9. 再次禁用光源,验证自定义光源被销毁并且默认灯光复亮 for (auto& light : snapshot.Lights) { if (light.ObjectId == lightId) {