style(inspector): 美化属性检查器外观,对标Unity深色扁平风格,重构材质槽位及二级折叠项

This commit is contained in:
ayuan9957 2026-05-25 16:34:37 +08:00
parent 36e3155a8a
commit 93fddb0296
9 changed files with 1549 additions and 438 deletions

Binary file not shown.

View File

@ -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"
}
]
}

View File

@ -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
]

View File

@ -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<int>(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<MetaCoreId> sameSourceModelObjects;
std::vector<MetaCoreId> sameSourceNodeObjects;
for (const auto& sceneObject : editorContext.GetScene().GetGameObjects()) {
if (!sceneObject.HasComponent<MetaCoreMeshRendererComponent>()) {
continue;
}
if (sceneObject.GetComponent<MetaCoreMeshRendererComponent>().SourceModelPath == meshRenderer.SourceModelPath) {
sameSourceModelObjects.push_back(sceneObject.GetId());
if (sceneObject.GetComponent<MetaCoreMeshRendererComponent>().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<int>(sameSourceModelObjects.size()));
// ImGui::Text("Same Node Instances: %d", static_cast<int>(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<MetaCoreMeshRendererComponent>() ? &selectedObject.GetComponent<MetaCoreMeshRendererComponent>().MeshAssetGuid : nullptr;
},
meshAssetGuid
);
}
ImGui::PopStyleVar();
ImGui::PopStyleColor();
// Materials 材质槽位折叠重构
if (ImGui::TreeNodeEx("Materials", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_SpanAvailWidth)) {
int materialSlotCount = static_cast<int>(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<MetaCoreMeshRendererComponent>().MaterialAssetGuids.resize(static_cast<std::size_t>(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<MetaCoreMeshRendererComponent>()) {
return;
}
if (selectedObject.GetComponent<MetaCoreMeshRendererComponent>().MaterialAssetGuids.size() <= materialIndex) {
selectedObject.GetComponent<MetaCoreMeshRendererComponent>().MaterialAssetGuids.resize(materialIndex + 1);
}
selectedObject.GetComponent<MetaCoreMeshRendererComponent>().MaterialAssetGuids[materialIndex] = materialGuid;
});
}
if (materialGuid != materialGuidBeforeEdit) {
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()) {
(void)MetaCoreApplyDraggedMaterialToSelectedObjects(editorContext, materialIndex, materialGuid);
const auto assetEditingService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetEditingService>();
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,29 +1803,63 @@ 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<MetaCoreMeshRendererComponent>()) {
return;
}
if (selectedObject.GetComponent<MetaCoreMeshRendererComponent>().MaterialAssetGuids.size() <= materialIndex) {
selectedObject.GetComponent<MetaCoreMeshRendererComponent>().MaterialAssetGuids.resize(materialIndex + 1);
}
selectedObject.GetComponent<MetaCoreMeshRendererComponent>().MaterialAssetGuids[materialIndex] = materialGuid;
});
}
ImGui::SameLine();
ImGui::TextDisabled("拖拽材质到此槽位");
if (ImGui::Button(("Clear Slot##" + std::to_string(materialIndex)).c_str())) {
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(4);
// 右侧小圆点按钮,点击同样激活选择器 Popup
ImGui::SameLine();
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("<None>", !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 {
MetaCoreClearMaterialSlotOnSelectedObjects(editorContext, materialIndex);
}
}
// 右键快捷弹出菜单,收容所有高级/调试指令
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(("Restore Default##" + std::to_string(materialIndex)).c_str())) {
if (ImGui::MenuItem("恢复模型默认材质 (Restore Default)")) {
if (MetaCoreRestoreDefaultMaterialSlotOnSelectedObjects(editorContext, materialIndex)) {
editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已恢复模型默认材质槽位");
if (gameObject.HasComponent<MetaCoreMeshRendererComponent>() &&
@ -1835,10 +1870,26 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon
editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Material", "当前槽位没有可恢复的模型默认材质");
}
}
if (materialGuid.IsValid()) {
ImGui::TextDisabled("资源槽位:引用材质资源;下面的 BaseColor / Visible 是当前实例。");
if (ImGui::Button(("Open Material Resource##" + std::to_string(materialIndex)).c_str())) {
ImGui::Separator();
if (ImGui::MenuItem("重新应用材质资源到实例 (Reapply to Instance)")) {
if (MetaCoreReapplyMaterialResourceToSelectedObjects(editorContext, materialIndex)) {
editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已将材质资源重新应用到实例");
} else {
editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Material", "当前槽位没有可重新应用的材质资源");
}
}
if (editorContext.GetSelectedObjectIds().size() == 1) {
if (ImGui::MenuItem("将实例预览写回材质资源 (Push to Resource)")) {
if (MetaCorePushSelectedMeshRendererPreviewToMaterialResource(editorContext, materialIndex)) {
editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已将当前实例预览写回材质资源");
} else {
editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Material", "实例预览写回材质资源失败");
}
}
}
ImGui::Separator();
if (ImGui::MenuItem("打开材质资源 (Open Material Resource)")) {
const auto assetEditingService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetEditingService>();
if (const auto resolvedMaterial = assetEditingService != nullptr
? assetEditingService->ResolveGeneratedAsset(materialGuid)
@ -1854,27 +1905,210 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon
editorContext.ClearSelection();
}
}
ImGui::SameLine();
if (ImGui::Button(("Reapply Resource To Instance##" + std::to_string(materialIndex)).c_str())) {
if (MetaCoreReapplyMaterialResourceToSelectedObjects(editorContext, materialIndex)) {
editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已将材质资源重新应用到实例");
} else {
editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Material", "当前槽位没有可重新应用的材质资源");
}
ImGui::EndPopup();
}
if (editorContext.GetSelectedObjectIds().size() == 1) {
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<glm::vec3>(
editorContext,
[](MetaCoreGameObject& selectedObject) -> std::optional<glm::vec3> {
return selectedObject.HasComponent<MetaCoreMeshRendererComponent>() ? std::optional<glm::vec3>(selectedObject.GetComponent<MetaCoreMeshRendererComponent>().BaseColor) : std::nullopt;
},
[](const glm::vec3& lhs, const glm::vec3& rhs) { return MetaCoreNearlyEqualVec3(lhs, rhs); }
);
glm::vec3 baseColor = sharedColor.value_or(gameObject.GetComponent<MetaCoreMeshRendererComponent>().BaseColor);
const auto sharedVisible = MetaCoreGetSharedSelectedValue<bool>(
editorContext,
[](MetaCoreGameObject& selectedObject) -> std::optional<bool> {
return selectedObject.HasComponent<MetaCoreMeshRendererComponent>() ? std::optional<bool>(selectedObject.GetComponent<MetaCoreMeshRendererComponent>().Visible) : std::nullopt;
},
[](bool lhs, bool rhs) { return lhs == rhs; }
);
bool visible = sharedVisible.value_or(gameObject.GetComponent<MetaCoreMeshRendererComponent>().Visible);
// Prefab 覆盖修改控制
if (prefabObject != nullptr && prefabObject->MeshRenderer.has_value()) {
bool hasColorOverride = !MetaCoreNearlyEqualVec3(gameObject.GetComponent<MetaCoreMeshRendererComponent>().BaseColor, prefabObject->MeshRenderer->BaseColor);
bool hasVisibleOverride = (gameObject.GetComponent<MetaCoreMeshRendererComponent>().Visible != prefabObject->MeshRenderer->Visible);
if (hasColorOverride || hasVisibleOverride) {
ImGui::Separator();
ImGui::TextColored(ImVec4(0.93F, 0.72F, 0.18F, 1.0F), "预制件覆写属性已检测到");
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", "已将当前实例预览写回材质资源");
} else {
editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Material", "实例预览写回材质资源失败");
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;
}
}
}
}
const std::string treeLabel = "编辑材质资源 " + std::to_string(materialIndex) + "##MaterialInspector_" + std::to_string(materialIndex);
if (ImGui::TreeNode(treeLabel.c_str())) {
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<MetaCoreMeshRendererComponent>() ? &selectedObject.GetComponent<MetaCoreMeshRendererComponent>().BaseColor : nullptr;
},
baseColor
);
}
}
ImGui::PopStyleVar();
ImGui::PopStyleColor();
ImGui::NextColumn();
ImGui::Text("Visible (Instance)");
ImGui::NextColumn();
if (multiEdit && !sharedVisible.has_value()) {
ImGui::TextDisabled("混合值");
} else {
if (ImGui::Checkbox("##InstVisible", &visible)) {
MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", false);
MetaCoreApplyValueToSelectedObjects(
editorContext,
[](MetaCoreGameObject& selectedObject) -> bool* {
return selectedObject.HasComponent<MetaCoreMeshRendererComponent>() ? &selectedObject.GetComponent<MetaCoreMeshRendererComponent>().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<MetaCoreIAssetEditingService>();
const auto resolvedMaterial = assetEditingService != nullptr
? assetEditingService->ResolveGeneratedAsset(materialGuid)
@ -1882,6 +2116,33 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon
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()) {
@ -1890,62 +2151,63 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon
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]};
// 材质基本属性编辑
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##Inline" + std::to_string(materialIndex)).c_str(), &materialAsset.Metallic, 0.0F, 1.0F)) {
if (ImGui::SliderFloat(("Metallic##Res" + 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)) {
if (ImGui::SliderFloat(("Roughness##Res" + 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)) {
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<int>(materialAsset.AlphaMode);
if (ImGui::Combo(("Alpha Mode##Inline" + std::to_string(materialIndex)).c_str(), &alphaModeIndex, alphaModeItems, IM_ARRAYSIZE(alphaModeItems))) {
if (ImGui::Combo(("Alpha Mode##Res" + std::to_string(materialIndex)).c_str(), &alphaModeIndex, alphaModeItems, IM_ARRAYSIZE(alphaModeItems))) {
materialAsset.AlphaMode = static_cast<MetaCoreMaterialAlphaMode>(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)) {
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##Inline" + std::to_string(materialIndex)).c_str(), textureChoices, 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##Inline" + std::to_string(materialIndex)).c_str(), textureChoices, metalRoughTexture)) {
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##Inline" + std::to_string(materialIndex)).c_str(), textureChoices, 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##Inline" + std::to_string(materialIndex)).c_str(), emissiveColor)) {
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##Inline" + std::to_string(materialIndex)).c_str(), textureChoices, 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##Inline" + std::to_string(materialIndex)).c_str(), textureChoices, aoTexture)) {
if (MetaCoreDrawGeneratedAssetCombo(("AO Texture##Res" + std::to_string(materialIndex)).c_str(), textureChoices, aoTexture)) {
materialAsset.AoTexture = aoTexture;
modified = true;
}
@ -1956,89 +2218,13 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon
modelDocument.has_value()) {
MetaCoreApplyGeneratedMaterialPreviewToScene(editorContext, *modelDocument);
}
ImGui::TextColored(ImVec4(0.32F, 0.85F, 0.42F, 1.0F), "已保存材质修改");
editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已保存材质资源修改");
} else {
ImGui::TextColored(ImVec4(0.92F, 0.32F, 0.28F, 1.0F), "材质修改保存失败");
}
}
}
ImGui::TreePop();
editorContext.AddConsoleMessage(MetaCoreLogLevel::Error, "Material", "材质修改保存失败");
}
}
}
}
const auto sharedColor = MetaCoreGetSharedSelectedValue<glm::vec3>(
editorContext,
[](MetaCoreGameObject& selectedObject) -> std::optional<glm::vec3> {
return selectedObject.HasComponent<MetaCoreMeshRendererComponent>() ? std::optional<glm::vec3>(selectedObject.GetComponent<MetaCoreMeshRendererComponent>().BaseColor) : std::nullopt;
},
[](const glm::vec3& lhs, const glm::vec3& rhs) { return MetaCoreNearlyEqualVec3(lhs, rhs); }
);
glm::vec3 baseColor = sharedColor.value_or(gameObject.GetComponent<MetaCoreMeshRendererComponent>().BaseColor);
if (multiEdit && !sharedColor.has_value()) {
ImGui::TextDisabled("BaseColor: Mixed");
}
if (prefabObject != nullptr && prefabObject->MeshRenderer.has_value() &&
!MetaCoreNearlyEqualVec3(gameObject.GetComponent<MetaCoreMeshRendererComponent>().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<MetaCoreMeshRendererComponent>() ? &selectedObject.GetComponent<MetaCoreMeshRendererComponent>().BaseColor : nullptr;
},
baseColor
);
}
const auto sharedVisible = MetaCoreGetSharedSelectedValue<bool>(
editorContext,
[](MetaCoreGameObject& selectedObject) -> std::optional<bool> {
return selectedObject.HasComponent<MetaCoreMeshRendererComponent>() ? std::optional<bool>(selectedObject.GetComponent<MetaCoreMeshRendererComponent>().Visible) : std::nullopt;
},
[](bool lhs, bool rhs) { return lhs == rhs; }
);
bool visible = sharedVisible.value_or(gameObject.GetComponent<MetaCoreMeshRendererComponent>().Visible);
if (multiEdit && !sharedVisible.has_value()) {
ImGui::TextDisabled("Visible: Mixed");
}
if (prefabObject != nullptr && prefabObject->MeshRenderer.has_value() &&
gameObject.GetComponent<MetaCoreMeshRendererComponent>().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::Checkbox("Visible", &visible);
MetaCoreTrackComponentInspectorEdit(editorContext, "修改检查器属性", false);
if (ImGui::IsItemEdited()) {
MetaCoreApplyValueToSelectedObjects(
editorContext,
[](MetaCoreGameObject& selectedObject) -> bool* {
return selectedObject.HasComponent<MetaCoreMeshRendererComponent>() ? &selectedObject.GetComponent<MetaCoreMeshRendererComponent>().Visible : nullptr;
},
visible
);
}
}

