diff --git a/SandboxProject/Assets/Materials/Material 1.mcmaterial.json b/SandboxProject/Assets/Materials/Material 1.mcmaterial.json new file mode 100644 index 0000000..9a9c4a8 --- /dev/null +++ b/SandboxProject/Assets/Materials/Material 1.mcmaterial.json @@ -0,0 +1,26 @@ +{ + "AlphaCutoff": 0.5, + "AlphaMode": 0, + "AoTexture": "", + "AssetGuid": "fc3fd465-4f39-44af-907a-4fc0494c2502", + "BaseColor": [ + 1.0, + 0.8078431487083435, + 0.5843137502670288 + ], + "BaseColorTexture": "", + "DoubleSided": false, + "EmissiveColor": [ + 0.0, + 0.0, + 0.0 + ], + "EmissiveTexture": "", + "Metallic": 0.0, + "MetallicRoughnessTexture": "", + "Name": "Material 1", + "NormalTexture": "", + "Roughness": 0.5690000057220459, + "ShaderModel": 0, + "StableImportKey": "" +} \ No newline at end of file diff --git a/SandboxProject/Assets/Materials/Material 1.mcmaterial.json.mcmeta b/SandboxProject/Assets/Materials/Material 1.mcmaterial.json.mcmeta new file mode 100644 index 0000000..e943751 --- /dev/null +++ b/SandboxProject/Assets/Materials/Material 1.mcmaterial.json.mcmeta @@ -0,0 +1,8 @@ +{ + "asset_type": "material", + "guid": "fc3fd465-4f39-44af-907a-4fc0494c2502", + "importer_id": "MaterialImporter", + "package_path": "Assets/Materials/Material 1.mcmaterial.json", + "source_hash": 1388934689574133717, + "source_path": "Assets/Materials/Material 1.mcmaterial.json" +} \ No newline at end of file diff --git a/SandboxProject/Assets/Materials/Material 2.mcmaterial.json b/SandboxProject/Assets/Materials/Material 2.mcmaterial.json new file mode 100644 index 0000000..3264e89 --- /dev/null +++ b/SandboxProject/Assets/Materials/Material 2.mcmaterial.json @@ -0,0 +1,26 @@ +{ + "AlphaCutoff": 0.5, + "AlphaMode": 0, + "AoTexture": "", + "AssetGuid": "579d1aa2-baae-4b56-9b05-86f9d2e9a2ff", + "BaseColor": [ + 0.7137255072593689, + 0.95686274766922, + 0.7647058963775635 + ], + "BaseColorTexture": "", + "DoubleSided": false, + "EmissiveColor": [ + 0.0, + 0.0, + 0.0 + ], + "EmissiveTexture": "", + "Metallic": 0.0, + "MetallicRoughnessTexture": "", + "Name": "Material 2", + "NormalTexture": "", + "Roughness": 0.4129999876022339, + "ShaderModel": 0, + "StableImportKey": "" +} \ No newline at end of file diff --git a/SandboxProject/Assets/Materials/Material 2.mcmaterial.json.mcmeta b/SandboxProject/Assets/Materials/Material 2.mcmaterial.json.mcmeta new file mode 100644 index 0000000..00f07f0 --- /dev/null +++ b/SandboxProject/Assets/Materials/Material 2.mcmaterial.json.mcmeta @@ -0,0 +1,8 @@ +{ + "asset_type": "material", + "guid": "579d1aa2-baae-4b56-9b05-86f9d2e9a2ff", + "importer_id": "MaterialImporter", + "package_path": "Assets/Materials/Material 2.mcmaterial.json", + "source_hash": 12206391660553642631, + "source_path": "Assets/Materials/Material 2.mcmaterial.json" +} \ No newline at end of file diff --git a/SandboxProject/Assets/Materials/Material 3.mcmaterial.json b/SandboxProject/Assets/Materials/Material 3.mcmaterial.json new file mode 100644 index 0000000..1e9db4b --- /dev/null +++ b/SandboxProject/Assets/Materials/Material 3.mcmaterial.json @@ -0,0 +1,26 @@ +{ + "AlphaCutoff": 0.5, + "AlphaMode": 0, + "AoTexture": "", + "AssetGuid": "fbe8b648-5368-4af6-b822-d2573ed5f194", + "BaseColor": [ + 0.545098066329956, + 0.30588236451148987, + 0.5647059082984924 + ], + "BaseColorTexture": "", + "DoubleSided": false, + "EmissiveColor": [ + 0.0, + 0.27843138575553894, + 0.0 + ], + "EmissiveTexture": "", + "Metallic": 0.0, + "MetallicRoughnessTexture": "", + "Name": "Material 3", + "NormalTexture": "", + "Roughness": 0.781000018119812, + "ShaderModel": 0, + "StableImportKey": "" +} \ No newline at end of file diff --git a/SandboxProject/Assets/Materials/Material 3.mcmaterial.json.mcmeta b/SandboxProject/Assets/Materials/Material 3.mcmaterial.json.mcmeta new file mode 100644 index 0000000..405378d --- /dev/null +++ b/SandboxProject/Assets/Materials/Material 3.mcmaterial.json.mcmeta @@ -0,0 +1,8 @@ +{ + "asset_type": "material", + "guid": "fbe8b648-5368-4af6-b822-d2573ed5f194", + "importer_id": "MaterialImporter", + "package_path": "Assets/Materials/Material 3.mcmaterial.json", + "source_hash": 7449433069631146521, + "source_path": "Assets/Materials/Material 3.mcmaterial.json" +} \ No newline at end of file diff --git a/SandboxProject/Assets/Materials/Material 4.mcmaterial.json b/SandboxProject/Assets/Materials/Material 4.mcmaterial.json new file mode 100644 index 0000000..4a80a25 --- /dev/null +++ b/SandboxProject/Assets/Materials/Material 4.mcmaterial.json @@ -0,0 +1,26 @@ +{ + "AlphaCutoff": 0.5, + "AlphaMode": 0, + "AoTexture": "", + "AssetGuid": "a23b9298-b98c-47ac-a2ad-3c9dac65c5af", + "BaseColor": [ + 0.7647058963775635, + 0.8392156958580017, + 0.7882353067398071 + ], + "BaseColorTexture": "", + "DoubleSided": false, + "EmissiveColor": [ + 0.0, + 0.0, + 0.0 + ], + "EmissiveTexture": "", + "Metallic": 0.6890000104904175, + "MetallicRoughnessTexture": "", + "Name": "Material 4", + "NormalTexture": "", + "Roughness": 0.5450000166893005, + "ShaderModel": 0, + "StableImportKey": "" +} \ No newline at end of file diff --git a/SandboxProject/Assets/Materials/Material 4.mcmaterial.json.mcmeta b/SandboxProject/Assets/Materials/Material 4.mcmaterial.json.mcmeta new file mode 100644 index 0000000..ec608c2 --- /dev/null +++ b/SandboxProject/Assets/Materials/Material 4.mcmaterial.json.mcmeta @@ -0,0 +1,8 @@ +{ + "asset_type": "material", + "guid": "a23b9298-b98c-47ac-a2ad-3c9dac65c5af", + "importer_id": "MaterialImporter", + "package_path": "Assets/Materials/Material 4.mcmaterial.json", + "source_hash": 12594992031322235982, + "source_path": "Assets/Materials/Material 4.mcmaterial.json" +} \ No newline at end of file diff --git a/SandboxProject/Assets/Materials/Material 5.mcmaterial.json b/SandboxProject/Assets/Materials/Material 5.mcmaterial.json new file mode 100644 index 0000000..a22db74 --- /dev/null +++ b/SandboxProject/Assets/Materials/Material 5.mcmaterial.json @@ -0,0 +1,26 @@ +{ + "AlphaCutoff": 0.5, + "AlphaMode": 0, + "AoTexture": "", + "AssetGuid": "39f65f8e-cdac-43fb-9762-e6c544547a71", + "BaseColor": [ + 0.8705882430076599, + 0.7921568751335144, + 0.8627451062202454 + ], + "BaseColorTexture": "", + "DoubleSided": false, + "EmissiveColor": [ + 0.0, + 0.0, + 0.0 + ], + "EmissiveTexture": "", + "Metallic": 0.0, + "MetallicRoughnessTexture": "", + "Name": "Material 5", + "NormalTexture": "", + "Roughness": 1.0, + "ShaderModel": 0, + "StableImportKey": "" +} \ No newline at end of file diff --git a/SandboxProject/Assets/Materials/Material 5.mcmaterial.json.mcmeta b/SandboxProject/Assets/Materials/Material 5.mcmaterial.json.mcmeta new file mode 100644 index 0000000..e5ea354 --- /dev/null +++ b/SandboxProject/Assets/Materials/Material 5.mcmaterial.json.mcmeta @@ -0,0 +1,8 @@ +{ + "asset_type": "material", + "guid": "39f65f8e-cdac-43fb-9762-e6c544547a71", + "importer_id": "MaterialImporter", + "package_path": "Assets/Materials/Material 5.mcmaterial.json", + "source_hash": 10101379247114018828, + "source_path": "Assets/Materials/Material 5.mcmaterial.json" +} \ No newline at end of file diff --git a/SandboxProject/Assets/Materials/Material.mcmaterial.json b/SandboxProject/Assets/Materials/Material.mcmaterial.json new file mode 100644 index 0000000..93c9860 --- /dev/null +++ b/SandboxProject/Assets/Materials/Material.mcmaterial.json @@ -0,0 +1,26 @@ +{ + "AlphaCutoff": 0.4129999876022339, + "AlphaMode": 0, + "AoTexture": "", + "AssetGuid": "5edbdbc8-4a0d-4991-82a7-782de9ed4e3a", + "BaseColor": [ + 0.9999995231628418, + 0.0, + 1.0 + ], + "BaseColorTexture": "", + "DoubleSided": true, + "EmissiveColor": [ + 0.12941177189350128, + 0.0, + 0.0 + ], + "EmissiveTexture": "", + "Metallic": 0.0, + "MetallicRoughnessTexture": "", + "Name": "Material", + "NormalTexture": "", + "Roughness": 1.0, + "ShaderModel": 0, + "StableImportKey": "" +} \ No newline at end of file diff --git a/SandboxProject/Assets/Materials/Material.mcmaterial.json.mcmeta b/SandboxProject/Assets/Materials/Material.mcmaterial.json.mcmeta new file mode 100644 index 0000000..5bf9307 --- /dev/null +++ b/SandboxProject/Assets/Materials/Material.mcmaterial.json.mcmeta @@ -0,0 +1,8 @@ +{ + "asset_type": "material", + "guid": "5edbdbc8-4a0d-4991-82a7-782de9ed4e3a", + "importer_id": "MaterialImporter", + "package_path": "Assets/Materials/Material.mcmaterial.json", + "source_hash": 7326866736359587430, + "source_path": "Assets/Materials/Material.mcmaterial.json" +} \ No newline at end of file diff --git a/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp b/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp index 3ac775b..1d23d1d 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp @@ -869,12 +869,33 @@ template } [[nodiscard]] std::vector MetaCoreCollectMaterialAssetChoices(MetaCoreEditorContext& editorContext) { - return MetaCoreCollectGeneratedAssetChoices( + std::vector choices; + + const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService(); + if (assetDatabaseService != nullptr && assetDatabaseService->HasProject()) { + for (const MetaCoreAssetRecord& record : assetDatabaseService->GetAssetRegistry()) { + if (record.Type != "material" || !record.Guid.IsValid()) { + continue; + } + choices.push_back(MetaCoreGeneratedAssetChoice{ + record.Guid, + record.RelativePath.filename().string() + " / Material" + }); + } + } + + auto generatedChoices = MetaCoreCollectGeneratedAssetChoices( editorContext, [](const MetaCoreAssetRecord& record, const MetaCoreMaterialAssetDocument& asset) { return record.RelativePath.filename().string() + " / Material / " + asset.Name; } ); + choices.insert(choices.end(), generatedChoices.begin(), generatedChoices.end()); + + std::sort(choices.begin(), choices.end(), [](const MetaCoreGeneratedAssetChoice& lhs, const MetaCoreGeneratedAssetChoice& rhs) { + return lhs.Label < rhs.Label; + }); + return choices; } [[nodiscard]] std::vector MetaCoreCollectTextureAssetChoices(MetaCoreEditorContext& editorContext) { @@ -1047,73 +1068,249 @@ void MetaCoreSyncGeneratedMaterialPreviewToMeshRenderer( assignTextureReference(materialAsset.AoTexture, meshRenderer.AoTextureGuid, meshRenderer.AoTexturePath); } +[[nodiscard]] std::string MetaCoreNormalizeModelRelativePath(std::string path) { + if (path.empty()) { + return {}; + } + std::replace(path.begin(), path.end(), '\\', '/'); + return std::filesystem::path(path).lexically_normal().generic_string(); +} + +[[nodiscard]] bool MetaCoreMaterialGuidListIsEmpty(const std::vector& materialGuids) { + return std::all_of(materialGuids.begin(), materialGuids.end(), [](const MetaCoreAssetGuid& guid) { + return !guid.IsValid(); + }); +} + +[[nodiscard]] std::string MetaCoreResolveSceneObjectModelPath( + const MetaCoreScene& scene, + MetaCoreGameObject object +) { + MetaCoreGameObject current = object; + while (current) { + if (current.HasComponent()) { + const auto& meshRenderer = current.GetComponent(); + if (!meshRenderer.SourceModelPath.empty()) { + return MetaCoreNormalizeModelRelativePath(meshRenderer.SourceModelPath); + } + } + + if (current.HasComponent()) { + const auto& tag = current.GetComponent(); + if (!tag.SourceModelPath.empty()) { + return MetaCoreNormalizeModelRelativePath(tag.SourceModelPath); + } + } + + if (current.GetParentId() == 0) { + break; + } + current = scene.FindGameObject(current.GetParentId()); + } + return {}; +} + +[[nodiscard]] std::optional MetaCoreFindGeneratedMaterialIndexByGuid( + const MetaCoreModelAssetDocument& document, + const MetaCoreAssetGuid& materialGuid +) { + if (!materialGuid.IsValid()) { + return std::nullopt; + } + + for (std::size_t index = 0; index < document.GeneratedMaterialAssets.size(); ++index) { + if (document.GeneratedMaterialAssets[index].AssetGuid == materialGuid) { + return index; + } + } + return std::nullopt; +} + +[[nodiscard]] std::optional MetaCoreResolveFirstGeneratedMaterialIndexForMeshRenderer( + const MetaCoreModelAssetDocument& document, + const MetaCoreMeshRendererComponent& meshRenderer +) { + if (meshRenderer.ModelNodeIndex < 0 || + static_cast(meshRenderer.ModelNodeIndex) >= document.Nodes.size()) { + return std::nullopt; + } + + const MetaCoreImportedGltfNodeDocument& node = document.Nodes[static_cast(meshRenderer.ModelNodeIndex)]; + if (node.MeshIndex < 0 || static_cast(node.MeshIndex) >= document.Meshes.size()) { + return std::nullopt; + } + + const auto& materialSlots = document.Meshes[static_cast(node.MeshIndex)].MaterialSlots; + for (const std::int32_t materialIndex : materialSlots) { + if (materialIndex >= 0 && static_cast(materialIndex) < document.GeneratedMaterialAssets.size()) { + return static_cast(materialIndex); + } + } + return std::nullopt; +} + +void MetaCoreRepairLegacyModelMaterialBinding( + MetaCoreMeshRendererComponent& meshRenderer, + const MetaCoreModelAssetDocument& document, + const MetaCoreAssetGuid& modelGuid +) { + if (meshRenderer.SourceModelPath.empty()) { + meshRenderer.SourceModelPath = document.SourcePath.generic_string(); + } + if (!meshRenderer.SourceModelAssetGuid.IsValid() && modelGuid.IsValid()) { + meshRenderer.SourceModelAssetGuid = modelGuid; + } + if (!MetaCoreMaterialGuidListIsEmpty(meshRenderer.MaterialAssetGuids)) { + return; + } + if (meshRenderer.ModelNodeIndex < 0 || + static_cast(meshRenderer.ModelNodeIndex) >= document.Nodes.size()) { + return; + } + + const MetaCoreImportedGltfNodeDocument& node = document.Nodes[static_cast(meshRenderer.ModelNodeIndex)]; + if (node.MeshIndex < 0 || static_cast(node.MeshIndex) >= document.Meshes.size()) { + return; + } + + const auto& materialSlots = document.Meshes[static_cast(node.MeshIndex)].MaterialSlots; + meshRenderer.MaterialAssetGuids.clear(); + for (const std::int32_t materialIndex : materialSlots) { + if (materialIndex >= 0 && static_cast(materialIndex) < document.GeneratedMaterialAssets.size()) { + meshRenderer.MaterialAssetGuids.push_back(document.GeneratedMaterialAssets[static_cast(materialIndex)].AssetGuid); + } else { + meshRenderer.MaterialAssetGuids.push_back(MetaCoreAssetGuid{}); + } + } +} + void MetaCoreApplyGeneratedMaterialPreviewToScene( MetaCoreEditorContext& editorContext, const MetaCoreModelAssetDocument& document ) { - std::unordered_map texturePaths; - for (const MetaCoreTextureAssetDocument& textureAsset : document.GeneratedTextureAssets) { - if (textureAsset.AssetGuid.IsValid()) { - texturePaths[textureAsset.AssetGuid] = textureAsset.SourcePath.generic_string(); - } - } - + const std::string documentSourcePath = MetaCoreNormalizeModelRelativePath(document.SourcePath.generic_string()); + const MetaCoreAssetGuid modelGuid = documentSourcePath.empty() + ? MetaCoreAssetGuid{} + : MetaCoreAssetRegistry::Get().ResolvePathToGuid(documentSourcePath); + bool changed = false; for (MetaCoreGameObject& sceneObject : editorContext.GetScene().GetGameObjects()) { if (!sceneObject.HasComponent()) { continue; } - for (const MetaCoreAssetGuid& materialGuid : sceneObject.GetComponent().MaterialAssetGuids) { - const auto materialIterator = std::find_if( - document.GeneratedMaterialAssets.begin(), - document.GeneratedMaterialAssets.end(), - [&](const MetaCoreMaterialAssetDocument& materialAsset) { - return materialAsset.AssetGuid == materialGuid; - } - ); - if (materialIterator == document.GeneratedMaterialAssets.end()) { - continue; + auto& meshRenderer = sceneObject.GetComponent(); + std::optional materialIndex; + for (const MetaCoreAssetGuid& materialGuid : meshRenderer.MaterialAssetGuids) { + materialIndex = MetaCoreFindGeneratedMaterialIndexByGuid(document, materialGuid); + if (materialIndex.has_value()) { + break; } - - sceneObject.GetComponent().BaseColor = materialIterator->BaseColor; - sceneObject.GetComponent().Metallic = materialIterator->Metallic; - sceneObject.GetComponent().Roughness = materialIterator->Roughness; - sceneObject.GetComponent().DoubleSided = materialIterator->DoubleSided; - sceneObject.GetComponent().EmissiveColor = materialIterator->EmissiveColor; - sceneObject.GetComponent().AlphaCutoff = materialIterator->AlphaCutoff; - sceneObject.GetComponent().AlphaMode = - materialIterator->AlphaMode == MetaCoreMaterialAlphaMode::Mask - ? MetaCoreMeshAlphaMode::Mask - : (materialIterator->AlphaMode == MetaCoreMaterialAlphaMode::Blend - ? MetaCoreMeshAlphaMode::Blend - : MetaCoreMeshAlphaMode::Opaque); - sceneObject.GetComponent().BaseColorTextureGuid = materialIterator->BaseColorTexture; - sceneObject.GetComponent().BaseColorTexturePath = - materialIterator->BaseColorTexture.IsValid() && texturePaths.contains(materialIterator->BaseColorTexture) - ? texturePaths[materialIterator->BaseColorTexture] - : std::string{}; - sceneObject.GetComponent().MetallicRoughnessTextureGuid = materialIterator->MetallicRoughnessTexture; - sceneObject.GetComponent().MetallicRoughnessTexturePath = - materialIterator->MetallicRoughnessTexture.IsValid() && texturePaths.contains(materialIterator->MetallicRoughnessTexture) - ? texturePaths[materialIterator->MetallicRoughnessTexture] - : std::string{}; - sceneObject.GetComponent().NormalTextureGuid = materialIterator->NormalTexture; - sceneObject.GetComponent().NormalTexturePath = - materialIterator->NormalTexture.IsValid() && texturePaths.contains(materialIterator->NormalTexture) - ? texturePaths[materialIterator->NormalTexture] - : std::string{}; - sceneObject.GetComponent().EmissiveTextureGuid = materialIterator->EmissiveTexture; - sceneObject.GetComponent().EmissiveTexturePath = - materialIterator->EmissiveTexture.IsValid() && texturePaths.contains(materialIterator->EmissiveTexture) - ? texturePaths[materialIterator->EmissiveTexture] - : std::string{}; - sceneObject.GetComponent().AoTextureGuid = materialIterator->AoTexture; - sceneObject.GetComponent().AoTexturePath = - materialIterator->AoTexture.IsValid() && texturePaths.contains(materialIterator->AoTexture) - ? texturePaths[materialIterator->AoTexture] - : std::string{}; - break; } + + const bool sameModelByGuid = + modelGuid.IsValid() && + meshRenderer.SourceModelAssetGuid.IsValid() && + meshRenderer.SourceModelAssetGuid == modelGuid; + const std::string sceneObjectModelPath = MetaCoreResolveSceneObjectModelPath(editorContext.GetScene(), sceneObject); + const bool sameModelByPath = !documentSourcePath.empty() && sceneObjectModelPath == documentSourcePath; + const bool sameModel = sameModelByGuid || sameModelByPath; + + if (!materialIndex.has_value() && sameModel && MetaCoreMaterialGuidListIsEmpty(meshRenderer.MaterialAssetGuids)) { + materialIndex = MetaCoreResolveFirstGeneratedMaterialIndexForMeshRenderer(document, meshRenderer); + } + + if (!materialIndex.has_value()) { + continue; + } + + if (sameModel) { + MetaCoreRepairLegacyModelMaterialBinding(meshRenderer, document, modelGuid); + } + MetaCoreSyncGeneratedMaterialPreviewToMeshRenderer(meshRenderer, document, *materialIndex); + changed = true; + } + + if (changed) { + editorContext.GetScene().IncrementRevision(); + } +} + +void MetaCoreSyncMaterialDocumentPreviewToMeshRenderer( + MetaCoreIAssetDatabaseService* assetDatabaseService, + MetaCoreIAssetEditingService* assetEditingService, + MetaCoreMeshRendererComponent& meshRenderer, + const MetaCoreMaterialAssetDocument& materialAsset +) { + const auto texturePathForGuid = [&](const MetaCoreAssetGuid& textureGuid) -> std::string { + if (!textureGuid.IsValid()) { + return {}; + } + if (assetDatabaseService != nullptr) { + if (const auto textureRecord = assetDatabaseService->FindAssetByGuid(textureGuid); textureRecord.has_value()) { + return textureRecord->RelativePath.generic_string(); + } + } + if (assetEditingService != nullptr) { + if (const auto textureAsset = assetEditingService->LoadTextureAsset(textureGuid); + textureAsset.has_value() && !textureAsset->SourcePath.empty()) { + return textureAsset->SourcePath.generic_string(); + } + } + return {}; + }; + + meshRenderer.BaseColor = materialAsset.BaseColor; + meshRenderer.DoubleSided = materialAsset.DoubleSided; + meshRenderer.Metallic = materialAsset.Metallic; + meshRenderer.Roughness = materialAsset.Roughness; + meshRenderer.AlphaCutoff = materialAsset.AlphaCutoff; + meshRenderer.AlphaMode = + materialAsset.AlphaMode == MetaCoreMaterialAlphaMode::Mask + ? MetaCoreMeshAlphaMode::Mask + : (materialAsset.AlphaMode == MetaCoreMaterialAlphaMode::Blend + ? MetaCoreMeshAlphaMode::Blend + : MetaCoreMeshAlphaMode::Opaque); + meshRenderer.EmissiveColor = materialAsset.EmissiveColor; + + meshRenderer.BaseColorTextureGuid = materialAsset.BaseColorTexture; + meshRenderer.BaseColorTexturePath = texturePathForGuid(materialAsset.BaseColorTexture); + meshRenderer.MetallicRoughnessTextureGuid = materialAsset.MetallicRoughnessTexture; + meshRenderer.MetallicRoughnessTexturePath = texturePathForGuid(materialAsset.MetallicRoughnessTexture); + meshRenderer.NormalTextureGuid = materialAsset.NormalTexture; + meshRenderer.NormalTexturePath = texturePathForGuid(materialAsset.NormalTexture); + meshRenderer.EmissiveTextureGuid = materialAsset.EmissiveTexture; + meshRenderer.EmissiveTexturePath = texturePathForGuid(materialAsset.EmissiveTexture); + meshRenderer.AoTextureGuid = materialAsset.AoTexture; + meshRenderer.AoTexturePath = texturePathForGuid(materialAsset.AoTexture); +} + +void MetaCoreApplyMaterialPreviewToSceneUsers( + MetaCoreEditorContext& editorContext, + const MetaCoreAssetGuid& materialGuid, + const MetaCoreMaterialAssetDocument& materialAsset +) { + const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService(); + const auto assetEditingService = editorContext.GetModuleRegistry().ResolveService(); + bool changed = false; + for (MetaCoreGameObject& sceneObject : editorContext.GetScene().GetGameObjects()) { + if (!sceneObject.HasComponent()) { + continue; + } + auto& meshRenderer = sceneObject.GetComponent(); + if (std::find(meshRenderer.MaterialAssetGuids.begin(), meshRenderer.MaterialAssetGuids.end(), materialGuid) == meshRenderer.MaterialAssetGuids.end()) { + continue; + } + MetaCoreSyncMaterialDocumentPreviewToMeshRenderer( + assetDatabaseService.get(), + assetEditingService.get(), + meshRenderer, + materialAsset + ); + changed = true; + } + + if (changed) { + editorContext.GetScene().IncrementRevision(); } } @@ -1159,17 +1356,20 @@ void MetaCoreForEachSelectedGameObject(MetaCoreEditorContext& editorContext, TAc return false; } - const auto resolvedMaterial = assetEditingService->ResolveGeneratedAsset(materialGuid); - if (!resolvedMaterial.has_value() || resolvedMaterial->GeneratedKind != "material") { + auto materialAsset = assetEditingService->LoadMaterialAsset(materialGuid); + if (!materialAsset.has_value()) { return false; } - const auto modelDocument = assetEditingService->LoadModelAsset(resolvedMaterial->SourceAsset.Guid); - if (!modelDocument.has_value() || resolvedMaterial->GeneratedIndex >= modelDocument->GeneratedMaterialAssets.size()) { - return false; + const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService(); + const auto resolvedMaterial = assetEditingService->ResolveGeneratedAsset(materialGuid); + std::optional modelDocument; + if (resolvedMaterial.has_value() && resolvedMaterial->GeneratedKind == "material") { + modelDocument = assetEditingService->LoadModelAsset(resolvedMaterial->SourceAsset.Guid); } MetaCoreTrackComponentInspectorEdit(editorContext, "拖拽材质到材质槽", false); + bool changed = false; MetaCoreForEachSelectedGameObject(editorContext, [&](MetaCoreGameObject& selectedObject) { if (!selectedObject.HasComponent()) { return; @@ -1178,13 +1378,29 @@ void MetaCoreForEachSelectedGameObject(MetaCoreEditorContext& editorContext, TAc selectedObject.GetComponent().MaterialAssetGuids.resize(materialSlotIndex + 1); } selectedObject.GetComponent().MaterialAssetGuids[materialSlotIndex] = materialGuid; - MetaCoreSyncGeneratedMaterialPreviewToMeshRenderer( - selectedObject.GetComponent(), - *modelDocument, - resolvedMaterial->GeneratedIndex - ); + if (resolvedMaterial.has_value() && + resolvedMaterial->GeneratedKind == "material" && + modelDocument.has_value() && + resolvedMaterial->GeneratedIndex < modelDocument->GeneratedMaterialAssets.size()) { + MetaCoreSyncGeneratedMaterialPreviewToMeshRenderer( + selectedObject.GetComponent(), + *modelDocument, + resolvedMaterial->GeneratedIndex + ); + } else { + MetaCoreSyncMaterialDocumentPreviewToMeshRenderer( + assetDatabaseService.get(), + assetEditingService.get(), + selectedObject.GetComponent(), + *materialAsset + ); + } + changed = true; }); - return true; + if (changed) { + editorContext.GetScene().IncrementRevision(); + } + return changed; } [[nodiscard]] bool MetaCoreReapplyMaterialResourceToSelectedObjects( @@ -1218,6 +1434,57 @@ void MetaCoreClearMaterialSlotOnSelectedObjects( }); } +[[nodiscard]] std::filesystem::path MetaCoreBuildUniqueMaterialPath(MetaCoreIAssetDatabaseService& assetDatabaseService) { + if (!assetDatabaseService.HasProject()) { + return {}; + } + + const std::filesystem::path relativeDirectory = std::filesystem::path("Assets") / "Materials"; + for (int suffix = 0; suffix < 1000; ++suffix) { + const std::string name = suffix == 0 ? "Material" : ("Material " + std::to_string(suffix)); + const std::filesystem::path relativePath = relativeDirectory / (name + ".mcmaterial.json"); + if (!std::filesystem::exists(assetDatabaseService.GetProjectDescriptor().RootPath / relativePath)) { + return relativePath; + } + } + + return relativeDirectory / "Material 1000.mcmaterial.json"; +} + +[[nodiscard]] bool MetaCoreCreateAndBindMaterialToSelectedObjects( + MetaCoreEditorContext& editorContext, + std::size_t materialSlotIndex +) { + const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService(); + if (assetDatabaseService == nullptr || !assetDatabaseService->HasProject()) { + return false; + } + + const std::filesystem::path materialPath = MetaCoreBuildUniqueMaterialPath(*assetDatabaseService); + if (materialPath.empty() || !assetDatabaseService->CreateMaterialAsset(materialPath)) { + return false; + } + + const auto materialRecord = assetDatabaseService->FindAssetByRelativePath(materialPath); + if (!materialRecord.has_value() || !materialRecord->Guid.IsValid()) { + return false; + } + + if (!MetaCoreApplyDraggedMaterialToSelectedObjects(editorContext, materialSlotIndex, materialRecord->Guid)) { + return false; + } + + editorContext.SelectAsset(MetaCoreSelectedAssetState{ + materialRecord->Guid, + materialRecord->RelativePath, + materialRecord->Type, + materialRecord->StorageKind + }); + editorContext.ClearSelectedAssetSubId(); + editorContext.ClearSelection(); + return true; +} + [[nodiscard]] std::optional MetaCoreResolveDefaultMaterialForMeshRendererSlot( MetaCoreEditorContext& editorContext, const MetaCoreMeshRendererComponent& meshRenderer, @@ -2308,6 +2575,31 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon ImGui::EndTable(); } + if (ImGui::Button("添加材质槽")) { + const std::size_t newSlotIndex = meshRenderer.MaterialAssetGuids.size(); + MetaCoreTrackComponentInspectorEdit(editorContext, "添加材质槽", false); + MetaCoreForEachSelectedGameObject(editorContext, [&](MetaCoreGameObject& selectedObject) { + if (selectedObject.HasComponent()) { + selectedObject.GetComponent().MaterialAssetGuids.resize(newSlotIndex + 1); + } + }); + } + ImGui::SameLine(); + std::size_t createSlotIndex = meshRenderer.MaterialAssetGuids.size(); + for (std::size_t materialIndex = 0; materialIndex < meshRenderer.MaterialAssetGuids.size(); ++materialIndex) { + if (!meshRenderer.MaterialAssetGuids[materialIndex].IsValid()) { + createSlotIndex = materialIndex; + break; + } + } + if (ImGui::Button("新建材质并绑定")) { + if (MetaCoreCreateAndBindMaterialToSelectedObjects(editorContext, createSlotIndex)) { + editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已新建材质并绑定到材质槽"); + } else { + editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Material", "新建材质并绑定失败"); + } + } + // 槽位列表循环 const auto materialChoices = MetaCoreCollectMaterialAssetChoices(editorContext); for (std::size_t materialIndex = 0; materialIndex < meshRenderer.MaterialAssetGuids.size(); ++materialIndex) { @@ -2412,6 +2704,14 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon if (ImGui::Selectable("", !materialGuid.IsValid())) { materialGuid = MetaCoreAssetGuid{}; } + if (ImGui::Selectable("新建材质并绑定", false)) { + if (MetaCoreCreateAndBindMaterialToSelectedObjects(editorContext, materialIndex)) { + editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已新建材质并绑定到材质槽"); + } else { + editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Material", "新建材质并绑定失败"); + } + } + ImGui::Separator(); for (const auto& choice : materialChoices) { bool isSelected = (choice.AssetGuid == materialGuid); if (ImGui::Selectable(choice.Label.c_str(), isSelected)) { @@ -2436,6 +2736,13 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon materialGuid = MetaCoreAssetGuid{}; editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已清空材质槽位"); } + if (ImGui::MenuItem("新建材质并绑定")) { + if (MetaCoreCreateAndBindMaterialToSelectedObjects(editorContext, materialIndex)) { + editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已新建材质并绑定到材质槽"); + } else { + editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Material", "新建材质并绑定失败"); + } + } if (ImGui::MenuItem("恢复模型默认材质 (Restore Default)")) { if (MetaCoreRestoreDefaultMaterialSlotOnSelectedObjects(editorContext, materialIndex)) { editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已恢复模型默认材质槽位"); @@ -2480,6 +2787,21 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon }); editorContext.SetSelectedAssetSubId("material:" + materialGuid.ToString()); editorContext.ClearSelection(); + } else { + const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService(); + const auto materialRecord = assetDatabaseService != nullptr + ? assetDatabaseService->FindAssetByGuid(materialGuid) + : std::nullopt; + if (materialRecord.has_value()) { + editorContext.SelectAsset(MetaCoreSelectedAssetState{ + materialRecord->Guid, + materialRecord->RelativePath, + materialRecord->Type, + materialRecord->StorageKind + }); + editorContext.ClearSelectedAssetSubId(); + editorContext.ClearSelection(); + } } } } @@ -2720,10 +3042,20 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon 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("未找到对应材质资源"); + if (!materialDocument.has_value()) { + ImGui::TextColored(ImVec4(0.95F, 0.38F, 0.32F, 1.0F), "未找到对应材质资源"); + ImGui::TextDisabled("可以清空槽位,或新建一个材质并绑定到当前槽位。"); + if (ImGui::Button(("新建材质并绑定##MissingMaterial_" + std::to_string(materialIndex)).c_str())) { + if (MetaCoreCreateAndBindMaterialToSelectedObjects(editorContext, materialIndex)) { + editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已新建材质并绑定到材质槽"); + } else { + editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Material", "新建材质并绑定失败"); + } + } + ImGui::SameLine(); + if (ImGui::Button(("清空槽位##MissingMaterial_" + std::to_string(materialIndex)).c_str())) { + MetaCoreClearMaterialSlotOnSelectedObjects(editorContext, materialIndex); + } } else { MetaCoreMaterialAssetDocument materialAsset = *materialDocument; bool modified = false; @@ -2791,9 +3123,13 @@ void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorCon if (modified) { if (assetEditingService->SaveMaterialAsset(materialGuid, materialAsset)) { - if (const auto modelDocument = assetEditingService->LoadModelAsset(resolvedMaterial->SourceAsset.Guid); - modelDocument.has_value()) { - MetaCoreApplyGeneratedMaterialPreviewToScene(editorContext, *modelDocument); + if (resolvedMaterial.has_value() && resolvedMaterial->GeneratedKind == "material") { + if (const auto modelDocument = assetEditingService->LoadModelAsset(resolvedMaterial->SourceAsset.Guid); + modelDocument.has_value()) { + MetaCoreApplyGeneratedMaterialPreviewToScene(editorContext, *modelDocument); + } + } else { + MetaCoreApplyMaterialPreviewToSceneUsers(editorContext, materialGuid, materialAsset); } editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Material", "已保存材质资源修改"); } else { @@ -5609,7 +5945,7 @@ bool MetaCoreBuiltinAssetEditingService::SaveMaterialAsset( } } - if (PackageService_ != nullptr && assetRecord.has_value()) { + if (PackageService_ != nullptr) { const auto resolved = MetaCoreResolveGeneratedAssetDocument( *AssetDatabaseService_, *PackageService_, @@ -5621,13 +5957,17 @@ bool MetaCoreBuiltinAssetEditingService::SaveMaterialAsset( MetaCoreModelAssetDocument editableDocument = resolved->Document; editableDocument.GeneratedMaterialAssets[resolved->GeneratedIndex] = document; editableDocument.GeneratedMaterialAssets[resolved->GeneratedIndex].AssetGuid = materialGuid; - return MetaCoreWriteImportedGltfAssetDocument( + const bool saved = MetaCoreWriteImportedGltfAssetDocument( *AssetDatabaseService_, *PackageService_, *ReflectionRegistry_, resolved->AssetRecord, editableDocument ); + if (saved) { + ModelCache_[resolved->AssetRecord.Guid] = editableDocument; + } + return saved; } } } diff --git a/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp b/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp index 5e9a8ee..dfa6e1f 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp @@ -1274,89 +1274,177 @@ void MetaCoreApplyMaterialPreviewToScene( return; } + const auto normalizeModelPath = [](std::string path) { + if (path.empty()) { + return std::string{}; + } + std::replace(path.begin(), path.end(), '\\', '/'); + return std::filesystem::path(path).lexically_normal().generic_string(); + }; + const auto materialGuidListIsEmpty = [](const std::vector& materialGuids) { + return std::all_of(materialGuids.begin(), materialGuids.end(), [](const MetaCoreAssetGuid& guid) { + return !guid.IsValid(); + }); + }; + const auto findMaterialIndexByGuid = [&](const MetaCoreAssetGuid& materialGuid) -> std::optional { + if (!materialGuid.IsValid()) { + return std::nullopt; + } + for (std::size_t index = 0; index < document.GeneratedMaterialAssets.size(); ++index) { + if (document.GeneratedMaterialAssets[index].AssetGuid == materialGuid) { + return index; + } + } + return std::nullopt; + }; + const auto resolveFirstMaterialIndexForMeshRenderer = [&](const MetaCoreMeshRendererComponent& meshRenderer) -> std::optional { + if (meshRenderer.ModelNodeIndex < 0 || + static_cast(meshRenderer.ModelNodeIndex) >= document.Nodes.size()) { + return std::nullopt; + } + const MetaCoreImportedGltfNodeDocument& node = document.Nodes[static_cast(meshRenderer.ModelNodeIndex)]; + if (node.MeshIndex < 0 || static_cast(node.MeshIndex) >= document.Meshes.size()) { + return std::nullopt; + } + for (const std::int32_t materialIndex : document.Meshes[static_cast(node.MeshIndex)].MaterialSlots) { + if (materialIndex >= 0 && static_cast(materialIndex) < document.GeneratedMaterialAssets.size()) { + return static_cast(materialIndex); + } + } + return std::nullopt; + }; + const auto resolveSceneObjectModelPath = [&](MetaCoreGameObject object) { + MetaCoreGameObject current = object; + while (current) { + if (current.HasComponent()) { + const auto& meshRenderer = current.GetComponent(); + if (!meshRenderer.SourceModelPath.empty()) { + return normalizeModelPath(meshRenderer.SourceModelPath); + } + } + if (current.HasComponent()) { + const auto& tag = current.GetComponent(); + if (!tag.SourceModelPath.empty()) { + return normalizeModelPath(tag.SourceModelPath); + } + } + if (current.GetParentId() == 0) { + break; + } + current = editorContext.GetScene().FindGameObject(current.GetParentId()); + } + return std::string{}; + }; + const auto repairLegacyBinding = [&](MetaCoreMeshRendererComponent& meshRenderer, const MetaCoreAssetGuid& modelGuid) { + if (meshRenderer.SourceModelPath.empty()) { + meshRenderer.SourceModelPath = document.SourcePath.generic_string(); + } + if (!meshRenderer.SourceModelAssetGuid.IsValid() && modelGuid.IsValid()) { + meshRenderer.SourceModelAssetGuid = modelGuid; + } + if (!materialGuidListIsEmpty(meshRenderer.MaterialAssetGuids) || + meshRenderer.ModelNodeIndex < 0 || + static_cast(meshRenderer.ModelNodeIndex) >= document.Nodes.size()) { + return; + } + const MetaCoreImportedGltfNodeDocument& node = document.Nodes[static_cast(meshRenderer.ModelNodeIndex)]; + if (node.MeshIndex < 0 || static_cast(node.MeshIndex) >= document.Meshes.size()) { + return; + } + meshRenderer.MaterialAssetGuids.clear(); + for (const std::int32_t materialIndex : document.Meshes[static_cast(node.MeshIndex)].MaterialSlots) { + if (materialIndex >= 0 && static_cast(materialIndex) < document.GeneratedMaterialAssets.size()) { + meshRenderer.MaterialAssetGuids.push_back(document.GeneratedMaterialAssets[static_cast(materialIndex)].AssetGuid); + } else { + meshRenderer.MaterialAssetGuids.push_back(MetaCoreAssetGuid{}); + } + } + }; + const auto applyMaterialIndex = [&](MetaCoreMeshRendererComponent& meshRenderer, std::size_t materialIndex) { + if (materialIndex >= document.GeneratedMaterialAssets.size()) { + return; + } + const MetaCoreMaterialAssetDocument& materialDocument = document.GeneratedMaterialAssets[materialIndex]; + const MetaCoreAssetGuid materialGuid = materialDocument.AssetGuid; + meshRenderer.BaseColor = materialDocument.BaseColor; + meshRenderer.BaseColorTextureGuid = materialDocument.BaseColorTexture; + meshRenderer.BaseColorTexturePath = + materialDocument.BaseColorTexture.IsValid() && texturePaths.contains(materialDocument.BaseColorTexture) + ? texturePaths[materialDocument.BaseColorTexture] + : std::string{}; + meshRenderer.MetallicRoughnessTextureGuid = materialDocument.MetallicRoughnessTexture; + meshRenderer.MetallicRoughnessTexturePath = + materialDocument.MetallicRoughnessTexture.IsValid() && texturePaths.contains(materialDocument.MetallicRoughnessTexture) + ? texturePaths[materialDocument.MetallicRoughnessTexture] + : std::string{}; + meshRenderer.DoubleSided = doubleSidedFlags.contains(materialGuid) ? doubleSidedFlags[materialGuid] : true; + meshRenderer.Metallic = metallicValues.contains(materialGuid) ? metallicValues[materialGuid] : 0.0F; + meshRenderer.Roughness = roughnessValues.contains(materialGuid) ? roughnessValues[materialGuid] : 1.0F; + meshRenderer.EmissiveColor = emissiveColors.contains(materialGuid) ? emissiveColors[materialGuid] : glm::vec3(0.0F, 0.0F, 0.0F); + const MetaCoreMaterialAlphaMode alphaMode = alphaModes.contains(materialGuid) ? alphaModes[materialGuid] : MetaCoreMaterialAlphaMode::Opaque; + meshRenderer.AlphaMode = + alphaMode == MetaCoreMaterialAlphaMode::Mask + ? MetaCoreMeshAlphaMode::Mask + : (alphaMode == MetaCoreMaterialAlphaMode::Blend ? MetaCoreMeshAlphaMode::Blend : MetaCoreMeshAlphaMode::Opaque); + meshRenderer.AlphaCutoff = alphaCutoffs.contains(materialGuid) ? alphaCutoffs[materialGuid] : 0.5F; + meshRenderer.NormalTextureGuid = materialDocument.NormalTexture; + meshRenderer.NormalTexturePath = + materialDocument.NormalTexture.IsValid() && texturePaths.contains(materialDocument.NormalTexture) + ? texturePaths[materialDocument.NormalTexture] + : std::string{}; + meshRenderer.EmissiveTextureGuid = materialDocument.EmissiveTexture; + meshRenderer.EmissiveTexturePath = + materialDocument.EmissiveTexture.IsValid() && texturePaths.contains(materialDocument.EmissiveTexture) + ? texturePaths[materialDocument.EmissiveTexture] + : std::string{}; + meshRenderer.AoTextureGuid = materialDocument.AoTexture; + meshRenderer.AoTexturePath = + materialDocument.AoTexture.IsValid() && texturePaths.contains(materialDocument.AoTexture) + ? texturePaths[materialDocument.AoTexture] + : std::string{}; + }; + + const std::string documentSourcePath = normalizeModelPath(document.SourcePath.generic_string()); + const MetaCoreAssetGuid modelGuid = documentSourcePath.empty() + ? MetaCoreAssetGuid{} + : MetaCoreAssetRegistry::Get().ResolvePathToGuid(documentSourcePath); + bool changed = false; for (MetaCoreGameObject& gameObject : editorContext.GetScene().GetGameObjects()) { if (!gameObject.HasComponent()) { continue; } - for (const MetaCoreAssetGuid& materialGuid : gameObject.GetComponent().MaterialAssetGuids) { - const auto iterator = materialBaseColors.find(materialGuid); - if (iterator != materialBaseColors.end()) { - gameObject.GetComponent().BaseColor = iterator->second; - const auto materialDocumentIterator = std::find_if( - document.GeneratedMaterialAssets.begin(), - document.GeneratedMaterialAssets.end(), - [&](const MetaCoreMaterialAssetDocument& materialAsset) { - return materialAsset.AssetGuid == materialGuid; - } - ); - if (materialDocumentIterator != document.GeneratedMaterialAssets.end() && - materialDocumentIterator->BaseColorTexture.IsValid()) { - const auto textureIterator = texturePaths.find(materialDocumentIterator->BaseColorTexture); - gameObject.GetComponent().BaseColorTexturePath = - textureIterator != texturePaths.end() ? textureIterator->second : std::string{}; - } else { - gameObject.GetComponent().BaseColorTexturePath.clear(); - } - if (materialDocumentIterator != document.GeneratedMaterialAssets.end() && - materialDocumentIterator->MetallicRoughnessTexture.IsValid()) { - const auto textureIterator = texturePaths.find(materialDocumentIterator->MetallicRoughnessTexture); - gameObject.GetComponent().MetallicRoughnessTexturePath = - textureIterator != texturePaths.end() ? textureIterator->second : std::string{}; - } else { - gameObject.GetComponent().MetallicRoughnessTexturePath.clear(); - } - const auto doubleSidedIterator = doubleSidedFlags.find(materialGuid); - gameObject.GetComponent().DoubleSided = - doubleSidedIterator != doubleSidedFlags.end() ? doubleSidedIterator->second : true; - const auto metallicIterator = metallicValues.find(materialGuid); - gameObject.GetComponent().Metallic = - metallicIterator != metallicValues.end() ? metallicIterator->second : 0.0F; - const auto roughnessIterator = roughnessValues.find(materialGuid); - gameObject.GetComponent().Roughness = - roughnessIterator != roughnessValues.end() ? roughnessIterator->second : 1.0F; - const auto emissiveColorIterator = emissiveColors.find(materialGuid); - gameObject.GetComponent().EmissiveColor = - emissiveColorIterator != emissiveColors.end() ? emissiveColorIterator->second : glm::vec3(0.0F, 0.0F, 0.0F); - const auto alphaModeIterator = alphaModes.find(materialGuid); - gameObject.GetComponent().AlphaMode = - alphaModeIterator != alphaModes.end() - ? (alphaModeIterator->second == MetaCoreMaterialAlphaMode::Mask - ? MetaCoreMeshAlphaMode::Mask - : (alphaModeIterator->second == MetaCoreMaterialAlphaMode::Blend - ? MetaCoreMeshAlphaMode::Blend - : MetaCoreMeshAlphaMode::Opaque)) - : MetaCoreMeshAlphaMode::Opaque; - const auto alphaCutoffIterator = alphaCutoffs.find(materialGuid); - gameObject.GetComponent().AlphaCutoff = - alphaCutoffIterator != alphaCutoffs.end() ? alphaCutoffIterator->second : 0.5F; - if (materialDocumentIterator != document.GeneratedMaterialAssets.end() && - materialDocumentIterator->NormalTexture.IsValid()) { - const auto textureIterator = texturePaths.find(materialDocumentIterator->NormalTexture); - gameObject.GetComponent().NormalTexturePath = - textureIterator != texturePaths.end() ? textureIterator->second : std::string{}; - } else { - gameObject.GetComponent().NormalTexturePath.clear(); - } - if (materialDocumentIterator != document.GeneratedMaterialAssets.end() && - materialDocumentIterator->EmissiveTexture.IsValid()) { - const auto textureIterator = texturePaths.find(materialDocumentIterator->EmissiveTexture); - gameObject.GetComponent().EmissiveTexturePath = - textureIterator != texturePaths.end() ? textureIterator->second : std::string{}; - } else { - gameObject.GetComponent().EmissiveTexturePath.clear(); - } - if (materialDocumentIterator != document.GeneratedMaterialAssets.end() && - materialDocumentIterator->AoTexture.IsValid()) { - const auto textureIterator = texturePaths.find(materialDocumentIterator->AoTexture); - gameObject.GetComponent().AoTexturePath = - textureIterator != texturePaths.end() ? textureIterator->second : std::string{}; - } else { - gameObject.GetComponent().AoTexturePath.clear(); - } + auto& meshRenderer = gameObject.GetComponent(); + std::optional materialIndex; + for (const MetaCoreAssetGuid& materialGuid : meshRenderer.MaterialAssetGuids) { + materialIndex = findMaterialIndexByGuid(materialGuid); + if (materialIndex.has_value()) { break; } } + + const bool sameModelByGuid = + modelGuid.IsValid() && + meshRenderer.SourceModelAssetGuid.IsValid() && + meshRenderer.SourceModelAssetGuid == modelGuid; + const bool sameModelByPath = !documentSourcePath.empty() && resolveSceneObjectModelPath(gameObject) == documentSourcePath; + const bool sameModel = sameModelByGuid || sameModelByPath; + if (!materialIndex.has_value() && sameModel && materialGuidListIsEmpty(meshRenderer.MaterialAssetGuids)) { + materialIndex = resolveFirstMaterialIndexForMeshRenderer(meshRenderer); + } + if (!materialIndex.has_value()) { + continue; + } + + if (sameModel) { + repairLegacyBinding(meshRenderer, modelGuid); + } + applyMaterialIndex(meshRenderer, *materialIndex); + changed = true; + } + + if (changed) { + editorContext.GetScene().IncrementRevision(); } } @@ -2686,6 +2774,472 @@ void DrawReferencedObjectList(MetaCoreEditorContext& editorContext, const std::v } } +[[nodiscard]] const char* MetaCoreMaterialShaderModelLabel(MetaCoreMaterialShaderModel shaderModel) { + switch (shaderModel) { + case MetaCoreMaterialShaderModel::PbrMetalRough: + return "PBR 金属粗糙度"; + default: + return "未知"; + } +} + +[[nodiscard]] const char* MetaCoreMaterialAlphaModeLabel(MetaCoreMaterialAlphaMode alphaMode) { + switch (alphaMode) { + case MetaCoreMaterialAlphaMode::Opaque: + return "不透明"; + case MetaCoreMaterialAlphaMode::Mask: + return "镂空裁剪"; + case MetaCoreMaterialAlphaMode::Blend: + return "透明混合"; + default: + return "未知"; + } +} + +[[nodiscard]] ImU32 MetaCoreMaterialColorToU32(const glm::vec3& color, int alpha = 255) { + const auto toByte = [](float value) { + return static_cast(std::clamp(value, 0.0F, 1.0F) * 255.0F + 0.5F); + }; + return IM_COL32(toByte(color.r), toByte(color.g), toByte(color.b), std::clamp(alpha, 0, 255)); +} + +void MetaCoreApplyMaterialDocumentToMeshRenderer( + MetaCoreIAssetDatabaseService& assetDatabaseService, + const MetaCoreIAssetEditingService* assetEditingService, + MetaCoreMeshRendererComponent& meshRenderer, + const MetaCoreMaterialAssetDocument& materialDocument +) { + const auto texturePathForGuid = [&](const MetaCoreAssetGuid& textureGuid) -> std::string { + if (!textureGuid.IsValid()) { + return {}; + } + const auto textureRecord = assetDatabaseService.FindAssetByGuid(textureGuid); + if (textureRecord.has_value()) { + return textureRecord->RelativePath.generic_string(); + } + if (assetEditingService != nullptr) { + const auto textureDocument = assetEditingService->LoadTextureAsset(textureGuid); + if (textureDocument.has_value() && !textureDocument->SourcePath.empty()) { + return textureDocument->SourcePath.generic_string(); + } + } + return {}; + }; + + meshRenderer.BaseColor = materialDocument.BaseColor; + meshRenderer.Metallic = materialDocument.Metallic; + meshRenderer.Roughness = materialDocument.Roughness; + meshRenderer.DoubleSided = materialDocument.DoubleSided; + meshRenderer.EmissiveColor = materialDocument.EmissiveColor; + meshRenderer.AlphaCutoff = materialDocument.AlphaCutoff; + meshRenderer.AlphaMode = + materialDocument.AlphaMode == MetaCoreMaterialAlphaMode::Mask + ? MetaCoreMeshAlphaMode::Mask + : (materialDocument.AlphaMode == MetaCoreMaterialAlphaMode::Blend + ? MetaCoreMeshAlphaMode::Blend + : MetaCoreMeshAlphaMode::Opaque); + + meshRenderer.BaseColorTextureGuid = materialDocument.BaseColorTexture; + meshRenderer.BaseColorTexturePath = texturePathForGuid(materialDocument.BaseColorTexture); + meshRenderer.NormalTextureGuid = materialDocument.NormalTexture; + meshRenderer.NormalTexturePath = texturePathForGuid(materialDocument.NormalTexture); + meshRenderer.MetallicRoughnessTextureGuid = materialDocument.MetallicRoughnessTexture; + meshRenderer.MetallicRoughnessTexturePath = texturePathForGuid(materialDocument.MetallicRoughnessTexture); + meshRenderer.EmissiveTextureGuid = materialDocument.EmissiveTexture; + meshRenderer.EmissiveTexturePath = texturePathForGuid(materialDocument.EmissiveTexture); + meshRenderer.AoTextureGuid = materialDocument.AoTexture; + meshRenderer.AoTexturePath = texturePathForGuid(materialDocument.AoTexture); +} + +void MetaCoreApplyStandaloneMaterialPreviewToScene( + MetaCoreEditorContext& editorContext, + MetaCoreIAssetDatabaseService& assetDatabaseService, + const MetaCoreMaterialAssetDocument& materialDocument +) { + if (!materialDocument.AssetGuid.IsValid()) { + return; + } + const auto assetEditingService = editorContext.GetModuleRegistry().ResolveService(); + bool changed = false; + for (MetaCoreGameObject& gameObject : editorContext.GetScene().GetGameObjects()) { + if (!gameObject.HasComponent()) { + continue; + } + const auto& materialGuids = gameObject.GetComponent().MaterialAssetGuids; + if (std::find(materialGuids.begin(), materialGuids.end(), materialDocument.AssetGuid) == materialGuids.end()) { + continue; + } + MetaCoreApplyMaterialDocumentToMeshRenderer( + assetDatabaseService, + assetEditingService.get(), + gameObject.GetComponent(), + materialDocument + ); + changed = true; + } + + if (changed) { + editorContext.GetScene().IncrementRevision(); + } +} + +struct MetaCoreMaterialTextureChoice { + MetaCoreAssetGuid AssetGuid{}; + std::string Label{}; +}; + +struct MetaCoreMaterialTextureBindingState { + std::string DisplayName = "无贴图"; + std::string DetailText{}; + bool Missing = false; + std::optional LocateAsset{}; + std::string LocateSubId{}; +}; + +[[nodiscard]] std::vector MetaCoreCollectMaterialTextureChoices( + MetaCoreIAssetDatabaseService& assetDatabaseService +) { + std::vector choices; + choices.reserve(assetDatabaseService.GetAssetRegistry().size()); + for (const MetaCoreAssetRecord& record : assetDatabaseService.GetAssetRegistry()) { + if (record.Type != "texture" || !record.Guid.IsValid()) { + continue; + } + std::string label = record.RelativePath.filename().string(); + if (!record.RelativePath.parent_path().empty()) { + label += " | " + record.RelativePath.parent_path().generic_string(); + } + choices.push_back(MetaCoreMaterialTextureChoice{record.Guid, std::move(label)}); + } + std::sort(choices.begin(), choices.end(), [](const auto& lhs, const auto& rhs) { + return lhs.Label < rhs.Label; + }); + return choices; +} + +[[nodiscard]] MetaCoreMaterialTextureBindingState MetaCoreResolveMaterialTextureBinding( + MetaCoreIAssetDatabaseService& assetDatabaseService, + const MetaCoreIAssetEditingService* assetEditingService, + const MetaCoreAssetGuid& textureGuid +) { + MetaCoreMaterialTextureBindingState state; + if (!textureGuid.IsValid()) { + return state; + } + if (const auto record = assetDatabaseService.FindAssetByGuid(textureGuid); record.has_value()) { + state.DisplayName = record->RelativePath.filename().string(); + state.DetailText = record->RelativePath.generic_string(); + state.LocateAsset = *record; + return state; + } + + if (assetEditingService != nullptr) { + const auto resolved = assetEditingService->ResolveGeneratedAsset(textureGuid); + const auto textureDocument = assetEditingService->LoadTextureAsset(textureGuid); + if (resolved.has_value() && resolved->GeneratedKind == "texture" && textureDocument.has_value()) { + state.DisplayName = !textureDocument->Name.empty() + ? textureDocument->Name + : (!textureDocument->SourcePath.empty() ? textureDocument->SourcePath.filename().string() : std::string("生成贴图")); + state.DetailText = "生成贴图: " + resolved->SourceAsset.RelativePath.generic_string(); + if (!textureDocument->SourcePath.empty()) { + state.DetailText += " / " + textureDocument->SourcePath.generic_string(); + } + state.LocateAsset = resolved->SourceAsset; + state.LocateSubId = MetaCoreBuildGeneratedResourceSubId("texture", textureGuid); + return state; + } + if (textureDocument.has_value()) { + state.DisplayName = !textureDocument->Name.empty() + ? textureDocument->Name + : (!textureDocument->SourcePath.empty() ? textureDocument->SourcePath.filename().string() : std::string("贴图资源")); + state.DetailText = textureDocument->SourcePath.generic_string(); + return state; + } + } + + state.DisplayName = "贴图缺失"; + state.Missing = true; + return state; +} + +void MetaCoreDrawMaterialPreviewCard(const MetaCoreMaterialAssetDocument& materialDocument) { + const float cardSize = std::min(std::max(ImGui::GetContentRegionAvail().x, 180.0F), 260.0F); + const ImVec2 canvasMin = ImGui::GetCursorScreenPos(); + const ImVec2 canvasSize(cardSize, cardSize + 72.0F); + const ImVec2 canvasMax(canvasMin.x + canvasSize.x, canvasMin.y + canvasSize.y); + ImGui::InvisibleButton("##MaterialPreviewCard", canvasSize); + + ImDrawList* drawList = ImGui::GetWindowDrawList(); + drawList->AddRectFilled(canvasMin, canvasMax, IM_COL32(28, 31, 38, 255), 12.0F); + drawList->AddRect(canvasMin, canvasMax, IM_COL32(255, 255, 255, 28), 12.0F); + + const ImVec2 previewMin(canvasMin.x + 14.0F, canvasMin.y + 14.0F); + const ImVec2 previewMax(canvasMax.x - 14.0F, canvasMin.y + cardSize - 6.0F); + const float checker = 14.0F; + for (float y = previewMin.y; y < previewMax.y; y += checker) { + for (float x = previewMin.x; x < previewMax.x; x += checker) { + const bool even = (static_cast((x - previewMin.x) / checker) + static_cast((y - previewMin.y) / checker)) % 2 == 0; + drawList->AddRectFilled( + ImVec2(x, y), + ImVec2(std::min(x + checker, previewMax.x), std::min(y + checker, previewMax.y)), + even ? IM_COL32(55, 58, 66, 255) : IM_COL32(39, 42, 50, 255) + ); + } + } + drawList->AddRectFilled(previewMin, previewMax, MetaCoreMaterialColorToU32(materialDocument.BaseColor, 238), 10.0F); + + const ImVec2 sphereCenter( + (previewMin.x + previewMax.x) * 0.5F, + (previewMin.y + previewMax.y) * 0.5F + ); + const float radius = std::min(previewMax.x - previewMin.x, previewMax.y - previewMin.y) * 0.31F; + drawList->AddCircleFilled(sphereCenter, radius, MetaCoreMaterialColorToU32(materialDocument.BaseColor), 48); + drawList->AddCircleFilled( + ImVec2(sphereCenter.x + radius * 0.20F, sphereCenter.y + radius * 0.22F), + radius * 0.78F, + IM_COL32(0, 0, 0, static_cast(50.0F + materialDocument.Roughness * 80.0F)), + 48 + ); + const float highlightRadius = std::max(3.0F, radius * (0.24F - materialDocument.Roughness * 0.14F)); + const int highlightAlpha = static_cast(95.0F + materialDocument.Metallic * 115.0F + (1.0F - materialDocument.Roughness) * 40.0F); + drawList->AddCircleFilled( + ImVec2(sphereCenter.x - radius * 0.34F, sphereCenter.y - radius * 0.36F), + highlightRadius, + IM_COL32(255, 255, 255, std::clamp(highlightAlpha, 80, 245)), + 24 + ); + const float emissiveStrength = std::max({materialDocument.EmissiveColor.r, materialDocument.EmissiveColor.g, materialDocument.EmissiveColor.b}); + if (emissiveStrength > 0.001F) { + drawList->AddCircle( + sphereCenter, + radius + 5.0F, + MetaCoreMaterialColorToU32(materialDocument.EmissiveColor, 180), + 48, + 3.0F + ); + } + drawList->AddRect(previewMin, previewMax, IM_COL32(255, 255, 255, 42), 10.0F); + + const float infoY = previewMax.y + 12.0F; + drawList->AddText(ImVec2(canvasMin.x + 16.0F, infoY), IM_COL32(225, 230, 238, 255), "材质预览"); + const std::string info = + "金属 " + std::to_string(static_cast(materialDocument.Metallic * 100.0F + 0.5F)) + + "% 粗糙 " + std::to_string(static_cast(materialDocument.Roughness * 100.0F + 0.5F)) + "%"; + drawList->AddText(ImVec2(canvasMin.x + 16.0F, infoY + ImGui::GetTextLineHeight() + 4.0F), IM_COL32(151, 160, 174, 255), info.c_str()); +} + +[[nodiscard]] bool MetaCoreDrawMaterialTextureSlot( + MetaCoreEditorContext& editorContext, + MetaCoreIAssetDatabaseService& assetDatabaseService, + const MetaCoreIAssetEditingService* assetEditingService, + const char* label, + const char* id, + MetaCoreAssetGuid& textureGuid, + const std::vector& textureChoices +) { + bool changed = false; + ImGui::PushID(id); + if (ImGui::BeginTable("TextureSlot", 2, ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_WidthFixed, 118.0F); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(label); + ImGui::TableNextColumn(); + + const MetaCoreMaterialTextureBindingState binding = + MetaCoreResolveMaterialTextureBinding(assetDatabaseService, assetEditingService, textureGuid); + ImGui::PushStyleColor(ImGuiCol_Button, binding.Missing ? ImVec4(0.34F, 0.13F, 0.12F, 1.0F) : ImVec4(0.12F, 0.13F, 0.16F, 1.0F)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, binding.Missing ? ImVec4(0.44F, 0.18F, 0.16F, 1.0F) : ImVec4(0.18F, 0.20F, 0.25F, 1.0F)); + const bool openPicker = + ImGui::Button((binding.DisplayName + "##TexturePickerButton").c_str(), ImVec2(std::max(ImGui::GetContentRegionAvail().x - 98.0F, 80.0F), 0.0F)); + ImGui::PopStyleColor(2); + + if (ImGui::BeginDragDropTarget()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(MetaCoreProjectAssetDragDropPayloadType); + payload != nullptr && payload->DataSize == sizeof(MetaCoreProjectAssetDragDropPayload)) { + const auto* assetPayload = static_cast(payload->Data); + if (assetPayload != nullptr && + !assetPayload->IsDirectory && + assetPayload->AssetGuid.IsValid() && + std::strcmp(assetPayload->AssetType, "texture") == 0) { + textureGuid = assetPayload->AssetGuid; + changed = true; + } + } + ImGui::EndDragDropTarget(); + } + + if (openPicker) { + ImGui::OpenPopup("TexturePickerPopup"); + } + if (ImGui::BeginPopup("TexturePickerPopup")) { + if (ImGui::Selectable("无贴图", !textureGuid.IsValid())) { + textureGuid = MetaCoreAssetGuid{}; + changed = true; + } + for (const auto& choice : textureChoices) { + const bool selected = choice.AssetGuid == textureGuid; + if (ImGui::Selectable(choice.Label.c_str(), selected)) { + textureGuid = choice.AssetGuid; + changed = true; + } + } + ImGui::EndPopup(); + } + + ImGui::SameLine(); + if (ImGui::SmallButton("清空")) { + if (textureGuid.IsValid()) { + textureGuid = MetaCoreAssetGuid{}; + changed = true; + } + } + ImGui::SameLine(); + const bool canLocate = binding.LocateAsset.has_value(); + ImGui::BeginDisabled(!canLocate); + if (ImGui::SmallButton("定位")) { + if (binding.LocateAsset.has_value()) { + const MetaCoreAssetRecord& textureRecord = *binding.LocateAsset; + editorContext.SelectAsset(MetaCoreSelectedAssetState{ + textureRecord.Guid, + textureRecord.RelativePath, + textureRecord.Type, + textureRecord.StorageKind + }); + if (binding.LocateSubId.empty()) { + editorContext.ClearSelectedAssetSubId(); + } else { + editorContext.SetSelectedAssetSubId(binding.LocateSubId); + } + editorContext.ClearSelection(); + } + } + ImGui::EndDisabled(); + + if (binding.Missing) { + ImGui::TextColored(ImVec4(0.95F, 0.38F, 0.32F, 1.0F), "贴图缺失: %s", textureGuid.ToString().c_str()); + } else if (!binding.DetailText.empty()) { + ImGui::TextDisabled("%s", binding.DetailText.c_str()); + } + ImGui::EndTable(); + } + ImGui::PopID(); + return changed; +} + +[[nodiscard]] bool MetaCoreDrawMaterialVisualEditor( + MetaCoreEditorContext& editorContext, + MetaCoreIAssetDatabaseService& assetDatabaseService, + MetaCoreMaterialAssetDocument& materialDocument, + const char* sourceLabel, + const std::filesystem::path& displayPath, + std::string_view idSuffix +) { + bool changed = false; + const std::string suffix(idSuffix); + const auto textureChoices = MetaCoreCollectMaterialTextureChoices(assetDatabaseService); + const auto assetEditingService = editorContext.GetModuleRegistry().ResolveService(); + + ImGui::PushID(suffix.c_str()); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0F, 4.0F)); + ImGui::TextUnformatted(materialDocument.Name.empty() ? "<未命名材质>" : materialDocument.Name.c_str()); + ImGui::SameLine(); + ImGui::TextDisabled("%s", sourceLabel); + ImGui::TextDisabled("GUID: %s", materialDocument.AssetGuid.ToString().c_str()); + if (!displayPath.empty()) { + ImGui::TextDisabled("路径: %s", displayPath.generic_string().c_str()); + } + ImGui::Separator(); + + const float availableWidth = ImGui::GetContentRegionAvail().x; + const bool useTwoColumns = availableWidth >= 520.0F; + if (useTwoColumns) { + ImGui::BeginTable("MaterialVisualLayout", 2, ImGuiTableFlags_SizingStretchProp); + ImGui::TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthStretch, 0.62F); + ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 0.38F); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + } + + if (ImGui::CollapsingHeader("基础参数", ImGuiTreeNodeFlags_DefaultOpen)) { + std::array nameBuffer{}; + std::snprintf(nameBuffer.data(), nameBuffer.size(), "%s", materialDocument.Name.c_str()); + ImGui::SetNextItemWidth(-1.0F); + if (ImGui::InputText("名称", nameBuffer.data(), nameBuffer.size())) { + materialDocument.Name = nameBuffer.data(); + changed = true; + } + + const char* shaderItems[] = {"PBR 金属粗糙度"}; + int shaderIndex = 0; + ImGui::BeginDisabled(); + ImGui::Combo("着色模型", &shaderIndex, shaderItems, IM_ARRAYSIZE(shaderItems)); + ImGui::EndDisabled(); + + float baseColor[3] = {materialDocument.BaseColor.r, materialDocument.BaseColor.g, materialDocument.BaseColor.b}; + if (ImGui::ColorEdit3("基础颜色", baseColor)) { + materialDocument.BaseColor = {baseColor[0], baseColor[1], baseColor[2]}; + changed = true; + } + if (ImGui::SliderFloat("金属度", &materialDocument.Metallic, 0.0F, 1.0F, "%.3f")) { + changed = true; + } + if (ImGui::SliderFloat("粗糙度", &materialDocument.Roughness, 0.0F, 1.0F, "%.3f")) { + changed = true; + } + float emissiveColor[3] = {materialDocument.EmissiveColor.r, materialDocument.EmissiveColor.g, materialDocument.EmissiveColor.b}; + if (ImGui::ColorEdit3("发光颜色", emissiveColor)) { + materialDocument.EmissiveColor = {emissiveColor[0], emissiveColor[1], emissiveColor[2]}; + changed = true; + } + } + + if (ImGui::CollapsingHeader("贴图", ImGuiTreeNodeFlags_DefaultOpen)) { + changed |= MetaCoreDrawMaterialTextureSlot(editorContext, assetDatabaseService, assetEditingService.get(), "基础颜色", "BaseColorTexture", materialDocument.BaseColorTexture, textureChoices); + changed |= MetaCoreDrawMaterialTextureSlot(editorContext, assetDatabaseService, assetEditingService.get(), "法线", "NormalTexture", materialDocument.NormalTexture, textureChoices); + changed |= MetaCoreDrawMaterialTextureSlot(editorContext, assetDatabaseService, assetEditingService.get(), "金属粗糙度", "MetallicRoughnessTexture", materialDocument.MetallicRoughnessTexture, textureChoices); + changed |= MetaCoreDrawMaterialTextureSlot(editorContext, assetDatabaseService, assetEditingService.get(), "遮蔽 AO", "AoTexture", materialDocument.AoTexture, textureChoices); + changed |= MetaCoreDrawMaterialTextureSlot(editorContext, assetDatabaseService, assetEditingService.get(), "发光", "EmissiveTexture", materialDocument.EmissiveTexture, textureChoices); + } + + if (ImGui::CollapsingHeader("透明与渲染选项", ImGuiTreeNodeFlags_DefaultOpen)) { + const char* alphaItems[] = {"不透明", "镂空裁剪", "透明混合"}; + int alphaIndex = static_cast(materialDocument.AlphaMode); + if (ImGui::Combo("透明模式", &alphaIndex, alphaItems, IM_ARRAYSIZE(alphaItems))) { + materialDocument.AlphaMode = static_cast(std::clamp(alphaIndex, 0, 2)); + changed = true; + } + if (materialDocument.AlphaMode == MetaCoreMaterialAlphaMode::Mask) { + if (ImGui::SliderFloat("裁剪阈值", &materialDocument.AlphaCutoff, 0.0F, 1.0F, "%.3f")) { + changed = true; + } + } + if (ImGui::Checkbox("双面渲染", &materialDocument.DoubleSided)) { + changed = true; + } + } + + if (ImGui::CollapsingHeader("高级信息")) { + ImGui::TextDisabled("着色模型: %s", MetaCoreMaterialShaderModelLabel(materialDocument.ShaderModel)); + ImGui::TextDisabled("透明模式: %s", MetaCoreMaterialAlphaModeLabel(materialDocument.AlphaMode)); + ImGui::TextDisabled("StableImportKey: %s", materialDocument.StableImportKey.empty() ? "<无>" : materialDocument.StableImportKey.c_str()); + } + + if (useTwoColumns) { + ImGui::TableNextColumn(); + MetaCoreDrawMaterialPreviewCard(materialDocument); + ImGui::EndTable(); + } else { + ImGui::Separator(); + MetaCoreDrawMaterialPreviewCard(materialDocument); + } + + ImGui::PopStyleVar(); + ImGui::PopID(); + return changed; +} + void DrawGeneratedMaterialDetails( MetaCoreEditorContext& editorContext, const MetaCoreMaterialAssetDocument& selectedMaterial @@ -2707,84 +3261,29 @@ void DrawGeneratedMaterialDetails( MetaCoreMaterialAssetDocument materialAsset = *loadedMaterial; ImGui::Separator(); - ImGui::TextDisabled("MATERIAL RESOURCE"); - ImGui::Text("Name: %s", materialAsset.Name.empty() ? "" : materialAsset.Name.c_str()); - ImGui::TextDisabled("Guid: %s", materialAsset.AssetGuid.ToString().c_str()); + const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService(); + if (assetDatabaseService == nullptr) { + ImGui::TextDisabled("资产数据库不可用。"); + return; + } + const auto references = FindSceneObjectsUsingMaterial(editorContext, materialAsset.AssetGuid); - ImGui::Text("Scene References: %d", static_cast(references.size())); + ImGui::TextDisabled("场景引用: %d", static_cast(references.size())); if (!references.empty()) { - if (ImGui::Button("Highlight Scene Objects##SelectedMaterial")) { + if (ImGui::Button("高亮引用对象##SelectedMaterial")) { editorContext.SetSelection(references, references.front()); } DrawReferencedObjectList(editorContext, references, "SelectedMaterialRefs"); } - bool modified = false; - float baseColor[3] = {materialAsset.BaseColor.x, materialAsset.BaseColor.y, materialAsset.BaseColor.z}; - if (ImGui::ColorEdit3("Base Color##SelectedMaterial", baseColor)) { - materialAsset.BaseColor = {baseColor[0], baseColor[1], baseColor[2]}; - modified = true; - } - - if (ImGui::SliderFloat("Metallic##SelectedMaterial", &materialAsset.Metallic, 0.0F, 1.0F)) { - modified = true; - } - if (ImGui::SliderFloat("Roughness##SelectedMaterial", &materialAsset.Roughness, 0.0F, 1.0F)) { - modified = true; - } - if (ImGui::Checkbox("Double Sided##SelectedMaterial", &materialAsset.DoubleSided)) { - modified = true; - } - - const char* alphaModeItems[] = {"Opaque", "Mask", "Blend"}; - int alphaModeIndex = static_cast(materialAsset.AlphaMode); - if (ImGui::Combo("Alpha Mode##SelectedMaterial", &alphaModeIndex, alphaModeItems, IM_ARRAYSIZE(alphaModeItems))) { - materialAsset.AlphaMode = static_cast(alphaModeIndex); - modified = true; - } - if (materialAsset.AlphaMode == MetaCoreMaterialAlphaMode::Mask) { - if (ImGui::SliderFloat("Alpha Cutoff##SelectedMaterial", &materialAsset.AlphaCutoff, 0.0F, 1.0F)) { - modified = true; - } - } - - const auto textureChoices = MetaCoreCollectTextureAssetChoices(editorContext); - MetaCoreAssetGuid baseColorTexture = materialAsset.BaseColorTexture; - if (MetaCoreDrawGeneratedAssetCombo("Base Color Texture##SelectedMaterial", textureChoices, baseColorTexture)) { - materialAsset.BaseColorTexture = baseColorTexture; - modified = true; - } - DrawMaterialTextureSlotSummary(editorContext, *assetEditingService, "BaseColor", materialAsset.BaseColorTexture); - MetaCoreAssetGuid normalTexture = materialAsset.NormalTexture; - if (MetaCoreDrawGeneratedAssetCombo("Normal Texture##SelectedMaterial", textureChoices, normalTexture)) { - materialAsset.NormalTexture = normalTexture; - modified = true; - } - DrawMaterialTextureSlotSummary(editorContext, *assetEditingService, "Normal", materialAsset.NormalTexture); - MetaCoreAssetGuid metalRoughTexture = materialAsset.MetallicRoughnessTexture; - if (MetaCoreDrawGeneratedAssetCombo("MetalRough Texture##SelectedMaterial", textureChoices, metalRoughTexture)) { - materialAsset.MetallicRoughnessTexture = metalRoughTexture; - modified = true; - } - DrawMaterialTextureSlotSummary(editorContext, *assetEditingService, "MetalRough", materialAsset.MetallicRoughnessTexture); - MetaCoreAssetGuid emissiveTexture = materialAsset.EmissiveTexture; - if (MetaCoreDrawGeneratedAssetCombo("Emissive Texture##SelectedMaterial", textureChoices, emissiveTexture)) { - materialAsset.EmissiveTexture = emissiveTexture; - modified = true; - } - DrawMaterialTextureSlotSummary(editorContext, *assetEditingService, "Emissive", materialAsset.EmissiveTexture); - MetaCoreAssetGuid aoTexture = materialAsset.AoTexture; - if (MetaCoreDrawGeneratedAssetCombo("AO Texture##SelectedMaterial", textureChoices, aoTexture)) { - materialAsset.AoTexture = aoTexture; - modified = true; - } - DrawMaterialTextureSlotSummary(editorContext, *assetEditingService, "AO", materialAsset.AoTexture); - - float emissiveColor[3] = {materialAsset.EmissiveColor.x, materialAsset.EmissiveColor.y, materialAsset.EmissiveColor.z}; - if (ImGui::ColorEdit3("Emissive Color##SelectedMaterial", emissiveColor)) { - materialAsset.EmissiveColor = {emissiveColor[0], emissiveColor[1], emissiveColor[2]}; - modified = true; - } + const bool modified = MetaCoreDrawMaterialVisualEditor( + editorContext, + *assetDatabaseService, + materialAsset, + "模型生成材质", + resolvedMaterial->SourceAsset.RelativePath, + "GeneratedMaterial" + ); if (modified) { if (assetEditingService->SaveMaterialAsset(materialAsset.AssetGuid, materialAsset)) { @@ -2792,7 +3291,7 @@ void DrawGeneratedMaterialDetails( modelDocument.has_value()) { MetaCoreApplyMaterialPreviewToScene(editorContext, *modelDocument); } - ImGui::TextColored(ImVec4(0.32F, 0.85F, 0.42F, 1.0F), "已保存材质资源修改"); + ImGui::TextColored(ImVec4(0.32F, 0.85F, 0.42F, 1.0F), "已自动保存材质资源"); } else { ImGui::TextColored(ImVec4(0.92F, 0.32F, 0.28F, 1.0F), "材质资源保存失败"); } @@ -3912,107 +4411,41 @@ void DrawMaterialAssetDetails( MetaCoreIAssetDatabaseService& assetDatabaseService, MetaCoreIReflectionRegistry& reflectionRegistry ) { + (void)reflectionRegistry; const MetaCoreSelectedAssetState& selectedAsset = editorContext.GetSelectedAsset(); const auto assetRecord = assetDatabaseService.FindAssetByGuid(selectedAsset.Guid); if (!assetRecord.has_value()) return; - - const std::filesystem::path absolutePath = assetDatabaseService.GetProjectDescriptor().RootPath / assetRecord->RelativePath; - - auto materialDocOpt = MetaCoreSceneSerializer::LoadMaterialFromJson(absolutePath, reflectionRegistry.GetTypeRegistry()); - if (!materialDocOpt.has_value()) { - ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "加载材质失败!"); + const auto assetEditingService = editorContext.GetModuleRegistry().ResolveService(); + if (assetEditingService == nullptr) { + ImGui::TextColored(ImVec4(1.0F, 0.4F, 0.4F, 1.0F), "材质编辑服务不可用。"); return; } - MetaCoreMaterialAssetDocument materialDoc = std::move(*materialDocOpt); + auto materialDocOpt = assetEditingService->LoadMaterialAsset(selectedAsset.Guid); + if (!materialDocOpt.has_value()) { + ImGui::TextColored(ImVec4(1.0F, 0.4F, 0.4F, 1.0F), "加载材质失败。"); + return; + } + MetaCoreMaterialAssetDocument materialDoc = *materialDocOpt; + if (!materialDoc.AssetGuid.IsValid()) { + materialDoc.AssetGuid = selectedAsset.Guid; + } - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4, 4)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0F, 4.0F)); if (ImGui::BeginChild("MaterialInspector", ImVec2(0, 0), true, 0)) { - ImGui::TextDisabled("MATERIAL ASSET"); - ImGui::Separator(); - ImGui::Spacing(); - - bool changed = false; - - float color[3] = { materialDoc.BaseColor.r, materialDoc.BaseColor.g, materialDoc.BaseColor.b }; - if (ImGui::ColorEdit3("基础颜色 (Base Color)", color)) { - materialDoc.BaseColor = glm::vec3(color[0], color[1], color[2]); - changed = true; - } - - float metallic = materialDoc.Metallic; - if (ImGui::SliderFloat("金属度 (Metallic)", &metallic, 0.0f, 1.0f)) { - materialDoc.Metallic = metallic; - changed = true; - } - - float roughness = materialDoc.Roughness; - if (ImGui::SliderFloat("粗糙度 (Roughness)", &roughness, 0.0f, 1.0f)) { - materialDoc.Roughness = roughness; - changed = true; - } - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Text("贴图绑定 (Textures)"); - - auto drawTextureCombo = [&](const char* label, MetaCoreAssetGuid& currentGuid) { - std::string previewName = "None"; - if (currentGuid.IsValid()) { - const auto record = assetDatabaseService.FindAssetByGuid(currentGuid); - if (record.has_value()) { - previewName = record->RelativePath.filename().string(); - } else { - previewName = "Missing (" + currentGuid.ToString() + ")"; - } - } - - if (ImGui::BeginCombo(label, previewName.c_str())) { - if (ImGui::Selectable("None", !currentGuid.IsValid())) { - if (currentGuid.IsValid()) { - currentGuid = MetaCoreAssetGuid{}; - changed = true; - } - } - - for (const auto& a : assetDatabaseService.GetAssetRegistry()) { - if (a.Type == "texture") { - const bool selected = (currentGuid == a.Guid); - const std::string name = a.RelativePath.filename().string(); - if (ImGui::Selectable(name.c_str(), selected)) { - if (currentGuid != a.Guid) { - currentGuid = a.Guid; - changed = true; - } - } - } - } - ImGui::EndCombo(); - } - }; - - drawTextureCombo("基础颜色贴图 (Base Color Texture)", materialDoc.BaseColorTexture); - drawTextureCombo("法线贴图 (Normal Texture)", materialDoc.NormalTexture); - drawTextureCombo("金属粗糙度贴图 (Metallic Roughness)", materialDoc.MetallicRoughnessTexture); - drawTextureCombo("遮蔽贴图 (Occlusion/Ao Texture)", materialDoc.AoTexture); - + const bool changed = MetaCoreDrawMaterialVisualEditor( + editorContext, + assetDatabaseService, + materialDoc, + "独立材质", + assetRecord->RelativePath, + "StandaloneMaterial" + ); if (changed) { - if (MetaCoreSceneSerializer::SaveMaterialToJson(absolutePath, materialDoc, reflectionRegistry.GetTypeRegistry())) { - const std::filesystem::path metaPath = assetDatabaseService.GetProjectDescriptor().RootPath / MetaCoreBuildMetaPath(assetRecord->RelativePath); - const std::uint64_t newHash = MetaCoreHashFile(absolutePath).value_or(0); - std::error_code ec; - if (std::filesystem::exists(metaPath, ec)) { - try { - std::ifstream input(metaPath); - nlohmann::json json; - input >> json; - input.close(); - json["source_hash"] = newHash; - std::ofstream output(metaPath); - output << json.dump(4); - output.close(); - } catch (...) {} - } - assetDatabaseService.Refresh(); + if (assetEditingService->SaveMaterialAsset(selectedAsset.Guid, materialDoc)) { + MetaCoreApplyStandaloneMaterialPreviewToScene(editorContext, assetDatabaseService, materialDoc); + ImGui::TextColored(ImVec4(0.32F, 0.85F, 0.42F, 1.0F), "已自动保存材质资源"); + } else { + ImGui::TextColored(ImVec4(0.92F, 0.32F, 0.28F, 1.0F), "材质资源保存失败"); } } } diff --git a/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp b/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp index 833773d..8e3c085 100644 --- a/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp +++ b/Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp @@ -5,6 +5,7 @@ #include "MetaCoreRender/MetaCoreRenderTypes.h" #include "MetaCoreRender/MetaCoreImGuiHelper.h" #include "MetaCoreRender/MetaCoreSceneRenderSync.h" +#include "stb/stb_image.h" #include #include "MetaCoreFoundation/MetaCoreAssetRegistry.h" #include "MetaCoreFoundation/MetaCoreRuntimeAssetRegistry.h" @@ -21,7 +22,9 @@ #include #include #include +#include #include +#include #include #include @@ -306,6 +309,7 @@ public: } } AssetDuplicatedMaterials_.clear(); + DestroyMaterialTextureCache(); for (auto& [id, entity] : SceneLightEntities_) { if (Scene_) { @@ -391,9 +395,121 @@ public: } void SetProjectRootPath(const std::filesystem::path& projectRootPath) { + if (ProjectRootPath_ != projectRootPath) { + DestroyMaterialTextureCache(); + } ProjectRootPath_ = projectRootPath; } + void DestroyMaterialTextureCache() { + if (Engine_) { + Engine_->flushAndWait(); + for (auto& [_, texture] : MaterialTextureCache_) { + if (texture != nullptr) { + Engine_->destroy(texture); + } + } + } + MaterialTextureCache_.clear(); + } + + [[nodiscard]] std::filesystem::path ResolveMaterialTexturePath( + const std::string& texturePath, + const std::string& sourceModelPath + ) const { + if (texturePath.empty()) { + return {}; + } + + const std::filesystem::path rawPath(texturePath); + if (rawPath.is_absolute()) { + return std::filesystem::exists(rawPath) ? rawPath : std::filesystem::path{}; + } + + const std::filesystem::path projectRelativePath = ProjectRootPath_ / rawPath; + if (std::filesystem::exists(projectRelativePath)) { + return projectRelativePath; + } + + if (!sourceModelPath.empty()) { + const std::filesystem::path modelRelativePath(sourceModelPath); + const std::filesystem::path modelDirectory = modelRelativePath.parent_path(); + if (!modelDirectory.empty()) { + const std::filesystem::path modelRelativeTexturePath = ProjectRootPath_ / modelDirectory / rawPath; + if (std::filesystem::exists(modelRelativeTexturePath)) { + return modelRelativeTexturePath; + } + } + } + + const std::filesystem::path assetsRelativePath = ProjectRootPath_ / "Assets" / rawPath; + if (std::filesystem::exists(assetsRelativePath)) { + return assetsRelativePath; + } + + return {}; + } + + [[nodiscard]] filament::Texture* LoadMaterialTexture( + const std::string& texturePath, + const std::string& sourceModelPath, + bool srgb + ) { + if (Engine_ == nullptr || texturePath.empty()) { + return nullptr; + } + + const std::filesystem::path absolutePath = ResolveMaterialTexturePath(texturePath, sourceModelPath); + if (absolutePath.empty()) { + return nullptr; + } + + const std::string cacheKey = absolutePath.lexically_normal().generic_string() + (srgb ? "|srgb" : "|linear"); + if (const auto cacheIterator = MaterialTextureCache_.find(cacheKey); + cacheIterator != MaterialTextureCache_.end()) { + return cacheIterator->second; + } + + int width = 0; + int height = 0; + int channels = 0; + unsigned char* pixels = stbi_load(absolutePath.string().c_str(), &width, &height, &channels, 4); + if (pixels == nullptr || width <= 0 || height <= 0) { + if (pixels != nullptr) { + stbi_image_free(pixels); + } + MetaCoreFilamentDebugLog("Material texture load failed path=\"" + absolutePath.generic_string() + "\""); + return nullptr; + } + + const std::size_t byteSize = static_cast(width) * static_cast(height) * 4U; + filament::Texture::PixelBufferDescriptor pixelBuffer( + pixels, + byteSize, + filament::Texture::Format::RGBA, + filament::Texture::Type::UBYTE, + [](void* buffer, size_t, void*) { + stbi_image_free(buffer); + } + ); + + filament::Texture* texture = filament::Texture::Builder() + .width(static_cast(width)) + .height(static_cast(height)) + .levels(1) + .sampler(filament::Texture::Sampler::SAMPLER_2D) + .format(srgb ? filament::Texture::InternalFormat::SRGB8_A8 : filament::Texture::InternalFormat::RGBA8) + .build(*Engine_); + + if (texture == nullptr) { + return nullptr; + } + + texture->setImage(*Engine_, 0, std::move(pixelBuffer)); + MaterialTextureCache_[cacheKey] = texture; + return texture; + } + void ParseModelHierarchyToEcs(MetaCoreScene& scene, MetaCoreGameObject& parentObject, filament::gltfio::FilamentAsset* asset) { if (!asset) return; @@ -1015,36 +1131,124 @@ public: } } - // 5. 同步所有存活网格的材质属性,例如 BaseColor + const auto setFloatParameterIfPresent = [](filament::MaterialInstance& materialInstance, const char* name, float value) { + const filament::Material* material = materialInstance.getMaterial(); + if (material != nullptr && material->hasParameter(name)) { + materialInstance.setParameter(name, value); + } + }; + const auto setFloat3ParameterIfPresent = [](filament::MaterialInstance& materialInstance, const char* name, filament::math::float3 value) { + const filament::Material* material = materialInstance.getMaterial(); + if (material != nullptr && material->hasParameter(name)) { + materialInstance.setParameter(name, value); + } + }; + const auto setFloat4ParameterIfPresent = [](filament::MaterialInstance& materialInstance, const char* name, filament::math::float4 value) { + const filament::Material* material = materialInstance.getMaterial(); + if (material != nullptr && material->hasParameter(name)) { + materialInstance.setParameter(name, value); + } + }; + const auto setTextureParameterIfPresent = []( + filament::MaterialInstance& materialInstance, + const char* name, + filament::Texture* texture, + const filament::TextureSampler& sampler + ) -> bool { + const filament::Material* material = materialInstance.getMaterial(); + if (material == nullptr || !material->hasParameter(name)) { + return false; + } + materialInstance.setParameter(name, texture, sampler); + return true; + }; + + // 5. 同步所有存活网格的材质预览属性。MaterialInstance 参数必须在渲染前更新。 auto& rm = Engine_->getRenderableManager(); + const filament::TextureSampler materialSampler( + filament::TextureSampler::MinFilter::LINEAR, + filament::TextureSampler::MagFilter::LINEAR, + filament::TextureSampler::WrapMode::REPEAT + ); 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) { + std::vector materialTargetEntities; + CollectRenderableDescendants(itEntity->second.first, itEntity->second.second, materialTargetEntities); + if (materialTargetEntities.empty()) { continue; } - auto instance = rm.getInstance(entity); - if (!instance) { - continue; - } + filament::Texture* baseColorTexture = LoadMaterialTexture( + renderable.BaseColorTexturePath, + renderable.SourceModelPath, + true + ); + filament::Texture* metallicRoughnessTexture = LoadMaterialTexture( + renderable.MetallicRoughnessTexturePath, + renderable.SourceModelPath, + false + ); + filament::Texture* normalTexture = LoadMaterialTexture( + renderable.NormalTexturePath, + renderable.SourceModelPath, + false + ); + filament::Texture* emissiveTexture = LoadMaterialTexture( + renderable.EmissiveTexturePath, + renderable.SourceModelPath, + true + ); + filament::Texture* aoTexture = LoadMaterialTexture( + renderable.AoTexturePath, + renderable.SourceModelPath, + false + ); - // 遍历所有 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); + for (utils::Entity materialTargetEntity : materialTargetEntities) { + const auto instance = rm.getInstance(materialTargetEntity); + if (!instance) { + continue; + } + + // 遍历所有 primitive,获取材质实例,并设置材质预览属性。 + const size_t primCount = rm.getPrimitiveCount(instance); + for (size_t primIndex = 0; primIndex < primCount; ++primIndex) { + filament::MaterialInstance* matInst = rm.getMaterialInstanceAt(instance, primIndex); + if (matInst) { + filament::math::float4 baseColorVec{ + renderable.BaseColor.x, + renderable.BaseColor.y, + renderable.BaseColor.z, + 1.0f + }; + setFloat4ParameterIfPresent(*matInst, "baseColorFactor", baseColorVec); + setFloatParameterIfPresent(*matInst, "metallicFactor", renderable.Metallic); + setFloatParameterIfPresent(*matInst, "roughnessFactor", renderable.Roughness); + setFloat3ParameterIfPresent( + *matInst, + "emissiveFactor", + filament::math::float3{ + renderable.EmissiveColor.x, + renderable.EmissiveColor.y, + renderable.EmissiveColor.z + } + ); + if (renderable.AlphaMode == MetaCoreMeshAlphaMode::Mask) { + setFloatParameterIfPresent(*matInst, "alphaCutoff", renderable.AlphaCutoff); + } + setTextureParameterIfPresent(*matInst, "baseColorMap", baseColorTexture, materialSampler); + setTextureParameterIfPresent(*matInst, "metallicRoughnessMap", metallicRoughnessTexture, materialSampler); + setTextureParameterIfPresent(*matInst, "normalMap", normalTexture, materialSampler); + setTextureParameterIfPresent(*matInst, "emissiveMap", emissiveTexture, materialSampler); + if (!setTextureParameterIfPresent(*matInst, "occlusionMap", aoTexture, materialSampler)) { + if (!setTextureParameterIfPresent(*matInst, "aoMap", aoTexture, materialSampler)) { + setTextureParameterIfPresent(*matInst, "ambientOcclusionMap", aoTexture, materialSampler); + } + } + } } } } @@ -1420,6 +1624,60 @@ private: return parentToChildren; } + void CollectRenderableDescendants( + filament::gltfio::FilamentAsset* asset, + utils::Entity rootEntity, + std::vector& outEntities + ) const { + if (!Engine_ || rootEntity.isNull()) { + return; + } + + auto& rm = Engine_->getRenderableManager(); + auto& tm = Engine_->getTransformManager(); + const auto addRenderable = [&](utils::Entity entity) { + if (entity.isNull() || !rm.getInstance(entity)) { + return; + } + if (std::find(outEntities.begin(), outEntities.end(), entity) == outEntities.end()) { + outEntities.push_back(entity); + } + }; + + addRenderable(rootEntity); + if (asset == nullptr) { + return; + } + + const utils::Entity* entities = asset->getEntities(); + const size_t entityCount = asset->getEntityCount(); + for (size_t i = 0; i < entityCount; ++i) { + utils::Entity candidate = entities[i]; + if (candidate.isNull() || candidate == rootEntity) { + continue; + } + + utils::Entity current = candidate; + bool isDescendant = false; + while (!current.isNull()) { + if (current == rootEntity) { + isDescendant = true; + break; + } + + const auto transformInstance = tm.getInstance(current); + if (!transformInstance) { + break; + } + current = tm.getParent(transformInstance); + } + + if (isDescendant) { + addRenderable(candidate); + } + } + } + static filament::math::float3 ToFilamentFloat3(const glm::vec3& value) { return filament::math::float3{ value.x, value.y, value.z }; } @@ -1565,6 +1823,7 @@ private: std::unordered_map HostRootSourceModelGuids_; std::unordered_set InvalidatedModelGuids_; std::unordered_map> AssetDuplicatedMaterials_; + std::unordered_map MaterialTextureCache_; std::unordered_map SceneLightEntities_; std::unordered_set EntitiesInScene_; diff --git a/Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp b/Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp index 825a61f..f1f8d77 100644 --- a/Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp +++ b/Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp @@ -180,7 +180,18 @@ MetaCoreSceneRenderSyncSnapshot MetaCoreSceneRenderSync::BuildSnapshot(const Met hostRoot.GetId(), hostRoot.GetName(), hostRoot.HasComponent(), - meshRenderer.BaseColor + meshRenderer.BaseColor, + meshRenderer.Metallic, + meshRenderer.Roughness, + meshRenderer.EmissiveColor, + meshRenderer.AlphaMode, + meshRenderer.AlphaCutoff, + meshRenderer.DoubleSided, + meshRenderer.BaseColorTexturePath, + meshRenderer.MetallicRoughnessTexturePath, + meshRenderer.NormalTexturePath, + meshRenderer.EmissiveTexturePath, + meshRenderer.AoTexturePath }); } diff --git a/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreSceneRenderSync.h b/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreSceneRenderSync.h index 871dcfa..4e65f3e 100644 --- a/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreSceneRenderSync.h +++ b/Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreSceneRenderSync.h @@ -36,6 +36,17 @@ struct MetaCoreRenderSyncRenderable { std::string HostRootName{}; bool HostRootHasModelRootTag = false; glm::vec3 BaseColor{1.0F, 1.0F, 1.0F}; + float Metallic = 0.0F; + float Roughness = 1.0F; + glm::vec3 EmissiveColor{0.0F, 0.0F, 0.0F}; + MetaCoreMeshAlphaMode AlphaMode = MetaCoreMeshAlphaMode::Opaque; + float AlphaCutoff = 0.5F; + bool DoubleSided = true; + std::string BaseColorTexturePath{}; + std::string MetallicRoughnessTexturePath{}; + std::string NormalTexturePath{}; + std::string EmissiveTexturePath{}; + std::string AoTexturePath{}; }; struct MetaCoreRenderSyncCamera { diff --git a/tests/MetaCoreSmokeTests.cpp b/tests/MetaCoreSmokeTests.cpp index ed52afb..f9caef1 100644 --- a/tests/MetaCoreSmokeTests.cpp +++ b/tests/MetaCoreSmokeTests.cpp @@ -652,6 +652,44 @@ void MetaCoreTestProjectAssetDatabaseOperations() { MetaCoreExpect(materialRecord.has_value(), "材质应进入资产数据库"); MetaCoreExpect(materialRecord->Guid.IsValid(), "材质 GUID 应有效"); MetaCoreExpect(materialRecord->Type == "material", "材质资产类型应正确"); + const auto assetEditingService = moduleRegistry.ResolveService(); + MetaCoreExpect(assetEditingService != nullptr, "应解析到 AssetEditingService"); + const std::filesystem::path materialMetaPath = tempProjectRoot / (materialPath.generic_string() + ".mcmeta"); + nlohmann::json oldMaterialMeta; + { + std::ifstream input(materialMetaPath); + input >> oldMaterialMeta; + } + const std::uint64_t oldMaterialHash = oldMaterialMeta.value("source_hash", 0ULL); + auto editableMaterial = assetEditingService->LoadMaterialAsset(materialRecord->Guid); + MetaCoreExpect(editableMaterial.has_value(), "AssetEditingService 应能加载独立材质"); + editableMaterial->Name = "PanelMaterialEdited"; + editableMaterial->BaseColor = glm::vec3(0.12F, 0.34F, 0.56F); + editableMaterial->Metallic = 0.42F; + editableMaterial->Roughness = 0.68F; + editableMaterial->DoubleSided = true; + editableMaterial->AlphaMode = MetaCore::MetaCoreMaterialAlphaMode::Mask; + editableMaterial->AlphaCutoff = 0.31F; + editableMaterial->EmissiveColor = glm::vec3(0.03F, 0.04F, 0.05F); + MetaCoreExpect(assetEditingService->SaveMaterialAsset(materialRecord->Guid, *editableMaterial), "AssetEditingService 应能保存独立材质"); + const auto reloadedMaterial = assetEditingService->LoadMaterialAsset(materialRecord->Guid); + MetaCoreExpect(reloadedMaterial.has_value(), "保存后应能重新加载独立材质"); + MetaCoreExpect(reloadedMaterial->Name == "PanelMaterialEdited", "材质名称应 round-trip"); + MetaCoreExpectVec3Near(reloadedMaterial->BaseColor, glm::vec3(0.12F, 0.34F, 0.56F), "材质 BaseColor 应 round-trip"); + MetaCoreExpect(std::abs(reloadedMaterial->Metallic - 0.42F) < 0.0001F, "材质 Metallic 应 round-trip"); + MetaCoreExpect(std::abs(reloadedMaterial->Roughness - 0.68F) < 0.0001F, "材质 Roughness 应 round-trip"); + MetaCoreExpect(reloadedMaterial->DoubleSided, "材质 DoubleSided 应 round-trip"); + MetaCoreExpect(reloadedMaterial->AlphaMode == MetaCore::MetaCoreMaterialAlphaMode::Mask, "材质 AlphaMode 应 round-trip"); + MetaCoreExpect(std::abs(reloadedMaterial->AlphaCutoff - 0.31F) < 0.0001F, "材质 AlphaCutoff 应 round-trip"); + nlohmann::json newMaterialMeta; + { + std::ifstream input(materialMetaPath); + input >> newMaterialMeta; + } + const std::uint64_t newMaterialHash = newMaterialMeta.value("source_hash", 0ULL); + MetaCoreExpect(newMaterialHash != 0ULL, "保存材质后 mcmeta source_hash 应有效"); + MetaCoreExpect(newMaterialHash != oldMaterialHash, "保存材质后 mcmeta source_hash 应更新"); + MetaCoreExpect(newMaterialHash == MetaCore::MetaCoreHashFile(tempProjectRoot / materialPath).value_or(0), "mcmeta source_hash 应匹配材质文件 hash"); const std::filesystem::path scenePath = std::filesystem::path("Scenes") / "PanelScene.mcscene.json"; MetaCoreExpect(assetDatabase->CreateEmptySceneAsset(scenePath, true), "应能创建空场景资源"); @@ -1268,6 +1306,17 @@ void MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot() { rootMesh.MeshSource = MetaCore::MetaCoreMeshSourceKind::Asset; rootMesh.SourceModelPath = "Assets/Models/SyncRoot.glb"; rootMesh.BaseColor = glm::vec3(0.5F, 0.6F, 0.7F); + rootMesh.Metallic = 0.35F; + rootMesh.Roughness = 0.45F; + rootMesh.EmissiveColor = glm::vec3(0.05F, 0.06F, 0.07F); + rootMesh.AlphaMode = MetaCore::MetaCoreMeshAlphaMode::Mask; + rootMesh.AlphaCutoff = 0.42F; + rootMesh.DoubleSided = false; + rootMesh.BaseColorTexturePath = "Assets/Textures/SyncBaseColor.png"; + rootMesh.MetallicRoughnessTexturePath = "Assets/Textures/SyncMetallicRoughness.png"; + rootMesh.NormalTexturePath = "Assets/Textures/SyncNormal.png"; + rootMesh.EmissiveTexturePath = "Assets/Textures/SyncEmissive.png"; + rootMesh.AoTexturePath = "Assets/Textures/SyncAo.png"; MetaCore::MetaCoreGameObject child = scene.CreateGameObject("RenderChild", root.GetId()); child.GetComponent().Position = glm::vec3(4.0F, 5.0F, 6.0F); @@ -1300,6 +1349,17 @@ void MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot() { 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 应正确"); + MetaCoreExpect(std::abs(renderable.Metallic - 0.35F) < 0.0001F, "RenderSync 根对象 Metallic 应正确"); + MetaCoreExpect(std::abs(renderable.Roughness - 0.45F) < 0.0001F, "RenderSync 根对象 Roughness 应正确"); + MetaCoreExpectVec3Near(renderable.EmissiveColor, glm::vec3(0.05F, 0.06F, 0.07F), "RenderSync 根对象 EmissiveColor 应正确"); + MetaCoreExpect(renderable.AlphaMode == MetaCore::MetaCoreMeshAlphaMode::Mask, "RenderSync 根对象 AlphaMode 应正确"); + MetaCoreExpect(std::abs(renderable.AlphaCutoff - 0.42F) < 0.0001F, "RenderSync 根对象 AlphaCutoff 应正确"); + MetaCoreExpect(renderable.DoubleSided == false, "RenderSync 根对象 DoubleSided 应正确"); + MetaCoreExpect(renderable.BaseColorTexturePath == "Assets/Textures/SyncBaseColor.png", "RenderSync 根对象 BaseColor 贴图路径应正确"); + MetaCoreExpect(renderable.MetallicRoughnessTexturePath == "Assets/Textures/SyncMetallicRoughness.png", "RenderSync 根对象金属粗糙贴图路径应正确"); + MetaCoreExpect(renderable.NormalTexturePath == "Assets/Textures/SyncNormal.png", "RenderSync 根对象法线贴图路径应正确"); + MetaCoreExpect(renderable.EmissiveTexturePath == "Assets/Textures/SyncEmissive.png", "RenderSync 根对象自发光贴图路径应正确"); + MetaCoreExpect(renderable.AoTexturePath == "Assets/Textures/SyncAo.png", "RenderSync 根对象 AO 贴图路径应正确"); } 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 应正确"); @@ -1903,10 +1963,12 @@ void MetaCoreTestInstantiateMultiNodeModelAssetIntoScene() { const auto sceneEditingService = moduleRegistry.ResolveService(); const auto packageService = moduleRegistry.ResolveService(); const auto reflectionRegistry = moduleRegistry.ResolveService(); + const auto assetEditingService = moduleRegistry.ResolveService(); MetaCoreExpect(assetDatabase != nullptr, "应解析到 AssetDatabaseService"); MetaCoreExpect(sceneEditingService != nullptr, "应解析到 SceneEditingService"); MetaCoreExpect(packageService != nullptr, "应解析到 PackageService"); MetaCoreExpect(reflectionRegistry != nullptr, "应解析到 ReflectionRegistry"); + MetaCoreExpect(assetEditingService != nullptr, "应解析到 AssetEditingService"); const auto modelRecord = assetDatabase->FindAssetByRelativePath(std::filesystem::path("Assets") / "Assembly.gltf"); MetaCoreExpect(modelRecord.has_value(), "应能找到导入后的多节点模型资源"); @@ -1985,6 +2047,36 @@ void MetaCoreTestInstantiateMultiNodeModelAssetIntoScene() { packageService->WritePackage(tempProjectRoot / modelRecord->PackagePath, std::move(package)), "应能写入多节点模型资源包" ); + MetaCoreExpect(assetDatabase->Refresh(), "写入模型包后应能刷新资产数据库"); + + { + MetaCore::MetaCoreMaterialAssetDocument generatedMaterial = materialAsset; + const MetaCore::MetaCoreAssetGuid generatedMaterialGuid = generatedMaterial.AssetGuid; + generatedMaterial.Name = "GeneratedMaterialEdited"; + generatedMaterial.BaseColor = glm::vec3(0.8F, 0.2F, 0.1F); + generatedMaterial.Metallic = 0.77F; + generatedMaterial.Roughness = 0.22F; + generatedMaterial.DoubleSided = true; + MetaCoreExpect(assetEditingService->SaveMaterialAsset(generatedMaterialGuid, generatedMaterial), "应能保存模型生成材质"); + const auto reloadedGeneratedMaterial = assetEditingService->LoadMaterialAsset(generatedMaterialGuid); + MetaCoreExpect(reloadedGeneratedMaterial.has_value(), "保存后应能重新加载模型生成材质"); + MetaCoreExpect(reloadedGeneratedMaterial->AssetGuid == generatedMaterialGuid, "保存模型生成材质应保持 GUID 不变"); + MetaCoreExpect(reloadedGeneratedMaterial->Name == "GeneratedMaterialEdited", "模型生成材质名称应写回"); + MetaCoreExpectVec3Near(reloadedGeneratedMaterial->BaseColor, glm::vec3(0.8F, 0.2F, 0.1F), "模型生成材质 BaseColor 应写回"); + MetaCoreExpect(std::abs(reloadedGeneratedMaterial->Metallic - 0.77F) < 0.0001F, "模型生成材质 Metallic 应写回"); + MetaCoreExpect(std::abs(reloadedGeneratedMaterial->Roughness - 0.22F) < 0.0001F, "模型生成材质 Roughness 应写回"); + const auto reloadedModelAfterMaterialEdit = assetEditingService->LoadModelAsset(modelRecord->Guid); + MetaCoreExpect(reloadedModelAfterMaterialEdit.has_value(), "保存生成材质后应能重新加载源模型"); + const auto generatedMaterialIterator = std::find_if( + reloadedModelAfterMaterialEdit->GeneratedMaterialAssets.begin(), + reloadedModelAfterMaterialEdit->GeneratedMaterialAssets.end(), + [&](const MetaCore::MetaCoreMaterialAssetDocument& material) { + return material.AssetGuid == generatedMaterialGuid; + } + ); + MetaCoreExpect(generatedMaterialIterator != reloadedModelAfterMaterialEdit->GeneratedMaterialAssets.end(), "源模型中应保留已编辑的生成材质"); + MetaCoreExpect(generatedMaterialIterator->Name == "GeneratedMaterialEdited", "源模型生成材质文档应更新"); + } const auto instantiatedRootId = sceneEditingService->InstantiateModelAsset(editorContext, modelRecord->Guid, std::nullopt); MetaCoreExpect(instantiatedRootId.has_value(), "应能将多节点模型实例化到场景");