Added configurable physics materials per asset in PhysX group in FBX Settings. (#1186)
- Added back the' Physics Materials from Asset' tick in the collider components. - Made physics materials names case insensitive. - Refactored how to gather material information from fbx and used the same code for exporter and physx groups.
This commit is contained in:
@@ -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::MaterialSelection>("Physics Material", "Select physics material library and which materials to use for the object")
|
||||
editContext->Class<Physics::MaterialSelection>("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)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace AZ
|
||||
|
||||
namespace Physics
|
||||
{
|
||||
static constexpr AZStd::string_view DefaultPhysicsMaterialLabel = "<Default Physics Material>";
|
||||
|
||||
/// Physics material
|
||||
/// =========================
|
||||
/// This is the interface to the wrapper around native material type (such as PxMaterial in PhysX gem)
|
||||
|
||||
@@ -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<AZ::SerializeContext*>(context))
|
||||
@@ -181,9 +166,10 @@ namespace Physics
|
||||
->RegisterGenericType<AZStd::shared_ptr<PhysicsAssetShapeConfiguration>>();
|
||||
|
||||
serializeContext->Class<PhysicsAssetShapeConfiguration, ShapeConfiguration>()
|
||||
->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")
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace Physics
|
||||
|
||||
AZ::Data::Asset<AZ::Data::AssetData> 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.
|
||||
};
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace PhysX
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigStringLineEditCtrl::SetForbiddenStrings(const AZStd::unordered_set<AZStd::string>& 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<AZStd::string>& 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<AZStd::string> forbiddenStringsUnorderedSet;
|
||||
UniqueStringContainer::StringSet forbiddenStringsUnorderedSet;
|
||||
AZStd::set<AZStd::string> forbiddenStringsSet;
|
||||
AZStd::vector<AZStd::string> forbiddenStringsVector;
|
||||
|
||||
if (attrValue->Read<AZStd::unordered_set<AZStd::string>>(forbiddenStringsUnorderedSet))
|
||||
if (attrValue->Read<UniqueStringContainer::StringSet>(forbiddenStringsUnorderedSet))
|
||||
{
|
||||
GUI->SetForbiddenStrings(forbiddenStringsUnorderedSet);
|
||||
}
|
||||
else if (attrValue->Read<AZStd::set<AZStd::string>>(forbiddenStringsSet))
|
||||
{
|
||||
forbiddenStringsUnorderedSet = AZStd::unordered_set<AZStd::string>(forbiddenStringsSet.begin()
|
||||
forbiddenStringsUnorderedSet = UniqueStringContainer::StringSet(forbiddenStringsSet.begin()
|
||||
, forbiddenStringsSet.end());
|
||||
GUI->SetForbiddenStrings(forbiddenStringsUnorderedSet);
|
||||
}
|
||||
else if (attrValue->Read<AZStd::vector<AZStd::string>>(forbiddenStringsVector))
|
||||
{
|
||||
forbiddenStringsUnorderedSet = AZStd::unordered_set<AZStd::string>(forbiddenStringsVector.begin()
|
||||
forbiddenStringsUnorderedSet = UniqueStringContainer::StringSet(forbiddenStringsVector.begin()
|
||||
, forbiddenStringsVector.end());
|
||||
GUI->SetForbiddenStrings(forbiddenStringsUnorderedSet);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace PhysX
|
||||
|
||||
void OnEditStart(AZ::Crc32 stringGroupId
|
||||
, const AZStd::string& stringToEdit
|
||||
, const AZStd::unordered_set<AZStd::string>& 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<AZStd::string> 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<AZStd::string>& 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<AZStd::string> 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.
|
||||
};
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
|
||||
#include <AzFramework/Physics/PropertyTypes.h>
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace PhysX
|
||||
{
|
||||
namespace Editor
|
||||
{
|
||||
static const char* const DefaultPhysicsMaterialLabel = "<Default Physics Material>";
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -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<AZStd::string>());
|
||||
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<AZStd::string>& 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<AZStd::string>& stringGroup = (stringGroupsIter == m_stringGroups.end())? AZStd::unordered_set<AZStd::string>():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<AZStd::string>& 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
|
||||
|
||||
@@ -16,13 +16,38 @@
|
||||
#include <AzCore/std/containers/unordered_map.h>
|
||||
#include <AzCore/std/containers/unordered_set.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzCore/StringFunc/StringFunc.h>
|
||||
|
||||
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<AZStd::string>{}(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<AZStd::string, CaseInsensitiveStringHash, CaseInsensitiveStringEqual>;
|
||||
|
||||
/// 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<AZStd::string>& 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<AZ::Crc32, AZStd::unordered_set<AZStd::string>>;
|
||||
using StringGroups = AZStd::unordered_map<AZ::Crc32, StringSet>;
|
||||
StringGroups m_stringGroups; ///< Collection of groups of unique strings, each group identified by an ID.
|
||||
};
|
||||
}
|
||||
} // namespace PhysX
|
||||
|
||||
@@ -62,8 +62,8 @@ namespace PhysX
|
||||
using ShapeConfigurationList = AZStd::vector<ShapeConfigurationPair>;
|
||||
|
||||
ShapeConfigurationList m_colliderShapes; //!< Shapes data with optional collider configuration override.
|
||||
AZStd::vector<AZStd::string> m_surfaceNames; //!< List of all surface names.
|
||||
AZStd::vector<AZStd::string> m_materialNames; //!< List of all material names.
|
||||
AZStd::vector<AZStd::string> m_materialNames; //!< List of material names of the mesh asset.
|
||||
AZStd::vector<AZStd::string> m_physicsMaterialNames; //!< List of physics material names associated with each material.
|
||||
AZStd::vector<AZ::u16> m_materialIndexPerShape; //!< An index of the material in m_materialNames for each shape.
|
||||
};
|
||||
|
||||
|
||||
@@ -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<Pipeline::MeshAsset>& 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<AZ::u16>& indexPerShape = physicsAsset->m_assetData.m_materialIndexPerShape;
|
||||
|
||||
AZStd::unordered_set<AZ::u16> 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<AZ::Data::AssetData> asset)
|
||||
|
||||
@@ -244,7 +244,7 @@ namespace PhysX
|
||||
AZ::Data::AssetId FindMatchingPhysicsAsset(const AZ::Data::Asset<AZ::Data::AssetData>& renderMeshAsset,
|
||||
const AZStd::vector<AZ::Data::AssetId>& physicsAssets);
|
||||
|
||||
void ValidateMaterialSurfaces();
|
||||
void ValidateAssetMaterials();
|
||||
|
||||
void InitEventHandlers();
|
||||
DebugDraw::Collider m_colliderDebugDraw;
|
||||
|
||||
@@ -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<AZStd::string>& meshMaterialNames = meshAsset->m_assetData.m_materialNames;
|
||||
for (size_t slotIndex = 0; slotIndex < meshMaterialNames.size(); ++slotIndex)
|
||||
const AZStd::vector<AZStd::string>& 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())
|
||||
{
|
||||
|
||||
@@ -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 = "<Default>";
|
||||
|
||||
/// PhysX implementation of Physics::Material interface
|
||||
/// ===================================================
|
||||
///
|
||||
|
||||
@@ -151,8 +151,8 @@ namespace PhysX
|
||||
|
||||
serializeContext->Class<MeshAssetData>()
|
||||
->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)
|
||||
;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<AZStd::string> m_fbxMaterialNames;
|
||||
|
||||
// Look-up table for fbxMaterialNames
|
||||
AZStd::unordered_map<AZStd::string, size_t> 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<AZ::SerializeContext*>(context);
|
||||
if (serializeContext)
|
||||
{
|
||||
serializeContext->Class<MeshExporter, AZ::SceneAPI::SceneCore::ExportingComponent>()->Version(4);
|
||||
serializeContext->Class<MeshExporter, AZ::SceneAPI::SceneCore::ExportingComponent>()->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<AZStd::string, AZStd::string>& materialToSurfaceTypeMap)
|
||||
bool UpdateAssetPhysicsMaterials(
|
||||
const AZStd::vector<AZStd::string>& newMaterials,
|
||||
AZStd::vector<AZStd::string>& materials,
|
||||
AZStd::vector<AZStd::string>& 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<char> 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<char> document;
|
||||
document.parse<AZ::rapidxml::parse_no_data_nodes>(buffer.data());
|
||||
|
||||
//Parse MTL file for materials and/or submaterials.
|
||||
AZ::rapidxml::xml_node<char>* rootMaterialNode = document.first_node(AZ::GFxFramework::MaterialExport::g_materialString);
|
||||
|
||||
AZ::rapidxml::xml_node<char>* subMaterialNode = rootMaterialNode->first_node(AZ::GFxFramework::MaterialExport::g_subMaterialString);
|
||||
|
||||
if (subMaterialNode)
|
||||
{
|
||||
for (AZ::rapidxml::xml_node<char>* materialNode = subMaterialNode->first_node(AZ::GFxFramework::MaterialExport::g_materialString);
|
||||
materialNode;
|
||||
materialNode = materialNode->next_sibling(AZ::GFxFramework::MaterialExport::g_materialString))
|
||||
{
|
||||
AZ::rapidxml::xml_attribute<char>* nameAttribute = materialNode->first_attribute(AZ::GFxFramework::MaterialExport::g_nameString);
|
||||
if (nameAttribute)
|
||||
{
|
||||
AZStd::string materialName = nameAttribute->value();
|
||||
AZStd::string surfaceTypeName = DefaultPhysicsMaterialNameFromPhysicsAsset;
|
||||
|
||||
AZ::rapidxml::xml_attribute<char>* 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<AZStd::string>& fbxMaterialNames,
|
||||
const AZStd::unordered_map<AZStd::string, AZStd::string>& materialToSurfaceTypeMap,
|
||||
MeshAssetData& assetData)
|
||||
{
|
||||
AZStd::vector<AZStd::string>& 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<AZStd::string> 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<AssetMaterialsData> 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<const AZ::SceneAPI::DataTypes::IMeshData*>(*sceneGraph.ConvertToStorageIterator(nodeIndex));
|
||||
if (!nodeMesh)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AZStd::string_view nodeName = sceneGraph.GetNodeName(nodeIndex).GetName();
|
||||
|
||||
const AZStd::vector<AZStd::string> 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<AZ::u16>(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<NodeCollisionGeomExportData>& 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<AZStd::string, AZStd::string> 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<Utils::AssetMaterialsData> assetMaterialData = Utils::GatherMaterialsFromMeshGroup(pxMeshGroup, graph);
|
||||
if (!assetMaterialData)
|
||||
{
|
||||
return SceneEvents::ProcessingResult::Failure;
|
||||
}
|
||||
|
||||
// Export data per node
|
||||
AZStd::vector<NodeCollisionGeomExportData> 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<AZStd::string> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<const AZ::SceneAPI::DataTypes::IMeshData>& 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<AZStd::string> m_fbxMaterialNames;
|
||||
|
||||
//! Look-up table for fbxMaterialNames.
|
||||
AZStd::unordered_map<AZStd::string, size_t> m_materialIndexByName;
|
||||
|
||||
//! Map of mesh nodes to their list of material indices associated to each face.
|
||||
AZStd::unordered_map<AZStd::string, AZStd::vector<AZ::u16>> m_nodesToPerFaceMaterialIndices;
|
||||
};
|
||||
|
||||
//! Returns the list of materials assigned to the triangles
|
||||
//! of the mesh nodes selected in a mesh group.
|
||||
AZStd::optional<AssetMaterialsData> 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<AZStd::string>& newMaterials,
|
||||
AZStd::vector<AZStd::string>& materials,
|
||||
AZStd::vector<AZStd::string>& physicsMaterials);
|
||||
} // namespace Utils
|
||||
} // namespace Pipeline
|
||||
} // namespace PhysX
|
||||
|
||||
@@ -20,8 +20,11 @@
|
||||
#include <SceneAPI/SceneCore/DataTypes/GraphData/IBoneData.h>
|
||||
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshData.h>
|
||||
#include <SceneAPI/SceneData/Rules/MaterialRule.h>
|
||||
#include <SceneAPI/SceneCore/Containers/SceneGraph.h>
|
||||
|
||||
#include <Source/Pipeline/MeshGroup.h>
|
||||
#include <Source/Pipeline/MeshExporter.h>
|
||||
#include <Source/Material.h>
|
||||
|
||||
#include <PxPhysicsAPI.h>
|
||||
|
||||
@@ -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
|
||||
"<span>Select the meshes to be included in the mesh group.</span>")
|
||||
->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",
|
||||
"<span>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",
|
||||
"<span>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.</span>")
|
||||
->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",
|
||||
"<span>Configure the parameters controlling the exported triangle mesh asset.</span>")
|
||||
@@ -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",
|
||||
"<span>Configure which physics materials to use for each element.</span>")
|
||||
->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<AZStd::string>& MeshGroup::GetPhysicsMaterials() const
|
||||
{
|
||||
return m_physicsMaterials;
|
||||
}
|
||||
|
||||
const AZStd::vector<AZStd::string>& 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<Utils::AssetMaterialsData> 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 "<Unknown>";
|
||||
}
|
||||
}
|
||||
|
||||
bool MeshGroup::VersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
|
||||
{
|
||||
// Remove the material rule.
|
||||
|
||||
@@ -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<AZStd::string>& GetPhysicsMaterials() const;
|
||||
const AZStd::vector<AZStd::string>& 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<AZStd::string> m_materialSlots;
|
||||
AZStd::vector<AZStd::string> m_physicsMaterials;
|
||||
|
||||
const AZ::SceneAPI::Containers::SceneGraph* m_graph = nullptr;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user