diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Material.cpp b/Code/Framework/AzFramework/AzFramework/Physics/Material.cpp index 78e0431753..4afbbaa4dc 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Material.cpp +++ b/Code/Framework/AzFramework/AzFramework/Physics/Material.cpp @@ -300,7 +300,7 @@ namespace Physics { auto foundMaterialConfiguration = AZStd::find_if(m_materialLibrary.begin(), m_materialLibrary.end(), [&materialName](const auto& data) { - return data.m_configuration.m_surfaceType == materialName; + return AZ::StringFunc::Equal(data.m_configuration.m_surfaceType, materialName, false/*bCaseSensitive*/); }); if (foundMaterialConfiguration != m_materialLibrary.end()) @@ -377,15 +377,16 @@ namespace Physics if (auto editContext = serializeContext->GetEditContext()) { - editContext->Class("Physics Material", "Select physics material library and which materials to use for the object") + editContext->Class("Physics Materials", "Select which physics materials to use for each element of this object") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->DataElement(AZ::Edit::UIHandlers::Default, &MaterialSelection::m_materialIdsAssignedToSlots, "Mesh Surfaces", "Specify which Physics Material to use for each element of this object") + ->DataElement(AZ::Edit::UIHandlers::Default, &MaterialSelection::m_materialIdsAssignedToSlots, "", "") ->ElementAttribute(Attributes::MaterialLibraryAssetId, &MaterialSelection::GetMaterialLibraryId) ->Attribute(AZ::Edit::Attributes::IndexedChildNameLabelOverride, &MaterialSelection::GetMaterialSlotLabel) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->ElementAttribute(AZ::Edit::Attributes::ReadOnly, &MaterialSelection::AreMaterialSlotsReadOnly) ->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, false) + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ; } } diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Material.h b/Code/Framework/AzFramework/AzFramework/Physics/Material.h index 69edf3ed25..067f7abe07 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Material.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/Material.h @@ -25,6 +25,8 @@ namespace AZ namespace Physics { + static constexpr AZStd::string_view DefaultPhysicsMaterialLabel = ""; + /// Physics material /// ========================= /// This is the interface to the wrapper around native material type (such as PxMaterial in PhysX gem) diff --git a/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.cpp b/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.cpp index 275103bc28..2c3b113d65 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.cpp +++ b/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.cpp @@ -17,21 +17,6 @@ namespace Physics { - namespace Internal - { - bool ShapeConfigurationVersionConverter( - [[maybe_unused]] AZ::SerializeContext& context, - AZ::SerializeContext::DataElementNode& classElement) - { - if (classElement.GetVersion() <= 1) - { - classElement.RemoveElementByName(AZ_CRC_CE("UseMaterialsFromAsset")); - } - - return true; - } - } - void ShapeConfiguration::Reflect(AZ::ReflectContext* context) { if (auto serializeContext = azrtti_cast(context)) @@ -181,9 +166,10 @@ namespace Physics ->RegisterGenericType>(); serializeContext->Class() - ->Version(2, &Internal::ShapeConfigurationVersionConverter) + ->Version(3) ->Field("PhysicsAsset", &PhysicsAssetShapeConfiguration::m_asset) ->Field("AssetScale", &PhysicsAssetShapeConfiguration::m_assetScale) + ->Field("UseMaterialsFromAsset", &PhysicsAssetShapeConfiguration::m_useMaterialsFromAsset) ->Field("SubdivisionLevel", &PhysicsAssetShapeConfiguration::m_subdivisionLevel) ; @@ -196,6 +182,7 @@ namespace Physics ->DataElement(AZ::Edit::UIHandlers::Default, &PhysicsAssetShapeConfiguration::m_assetScale, "Asset Scale", "The scale of the asset shape") ->Attribute(AZ::Edit::Attributes::Min, 0.0f) ->Attribute(AZ::Edit::Attributes::Step, 0.01f) + ->DataElement(AZ::Edit::UIHandlers::Default, &PhysicsAssetShapeConfiguration::m_useMaterialsFromAsset, "Physics materials from asset", "Auto-set physics materials using asset's physics material names") ; } } diff --git a/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.h b/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.h index 8234ef9173..b3d04a10c9 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.h @@ -140,7 +140,7 @@ namespace Physics AZ::Data::Asset m_asset{ AZ::Data::AssetLoadBehavior::PreLoad }; AZ::Vector3 m_assetScale = AZ::Vector3::CreateOne(); - bool m_useMaterialsFromAsset = false; // Not reflected or exposed to the user until there is a way to auto-match mesh's materials with physics materials + bool m_useMaterialsFromAsset = true; AZ::u8 m_subdivisionLevel = 4; ///< The level of subdivision if a primitive shape is replaced with a convex mesh due to scaling. }; diff --git a/Gems/PhysX/Code/Editor/ConfigStringLineEditCtrl.cpp b/Gems/PhysX/Code/Editor/ConfigStringLineEditCtrl.cpp index 6aaf45b184..704b538a5f 100644 --- a/Gems/PhysX/Code/Editor/ConfigStringLineEditCtrl.cpp +++ b/Gems/PhysX/Code/Editor/ConfigStringLineEditCtrl.cpp @@ -93,7 +93,7 @@ namespace PhysX } } - void ConfigStringLineEditCtrl::SetForbiddenStrings(const AZStd::unordered_set& forbiddenStrings) + void ConfigStringLineEditCtrl::SetForbiddenStrings(const UniqueStringContainer::StringSet& forbiddenStrings) { m_forbiddenStrings = forbiddenStrings; } @@ -122,7 +122,7 @@ namespace PhysX void ConfigStringLineEditValidator::OnEditStart(AZ::Crc32 stringGroupId , const AZStd::string& stringToEdit - , const AZStd::unordered_set& forbiddenStrings + , const UniqueStringContainer::StringSet& forbiddenStrings , int stringMaxLength , bool removeEditedString) { @@ -229,23 +229,23 @@ namespace PhysX } else if (attrib == Physics::MaterialConfiguration::s_forbiddenStringSet) { - AZStd::unordered_set forbiddenStringsUnorderedSet; + UniqueStringContainer::StringSet forbiddenStringsUnorderedSet; AZStd::set forbiddenStringsSet; AZStd::vector forbiddenStringsVector; - if (attrValue->Read>(forbiddenStringsUnorderedSet)) + if (attrValue->Read(forbiddenStringsUnorderedSet)) { GUI->SetForbiddenStrings(forbiddenStringsUnorderedSet); } else if (attrValue->Read>(forbiddenStringsSet)) { - forbiddenStringsUnorderedSet = AZStd::unordered_set(forbiddenStringsSet.begin() + forbiddenStringsUnorderedSet = UniqueStringContainer::StringSet(forbiddenStringsSet.begin() , forbiddenStringsSet.end()); GUI->SetForbiddenStrings(forbiddenStringsUnorderedSet); } else if (attrValue->Read>(forbiddenStringsVector)) { - forbiddenStringsUnorderedSet = AZStd::unordered_set(forbiddenStringsVector.begin() + forbiddenStringsUnorderedSet = UniqueStringContainer::StringSet(forbiddenStringsVector.begin() , forbiddenStringsVector.end()); GUI->SetForbiddenStrings(forbiddenStringsUnorderedSet); } diff --git a/Gems/PhysX/Code/Editor/ConfigStringLineEditCtrl.h b/Gems/PhysX/Code/Editor/ConfigStringLineEditCtrl.h index e672a9afd8..f9e7b752ec 100644 --- a/Gems/PhysX/Code/Editor/ConfigStringLineEditCtrl.h +++ b/Gems/PhysX/Code/Editor/ConfigStringLineEditCtrl.h @@ -47,7 +47,7 @@ namespace PhysX void OnEditStart(AZ::Crc32 stringGroupId , const AZStd::string& stringToEdit - , const AZStd::unordered_set& forbiddenStrings + , const UniqueStringContainer::StringSet& forbiddenStrings , int stringMaxLength , bool removeEditedString = true); @@ -62,7 +62,7 @@ namespace PhysX private: AZ::Crc32 m_currStringGroup = s_groupStringNotUnique; ///< Group of string field undergoing edit. int m_currStringMaxLen = s_qtLineEditMaxLen; ///< Max length of string field undergoing edit. - AZStd::unordered_set m_forbiddenStrings; ///< Value of string edit widget cannot be any of these strings. + UniqueStringContainer::StringSet m_forbiddenStrings; ///< Value of string edit widget cannot be any of these strings. UniqueStringContainer m_uniqueStringContainer; ///< Collection of groups of unique strings. Serves for validation and fixing of string input. }; @@ -79,7 +79,7 @@ namespace PhysX , ConfigStringLineEditValidator* validator = nullptr); virtual ~ConfigStringLineEditCtrl(); - void SetForbiddenStrings(const AZStd::unordered_set& forbiddenStrings); + void SetForbiddenStrings(const UniqueStringContainer::StringSet& forbiddenStrings); void SetUniqueGroup(AZ::Crc32 uniqueGroup); AZStd::string Value() const; @@ -92,7 +92,7 @@ namespace PhysX protected: void ConnectWidgets() override; - AZStd::unordered_set m_forbiddenStrings; ///< Value of this line edit ctrl cannot be any of these forbidden strings. + UniqueStringContainer::StringSet m_forbiddenStrings; ///< Value of this line edit ctrl cannot be any of these forbidden strings. ConfigStringLineEditValidator* m_pValidator = nullptr; ///< Validator for line edit widget. AZ::Crc32 m_uniqueGroup = ConfigStringLineEditValidator::s_groupStringNotUnique; ///< String group in which line edit value must remain unique. }; diff --git a/Gems/PhysX/Code/Editor/MaterialIdWidget.cpp b/Gems/PhysX/Code/Editor/MaterialIdWidget.cpp index 44787ddd20..30db8aa76d 100644 --- a/Gems/PhysX/Code/Editor/MaterialIdWidget.cpp +++ b/Gems/PhysX/Code/Editor/MaterialIdWidget.cpp @@ -15,12 +15,12 @@ #include +#include + namespace PhysX { namespace Editor { - static const char* const DefaultPhysicsMaterialLabel = ""; - AZ::u32 MaterialIdWidget::GetHandlerName() const { return Physics::Edit::MaterialIdSelector; @@ -74,7 +74,7 @@ namespace PhysX auto lockToDefault = [gui]() { - gui->addItem(DefaultPhysicsMaterialLabel); + gui->addItem(QLatin1String(Physics::DefaultPhysicsMaterialLabel.data(), Physics::DefaultPhysicsMaterialLabel.size())); gui->setCurrentIndex(0); return false; }; @@ -103,7 +103,7 @@ namespace PhysX // Add default physics material first m_libraryIds.push_back(Physics::MaterialId()); - gui->addItem(DefaultPhysicsMaterialLabel); + gui->addItem(QLatin1String(Physics::DefaultPhysicsMaterialLabel.data(), Physics::DefaultPhysicsMaterialLabel.size())); for (const auto& material : materials) { diff --git a/Gems/PhysX/Code/Editor/UniqueStringContainer.cpp b/Gems/PhysX/Code/Editor/UniqueStringContainer.cpp index 6482624dc4..1d92471919 100644 --- a/Gems/PhysX/Code/Editor/UniqueStringContainer.cpp +++ b/Gems/PhysX/Code/Editor/UniqueStringContainer.cpp @@ -23,7 +23,7 @@ namespace PhysX StringGroups::iterator stringGroupsIter = m_stringGroups.find(stringGroupId); if (stringGroupsIter == m_stringGroups.end()) { - m_stringGroups.emplace(stringGroupId, AZStd::unordered_set()); + m_stringGroups.emplace(stringGroupId, StringSet()); } m_stringGroups[stringGroupId].insert(stringIn); } @@ -31,7 +31,7 @@ namespace PhysX AZStd::string UniqueStringContainer::GetUniqueString(AZ::Crc32 stringGroupId , const AZStd::string& stringIn , AZ::u64 maxStringLength - , const AZStd::unordered_set& forbiddenStrings) const + , const StringSet& forbiddenStrings) const { StringGroups::const_iterator stringGroupsIter = m_stringGroups.find(stringGroupId); @@ -45,7 +45,7 @@ namespace PhysX } AZStd::string stringOut; - const AZStd::unordered_set& stringGroup = (stringGroupsIter == m_stringGroups.end())? AZStd::unordered_set():stringGroupsIter->second; + const StringSet& stringGroup = (stringGroupsIter == m_stringGroups.end())? StringSet():stringGroupsIter->second; // Attempts to append a post-fix value, e.g. "_1" etc., to the original string so it is unique. // A unique post-fix index can be found by iterating total number of invalid string plus 1. @@ -86,8 +86,8 @@ namespace PhysX return true; } - const AZStd::unordered_set& stringGroup = stringGroupsIter->second; - return stringGroup.find(stringIn) == stringGroup.end(); + const StringSet& stringSet = stringGroupsIter->second; + return stringSet.find(stringIn) == stringSet.end(); } void UniqueStringContainer::RemoveString(AZ::Crc32 stringGroupId diff --git a/Gems/PhysX/Code/Editor/UniqueStringContainer.h b/Gems/PhysX/Code/Editor/UniqueStringContainer.h index 176063f341..7144137f8a 100644 --- a/Gems/PhysX/Code/Editor/UniqueStringContainer.h +++ b/Gems/PhysX/Code/Editor/UniqueStringContainer.h @@ -16,13 +16,38 @@ #include #include #include +#include namespace PhysX { - /// Class that keeps track of unique strings in groups. + /// Class that keeps track of unique strings (case insensitive) in groups. class UniqueStringContainer { public: + struct CaseInsensitiveStringHash + { + AZ_TYPE_INFO(UniqueStringContainer::CaseInsensitiveStringHash, "{EB80F2A1-2DEB-47CC-ABF7-592F492C20A9}"); + + size_t operator()(const AZStd::string& str) const + { + AZStd::string lowerStr = str; + AZStd::to_lower(lowerStr.begin(), lowerStr.end()); + return AZStd::hash{}(lowerStr); + } + }; + + struct CaseInsensitiveStringEqual + { + AZ_TYPE_INFO(UniqueStringContainer::CaseInsensitiveStringEqual, "{6ADEA1D9-27B8-4C7A-913D-EC8191F1B6A9}"); + + bool operator()(const AZStd::string& arg0, const AZStd::string& arg1) const + { + return AZ::StringFunc::Equal(arg0, arg1, false/*bCaseSensitive*/); + } + }; + + using StringSet = AZStd::unordered_set; + /// Add a unique string to a group of unique strings. void AddString(AZ::Crc32 stringGroupId, const AZStd::string& stringIn); @@ -30,7 +55,7 @@ namespace PhysX AZStd::string GetUniqueString(AZ::Crc32 stringGroupId , const AZStd::string& stringIn , AZ::u64 maxStringLength - , const AZStd::unordered_set& forbiddenStrings) const; + , const StringSet& forbiddenStrings) const; /// Checks if a string would be unique in the identified string group. bool IsStringUnique(AZ::Crc32 stringGroupId, const AZStd::string& stringIn) const; @@ -39,7 +64,7 @@ namespace PhysX void RemoveString(AZ::Crc32 stringGroupId, const AZStd::string& stringIn); private: - using StringGroups = AZStd::unordered_map>; + using StringGroups = AZStd::unordered_map; StringGroups m_stringGroups; ///< Collection of groups of unique strings, each group identified by an ID. }; -} +} // namespace PhysX diff --git a/Gems/PhysX/Code/Include/PhysX/MeshAsset.h b/Gems/PhysX/Code/Include/PhysX/MeshAsset.h index 8467c04359..edfd82439a 100644 --- a/Gems/PhysX/Code/Include/PhysX/MeshAsset.h +++ b/Gems/PhysX/Code/Include/PhysX/MeshAsset.h @@ -62,8 +62,8 @@ namespace PhysX using ShapeConfigurationList = AZStd::vector; ShapeConfigurationList m_colliderShapes; //!< Shapes data with optional collider configuration override. - AZStd::vector m_surfaceNames; //!< List of all surface names. - AZStd::vector m_materialNames; //!< List of all material names. + AZStd::vector m_materialNames; //!< List of material names of the mesh asset. + AZStd::vector m_physicsMaterialNames; //!< List of physics material names associated with each material. AZStd::vector m_materialIndexPerShape; //!< An index of the material in m_materialNames for each shape. }; diff --git a/Gems/PhysX/Code/Source/EditorColliderComponent.cpp b/Gems/PhysX/Code/Source/EditorColliderComponent.cpp index 4454429092..1cb60e0af2 100644 --- a/Gems/PhysX/Code/Source/EditorColliderComponent.cpp +++ b/Gems/PhysX/Code/Source/EditorColliderComponent.cpp @@ -698,10 +698,10 @@ namespace PhysX AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree); - ValidateMaterialSurfaces(); + ValidateAssetMaterials(); } - void EditorColliderComponent::ValidateMaterialSurfaces() + void EditorColliderComponent::ValidateAssetMaterials() { const AZ::Data::Asset& physicsAsset = m_shapeConfiguration.m_physicsAsset.m_pxAsset; @@ -712,7 +712,7 @@ namespace PhysX // Here we check the material indices assigned to every shape and validate that every index is used at least once. // It's not an error if the validation fails here but something we want to let the designers know about. - [[maybe_unused]] size_t surfacesNum = physicsAsset->m_assetData.m_surfaceNames.size(); + [[maybe_unused]] size_t materialsNum = physicsAsset->m_assetData.m_materialNames.size(); const AZStd::vector& indexPerShape = physicsAsset->m_assetData.m_materialIndexPerShape; AZStd::unordered_set usedIndices; @@ -728,10 +728,10 @@ namespace PhysX usedIndices.insert(index); } - AZ_Warning("PhysX", usedIndices.size() == surfacesNum, - "EditorColliderComponent::ValidateMaterialSurfaces. Entity: %s. Number of surfaces used by the shape (%d) does not match the " - "total number of surfaces in the asset (%d). Please check that there are no convex meshes with per-face materials. Asset: %s", - GetEntity()->GetName().c_str(), usedIndices.size(), surfacesNum, physicsAsset.GetHint().c_str()) + AZ_Warning("PhysX", usedIndices.size() == materialsNum, + "EditorColliderComponent::ValidateMaterialSurfaces. Entity: %s. Number of materials used by the shape (%d) does not match the " + "total number of materials in the asset (%d). Please check that there are no convex meshes with per-face materials. Asset: %s", + GetEntity()->GetName().c_str(), usedIndices.size(), materialsNum, physicsAsset.GetHint().c_str()) } void EditorColliderComponent::OnAssetReady(AZ::Data::Asset asset) diff --git a/Gems/PhysX/Code/Source/EditorColliderComponent.h b/Gems/PhysX/Code/Source/EditorColliderComponent.h index 07e1131ea9..603e0bf0b3 100644 --- a/Gems/PhysX/Code/Source/EditorColliderComponent.h +++ b/Gems/PhysX/Code/Source/EditorColliderComponent.h @@ -244,7 +244,7 @@ namespace PhysX AZ::Data::AssetId FindMatchingPhysicsAsset(const AZ::Data::Asset& renderMeshAsset, const AZStd::vector& physicsAssets); - void ValidateMaterialSurfaces(); + void ValidateAssetMaterials(); void InitEventHandlers(); DebugDraw::Collider m_colliderDebugDraw; diff --git a/Gems/PhysX/Code/Source/Material.cpp b/Gems/PhysX/Code/Source/Material.cpp index 5e8e3bf759..45a7d64c55 100644 --- a/Gems/PhysX/Code/Source/Material.cpp +++ b/Gems/PhysX/Code/Source/Material.cpp @@ -410,7 +410,7 @@ namespace PhysX } // Set the slots from the mesh asset - materialSelection.SetMaterialSlots(meshAsset->m_assetData.m_surfaceNames); + materialSelection.SetMaterialSlots(meshAsset->m_assetData.m_materialNames); if (!assetConfiguration.m_useMaterialsFromAsset) { @@ -419,12 +419,14 @@ namespace PhysX } // Update material IDs in the selection for each slot - const AZStd::vector& meshMaterialNames = meshAsset->m_assetData.m_materialNames; - for (size_t slotIndex = 0; slotIndex < meshMaterialNames.size(); ++slotIndex) + const AZStd::vector& physicsMaterialNames = meshAsset->m_assetData.m_physicsMaterialNames; + for (size_t slotIndex = 0; slotIndex < physicsMaterialNames.size(); ++slotIndex) { - const AZStd::string& physicsMaterialNameFromPhysicsAsset = meshMaterialNames[slotIndex]; - if (physicsMaterialNameFromPhysicsAsset == DefaultPhysicsMaterialNameFromPhysicsAsset) + const AZStd::string& physicsMaterialNameFromPhysicsAsset = physicsMaterialNames[slotIndex]; + if (physicsMaterialNameFromPhysicsAsset.empty() || + physicsMaterialNameFromPhysicsAsset == Physics::DefaultPhysicsMaterialLabel) { + materialSelection.SetMaterialId(Physics::MaterialId(), slotIndex); continue; } @@ -436,9 +438,9 @@ namespace PhysX else { AZ_Warning("PhysX", false, - "UpdateMaterialSelectionFromPhysicsAsset: Physics material '%s' not found in the material library. Mesh surface '%s' will use the default material.", + "UpdateMaterialSelectionFromPhysicsAsset: Physics material '%s' not found in the material library. Mesh material '%s' will use the default physics material.", physicsMaterialNameFromPhysicsAsset.c_str(), - meshAsset->m_assetData.m_surfaceNames[slotIndex].c_str()); + meshAsset->m_assetData.m_materialNames[slotIndex].c_str()); } } } @@ -516,7 +518,7 @@ namespace PhysX auto it = AZStd::find_if(m_materials.begin(), m_materials.end(), [&materialName](const auto& data) { - return data.second->GetSurfaceTypeName() == materialName; + return AZ::StringFunc::Equal(data.second->GetSurfaceTypeName(), materialName, false/*bCaseSensitive*/); }); if (it != m_materials.end()) { diff --git a/Gems/PhysX/Code/Source/Material.h b/Gems/PhysX/Code/Source/Material.h index 1541d156d8..694c48ea7c 100644 --- a/Gems/PhysX/Code/Source/Material.h +++ b/Gems/PhysX/Code/Source/Material.h @@ -21,12 +21,6 @@ namespace PhysX { - /// Name used by physx asset exporter to indicate that the default - /// physics material should be used for a mesh surface. The exporter - /// will use it as the fallback option when it's not possible to obtain - /// the surface information from the mesh material. - static const char* const DefaultPhysicsMaterialNameFromPhysicsAsset = ""; - /// PhysX implementation of Physics::Material interface /// =================================================== /// diff --git a/Gems/PhysX/Code/Source/Pipeline/MeshAssetHandler.cpp b/Gems/PhysX/Code/Source/Pipeline/MeshAssetHandler.cpp index 430ef7aef3..24fcd9d5e7 100644 --- a/Gems/PhysX/Code/Source/Pipeline/MeshAssetHandler.cpp +++ b/Gems/PhysX/Code/Source/Pipeline/MeshAssetHandler.cpp @@ -151,8 +151,8 @@ namespace PhysX serializeContext->Class() ->Field("ColliderShapes", &MeshAssetData::m_colliderShapes) - ->Field("SurfaceNames", &MeshAssetData::m_surfaceNames) - ->Field("MaterialNames", &MeshAssetData::m_materialNames) + ->Field("SurfaceNames", &MeshAssetData::m_materialNames) + ->Field("MaterialNames", &MeshAssetData::m_physicsMaterialNames) ->Field("MaterialIndexPerShape", &MeshAssetData::m_materialIndexPerShape) ; } diff --git a/Gems/PhysX/Code/Source/Pipeline/MeshBehavior.cpp b/Gems/PhysX/Code/Source/Pipeline/MeshBehavior.cpp index ce46d72fb4..8762349ad1 100644 --- a/Gems/PhysX/Code/Source/Pipeline/MeshBehavior.cpp +++ b/Gems/PhysX/Code/Source/Pipeline/MeshBehavior.cpp @@ -97,6 +97,10 @@ namespace PhysX nodeSelectionList.AddSelectedNode(graph.GetNodeName(nodeIndex).GetPath()); } } + + // Update list of materials slots after the group's node selection list has been gathered + group->SetSceneGraph(&graph); + group->UpdateMaterialSlots(); } AZ::SceneAPI::Events::ProcessingResult MeshBehavior::UpdateManifest(AZ::SceneAPI::Containers::Scene& scene, ManifestAction action, @@ -129,6 +133,8 @@ namespace PhysX // in the same way again. To guarantee the same uuid, generate a stable one instead. group->OverrideId(AZ::SceneAPI::DataTypes::Utilities::CreateStableUuid(scene, MeshGroup::TYPEINFO_Uuid())); + group->SetSceneGraph(&scene.GetGraph()); + EBUS_EVENT(AZ::SceneAPI::Events::ManifestMetaInfoBus, InitializeObject, scene, *group); scene.GetManifest().AddEntry(AZStd::move(group)); @@ -149,10 +155,15 @@ namespace PhysX } AZ::SceneAPI::Utilities::SceneGraphSelector::UpdateNodeSelection(scene.GetGraph(), group.GetSceneNodeSelectionList()); + + // Update list of materials slots after the group's node selection list has been updated + group.SetSceneGraph(&scene.GetGraph()); + group.UpdateMaterialSlots(); + updated = true; } return updated ? AZ::SceneAPI::Events::ProcessingResult::Success : AZ::SceneAPI::Events::ProcessingResult::Ignored; } - } // namespace SceneAPI -} // namespace AZ + } // namespace Pipeline +} // namespace PhysX diff --git a/Gems/PhysX/Code/Source/Pipeline/MeshExporter.cpp b/Gems/PhysX/Code/Source/Pipeline/MeshExporter.cpp index 24e532e2d1..e968e2279a 100644 --- a/Gems/PhysX/Code/Source/Pipeline/MeshExporter.cpp +++ b/Gems/PhysX/Code/Source/Pipeline/MeshExporter.cpp @@ -68,16 +68,6 @@ namespace PhysX } } pxDefaultErrorCallback; - // A struct to store the asset-wide material names shared by multiple shapes - struct AssetMaterialsData - { - // Material names coming from FBX, these will be Mesh Surfaces in the Collider Component - AZStd::vector m_fbxMaterialNames; - - // Look-up table for fbxMaterialNames - AZStd::unordered_map m_materialIndexByName; - }; - // A struct to store the geometry data per FBX node struct NodeCollisionGeomExportData { @@ -153,7 +143,7 @@ namespace PhysX AZ::SerializeContext* serializeContext = azrtti_cast(context); if (serializeContext) { - serializeContext->Class()->Version(4); + serializeContext->Class()->Version(5); } } @@ -182,100 +172,49 @@ namespace PhysX return newIndex; } - // Building a map between FBX material name and the corresponding Cry surface type that is set in the .mtl file. - void BuildMaterialToSurfaceTypeMap(const AZStd::string& materialFilename, - AZStd::unordered_map& materialToSurfaceTypeMap) + bool UpdateAssetPhysicsMaterials( + const AZStd::vector& newMaterials, + AZStd::vector& materials, + AZStd::vector& physicsMaterials) { - AZ::IO::SystemFile mtlFile; - bool fileOpened = mtlFile.Open(materialFilename.c_str(), AZ::IO::SystemFile::SF_OPEN_READ_ONLY); - if (fileOpened && mtlFile.Length() != 0) + if (materials.size() != physicsMaterials.size()) { - //Read material override file into a buffer - AZStd::vector buffer(mtlFile.Length()); - mtlFile.Read(mtlFile.Length(), buffer.data()); - mtlFile.Close(); - - //Apparently in rapidxml if 'parse_no_data_nodes' isn't set it creates both value and data nodes - //with the data nodes having precedence such that updating values doesn't work. - AZ::rapidxml::xml_document document; - document.parse(buffer.data()); - - //Parse MTL file for materials and/or submaterials. - AZ::rapidxml::xml_node* rootMaterialNode = document.first_node(AZ::GFxFramework::MaterialExport::g_materialString); - - AZ::rapidxml::xml_node* subMaterialNode = rootMaterialNode->first_node(AZ::GFxFramework::MaterialExport::g_subMaterialString); - - if (subMaterialNode) - { - for (AZ::rapidxml::xml_node* materialNode = subMaterialNode->first_node(AZ::GFxFramework::MaterialExport::g_materialString); - materialNode; - materialNode = materialNode->next_sibling(AZ::GFxFramework::MaterialExport::g_materialString)) - { - AZ::rapidxml::xml_attribute* nameAttribute = materialNode->first_attribute(AZ::GFxFramework::MaterialExport::g_nameString); - if (nameAttribute) - { - AZStd::string materialName = nameAttribute->value(); - AZStd::string surfaceTypeName = DefaultPhysicsMaterialNameFromPhysicsAsset; - - AZ::rapidxml::xml_attribute* surfaceTypeNode = materialNode->first_attribute("SurfaceType"); - if (surfaceTypeNode && surfaceTypeNode->value_size() != 0) - { - surfaceTypeName = surfaceTypeNode->value(); - } - - materialToSurfaceTypeMap[materialName] = surfaceTypeName; - } - else - { - AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow, "A SubMaterial without Name found in the .mtl file: %s", materialFilename.c_str()); - } - } - } - else - { - AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow, "No SubMaterial node in the .mtl file: %s", materialFilename.c_str()); - } - } - } - - void UpdateAssetMaterialsFromCrySurfaceTypes(const AZStd::vector& fbxMaterialNames, - const AZStd::unordered_map& materialToSurfaceTypeMap, - MeshAssetData& assetData) - { - AZStd::vector& materialNames = assetData.m_materialNames; - AZ_Assert(materialNames.empty(), - "UpdateAssetMaterialsFromCrySurfaceTypes: Mesh Asset Data should not have materials already assigned."); - - materialNames.clear(); - materialNames.reserve(fbxMaterialNames.size()); - - for (const AZStd::string& fbxMaterial : fbxMaterialNames) - { - AZStd::string materialName; - - // Here we assign the actual engine surface type based on the material name - auto materialToSurfaceIt = materialToSurfaceTypeMap.find(fbxMaterial); - if (materialToSurfaceIt != materialToSurfaceTypeMap.end() - && !materialToSurfaceIt->second.empty()) - { - materialName = materialToSurfaceIt->second; - - // Remove the mat_ prefix since the material library generated from surface types doesn't have it. - if (materialName.find("mat_") == 0) - { - materialName = materialName.substr(4); - } - } - else - { - materialName = DefaultPhysicsMaterialNameFromPhysicsAsset; - } - - materialNames.emplace_back(AZStd::move(materialName)); + AZ_TracePrintf( + AZ::SceneAPI::Utilities::WarningWindow, + "Materials and Physics Materials have different number of elements. %d materials and %d physics materials.", + materials.size(), physicsMaterials.size()); + return false; } - // Asset mesh surfaces match FBX materials. These are the names that users see in the Collider Component in the Editor. - assetData.m_surfaceNames = fbxMaterialNames; + AZStd::vector newPhysicsMaterials; + newPhysicsMaterials.reserve(newMaterials.size()); + + // In the new material list, the materials might have changed slots. + // Form the new list of physics materials by looking at the previous list + // and keeping the same physics materials association when found. + for (const auto& newMaterial : newMaterials) + { + AZStd::string physicsMaterialName = Physics::DefaultPhysicsMaterialLabel; + + for (size_t slotId = 0; slotId < materials.size(); ++slotId) + { + if (AZ::StringFunc::Equal(materials[slotId], newMaterial, false/*bCaseSensitive*/)) + { + if (!physicsMaterials[slotId].empty()) + { + physicsMaterialName = physicsMaterials[slotId]; + } + break; + } + } + + newPhysicsMaterials.emplace_back(AZStd::move(physicsMaterialName)); + } + + materials = newMaterials; + physicsMaterials = AZStd::move(newPhysicsMaterials); + + return true; } bool ValidateCookedTriangleMesh(void* assetData, AZ::u32 assetDataSize) @@ -320,6 +259,86 @@ namespace PhysX return materialNames; } + + AZStd::optional GatherMaterialsFromMeshGroup( + const MeshGroup& meshGroup, + const AZ::SceneAPI::Containers::SceneGraph& sceneGraph) + { + AssetMaterialsData assetMaterialData; + + const auto& sceneNodeSelectionList = meshGroup.GetSceneNodeSelectionList(); + size_t selectedNodeCount = sceneNodeSelectionList.GetSelectedNodeCount(); + + for (size_t index = 0; index < selectedNodeCount; index++) + { + AZ::SceneAPI::Containers::SceneGraph::NodeIndex nodeIndex = sceneGraph.Find(sceneNodeSelectionList.GetSelectedNode(index)); + if (!nodeIndex.IsValid()) + { + AZ_TracePrintf( + AZ::SceneAPI::Utilities::WarningWindow, + "Node '%s' was not found in the scene graph.", + sceneNodeSelectionList.GetSelectedNode(index).c_str() + ); + continue; + } + auto nodeMesh = azrtti_cast(*sceneGraph.ConvertToStorageIterator(nodeIndex)); + if (!nodeMesh) + { + continue; + } + + AZStd::string_view nodeName = sceneGraph.GetNodeName(nodeIndex).GetName(); + + const AZStd::vector localFbxMaterialsList = GenerateLocalNodeMaterialMap(sceneGraph, nodeIndex); + if (localFbxMaterialsList.empty()) + { + AZ_TracePrintf( + AZ::SceneAPI::Utilities::WarningWindow, + "Node '%.*s' does not have any material assigned to it. Material '%s' will be used.", + nodeName.size(), nodeName, DefaultMaterialName + ); + } + + const AZ::u32 faceCount = nodeMesh->GetFaceCount(); + + assetMaterialData.m_nodesToPerFaceMaterialIndices.emplace(nodeName, AZStd::vector(faceCount)); + + // Convex and primitive methods can only have 1 material + const bool limitToOneMaterial = meshGroup.GetExportAsConvex() || meshGroup.GetExportAsPrimitive(); + + for (AZ::u32 faceIndex = 0; faceIndex < faceCount; ++faceIndex) + { + AZStd::string materialName = DefaultMaterialName; + if (!localFbxMaterialsList.empty()) + { + int materialId = nodeMesh->GetFaceMaterialId(faceIndex); + if (materialId >= localFbxMaterialsList.size()) + { + AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow, + "materialId %d for face %d is out of bound for localFbxMaterialsList (size %d).", + materialId, faceIndex, localFbxMaterialsList.size()); + + return AZStd::nullopt; + } + + materialName = localFbxMaterialsList[materialId]; + + // Keep using the first material when it has to be limited to one. + if (limitToOneMaterial && + assetMaterialData.m_fbxMaterialNames.size() == 1 && + assetMaterialData.m_fbxMaterialNames[0] != materialName) + { + materialName = assetMaterialData.m_fbxMaterialNames[0]; + } + } + + AZ::u16 materialIndex = InsertMaterialIndexByName(materialName, assetMaterialData); + assetMaterialData.m_nodesToPerFaceMaterialIndices[nodeName][faceIndex] = materialIndex; + } + } + + return assetMaterialData; + } } static physx::PxMeshMidPhase::Enum GetMidPhaseStructureType(const AZStd::string& platformIdentifier) @@ -468,21 +487,11 @@ namespace PhysX return cookingSuccessful; } - // Utility function finding out the .mtl file for a given FBX (at the moment it's the same name as FBX but with .mtl extension) - static AZStd::string GetAssetMaterialFilename(const AZ::SceneAPI::Events::ExportEventContext& context) - { - const AZ::SceneAPI::Containers::Scene& scene = context.GetScene(); - - AZStd::string materialFilename = scene.GetSourceFilename(); - AzFramework::StringFunc::Path::ReplaceExtension(materialFilename, ".mtl"); - return materialFilename; - } - // Processes the collected data and writes into a file static AZ::SceneAPI::Events::ProcessingResult WritePxMeshAsset( AZ::SceneAPI::Events::ExportEventContext& context, const AZStd::vector& totalExportData, - const AssetMaterialsData &assetMaterialsData, + const Utils::AssetMaterialsData &assetMaterialsData, const MeshGroup& meshGroup) { SceneEvents::ProcessingResult result = SceneEvents::ProcessingResult::Ignored; @@ -492,14 +501,18 @@ namespace PhysX MeshAssetData assetData; - const AZStd::string& materialFilename = GetAssetMaterialFilename(context); - - // Read the information about surface type for each material from the .mtl file - AZStd::unordered_map fbxMaterialToCrySurfaceTypeMap; - Utils::BuildMaterialToSurfaceTypeMap(materialFilename, fbxMaterialToCrySurfaceTypeMap); - // Assign the materials into cooked data - Utils::UpdateAssetMaterialsFromCrySurfaceTypes(assetMaterialsData.m_fbxMaterialNames, fbxMaterialToCrySurfaceTypeMap, assetData); + assetData.m_materialNames = meshGroup.GetMaterialSlots(); + assetData.m_physicsMaterialNames = meshGroup.GetPhysicsMaterials(); + + // Updating materials lists from new materials gathered from fbx + // because this exporter runs when the FBX is being processed, which + // could have a different content from when the mesh group info was + // entered in FBX Settings Editor. + if (!Utils::UpdateAssetPhysicsMaterials(assetMaterialsData.m_fbxMaterialNames, assetData.m_materialNames, assetData.m_physicsMaterialNames)) + { + return SceneEvents::ProcessingResult::Failure; + } for (const NodeCollisionGeomExportData& subMesh : totalExportData) { @@ -714,9 +727,15 @@ namespace PhysX for (const MeshGroup& pxMeshGroup : view) { + // Gather material data from asset for the mesh group + AZStd::optional assetMaterialData = Utils::GatherMaterialsFromMeshGroup(pxMeshGroup, graph); + if (!assetMaterialData) + { + return SceneEvents::ProcessingResult::Failure; + } + // Export data per node AZStd::vector totalExportData; - AssetMaterialsData assetMaterialData; const AZStd::string& groupName = pxMeshGroup.GetName(); @@ -764,7 +783,6 @@ namespace PhysX const AZ::SceneAPI::Containers::SceneGraph::Name& nodeName = graph.GetNodeName(nodeIndex); - const AZStd::vector localFbxMaterialsList = Utils::GenerateLocalNodeMaterialMap(graph, nodeIndex); const AZ::SceneAPI::DataTypes::MatrixType worldTransform = SceneUtil::BuildWorldTransform(graph, nodeIndex); NodeCollisionGeomExportData nodeExportData; @@ -783,53 +801,24 @@ namespace PhysX } nodeExportData.m_indices.resize(faceCount * 3); - nodeExportData.m_perFaceMaterialIndices.resize(faceCount); - if (localFbxMaterialsList.empty()) + nodeExportData.m_perFaceMaterialIndices = assetMaterialData->m_nodesToPerFaceMaterialIndices[nodeExportData.m_nodeName]; + if (nodeExportData.m_perFaceMaterialIndices.size() != faceCount) { AZ_TracePrintf( AZ::SceneAPI::Utilities::WarningWindow, - "Node '%s' does not have any material assigned to it. Material '%s' will be used.", - nodeExportData.m_nodeName.c_str(), DefaultMaterialName + "Node '%s' material information face count %d does not match the node's %d.", + nodeExportData.m_nodeName.c_str(), nodeExportData.m_perFaceMaterialIndices.size(), faceCount ); + return SceneEvents::ProcessingResult::Failure; } - // Convex and primitive methods can only have 1 material - const bool limitToOneMaterial = pxMeshGroup.GetExportAsConvex() || pxMeshGroup.GetExportAsPrimitive(); - for (AZ::u32 faceIndex = 0; faceIndex < faceCount; ++faceIndex) { - AZStd::string materialName = DefaultMaterialName; - if (!localFbxMaterialsList.empty()) - { - int materialId = nodeMesh->GetFaceMaterialId(faceIndex); - if (materialId >= localFbxMaterialsList.size()) - { - AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow, - "materialId %d for face %d is out of bound for localFbxMaterialsList (size %d).", - materialId, faceIndex, localFbxMaterialsList.size()); - - return SceneEvents::ProcessingResult::Failure; - } - - materialName = localFbxMaterialsList[materialId]; - - // Keep using the first material when it has to be limited to one. - if (limitToOneMaterial && - assetMaterialData.m_fbxMaterialNames.size() == 1 && - assetMaterialData.m_fbxMaterialNames[0] != materialName) - { - materialName = assetMaterialData.m_fbxMaterialNames[0]; - } - } - const AZ::SceneAPI::DataTypes::IMeshData::Face& face = nodeMesh->GetFaceInfo(faceIndex); nodeExportData.m_indices[faceIndex * 3] = face.vertexIndex[0]; nodeExportData.m_indices[faceIndex * 3 + 1] = face.vertexIndex[1]; nodeExportData.m_indices[faceIndex * 3 + 2] = face.vertexIndex[2]; - - AZ::u16 materialIndex = Utils::InsertMaterialIndexByName(materialName, assetMaterialData); - nodeExportData.m_perFaceMaterialIndices[faceIndex] = materialIndex; } if (pxMeshGroup.GetDecomposeMeshes()) @@ -880,7 +869,7 @@ namespace PhysX if (!totalExportData.empty()) { - result += WritePxMeshAsset(context, totalExportData, assetMaterialData, pxMeshGroup); + result += WritePxMeshAsset(context, totalExportData, *assetMaterialData, pxMeshGroup); } } diff --git a/Gems/PhysX/Code/Source/Pipeline/MeshExporter.h b/Gems/PhysX/Code/Source/Pipeline/MeshExporter.h index ef0e58cecb..0593b27301 100644 --- a/Gems/PhysX/Code/Source/Pipeline/MeshExporter.h +++ b/Gems/PhysX/Code/Source/Pipeline/MeshExporter.h @@ -26,6 +26,7 @@ namespace AZ namespace Containers { class Scene; + class SceneGraph; } namespace DataTypes @@ -57,5 +58,34 @@ namespace PhysX private: AZ::SceneAPI::Events::ProcessingResult ExportMeshObject(AZ::SceneAPI::Events::ExportEventContext& context, const AZStd::shared_ptr& meshToExport, const AZStd::string& nodePath, const Pipeline::MeshGroup& pxMeshGroup) const; }; - } -} + + namespace Utils + { + //! A struct to store the materials of the mesh nodes selected in a mesh group. + struct AssetMaterialsData + { + //! Material names coming from FBX. + AZStd::vector m_fbxMaterialNames; + + //! Look-up table for fbxMaterialNames. + AZStd::unordered_map m_materialIndexByName; + + //! Map of mesh nodes to their list of material indices associated to each face. + AZStd::unordered_map> m_nodesToPerFaceMaterialIndices; + }; + + //! Returns the list of materials assigned to the triangles + //! of the mesh nodes selected in a mesh group. + AZStd::optional GatherMaterialsFromMeshGroup( + const MeshGroup& meshGroup, + const AZ::SceneAPI::Containers::SceneGraph& sceneGraph); + + //! Function to update a list of materials and physics materials from a new list. + //! All those new materials not found in the previous list will fallback to default physics material. + bool UpdateAssetPhysicsMaterials( + const AZStd::vector& newMaterials, + AZStd::vector& materials, + AZStd::vector& physicsMaterials); + } // namespace Utils + } // namespace Pipeline +} // namespace PhysX diff --git a/Gems/PhysX/Code/Source/Pipeline/MeshGroup.cpp b/Gems/PhysX/Code/Source/Pipeline/MeshGroup.cpp index db6e7e12e0..4789377852 100644 --- a/Gems/PhysX/Code/Source/Pipeline/MeshGroup.cpp +++ b/Gems/PhysX/Code/Source/Pipeline/MeshGroup.cpp @@ -20,8 +20,11 @@ #include #include #include +#include #include +#include +#include #include @@ -602,6 +605,8 @@ namespace PhysX ->Field("PrimitiveAssetParams", &MeshGroup::m_primitiveAssetParams) ->Field("DecomposeMeshes", &MeshGroup::m_decomposeMeshes) ->Field("ConvexDecompositionParams", &MeshGroup::m_convexDecompositionParams) + ->Field("MaterialSlots", &MeshGroup::m_materialSlots) + ->Field("PhysicsMaterials", &MeshGroup::m_physicsMaterials) ->Field("rules", &MeshGroup::m_rules); if ( @@ -622,6 +627,7 @@ namespace PhysX "Select the meshes to be included in the mesh group.") ->Attribute("FilterName", "meshes") ->Attribute("FilterType", AZ::SceneAPI::DataTypes::IMeshData::TYPEINFO_Uuid()) + ->Attribute(AZ::Edit::Attributes::ChangeNotify, &MeshGroup::OnNodeSelectionChanged) ->DataElement(AZ::Edit::UIHandlers::ComboBox, &MeshGroup::m_exportMethod, "Export As", "The cooking method to be applied to this mesh group. For the asset to be usable as " @@ -629,14 +635,14 @@ namespace PhysX ->EnumAttribute(MeshExportMethod::TriMesh, "Triangle Mesh") ->EnumAttribute(MeshExportMethod::Convex, "Convex") ->EnumAttribute(MeshExportMethod::Primitive, "Primitive") - ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree) + ->Attribute(AZ::Edit::Attributes::ChangeNotify, &MeshGroup::OnExportMethodChanged) ->DataElement(AZ_CRC("DecomposeMeshes", 0xe0e2ac1e), &MeshGroup::m_decomposeMeshes, "Decompose Meshes", "If enables, this option will apply the V-HACD algorithm to split each node " "into approximately convex parts. Each part will individually be exported as a convex " "collider using the parameters configured above.") ->Attribute(AZ::Edit::Attributes::Visibility, &MeshGroup::GetDecomposeMeshesVisibility) - ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree) + ->Attribute(AZ::Edit::Attributes::ChangeNotify, &MeshGroup::OnDecomposeMeshesChanged) ->DataElement(AZ_CRC("TriangleMeshAssetParams", 0x1a408def), &MeshGroup::m_triangleMeshAssetParams, "Triangle Mesh Asset Parameters", "Configure the parameters controlling the exported triangle mesh asset.") @@ -658,6 +664,12 @@ namespace PhysX ->Attribute(AZ::Edit::Attributes::Visibility, &MeshGroup::GetDecomposeMeshes) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->DataElement(AZ::Edit::UIHandlers::Default, &MeshGroup::m_physicsMaterials, "Physics Materials", + "Configure which physics materials to use for each element.") + ->Attribute(AZ::Edit::Attributes::IndexedChildNameLabelOverride, &MeshGroup::GetMaterialSlotLabel) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, false) + ->DataElement(AZ::Edit::UIHandlers::Default, &MeshGroup::m_rules, "", "Add or remove rules to fine-tune the export process.") ->Attribute(AZ::Edit::Attributes::Visibility, AZ_CRC("PropertyVisibility_ShowChildrenOnly", 0xef428f20)); @@ -710,6 +722,37 @@ namespace PhysX return (GetExportAsConvex() || GetExportAsPrimitive()) && m_decomposeMeshes; } + const AZStd::vector& MeshGroup::GetPhysicsMaterials() const + { + return m_physicsMaterials; + } + + const AZStd::vector& MeshGroup::GetMaterialSlots() const + { + return m_materialSlots; + } + + void MeshGroup::SetSceneGraph(const AZ::SceneAPI::Containers::SceneGraph* graph) + { + m_graph = graph; + } + + void MeshGroup::UpdateMaterialSlots() + { + if (!m_graph) + { + return; + } + + AZStd::optional assetMaterialData = Utils::GatherMaterialsFromMeshGroup(*this, *m_graph); + if (!assetMaterialData) + { + return; + } + + Utils::UpdateAssetPhysicsMaterials(assetMaterialData->m_fbxMaterialNames, m_materialSlots, m_physicsMaterials); + } + AZ::SceneAPI::Containers::RuleContainer& MeshGroup::GetRuleContainer() { return m_rules; @@ -770,11 +813,50 @@ namespace PhysX return m_convexDecompositionParams; } + AZ::u32 MeshGroup::OnNodeSelectionChanged() + { + UpdateMaterialSlots(); + return AZ::Edit::PropertyRefreshLevels::EntireTree; + } + + AZ::u32 MeshGroup::OnExportMethodChanged() + { + UpdateMaterialSlots(); + return AZ::Edit::PropertyRefreshLevels::EntireTree; + } + + AZ::u32 MeshGroup::OnDecomposeMeshesChanged() + { + UpdateMaterialSlots(); + return AZ::Edit::PropertyRefreshLevels::EntireTree; + } + bool MeshGroup::GetDecomposeMeshesVisibility() const { return GetExportAsConvex() || GetExportAsPrimitive(); } + AZStd::string MeshGroup::GetMaterialSlotLabel(int index) const + { + if (index < m_materialSlots.size()) + { + // When limited to one material, clarify in the label the material + // will be used for the entire object. + if (index == 0 && (GetExportAsConvex() || GetExportAsPrimitive())) + { + return m_materialSlots[index] + " (entire object)"; + } + else + { + return m_materialSlots[index]; + } + } + else + { + return ""; + } + } + bool MeshGroup::VersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement) { // Remove the material rule. diff --git a/Gems/PhysX/Code/Source/Pipeline/MeshGroup.h b/Gems/PhysX/Code/Source/Pipeline/MeshGroup.h index 0095a3dce2..a2c270234a 100644 --- a/Gems/PhysX/Code/Source/Pipeline/MeshGroup.h +++ b/Gems/PhysX/Code/Source/Pipeline/MeshGroup.h @@ -23,6 +23,11 @@ namespace AZ { class ReflectContext; + + namespace SceneAPI::Containers + { + class SceneGraph; + } } namespace PhysX @@ -185,6 +190,11 @@ namespace PhysX bool GetExportAsTriMesh() const; bool GetExportAsPrimitive() const; bool GetDecomposeMeshes() const; + const AZStd::vector& GetPhysicsMaterials() const; + const AZStd::vector& GetMaterialSlots() const; + + void SetSceneGraph(const AZ::SceneAPI::Containers::SceneGraph* graph); + void UpdateMaterialSlots(); AZ::SceneAPI::Containers::RuleContainer& GetRuleContainer() override; const AZ::SceneAPI::Containers::RuleContainer& GetRuleContainerConst() const override; @@ -207,8 +217,14 @@ namespace PhysX protected: static bool VersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement); + AZ::u32 OnNodeSelectionChanged(); + AZ::u32 OnExportMethodChanged(); + AZ::u32 OnDecomposeMeshesChanged(); + bool GetDecomposeMeshesVisibility() const; + AZStd::string GetMaterialSlotLabel(int index) const; + AZ::Uuid m_id{}; AZStd::string m_name{}; AZ::SceneAPI::SceneData::SceneNodeSelectionList m_nodeSelectionList{}; @@ -219,6 +235,10 @@ namespace PhysX PrimitiveAssetParams m_primitiveAssetParams{}; ConvexDecompositionParams m_convexDecompositionParams{}; AZ::SceneAPI::Containers::RuleContainer m_rules{}; + AZStd::vector m_materialSlots; + AZStd::vector m_physicsMaterials; + + const AZ::SceneAPI::Containers::SceneGraph* m_graph = nullptr; }; } }