View File

@ -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<MetaCoreMeshRendererComponent>()) {
active = selectedObject.GetComponent<MetaCoreMeshRendererComponent>().Visible;
hasActiveProp = true;
} else if (selectedObject.HasComponent<MetaCoreLightComponent>()) {
active = selectedObject.GetComponent<MetaCoreLightComponent>().Enabled;
hasActiveProp = true;
} else if (selectedObject.HasComponent<MetaCoreCameraComponent>()) {
active = selectedObject.GetComponent<MetaCoreCameraComponent>().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<MetaCoreMeshRendererComponent>()) {
if (obj.GetComponent<MetaCoreMeshRendererComponent>().Visible != activeState) {
obj.GetComponent<MetaCoreMeshRendererComponent>().Visible = activeState;
anyChanged = true;
}
}
if (obj.HasComponent<MetaCoreLightComponent>()) {
if (obj.GetComponent<MetaCoreLightComponent>().Enabled != activeState) {
obj.GetComponent<MetaCoreLightComponent>().Enabled = activeState;
anyChanged = true;
}
}
if (obj.HasComponent<MetaCoreCameraComponent>()) {
if (obj.GetComponent<MetaCoreCameraComponent>().Enabled != activeState) {
obj.GetComponent<MetaCoreCameraComponent>().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);

View File

@ -19,6 +19,8 @@
#include <filament/Texture.h>
#include <filament/RenderTarget.h>
#include <filament/LightManager.h>
#include <filament/RenderableManager.h>
#include <filament/MaterialInstance.h>
#include <gltfio/AssetLoader.h>
#include <gltfio/FilamentAsset.h>
@ -44,6 +46,7 @@
#include <unordered_set>
#include <map>
#include <vector>
#include <functional>
// 引入 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,85 +295,32 @@ 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<utils::Entity, MetaCoreGameObject> 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<MetaCoreGameObject> subtreeObjects;
// 1. 自底向上建立完整的 Filament 父子关系映射,解决隐藏过渡节点导致的层级断裂问题
std::unordered_map<utils::Entity, std::vector<utils::Entity>, utils::Entity::Hasher> parentToChildren =
BuildFullParentToChildrenMap(asset);
// 2. 建立 ECS 的父子关系映射
std::unordered_map<MetaCoreId, std::vector<MetaCoreGameObject>> ecsParentToChildren;
std::vector<MetaCoreId> subtreeIds = scene.GetSubtreeObjectIds(parentObject.GetId());
for (MetaCoreId subId : subtreeIds) {
auto subObj = scene.FindGameObject(subId);
if (subObj && subObj.GetId() != parentObject.GetId()) {
subtreeObjects.push_back(subObj);
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<void(MetaCoreGameObject, utils::Entity)> 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));
MetaCoreGameObject childObj;
bool isExisting = false;
// 1. 优先通过 ModelNodeIndex 进行精确匹配(针对带有网格的节点)
for (auto& sceneObj : subtreeObjects) {
if (sceneObj.HasComponent<MetaCoreMeshRendererComponent>()) {
auto& mesh = sceneObj.GetComponent<MetaCoreMeshRendererComponent>();
if (mesh.ModelNodeIndex == static_cast<std::int32_t>(i)) {
childObj = sceneObj;
isExisting = true;
break;
}
}
}
// 2. 如果没找到(针对没有网格的空节点),通过名字进行匹配复用
if (!isExisting) {
for (auto& sceneObj : subtreeObjects) {
if (sceneObj.GetName() == name) {
// 且该节点不能已经被其他实体映射占用
bool alreadyMapped = false;
for (auto& [ent, obj] : entityToObj) {
if (obj.GetId() == sceneObj.GetId()) {
alreadyMapped = true;
break;
}
}
if (!alreadyMapped) {
childObj = sceneObj;
isExisting = true;
break;
}
}
}
}
// 3. 实在没有找到,才进行动态创建(仅作为降级兜底)
if (!isExisting) {
childObj = scene.CreateGameObject(name, parentObject.GetId());
auto& meshRenderer = childObj.AddComponent<MetaCoreMeshRendererComponent>();
meshRenderer.MeshSource = MetaCoreMeshSourceKind::Asset;
meshRenderer.ModelNodeIndex = static_cast<std::int32_t>(i);
}
entityToObj[entity] = childObj;
ObjectToFilamentEntity_[childObj.GetId()] = { asset, entity };
// 获取并设置 Transform
auto instance = tm.getInstance(entity);
if (currentEntity != asset->getRoot()) {
auto instance = tm.getInstance(currentEntity);
if (instance) {
filament::math::mat4f localTransform = tm.getTransform(instance);
@ -354,28 +339,46 @@ public:
glm::vec4 perspective;
glm::decompose(matrix, scale, rotation, translation, skew, perspective);
auto& transform = childObj.GetComponent<MetaCoreTransformComponent>();
auto& transform = currentObj.GetComponent<MetaCoreTransformComponent>();
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;
// 穿透过渡节点获取当前实体的有效 Filament 子实体列表
std::vector<utils::Entity> fChildren;
GetEffectiveFilamentChildren(currentEntity, asset, parentToChildren, fChildren);
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());
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);
}
}
};
// 从根开始执行递归层级树匹配
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<MetaCoreId, const MetaCoreRenderSyncRenderable*> renderableMap;
for (const auto& r : snapshot.Renderables) {
renderableMap[r.ObjectId] = &r;
}
// 定义用于检测场景中某个对象及其所有祖先节点当前在快照里是否应为可见状态的辅助方法
std::unordered_map<MetaCoreId, bool> visibilityCache;
std::function<bool(MetaCoreId)> 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 {
@ -538,6 +597,32 @@ public:
LoadedAssets_[hostRootId] = asset;
// 复制材质实例防止共享干扰,放在加载好资源后
{
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
auto hostRootObj = scene->FindGameObject(hostRootId);
@ -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();
// 1. 自底向上建立完整的 Filament 父子关系映射,解决隐藏过渡节点导致的层级断裂问题
std::unordered_map<utils::Entity, std::vector<utils::Entity>, utils::Entity::Hasher> parentToChildren =
BuildFullParentToChildrenMap(asset);
std::vector<MetaCoreRenderSyncRenderable> candidates;
// 2. 建立快照中的父子关系映射
std::unordered_map<MetaCoreId, std::vector<MetaCoreRenderSyncRenderable>> 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<utils::Entity, MetaCoreId> entityToId;
entityToId[asset->getRoot()] = hostRootId;
// 3. 递归 DFS 进行精确层级树结构匹配,穿透所有过渡节点
std::function<void(MetaCoreId, utils::Entity)> 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<utils::Entity> 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<std::int32_t>(i)) {
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;
}
}
// 2. 其次通过名字匹配
if (!isExisting) {
for (const auto& cand : candidates) {
if (cand.Name == name) {
bool alreadyMapped = false;
for (auto& [ent, id] : entityToId) {
if (id == cand.ObjectId) {
alreadyMapped = true;
break;
}
}
if (!alreadyMapped) {
childObjectId = cand.ObjectId;
isExisting = true;
break;
}
}
}
// 兜底匹配
if (matchedChildId == 0 && name.empty() && !sChildren.empty()) {
matchedChildId = sChildren.front().ObjectId;
sChildren.erase(sChildren.begin());
}
if (isExisting && childObjectId != 0) {
entityToId[entity] = childObjectId;
ObjectToFilamentEntity_[childObjectId] = { asset, entity };
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, std::vector<utils::Entity>, utils::Entity::Hasher>& parentToChildren,
std::vector<utils::Entity>& 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, std::vector<utils::Entity>, utils::Entity::Hasher>
BuildFullParentToChildrenMap(filament::gltfio::FilamentAsset* asset) const {
std::unordered_map<utils::Entity, std::vector<utils::Entity>, 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<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, utils::Entity> SceneLightEntities_;
std::unordered_set<utils::Entity, utils::Entity::Hasher> EntitiesInScene_;

View File

@ -137,7 +137,8 @@ MetaCoreSceneRenderSyncSnapshot MetaCoreSceneRenderSync::BuildSnapshot(const Met
meshRenderer.ModelNodeIndex,
hostRoot.GetId(),
hostRoot.GetName(),
hostRoot.HasComponent<MetaCoreModelRootTag>()
hostRoot.HasComponent<MetaCoreModelRootTag>(),
meshRenderer.BaseColor
});
}

View File

@ -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 {

View File

@ -507,12 +507,14 @@ void MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot() {
auto& rootMesh = root.AddComponent<MetaCore::MetaCoreMeshRendererComponent>();
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<MetaCore::MetaCoreTransformComponent>().Position = glm::vec3(4.0F, 5.0F, 6.0F);
auto& childMesh = child.AddComponent<MetaCore::MetaCoreMeshRendererComponent>();
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<MetaCore::MetaCoreCameraComponent>();
@ -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) {