Merge remote-tracking branch 'upstream/development' into thin-transmission

monroegm-disable-blank-issue-2
Santi Paprika 4 years ago
commit fe163bc930

@ -114,11 +114,11 @@ def get_property(document_id, property_name):
""" """
:return: property value or invalid value if the document is not open or the property_name can't be found :return: property value or invalid value if the document is not open or the property_name can't be found
""" """
return azlmbr.atomtools.AtomToolsDocumentRequestBus(bus.Event, "GetPropertyValue", document_id, property_name) return azlmbr.materialeditor.MaterialDocumentRequestBus(bus.Event, "GetPropertyValue", document_id, property_name)
def set_property(document_id, property_name, value): def set_property(document_id, property_name, value):
azlmbr.atomtools.AtomToolsDocumentRequestBus(bus.Event, "SetPropertyValue", document_id, property_name, value) azlmbr.materialeditor.MaterialDocumentRequestBus(bus.Event, "SetPropertyValue", document_id, property_name, value)
def is_pane_visible(pane_name): def is_pane_visible(pane_name):

@ -136,7 +136,7 @@ namespace AZ::CommonOnDemandReflections
->template Constructor<typename ContainerType::value_type*>() ->template Constructor<typename ContainerType::value_type*>()
->Attribute(AZ::Script::Attributes::ConstructorOverride, &OnDemandLuaFunctions::ConstructStringView<ContainerType::value_type, ContainerType::traits_type>) ->Attribute(AZ::Script::Attributes::ConstructorOverride, &OnDemandLuaFunctions::ConstructStringView<ContainerType::value_type, ContainerType::traits_type>)
->Attribute(AZ::Script::Attributes::ReaderWriterOverride, ScriptContext::CustomReaderWriter(&OnDemandLuaFunctions::StringTypeToLua<ContainerType>, &OnDemandLuaFunctions::StringTypeFromLua<ContainerType>)) ->Attribute(AZ::Script::Attributes::ReaderWriterOverride, ScriptContext::CustomReaderWriter(&OnDemandLuaFunctions::StringTypeToLua<ContainerType>, &OnDemandLuaFunctions::StringTypeFromLua<ContainerType>))
->Method("ToString", [](const ContainerType& stringView) { return static_cast<AZStd::string>(stringView).c_str(); }, { { { "Reference", "String view object being converted to string" } } }) ->Method("ToString", [](const ContainerType& stringView) { return stringView.data(); }, { { { "Reference", "String view object being converted to string" } } })
->Attribute(AZ::Script::Attributes::ToolTip, "Converts string_view to string") ->Attribute(AZ::Script::Attributes::ToolTip, "Converts string_view to string")
->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::ToString) ->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::ToString)
->template WrappingMember<const char*>(&ContainerType::data) ->template WrappingMember<const char*>(&ContainerType::data)

@ -815,7 +815,8 @@ namespace AzToolsFramework
if (templateRef.has_value()) if (templateRef.has_value())
{ {
return templateRef->get().IsDirty(); return !templateRef->get().IsProcedural() && // all procedural prefabs are read-only
templateRef->get().IsDirty();
} }
return false; return false;

@ -50,8 +50,7 @@
"FilePath": "Shaders/PostProcessing/SsaoCompute.shader" "FilePath": "Shaders/PostProcessing/SsaoCompute.shader"
}, },
"Make Fullscreen Pass": true, "Make Fullscreen Pass": true,
"PipelineViewTag": "MainCamera", "PipelineViewTag": "MainCamera"
"Use Async Compute": true
}, },
"FallbackConnections": [ "FallbackConnections": [
{ {

@ -39,59 +39,21 @@ namespace AZ
; ;
} }
} }
void Transform2DFunctor::Process(RuntimeContext& context) void Transform2DFunctor::Process(RuntimeContext& context)
{ {
using namespace RPI; using namespace RPI;
auto center = context.GetMaterialPropertyValue<Vector2>(m_center); UvTransformDescriptor desc;
auto scale = context.GetMaterialPropertyValue<float>(m_scale); desc.m_center = context.GetMaterialPropertyValue<Vector2>(m_center);
auto scaleX = context.GetMaterialPropertyValue<float>(m_scaleX); desc.m_scale = context.GetMaterialPropertyValue<float>(m_scale);
auto scaleY = context.GetMaterialPropertyValue<float>(m_scaleY); desc.m_scaleX = context.GetMaterialPropertyValue<float>(m_scaleX);
auto translateX = context.GetMaterialPropertyValue<float>(m_translateX); desc.m_scaleY = context.GetMaterialPropertyValue<float>(m_scaleY);
auto translateY = context.GetMaterialPropertyValue<float>(m_translateY); desc.m_translateX = context.GetMaterialPropertyValue<float>(m_translateX);
auto rotateDegrees = context.GetMaterialPropertyValue<float>(m_rotateDegrees); desc.m_translateY = context.GetMaterialPropertyValue<float>(m_translateY);
desc.m_rotateDegrees = context.GetMaterialPropertyValue<float>(m_rotateDegrees);
if (scaleX != 0.0f)
{
translateX *= (1.0f / scaleX);
}
if (scaleY != 0.0f)
{
translateY *= (1.0f / scaleY);
}
Matrix3x3 translateCenter2D = Matrix3x3::CreateIdentity();
translateCenter2D.SetBasisZ(-center.GetX(), -center.GetY(), 1.0f);
Matrix3x3 translateCenterInv2D = Matrix3x3::CreateIdentity();
translateCenterInv2D.SetBasisZ(center.GetX(), center.GetY(), 1.0f);
Matrix3x3 scale2D = Matrix3x3::CreateDiagonal(AZ::Vector3(scaleX * scale, scaleY * scale, 1.0f));
Matrix3x3 translate2D = Matrix3x3::CreateIdentity();
translate2D.SetBasisZ(translateX, translateY, 1.0f);
Matrix3x3 rotate2D = Matrix3x3::CreateRotationZ(AZ::DegToRad(rotateDegrees)); Matrix3x3 transform = CreateUvTransformMatrix(desc, m_transformOrder);
Matrix3x3 transform = translateCenter2D;
for (auto transformType : m_transformOrder)
{
switch (transformType)
{
case TransformType::Scale:
transform = scale2D * transform;
break;
case TransformType::Rotate:
transform = rotate2D * transform;
break;
case TransformType::Translate:
transform = translate2D * transform;
break;
}
}
transform = translateCenterInv2D * transform;
context.GetShaderResourceGroup()->SetConstant(m_transformMatrix, transform); context.GetShaderResourceGroup()->SetConstant(m_transformMatrix, transform);

@ -11,6 +11,7 @@
#include <Atom/RPI.Reflect/Material/MaterialFunctor.h> #include <Atom/RPI.Reflect/Material/MaterialFunctor.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h> #include <Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h>
#include <Atom/RHI.Reflect/Limits.h> #include <Atom/RHI.Reflect/Limits.h>
#include <Atom/Utils/MaterialUtils.h>
namespace AZ namespace AZ
{ {
@ -24,14 +25,6 @@ namespace AZ
public: public:
AZ_RTTI(Transform2DFunctor, "{3E9C4357-6B2D-4A22-89DB-462441C9D8CD}", RPI::MaterialFunctor); AZ_RTTI(Transform2DFunctor, "{3E9C4357-6B2D-4A22-89DB-462441C9D8CD}", RPI::MaterialFunctor);
enum class TransformType
{
Invalid,
Scale,
Rotate,
Translate
};
static void Reflect(ReflectContext* context); static void Reflect(ReflectContext* context);
using RPI::MaterialFunctor::Process; using RPI::MaterialFunctor::Process;
@ -57,6 +50,4 @@ namespace AZ
} // namespace Render } // namespace Render
AZ_TYPE_INFO_SPECIALIZE(Render::Transform2DFunctor::TransformType, "{D8C15D33-CE3D-4297-A646-030B0625BF84}");
} // namespace AZ } // namespace AZ

@ -85,13 +85,13 @@ namespace AZ
functor->m_transformOrder = m_transformOrder; functor->m_transformOrder = m_transformOrder;
AZStd::set<Transform2DFunctor::TransformType> transformSet{m_transformOrder.begin(), m_transformOrder.end()}; AZStd::set<TransformType> transformSet{m_transformOrder.begin(), m_transformOrder.end()};
if (m_transformOrder.size() != transformSet.size()) if (m_transformOrder.size() != transformSet.size())
{ {
AZ_Warning("Transform2DFunctor", false, "transformOrder field contains duplicate entries"); AZ_Warning("Transform2DFunctor", false, "transformOrder field contains duplicate entries");
} }
if (transformSet.find(Transform2DFunctor::TransformType::Invalid) != transformSet.end()) if (transformSet.find(TransformType::Invalid) != transformSet.end())
{ {
AZ_Warning("Transform2DFunctor", false, "transformOrder contains invalid entries"); AZ_Warning("Transform2DFunctor", false, "transformOrder contains invalid entries");
} }

@ -10,6 +10,7 @@
#include "./Transform2DFunctor.h" #include "./Transform2DFunctor.h"
#include <Atom/RPI.Edit/Material/MaterialFunctorSourceData.h> #include <Atom/RPI.Edit/Material/MaterialFunctorSourceData.h>
#include <Atom/Utils/MaterialUtils.h>
namespace AZ namespace AZ
{ {
@ -30,7 +31,7 @@ namespace AZ
private: private:
AZStd::vector<Transform2DFunctor::TransformType> m_transformOrder; //!< Controls the order in which Scale, Translate, Rotate are performed AZStd::vector<TransformType> m_transformOrder; //!< Controls the order in which Scale, Translate, Rotate are performed
// Material property inputs... // Material property inputs...
AZStd::string m_center; //!< material property for center of scaling and rotation AZStd::string m_center; //!< material property for center of scaling and rotation

@ -32,10 +32,7 @@ namespace AtomToolsFramework
// AtomToolsDocumentRequestBus::Handler overrides... // AtomToolsDocumentRequestBus::Handler overrides...
AZStd::string_view GetAbsolutePath() const override; AZStd::string_view GetAbsolutePath() const override;
const AZStd::any& GetPropertyValue(const AZ::Name& propertyId) const override; AZStd::vector<DocumentObjectInfo> GetObjectInfo() const override;
const AtomToolsFramework::DynamicProperty& GetProperty(const AZ::Name& propertyId) const override;
bool IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const override;
void SetPropertyValue(const AZ::Name& propertyId, const AZStd::any& value) override;
bool Open(AZStd::string_view loadPath) override; bool Open(AZStd::string_view loadPath) override;
bool Reopen() override; bool Reopen() override;
bool Save() override; bool Save() override;
@ -78,10 +75,6 @@ namespace AtomToolsFramework
//! The normalized, absolute path where the document will be saved. //! The normalized, absolute path where the document will be saved.
AZStd::string m_savePathNormalized; AZStd::string m_savePathNormalized;
AZStd::any m_invalidValue;
AtomToolsFramework::DynamicProperty m_invalidProperty;
//! This contains absolute paths of other source files that affect this document. //! This contains absolute paths of other source files that affect this document.
//! If any of the source files in this container are modified, the document system is notified to reload this document. //! If any of the source files in this container are modified, the document system is notified to reload this document.
AZStd::unordered_set<AZStd::string> m_sourceDependencies; AZStd::unordered_set<AZStd::string> m_sourceDependencies;

@ -10,11 +10,10 @@
#include <AzCore/Asset/AssetCommon.h> #include <AzCore/Asset/AssetCommon.h>
#include <AzCore/std/any.h> #include <AzCore/std/any.h>
#include <AtomToolsFramework/DynamicProperty/DynamicProperty.h>
#include <AtomToolsFramework/DynamicProperty/DynamicPropertyGroup.h>
namespace AtomToolsFramework namespace AtomToolsFramework
{ {
struct DocumentObjectInfo;
class AtomToolsDocumentNotifications class AtomToolsDocumentNotifications
: public AZ::EBusTraits : public AZ::EBusTraits
{ {
@ -61,22 +60,12 @@ namespace AtomToolsFramework
//! Signal that a document undo state was updated //! Signal that a document undo state was updated
//! @param documentId unique id of document for which the notification is sent //! @param documentId unique id of document for which the notification is sent
virtual void OnDocumentUndoStateChanged([[maybe_unused]] const AZ::Uuid& documentId) {} virtual void OnDocumentUndoStateChanged([[maybe_unused]] const AZ::Uuid& documentId) {}
//! Signal that a property changed
//! @param documentId unique id of document for which the notification is sent
//! @param property object containing the property value and configuration that was modified
virtual void OnDocumentPropertyValueModified([[maybe_unused]] const AZ::Uuid& documentId, [[maybe_unused]] const AtomToolsFramework::DynamicProperty& property) {}
//! Signal that the property configuration has been changed.
//! @param documentId unique id of document for which the notification is sent
//! @param property object containing the property value and configuration that was modified
virtual void OnDocumentPropertyConfigModified([[maybe_unused]] const AZ::Uuid& documentId, [[maybe_unused]] const AtomToolsFramework::DynamicProperty& property) {}
//! Signal that the property group visibility has been changed. //! Signal that the group has been changed.
//! @param documentId unique id of document for which the notification is sent //! @param documentId unique id of document for which the notification is sent
//! @param groupId id of the group that changed //! @param objectInfo description of the reflected object that's been modified
//! @param visible whether the property group is visible //! @param rebuilt signifies if it was a structural change that might require ui to be rebuilt
virtual void OnDocumentPropertyGroupVisibilityChanged([[maybe_unused]] const AZ::Uuid& documentId, [[maybe_unused]] const AZ::Name& groupId, [[maybe_unused]] bool visible) {} virtual void OnDocumentObjectInfoChanged([[maybe_unused]] const AZ::Uuid& documentId, [[maybe_unused]] const DocumentObjectInfo& objectInfo, [[maybe_unused]] bool rebuilt) {}
}; };
using AtomToolsDocumentNotificationBus = AZ::EBus<AtomToolsDocumentNotifications>; using AtomToolsDocumentNotificationBus = AZ::EBus<AtomToolsDocumentNotifications>;

@ -14,6 +14,20 @@
namespace AtomToolsFramework namespace AtomToolsFramework
{ {
//! Structure used as an opaque description of objects reflected inside of a document.
//! An example use would be semi automatic handling of serialization or reflection to a property editor.
struct DocumentObjectInfo
{
bool m_visible = true;
AZStd::string m_name;
AZStd::string m_displayName;
AZStd::string m_description;
AZ::Uuid m_objectType = AZ::Uuid::CreateNull();
void* m_objectPtr = {};
};
//! This bus provides the most basic interface for implementing a document that works with the document system.
//! Any extensions or application specific functionality should be added using a domain specific buses.
class AtomToolsDocumentRequests class AtomToolsDocumentRequests
: public AZ::EBusTraits : public AZ::EBusTraits
{ {
@ -25,20 +39,8 @@ namespace AtomToolsFramework
//! Get absolute path of document //! Get absolute path of document
virtual AZStd::string_view GetAbsolutePath() const = 0; virtual AZStd::string_view GetAbsolutePath() const = 0;
//! Return property value //! Returns a container describing all reflected objects contained in a document
//! If the document is not open or the id can't be found, an invalid value is returned instead. virtual AZStd::vector<DocumentObjectInfo> GetObjectInfo() const = 0;
virtual const AZStd::any& GetPropertyValue(const AZ::Name& propertyFullName) const = 0;
//! Returns a property object
//! If the document is not open or the id can't be found, an invalid property is returned.
virtual const AtomToolsFramework::DynamicProperty& GetProperty(const AZ::Name& propertyFullName) const = 0;
//! Returns whether a property group is visible
//! If the document is not open or the id can't be found, returns false.
virtual bool IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const = 0;
//! Modify document property value
virtual void SetPropertyValue(const AZ::Name& propertyFullName, const AZStd::any& value) = 0;
//! Load document and related data //! Load document and related data
//! @param loadPath absolute path of document to load //! @param loadPath absolute path of document to load

@ -25,11 +25,11 @@ namespace AtomToolsFramework
//! Register a document factory function used to create specific document types //! Register a document factory function used to create specific document types
virtual void RegisterDocumentType(AZStd::function<AtomToolsDocument*()> documentCreator) = 0; virtual void RegisterDocumentType(AZStd::function<AtomToolsDocument*()> documentCreator) = 0;
//! Create a document object //! Create a document
//! @return Uuid of new document, or null Uuid if failed //! @return Uuid of new document, or null Uuid if failed
virtual AZ::Uuid CreateDocument() = 0; virtual AZ::Uuid CreateDocument() = 0;
//! Destroy a document object with the specified id //! Destroy a document with the specified id
//! @return true if Uuid was found and removed, otherwise false //! @return true if Uuid was found and removed, otherwise false
virtual bool DestroyDocument(const AZ::Uuid& documentId) = 0; virtual bool DestroyDocument(const AZ::Uuid& documentId) = 0;

@ -46,6 +46,7 @@ namespace AtomToolsFramework
AZStd::string m_name; AZStd::string m_name;
AZStd::string m_displayName; AZStd::string m_displayName;
AZStd::string m_groupName; AZStd::string m_groupName;
AZStd::string m_groupDisplayName;
AZStd::string m_description; AZStd::string m_description;
AZStd::any m_defaultValue; AZStd::any m_defaultValue;
AZStd::any m_parentValue; AZStd::any m_parentValue;
@ -60,6 +61,7 @@ namespace AtomToolsFramework
bool m_visible = true; bool m_visible = true;
bool m_readOnly = false; bool m_readOnly = false;
bool m_showThumbnail = false; bool m_showThumbnail = false;
AZStd::function<AZ::u32(const AZStd::any&)> m_dataChangeCallback;
}; };
//! Wraps an AZStd::any value and configuration so that it can be displayed and edited in a ReflectedPropertyEditor. //! Wraps an AZStd::any value and configuration so that it can be displayed and edited in a ReflectedPropertyEditor.
@ -106,7 +108,7 @@ namespace AtomToolsFramework
private: private:
// Functions used to configure edit data attributes. // Functions used to configure edit data attributes.
AZStd::string GetDisplayName() const; AZStd::string GetDisplayName() const;
AZStd::string GetGroupName() const; AZStd::string GetGroupDisplayName() const;
AZStd::string GetAssetPickerTitle() const; AZStd::string GetAssetPickerTitle() const;
AZStd::string GetDescription() const; AZStd::string GetDescription() const;
AZStd::vector<AZ::Edit::EnumConstant<uint32_t>> GetEnumValues() const; AZStd::vector<AZ::Edit::EnumConstant<uint32_t>> GetEnumValues() const;

@ -9,6 +9,7 @@
#pragma once #pragma once
#include <AzCore/std/containers/vector.h> #include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AtomToolsFramework/DynamicProperty/DynamicProperty.h> #include <AtomToolsFramework/DynamicProperty/DynamicProperty.h>
namespace AtomToolsFramework namespace AtomToolsFramework
@ -22,6 +23,11 @@ namespace AtomToolsFramework
static void Reflect(AZ::ReflectContext* context); static void Reflect(AZ::ReflectContext* context);
bool m_visible = true;
AZStd::string m_name;
AZStd::string m_displayName;
AZStd::string m_description;
AZStd::vector<AtomToolsFramework::DynamicProperty> m_properties; AZStd::vector<AtomToolsFramework::DynamicProperty> m_properties;
AZStd::vector<AZStd::shared_ptr<DynamicPropertyGroup>> m_groups;
}; };
} // namespace AtomToolsFramework } // namespace AtomToolsFramework

@ -36,32 +36,10 @@ namespace AtomToolsFramework
return m_absolutePath; return m_absolutePath;
} }
const AZStd::any& AtomToolsDocument::GetPropertyValue([[maybe_unused]] const AZ::Name& propertyId) const AZStd::vector<DocumentObjectInfo> AtomToolsDocument::GetObjectInfo() const
{ {
AZ_UNUSED(propertyId); AZ_Warning("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__); return AZStd::vector<DocumentObjectInfo>();
return m_invalidValue;
}
const AtomToolsFramework::DynamicProperty& AtomToolsDocument::GetProperty([[maybe_unused]] const AZ::Name& propertyId) const
{
AZ_UNUSED(propertyId);
AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
return m_invalidProperty;
}
bool AtomToolsDocument::IsPropertyGroupVisible([[maybe_unused]] const AZ::Name& propertyGroupFullName) const
{
AZ_UNUSED(propertyGroupFullName);
AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
return false;
}
void AtomToolsDocument::SetPropertyValue([[maybe_unused]] const AZ::Name& propertyId, [[maybe_unused]] const AZStd::any& value)
{
AZ_UNUSED(propertyId);
AZ_UNUSED(value);
AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
} }
bool AtomToolsDocument::Open(AZStd::string_view loadPath) bool AtomToolsDocument::Open(AZStd::string_view loadPath)
@ -256,13 +234,13 @@ namespace AtomToolsFramework
bool AtomToolsDocument::BeginEdit() bool AtomToolsDocument::BeginEdit()
{ {
AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__); AZ_Warning("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
return false; return false;
} }
bool AtomToolsDocument::EndEdit() bool AtomToolsDocument::EndEdit()
{ {
AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__); AZ_Warning("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
return false; return false;
} }

@ -77,8 +77,6 @@ namespace AtomToolsFramework
->Attribute(AZ::Script::Attributes::Category, "Editor") ->Attribute(AZ::Script::Attributes::Category, "Editor")
->Attribute(AZ::Script::Attributes::Module, "atomtools") ->Attribute(AZ::Script::Attributes::Module, "atomtools")
->Event("GetAbsolutePath", &AtomToolsDocumentRequestBus::Events::GetAbsolutePath) ->Event("GetAbsolutePath", &AtomToolsDocumentRequestBus::Events::GetAbsolutePath)
->Event("GetPropertyValue", &AtomToolsDocumentRequestBus::Events::GetPropertyValue)
->Event("SetPropertyValue", &AtomToolsDocumentRequestBus::Events::SetPropertyValue)
->Event("Open", &AtomToolsDocumentRequestBus::Events::Open) ->Event("Open", &AtomToolsDocumentRequestBus::Events::Open)
->Event("Reopen", &AtomToolsDocumentRequestBus::Events::Reopen) ->Event("Reopen", &AtomToolsDocumentRequestBus::Events::Reopen)
->Event("Close", &AtomToolsDocumentRequestBus::Events::Close) ->Event("Close", &AtomToolsDocumentRequestBus::Events::Close)

@ -195,14 +195,14 @@ namespace AtomToolsFramework
return !m_config.m_displayName.empty() ? m_config.m_displayName : m_config.m_name; return !m_config.m_displayName.empty() ? m_config.m_displayName : m_config.m_name;
} }
AZStd::string DynamicProperty::GetGroupName() const AZStd::string DynamicProperty::GetGroupDisplayName() const
{ {
return m_config.m_groupName; return m_config.m_groupDisplayName;
} }
AZStd::string DynamicProperty::GetAssetPickerTitle() const AZStd::string DynamicProperty::GetAssetPickerTitle() const
{ {
return GetGroupName().empty() ? GetDisplayName() : GetGroupName() + " " + GetDisplayName(); return GetGroupDisplayName().empty() ? GetDisplayName() : GetGroupDisplayName() + " " + GetDisplayName();
} }
AZStd::string DynamicProperty::GetDescription() const AZStd::string DynamicProperty::GetDescription() const
@ -235,6 +235,10 @@ namespace AtomToolsFramework
AZ::u32 DynamicProperty::OnDataChanged() const AZ::u32 DynamicProperty::OnDataChanged() const
{ {
if (m_config.m_dataChangeCallback)
{
return m_config.m_dataChangeCallback(GetValue());
}
return AZ::Edit::PropertyRefreshLevels::AttributesAndValues; return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
} }

@ -17,7 +17,12 @@ namespace AtomToolsFramework
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context)) if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{ {
serializeContext->Class<DynamicPropertyGroup>() serializeContext->Class<DynamicPropertyGroup>()
->Field("visible", &DynamicPropertyGroup::m_visible)
->Field("name", &DynamicPropertyGroup::m_name)
->Field("displayName", &DynamicPropertyGroup::m_displayName)
->Field("description", &DynamicPropertyGroup::m_description)
->Field("properties", &DynamicPropertyGroup::m_properties) ->Field("properties", &DynamicPropertyGroup::m_properties)
->Field("groups", &DynamicPropertyGroup::m_groups)
; ;
if (auto editContext = serializeContext->GetEditContext()) if (auto editContext = serializeContext->GetEditContext())
@ -28,6 +33,9 @@ namespace AtomToolsFramework
->DataElement(AZ::Edit::UIHandlers::Default, &DynamicPropertyGroup::m_properties, "properties", "") ->DataElement(AZ::Edit::UIHandlers::Default, &DynamicPropertyGroup::m_properties, "properties", "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) // hides the m_properties row ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) // hides the m_properties row
->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, false) // probably not necessary since Visibility is children-only ->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, false) // probably not necessary since Visibility is children-only
->DataElement(AZ::Edit::UIHandlers::Default, &DynamicPropertyGroup::m_groups, "groups", "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) // hides the m_groups row
->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, false) // probably not necessary since Visibility is children-only
; ;
} }
} }

@ -53,112 +53,93 @@ namespace MaterialEditor
return &m_materialTypeSourceData; return &m_materialTypeSourceData;
} }
const AZStd::any& MaterialDocument::GetPropertyValue(const AZ::Name& propertyId) const void MaterialDocument::SetPropertyValue(const AZ::Name& propertyId, const AZStd::any& value)
{ {
if (!IsOpen()) if (!IsOpen())
{ {
AZ_Error("MaterialDocument", false, "Document is not open."); AZ_Error("MaterialDocument", false, "Document is not open.");
return m_invalidValue; return;
} }
const auto it = m_properties.find(propertyId); AtomToolsFramework::DynamicProperty* foundProperty = {};
if (it == m_properties.end()) TraverseGroups(m_groups, [&, this](auto& group) {
{ for (auto& property : group->m_properties)
AZ_Error("MaterialDocument", false, "Document property could not be found: '%s'.", propertyId.GetCStr()); {
return m_invalidValue; if (property.GetId() == propertyId)
} {
foundProperty = &property;
const AtomToolsFramework::DynamicProperty& property = it->second; // This first converts to an acceptable runtime type in case the value came from script
return property.GetValue(); const AZ::RPI::MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(value);
}
const AtomToolsFramework::DynamicProperty& MaterialDocument::GetProperty(const AZ::Name& propertyId) const property.SetValue(AtomToolsFramework::ConvertToEditableType(propertyValue));
{
if (!IsOpen()) const auto propertyIndex = m_materialInstance->FindPropertyIndex(propertyId);
{ if (!propertyIndex.IsNull())
AZ_Error("MaterialDocument", false, "Document is not open."); {
return m_invalidProperty; if (m_materialInstance->SetPropertyValue(propertyIndex, propertyValue))
} {
AZ::RPI::MaterialPropertyFlags dirtyFlags = m_materialInstance->GetPropertyDirtyFlags();
const auto it = m_properties.find(propertyId); Recompile();
if (it == m_properties.end()) RunEditorMaterialFunctors(dirtyFlags);
}
}
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentObjectInfoChanged, m_id,
GetObjectInfoFromDynamicPropertyGroup(group.get()), false);
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentModified, m_id);
return false;
}
}
return true;
});
if (!foundProperty)
{ {
AZ_Error("MaterialDocument", false, "Document property could not be found: '%s'.", propertyId.GetCStr()); AZ_Error("MaterialDocument", false, "Document property could not be found: '%s'.", propertyId.GetCStr());
return m_invalidProperty;
} }
const AtomToolsFramework::DynamicProperty& property = it->second;
return property;
} }
bool MaterialDocument::IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const const AZStd::any& MaterialDocument::GetPropertyValue(const AZ::Name& propertyId) const
{ {
if (!IsOpen()) if (!IsOpen())
{ {
AZ_Error("MaterialDocument", false, "Document is not open."); AZ_Error("MaterialDocument", false, "Document is not open.");
return false; return m_invalidValue;
} }
const auto it = m_propertyGroupVisibility.find(propertyGroupFullName); auto property = FindProperty(propertyId);
if (it == m_propertyGroupVisibility.end()) if (!property)
{ {
AZ_Error("MaterialDocument", false, "Document property group could not be found: '%s'.", propertyGroupFullName.GetCStr()); AZ_Error("MaterialDocument", false, "Document property could not be found: '%s'.", propertyId.GetCStr());
return false; return m_invalidValue;
} }
return it->second; return property->GetValue();
} }
void MaterialDocument::SetPropertyValue(const AZ::Name& propertyId, const AZStd::any& value) AZStd::vector<AtomToolsFramework::DocumentObjectInfo> MaterialDocument::GetObjectInfo() const
{ {
if (!IsOpen()) if (!IsOpen())
{ {
AZ_Error("MaterialDocument", false, "Document is not open."); AZ_Error("MaterialDocument", false, "Document is not open.");
return; return {};
} }
const auto it = m_properties.find(propertyId); AZStd::vector<AtomToolsFramework::DocumentObjectInfo> objects;
if (it == m_properties.end()) objects.reserve(m_groups.size());
{
AZ_Error("MaterialDocument", false, "Document property could not be found: '%s'.", propertyId.GetCStr());
return;
}
// This first converts to an acceptable runtime type in case the value came from script
const AZ::RPI::MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(value);
AtomToolsFramework::DynamicProperty& property = it->second;
property.SetValue(AtomToolsFramework::ConvertToEditableType(propertyValue));
const auto propertyIndex = m_materialInstance->FindPropertyIndex(propertyId); AtomToolsFramework::DocumentObjectInfo objectInfo;
if (!propertyIndex.IsNull()) for (const auto& group : m_groups)
{ {
if (m_materialInstance->SetPropertyValue(propertyIndex, propertyValue)) objects.push_back(GetObjectInfoFromDynamicPropertyGroup(group.get()));
{
AZ::RPI::MaterialPropertyFlags dirtyFlags = m_materialInstance->GetPropertyDirtyFlags();
Recompile();
EditorMaterialFunctorResult result = RunEditorMaterialFunctors(dirtyFlags);
for (const AZ::Name& changedPropertyGroupName : result.m_updatedPropertyGroups)
{
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentPropertyGroupVisibilityChanged, m_id,
changedPropertyGroupName, IsPropertyGroupVisible(changedPropertyGroupName));
}
for (const AZ::Name& changedPropertyName : result.m_updatedProperties)
{
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentPropertyConfigModified, m_id,
GetProperty(changedPropertyName));
}
}
} }
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast( return objects;
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentPropertyValueModified, m_id, property);
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentModified, m_id);
} }
bool MaterialDocument::Save() bool MaterialDocument::Save()
@ -185,14 +166,15 @@ namespace MaterialEditor
} }
// after saving, reset to a clean state // after saving, reset to a clean state
for (auto& propertyPair : m_properties) TraverseGroups(m_groups, [&](auto& group) {
{ for (auto& property : group->m_properties)
AtomToolsFramework::DynamicProperty& property = propertyPair.second; {
auto propertyConfig = property.GetConfig(); auto propertyConfig = property.GetConfig();
propertyConfig.m_originalValue = property.GetValue(); propertyConfig.m_originalValue = property.GetValue();
property.SetConfig(propertyConfig); property.SetConfig(propertyConfig);
} }
return true;
});
return SaveSucceeded(); return SaveSucceeded();
} }
@ -273,12 +255,19 @@ namespace MaterialEditor
bool MaterialDocument::IsModified() const bool MaterialDocument::IsModified() const
{ {
return AZStd::any_of(m_properties.begin(), m_properties.end(), bool result = false;
[](const auto& propertyPair) TraverseGroups(m_groups, [&](auto& group) {
{ for (auto& property : group->m_properties)
const AtomToolsFramework::DynamicProperty& property = propertyPair.second; {
return !AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_originalValue); if (!AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_originalValue))
{
result = true;
return false;
}
}
return true;
}); });
return result;
} }
bool MaterialDocument::IsSavable() const bool MaterialDocument::IsSavable() const
@ -290,11 +279,13 @@ namespace MaterialEditor
{ {
// Save the current properties as a momento for undo before any changes are applied // Save the current properties as a momento for undo before any changes are applied
m_propertyValuesBeforeEdit.clear(); m_propertyValuesBeforeEdit.clear();
for (const auto& propertyPair : m_properties) TraverseGroups(m_groups, [this](auto& group) {
{ for (auto& property : group->m_properties)
const AtomToolsFramework::DynamicProperty& property = propertyPair.second; {
m_propertyValuesBeforeEdit[property.GetId()] = property.GetValue(); m_propertyValuesBeforeEdit[property.GetId()] = property.GetValue();
} }
return true;
});
return true; return true;
} }
@ -348,10 +339,10 @@ namespace MaterialEditor
AZ::Name propertyId{propertyIdContext + propertyDefinition->GetName()}; AZ::Name propertyId{propertyIdContext + propertyDefinition->GetName()};
const auto it = m_properties.find(propertyId); const auto property = FindProperty(propertyId);
if (it != m_properties.end() && propertyFilter(it->second)) if (property && propertyFilter(*property))
{ {
AZ::RPI::MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(it->second.GetValue()); AZ::RPI::MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(property->GetValue());
if (propertyValue.IsValid()) if (propertyValue.IsValid())
{ {
if (!AtomToolsFramework::ConvertToExportFormat(m_savePathNormalized, propertyId, *propertyDefinition, propertyValue)) if (!AtomToolsFramework::ConvertToExportFormat(m_savePathNormalized, propertyId, *propertyDefinition, propertyValue))
@ -394,52 +385,17 @@ namespace MaterialEditor
// The material document and inspector are constructed from source data // The material document and inspector are constructed from source data
if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), AZ::RPI::MaterialSourceData::Extension)) if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), AZ::RPI::MaterialSourceData::Extension))
{ {
// Load the material source data so that we can check properties and create a material asset from it if (!LoadMaterialSourceData())
if (!AZ::RPI::JsonUtils::LoadObjectFromFile(m_absolutePath, m_materialSourceData))
{ {
AZ_Error("MaterialDocument", false, "Material source data could not be loaded: '%s'.", m_absolutePath.c_str());
return OpenFailed(); return OpenFailed();
} }
// We always need the absolute path for the material type and parent material to load source data and resolving
// relative paths when saving. This will convert and store them as absolute paths for use within the document.
if (!m_materialSourceData.m_parentMaterial.empty())
{
m_materialSourceData.m_parentMaterial =
AZ::RPI::AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_parentMaterial);
}
if (!m_materialSourceData.m_materialType.empty())
{
m_materialSourceData.m_materialType =
AZ::RPI::AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_materialType);
}
// Load the material type source data which provides the layout and default values of all of the properties
auto materialTypeOutcome = AZ::RPI::MaterialUtils::LoadMaterialTypeSourceData(m_materialSourceData.m_materialType);
if (!materialTypeOutcome.IsSuccess())
{
AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_materialSourceData.m_materialType.c_str());
return OpenFailed();
}
m_materialTypeSourceData = materialTypeOutcome.TakeValue();
} }
else if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), AZ::RPI::MaterialTypeSourceData::Extension)) else if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), AZ::RPI::MaterialTypeSourceData::Extension))
{ {
// A material document can be created or loaded from material or material type source data. If we are attempting to load if (!LoadMaterialTypeSourceData())
// material type source data then the material source data object can be created just by referencing the document path as the
// material type path.
auto materialTypeOutcome = AZ::RPI::MaterialUtils::LoadMaterialTypeSourceData(m_absolutePath);
if (!materialTypeOutcome.IsSuccess())
{ {
AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_absolutePath.c_str());
return OpenFailed(); return OpenFailed();
} }
m_materialTypeSourceData = materialTypeOutcome.TakeValue();
// We are storing absolute paths in the loaded version of the source data so that the files can be resolved at all times.
m_materialSourceData.m_materialType = m_absolutePath;
m_materialSourceData.m_parentMaterial.clear();
} }
else else
{ {
@ -519,58 +475,21 @@ namespace MaterialEditor
// where such changes are supported at runtime. // where such changes are supported at runtime.
m_materialInstance->SetPsoHandlingOverride(AZ::RPI::MaterialPropertyPsoHandling::Allowed); m_materialInstance->SetPsoHandlingOverride(AZ::RPI::MaterialPropertyPsoHandling::Allowed);
// Populate the property map from a combination of source data and assets // Adding properties for material type and parent as part of making dynamic properties and the inspector more general purpose. This
// Assets must still be used for now because they contain the final accumulated value after all other materials // allows the read only properties to appear in the inspector like any other property. This may change or be removed once support
// in the hierarchy are applied // for changing the material parent is implemented.
m_materialTypeSourceData.EnumeratePropertyGroups([this, &parentPropertyValues](const AZStd::string& propertyIdContext, const AZ::RPI::MaterialTypeSourceData::PropertyGroup* propertyGroup) m_groups.emplace_back(aznew AtomToolsFramework::DynamicPropertyGroup);
{ m_groups.back()->m_name = "overview";
AtomToolsFramework::DynamicPropertyConfig propertyConfig; m_groups.back()->m_displayName = "Overview";
m_groups.back()->m_description = m_materialSourceData.m_description;
for (const auto& propertyDefinition : propertyGroup->GetProperties())
{
// Assign id before conversion so it can be used in dynamic description
propertyConfig.m_id = propertyIdContext + propertyGroup->GetName() + "." + propertyDefinition->GetName();
const auto& propertyIndex = m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id);
const bool propertyIndexInBounds = propertyIndex.IsValid() && propertyIndex.GetIndex() < m_materialAsset->GetPropertyValues().size();
AZ_Warning("MaterialDocument", propertyIndexInBounds, "Failed to add material property '%s' to document '%s'.", propertyConfig.m_id.GetCStr(), m_absolutePath.c_str());
if (propertyIndexInBounds)
{
AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, *propertyDefinition);
propertyConfig.m_showThumbnail = true;
propertyConfig.m_originalValue = AtomToolsFramework::ConvertToEditableType(m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]);
propertyConfig.m_parentValue = AtomToolsFramework::ConvertToEditableType(parentPropertyValues[propertyIndex.GetIndex()]);
// TODO: Support populating the Material Editor with nested property groups, not just the top level.
// (Does DynamicPropertyConfig really even need m_groupName?)
propertyConfig.m_groupName = propertyGroup->GetDisplayName();
m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig);
}
}
return true;
});
// Populate the property group visibility map
// TODO: Support populating the Material Editor with nested property groups, not just the top level.
for (const AZStd::unique_ptr<AZ::RPI::MaterialTypeSourceData::PropertyGroup>& propertyGroup : m_materialTypeSourceData.GetPropertyLayout().m_propertyGroups)
{
m_propertyGroupVisibility[AZ::Name{propertyGroup->GetName()}] = true;
}
// Adding properties for material type and parent as part of making dynamic
// properties and the inspector more general purpose.
// This allows the read only properties to appear in the inspector like any
// other property.
// This may change or be removed once support for changing the material parent
// is implemented.
AtomToolsFramework::DynamicPropertyConfig propertyConfig; AtomToolsFramework::DynamicPropertyConfig propertyConfig;
propertyConfig.m_dataType = AtomToolsFramework::DynamicPropertyType::Asset; propertyConfig.m_dataType = AtomToolsFramework::DynamicPropertyType::Asset;
propertyConfig.m_id = "overview.materialType"; propertyConfig.m_id = "overview.materialType";
propertyConfig.m_name = "materialType"; propertyConfig.m_name = "materialType";
propertyConfig.m_displayName = "Material Type"; propertyConfig.m_displayName = "Material Type";
propertyConfig.m_groupName = "Overview"; propertyConfig.m_groupName = "overview";
propertyConfig.m_groupDisplayName = "Overview";
propertyConfig.m_description = "The material type defines the layout, properties, default values, shader connections, and other " propertyConfig.m_description = "The material type defines the layout, properties, default values, shader connections, and other "
"data needed to create and edit a derived material."; "data needed to create and edit a derived material.";
propertyConfig.m_defaultValue = AZStd::any(materialTypeAsset); propertyConfig.m_defaultValue = AZStd::any(materialTypeAsset);
@ -578,14 +497,15 @@ namespace MaterialEditor
propertyConfig.m_parentValue = propertyConfig.m_defaultValue; propertyConfig.m_parentValue = propertyConfig.m_defaultValue;
propertyConfig.m_readOnly = true; propertyConfig.m_readOnly = true;
m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig); m_groups.back()->m_properties.push_back(AtomToolsFramework::DynamicProperty(propertyConfig));
propertyConfig = {}; propertyConfig = {};
propertyConfig.m_dataType = AtomToolsFramework::DynamicPropertyType::Asset; propertyConfig.m_dataType = AtomToolsFramework::DynamicPropertyType::Asset;
propertyConfig.m_id = "overview.parentMaterial"; propertyConfig.m_id = "overview.parentMaterial";
propertyConfig.m_name = "parentMaterial"; propertyConfig.m_name = "parentMaterial";
propertyConfig.m_displayName = "Parent Material"; propertyConfig.m_displayName = "Parent Material";
propertyConfig.m_groupName = "Overview"; propertyConfig.m_groupName = "overview";
propertyConfig.m_groupDisplayName = "Overview";
propertyConfig.m_description = propertyConfig.m_description =
"The parent material provides an initial configuration whose properties are inherited and overriden by a derived material."; "The parent material provides an initial configuration whose properties are inherited and overriden by a derived material.";
propertyConfig.m_defaultValue = AZStd::any(parentMaterialAsset); propertyConfig.m_defaultValue = AZStd::any(parentMaterialAsset);
@ -594,9 +514,14 @@ namespace MaterialEditor
propertyConfig.m_readOnly = true; propertyConfig.m_readOnly = true;
propertyConfig.m_showThumbnail = true; propertyConfig.m_showThumbnail = true;
m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig); m_groups.back()->m_properties.push_back(AtomToolsFramework::DynamicProperty(propertyConfig));
m_groups.emplace_back(aznew AtomToolsFramework::DynamicPropertyGroup);
m_groups.back()->m_name = UvGroupName;
m_groups.back()->m_displayName = "UV Sets";
m_groups.back()->m_description = "UV set names in this material, which can be renamed to match those in the model.";
//Add UV name customization properties // Add UV name customization properties
const AZ::RPI::MaterialUvNameMap& uvNameMap = materialTypeAsset->GetUvNameMap(); const AZ::RPI::MaterialUvNameMap& uvNameMap = materialTypeAsset->GetUvNameMap();
for (const AZ::RPI::UvNamePair& uvNamePair : uvNameMap) for (const AZ::RPI::UvNamePair& uvNamePair : uvNameMap)
{ {
@ -608,61 +533,69 @@ namespace MaterialEditor
propertyConfig.m_id = AZ::RPI::MaterialPropertyId(UvGroupName, shaderInput); propertyConfig.m_id = AZ::RPI::MaterialPropertyId(UvGroupName, shaderInput);
propertyConfig.m_name = shaderInput; propertyConfig.m_name = shaderInput;
propertyConfig.m_displayName = shaderInput; propertyConfig.m_displayName = shaderInput;
propertyConfig.m_groupName = "UV Sets"; propertyConfig.m_groupName = UvGroupName;
propertyConfig.m_groupDisplayName = "UV Sets";
propertyConfig.m_description = shaderInput; propertyConfig.m_description = shaderInput;
propertyConfig.m_defaultValue = uvName; propertyConfig.m_defaultValue = uvName;
propertyConfig.m_originalValue = uvName; propertyConfig.m_originalValue = uvName;
propertyConfig.m_parentValue = uvName; propertyConfig.m_parentValue = uvName;
propertyConfig.m_readOnly = true; propertyConfig.m_readOnly = true;
m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig); m_groups.back()->m_properties.push_back(AtomToolsFramework::DynamicProperty(propertyConfig));
} }
// Add material functors that are in the top-level functors list. // Populate the property map from a combination of source data and assets
const AZ::RPI::MaterialFunctorSourceData::EditorContext editorContext = // Assets must still be used for now because they contain the final accumulated value after all other materials
AZ::RPI::MaterialFunctorSourceData::EditorContext(m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout()); // in the hierarchy are applied
for (AZ::RPI::Ptr<AZ::RPI::MaterialFunctorSourceDataHolder> functorData : m_materialTypeSourceData.m_materialFunctorSourceData) bool enumerateResult = m_materialTypeSourceData.EnumeratePropertyGroups(
{ [this, &parentPropertyValues](
AZ::RPI::MaterialFunctorSourceData::FunctorResult result2 = functorData->CreateFunctor(editorContext); const AZStd::string& propertyIdContext, const AZ::RPI::MaterialTypeSourceData::PropertyGroup* propertyGroup)
if (result2.IsSuccess())
{ {
AZ::RPI::Ptr<AZ::RPI::MaterialFunctor>& functor = result2.GetValue(); // Add any material functors that are located inside each property group.
if (functor != nullptr) if (!AddEditorMaterialFunctors(propertyGroup->GetFunctors()))
{ {
m_editorFunctors.push_back(functor); return false;
} }
}
else
{
AZ_Error("MaterialDocument", false, "Material functors were not created: '%s'.", m_absolutePath.c_str());
return OpenFailed();
}
}
// Add any material functors that are located inside each property group.
bool enumerateResult = m_materialTypeSourceData.EnumeratePropertyGroups(
[this](const AZStd::string&, const AZ::RPI::MaterialTypeSourceData::PropertyGroup* propertyGroup)
{
const AZ::RPI::MaterialFunctorSourceData::EditorContext editorContext = AZ::RPI::MaterialFunctorSourceData::EditorContext(
m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout());
for (AZ::RPI::Ptr<AZ::RPI::MaterialFunctorSourceDataHolder> functorData : propertyGroup->GetFunctors()) m_groups.emplace_back(aznew AtomToolsFramework::DynamicPropertyGroup);
m_groups.back()->m_name = propertyIdContext + propertyGroup->GetName();
m_groups.back()->m_displayName = propertyGroup->GetDisplayName();
m_groups.back()->m_description = propertyGroup->GetDescription();
for (const auto& propertyDefinition : propertyGroup->GetProperties())
{ {
AZ::RPI::MaterialFunctorSourceData::FunctorResult result = functorData->CreateFunctor(editorContext); // Assign id before conversion so it can be used in dynamic description
AtomToolsFramework::DynamicPropertyConfig propertyConfig;
propertyConfig.m_id = m_groups.back()->m_name + "." + propertyDefinition->GetName();
const auto& propertyIndex = m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id);
const bool propertyIndexInBounds =
propertyIndex.IsValid() && propertyIndex.GetIndex() < m_materialAsset->GetPropertyValues().size();
AZ_Warning(
"MaterialDocument", propertyIndexInBounds, "Failed to add material property '%s' to document '%s'.",
propertyConfig.m_id.GetCStr(), m_absolutePath.c_str());
if (result.IsSuccess()) if (propertyIndexInBounds)
{ {
AZ::RPI::Ptr<AZ::RPI::MaterialFunctor>& functor = result.GetValue(); AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, *propertyDefinition);
if (functor != nullptr)
// TODO: Support populating the Material Editor with nested property groups, not just the top level.
// (Does DynamicPropertyConfig really even need m_groupDisplayName?)
propertyConfig.m_groupName = m_groups.back()->m_name;
propertyConfig.m_groupDisplayName = m_groups.back()->m_displayName;
propertyConfig.m_showThumbnail = true;
propertyConfig.m_originalValue =
AtomToolsFramework::ConvertToEditableType(m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]);
propertyConfig.m_parentValue =
AtomToolsFramework::ConvertToEditableType(parentPropertyValues[propertyIndex.GetIndex()]);
propertyConfig.m_dataChangeCallback = [documentId = m_id, propertyId = propertyConfig.m_id](const AZStd::any& value)
{ {
m_editorFunctors.push_back(functor); MaterialDocumentRequestBus::Event(
} documentId, &MaterialDocumentRequestBus::Events::SetPropertyValue, propertyId, value);
} return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
else };
{
AZ_Error("MaterialDocument", false, "Material functors were not created: '%s'.", m_absolutePath.c_str()); m_groups.back()->m_properties.push_back(AtomToolsFramework::DynamicProperty(propertyConfig));
return false;
} }
} }
@ -674,6 +607,12 @@ namespace MaterialEditor
return OpenFailed(); return OpenFailed();
} }
// Add material functors that are in the top-level functors list.
if (!AddEditorMaterialFunctors(m_materialTypeSourceData.m_materialFunctorSourceData))
{
return OpenFailed();
}
AZ::RPI::MaterialPropertyFlags dirtyFlags; AZ::RPI::MaterialPropertyFlags dirtyFlags;
dirtyFlags.set(); // Mark all properties as dirty since we just loaded the material and need to initialize property visibility dirtyFlags.set(); // Mark all properties as dirty since we just loaded the material and need to initialize property visibility
RunEditorMaterialFunctors(dirtyFlags); RunEditorMaterialFunctors(dirtyFlags);
@ -681,17 +620,35 @@ namespace MaterialEditor
return OpenSucceeded(); return OpenSucceeded();
} }
void MaterialDocument::Clear()
{
AtomToolsFramework::AtomToolsDocument::Clear();
AZ::TickBus::Handler::BusDisconnect();
m_materialAsset = {};
m_materialInstance = {};
m_compilePending = {};
m_groups.clear();
m_editorFunctors.clear();
m_materialTypeSourceData = AZ::RPI::MaterialTypeSourceData();
m_materialSourceData = AZ::RPI::MaterialSourceData();
m_propertyValuesBeforeEdit.clear();
}
bool MaterialDocument::ReopenRecordState() bool MaterialDocument::ReopenRecordState()
{ {
m_propertyValuesBeforeReopen.clear(); m_propertyValuesBeforeReopen.clear();
for (const auto& propertyPair : m_properties) TraverseGroups(m_groups, [this](auto& group) {
{ for (auto& property : group->m_properties)
const AtomToolsFramework::DynamicProperty& property = propertyPair.second;
if (!AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_parentValue))
{ {
m_propertyValuesBeforeReopen[property.GetId()] = property.GetValue(); if (!AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_parentValue))
{
m_propertyValuesBeforeReopen[property.GetId()] = property.GetValue();
}
} }
} return true;
});
return AtomToolsDocument::ReopenRecordState(); return AtomToolsDocument::ReopenRecordState();
} }
@ -711,20 +668,59 @@ namespace MaterialEditor
} }
} }
void MaterialDocument::Clear() bool MaterialDocument::LoadMaterialSourceData()
{ {
AtomToolsFramework::AtomToolsDocument::Clear(); // Load the material source data so that we can check properties and create a material asset from it
if (!AZ::RPI::JsonUtils::LoadObjectFromFile(m_absolutePath, m_materialSourceData))
{
AZ_Error("MaterialDocument", false, "Material source data could not be loaded: '%s'.", m_absolutePath.c_str());
return false;
}
AZ::TickBus::Handler::BusDisconnect(); // We always need the absolute path for the material type and parent material to load source data and resolving
// relative paths when saving. This will convert and store them as absolute paths for use within the document.
if (!m_materialSourceData.m_parentMaterial.empty())
{
m_materialSourceData.m_parentMaterial =
AZ::RPI::AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_parentMaterial);
}
m_materialAsset = {}; if (!m_materialSourceData.m_materialType.empty())
m_materialInstance = {}; {
m_compilePending = {}; m_materialSourceData.m_materialType =
m_properties.clear(); AZ::RPI::AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_materialType);
m_editorFunctors.clear(); }
m_materialTypeSourceData = AZ::RPI::MaterialTypeSourceData();
m_materialSourceData = AZ::RPI::MaterialSourceData(); // Load the material type source data which provides the layout and default values of all of the properties
m_propertyValuesBeforeEdit.clear(); auto materialTypeOutcome = AZ::RPI::MaterialUtils::LoadMaterialTypeSourceData(m_materialSourceData.m_materialType);
if (!materialTypeOutcome.IsSuccess())
{
AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_materialSourceData.m_materialType.c_str());
return false;
}
m_materialTypeSourceData = materialTypeOutcome.TakeValue();
return true;
}
bool MaterialDocument::LoadMaterialTypeSourceData()
{
// A material document can be created or loaded from material or material type source data. If we are attempting to load
// material type source data then the material source data object can be created just by referencing the document path as the
// material type path.
auto materialTypeOutcome = AZ::RPI::MaterialUtils::LoadMaterialTypeSourceData(m_absolutePath);
if (!materialTypeOutcome.IsSuccess())
{
AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_absolutePath.c_str());
return false;
}
m_materialTypeSourceData = materialTypeOutcome.TakeValue();
// We are storing absolute paths in the loaded version of the source data so that the files can be resolved at all times.
m_materialSourceData.m_materialType = m_absolutePath;
m_materialSourceData.m_parentMaterial.clear();
return true;
} }
void MaterialDocument::RestorePropertyValues(const PropertyValueMap& propertyValues) void MaterialDocument::RestorePropertyValues(const PropertyValueMap& propertyValues)
@ -737,60 +733,186 @@ namespace MaterialEditor
} }
} }
MaterialDocument::EditorMaterialFunctorResult MaterialDocument::RunEditorMaterialFunctors(AZ::RPI::MaterialPropertyFlags dirtyFlags) bool MaterialDocument::AddEditorMaterialFunctors(
const AZStd::vector<AZ::RPI::Ptr<AZ::RPI::MaterialFunctorSourceDataHolder>>& functorSourceDataHolders)
{ {
EditorMaterialFunctorResult result; const AZ::RPI::MaterialFunctorSourceData::EditorContext editorContext = AZ::RPI::MaterialFunctorSourceData::EditorContext(
m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout());
AZStd::unordered_map<AZ::Name, AZ::RPI::MaterialPropertyDynamicMetadata> propertyDynamicMetadata; for (AZ::RPI::Ptr<AZ::RPI::MaterialFunctorSourceDataHolder> functorData : functorSourceDataHolders)
AZStd::unordered_map<AZ::Name, AZ::RPI::MaterialPropertyGroupDynamicMetadata> propertyGroupDynamicMetadata;
for (auto& propertyPair : m_properties)
{
AtomToolsFramework::DynamicProperty& property = propertyPair.second;
AtomToolsFramework::ConvertToPropertyMetaData(propertyDynamicMetadata[property.GetId()], property.GetConfig());
}
for (auto& groupPair : m_propertyGroupVisibility)
{ {
AZ::RPI::MaterialPropertyGroupDynamicMetadata& metadata = propertyGroupDynamicMetadata[AZ::Name{groupPair.first}]; AZ::RPI::MaterialFunctorSourceData::FunctorResult result = functorData->CreateFunctor(editorContext);
bool visible = groupPair.second; if (result.IsSuccess())
metadata.m_visibility = visible ? {
AZ::RPI::MaterialPropertyGroupVisibility::Enabled : AZ::RPI::MaterialPropertyGroupVisibility::Hidden; AZ::RPI::Ptr<AZ::RPI::MaterialFunctor>& functor = result.GetValue();
if (functor != nullptr)
{
m_editorFunctors.push_back(functor);
}
}
else
{
AZ_Error("MaterialDocument", false, "Material functors were not created: '%s'.", m_absolutePath.c_str());
return false;
}
} }
return true;
}
void MaterialDocument::RunEditorMaterialFunctors(AZ::RPI::MaterialPropertyFlags dirtyFlags)
{
AZStd::unordered_map<AZ::Name, AZ::RPI::MaterialPropertyDynamicMetadata> propertyDynamicMetadata;
AZStd::unordered_map<AZ::Name, AZ::RPI::MaterialPropertyGroupDynamicMetadata> propertyGroupDynamicMetadata;
TraverseGroups(m_groups, [&](auto& group) {
AZ::RPI::MaterialPropertyGroupDynamicMetadata& metadata = propertyGroupDynamicMetadata[AZ::Name{ group->m_name }];
metadata.m_visibility = group->m_visible ? AZ::RPI::MaterialPropertyGroupVisibility::Enabled : AZ::RPI::MaterialPropertyGroupVisibility::Hidden;
for (auto& property : group->m_properties)
{
AtomToolsFramework::ConvertToPropertyMetaData(propertyDynamicMetadata[property.GetId()], property.GetConfig());
}
return true;
});
AZStd::unordered_set<AZ::Name> updatedProperties;
AZStd::unordered_set<AZ::Name> updatedPropertyGroups;
for (AZ::RPI::Ptr<AZ::RPI::MaterialFunctor>& functor : m_editorFunctors) for (AZ::RPI::Ptr<AZ::RPI::MaterialFunctor>& functor : m_editorFunctors)
{ {
const AZ::RPI::MaterialPropertyFlags& materialPropertyDependencies = functor->GetMaterialPropertyDependencies(); const AZ::RPI::MaterialPropertyFlags& materialPropertyDependencies = functor->GetMaterialPropertyDependencies();
// None also covers case that the client code doesn't register material properties to dependencies, // None also covers case that the client code doesn't register material properties to dependencies,
// which will later get caught in Process() when trying to access a property. // which will later get caught in Process() when trying to access a property.
if (materialPropertyDependencies.none() || functor->NeedsProcess(dirtyFlags)) if (materialPropertyDependencies.none() || functor->NeedsProcess(dirtyFlags))
{ {
AZ::RPI::MaterialFunctor::EditorContext context = AZ::RPI::MaterialFunctor::EditorContext( AZ::RPI::MaterialFunctor::EditorContext context = AZ::RPI::MaterialFunctor::EditorContext(
m_materialInstance->GetPropertyValues(), m_materialInstance->GetPropertyValues(), m_materialInstance->GetMaterialPropertiesLayout(), propertyDynamicMetadata,
m_materialInstance->GetMaterialPropertiesLayout(), propertyGroupDynamicMetadata, updatedProperties, updatedPropertyGroups,
propertyDynamicMetadata, &materialPropertyDependencies);
propertyGroupDynamicMetadata,
result.m_updatedProperties,
result.m_updatedPropertyGroups,
&materialPropertyDependencies
);
functor->Process(context); functor->Process(context);
} }
} }
for (auto& propertyPair : m_properties) TraverseGroups(m_groups, [&](auto& group) {
bool groupChange = false;
bool groupRebuilt = false;
if (updatedPropertyGroups.find(AZ::Name(group->m_name)) != updatedPropertyGroups.end())
{
AZ::RPI::MaterialPropertyGroupDynamicMetadata& metadata = propertyGroupDynamicMetadata[AZ::Name{ group->m_name }];
group->m_visible = metadata.m_visibility != AZ::RPI::MaterialPropertyGroupVisibility::Hidden;
groupChange = true;
}
for (auto& property : group->m_properties)
{
if (updatedProperties.find(AZ::Name(property.GetId())) != updatedProperties.end())
{
const bool visibleBefore = property.GetConfig().m_visible;
AtomToolsFramework::DynamicPropertyConfig propertyConfig = property.GetConfig();
AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, propertyDynamicMetadata[property.GetId()]);
property.SetConfig(propertyConfig);
groupChange = true;
groupRebuilt |= visibleBefore != property.GetConfig().m_visible;
}
}
if (groupChange || groupRebuilt)
{
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentObjectInfoChanged, m_id,
GetObjectInfoFromDynamicPropertyGroup(group.get()), groupRebuilt);
}
return true;
});
}
AtomToolsFramework::DocumentObjectInfo MaterialDocument::GetObjectInfoFromDynamicPropertyGroup(
const AtomToolsFramework::DynamicPropertyGroup* group) const
{
AtomToolsFramework::DocumentObjectInfo objectInfo;
objectInfo.m_visible = group->m_visible;
objectInfo.m_name = group->m_name;
objectInfo.m_displayName = group->m_displayName;
objectInfo.m_description = group->m_description;
objectInfo.m_objectType = azrtti_typeid<AtomToolsFramework::DynamicPropertyGroup>();
objectInfo.m_objectPtr = const_cast<AtomToolsFramework::DynamicPropertyGroup*>(group);
return objectInfo;
}
bool MaterialDocument::TraverseGroups(
AZStd::vector<AZStd::shared_ptr<AtomToolsFramework::DynamicPropertyGroup>>& groups,
AZStd::function<bool(AZStd::shared_ptr<AtomToolsFramework::DynamicPropertyGroup>&)> callback)
{
if (!callback)
{
return false;
}
for (auto& group : groups)
{
if (!callback(group) || !TraverseGroups(group->m_groups, callback))
{
return false;
}
}
return true;
}
bool MaterialDocument::TraverseGroups(
const AZStd::vector<AZStd::shared_ptr<AtomToolsFramework::DynamicPropertyGroup>>& groups,
AZStd::function<bool(const AZStd::shared_ptr<AtomToolsFramework::DynamicPropertyGroup>&)> callback) const
{
if (!callback)
{ {
AtomToolsFramework::DynamicProperty& property = propertyPair.second; return false;
AtomToolsFramework::DynamicPropertyConfig propertyConfig = property.GetConfig();
AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, propertyDynamicMetadata[property.GetId()]);
property.SetConfig(propertyConfig);
} }
for (auto& updatedPropertyGroup : result.m_updatedPropertyGroups) for (auto& group : groups)
{ {
bool visible = propertyGroupDynamicMetadata[updatedPropertyGroup].m_visibility == AZ::RPI::MaterialPropertyGroupVisibility::Enabled; if (!callback(group) || !TraverseGroups(group->m_groups, callback))
m_propertyGroupVisibility[updatedPropertyGroup] = visible; {
return false;
}
} }
return true;
}
AtomToolsFramework::DynamicProperty* MaterialDocument::FindProperty(const AZ::Name& propertyId)
{
AtomToolsFramework::DynamicProperty* result = nullptr;
TraverseGroups(m_groups, [&](auto& group) {
for (auto& property : group->m_properties)
{
if (property.GetId() == propertyId)
{
result = &property;
return false;
}
}
return true;
});
return result;
}
const AtomToolsFramework::DynamicProperty* MaterialDocument::FindProperty(const AZ::Name& propertyId) const
{
AtomToolsFramework::DynamicProperty* result = nullptr;
TraverseGroups(m_groups, [&](auto& group) {
for (auto& property : group->m_properties)
{
if (property.GetId() == propertyId)
{
result = &property;
return false;
}
}
return true;
});
return result; return result;
} }
} // namespace MaterialEditor } // namespace MaterialEditor

@ -37,10 +37,7 @@ namespace MaterialEditor
virtual ~MaterialDocument(); virtual ~MaterialDocument();
// AtomToolsFramework::AtomToolsDocument overrides... // AtomToolsFramework::AtomToolsDocument overrides...
const AZStd::any& GetPropertyValue(const AZ::Name& propertyId) const override; AZStd::vector<AtomToolsFramework::DocumentObjectInfo> GetObjectInfo() const override;
const AtomToolsFramework::DynamicProperty& GetProperty(const AZ::Name& propertyId) const override;
bool IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const override;
void SetPropertyValue(const AZ::Name& propertyId, const AZStd::any& value) override;
bool Open(AZStd::string_view loadPath) override; bool Open(AZStd::string_view loadPath) override;
bool Save() override; bool Save() override;
bool SaveAsCopy(AZStd::string_view savePath) override; bool SaveAsCopy(AZStd::string_view savePath) override;
@ -56,20 +53,16 @@ namespace MaterialEditor
AZ::Data::Instance<AZ::RPI::Material> GetInstance() const override; AZ::Data::Instance<AZ::RPI::Material> GetInstance() const override;
const AZ::RPI::MaterialSourceData* GetMaterialSourceData() const override; const AZ::RPI::MaterialSourceData* GetMaterialSourceData() const override;
const AZ::RPI::MaterialTypeSourceData* GetMaterialTypeSourceData() const override; const AZ::RPI::MaterialTypeSourceData* GetMaterialTypeSourceData() const override;
void SetPropertyValue(const AZ::Name& propertyId, const AZStd::any& value) override;
const AZStd::any& GetPropertyValue(const AZ::Name& propertyId) const override;
private: private:
// Predicate for evaluating properties // Predicate for evaluating properties
using PropertyFilterFunction = AZStd::function<bool(const AtomToolsFramework::DynamicProperty&)>; using PropertyFilterFunction = AZStd::function<bool(const AtomToolsFramework::DynamicProperty&)>;
// Map of document's properties
using PropertyMap = AZStd::unordered_map<AZ::Name, AtomToolsFramework::DynamicProperty>;
// Map of raw property values for undo/redo comparison and storage // Map of raw property values for undo/redo comparison and storage
using PropertyValueMap = AZStd::unordered_map<AZ::Name, AZStd::any>; using PropertyValueMap = AZStd::unordered_map<AZ::Name, AZStd::any>;
// Map of document's property group visibility flags
using PropertyGroupVisibilityMap = AZStd::unordered_map<AZ::Name, bool>;
// AZ::TickBus overrides... // AZ::TickBus overrides...
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
@ -78,47 +71,60 @@ namespace MaterialEditor
// AtomToolsFramework::AtomToolsDocument overrides... // AtomToolsFramework::AtomToolsDocument overrides...
void Clear() override; void Clear() override;
bool ReopenRecordState() override; bool ReopenRecordState() override;
bool ReopenRestoreState() override; bool ReopenRestoreState() override;
void Recompile(); void Recompile();
bool LoadMaterialSourceData();
bool LoadMaterialTypeSourceData();
void RestorePropertyValues(const PropertyValueMap& propertyValues); void RestorePropertyValues(const PropertyValueMap& propertyValues);
struct EditorMaterialFunctorResult bool AddEditorMaterialFunctors(
{ const AZStd::vector<AZ::RPI::Ptr<AZ::RPI::MaterialFunctorSourceDataHolder>>& functorSourceDataHolders);
AZStd::unordered_set<AZ::Name> m_updatedProperties;
AZStd::unordered_set<AZ::Name> m_updatedPropertyGroups;
};
// Run editor material functor to update editor metadata. // Run editor material functor to update editor metadata.
// @param dirtyFlags indicates which properties have changed, and thus which MaterialFunctors need to be run. // @param dirtyFlags indicates which properties have changed, and thus which MaterialFunctors need to be run.
// @return names for the set of properties and groups that have been changed or need update. void RunEditorMaterialFunctors(AZ::RPI::MaterialPropertyFlags dirtyFlags);
EditorMaterialFunctorResult RunEditorMaterialFunctors(AZ::RPI::MaterialPropertyFlags dirtyFlags);
// Convert a dynamic property group pointer into generic document object info used to populate the inspector
AtomToolsFramework::DocumentObjectInfo GetObjectInfoFromDynamicPropertyGroup(
const AtomToolsFramework::DynamicPropertyGroup* group) const;
// Underlying material asset // In order traversal of dynamic property groups
bool TraverseGroups(
AZStd::vector<AZStd::shared_ptr<AtomToolsFramework::DynamicPropertyGroup>>& groups,
AZStd::function<bool(AZStd::shared_ptr<AtomToolsFramework::DynamicPropertyGroup>&)> callback);
// In order traversal of dynamic property groups
bool TraverseGroups(
const AZStd::vector<AZStd::shared_ptr<AtomToolsFramework::DynamicPropertyGroup>>& groups,
AZStd::function<bool(const AZStd::shared_ptr<AtomToolsFramework::DynamicPropertyGroup>&)> callback) const;
// Traverses dynamic property groups to find a property with a specific ID
AtomToolsFramework::DynamicProperty* FindProperty(const AZ::Name& propertyId);
// Traverses dynamic property groups to find a property with a specific ID
const AtomToolsFramework::DynamicProperty* FindProperty(const AZ::Name& propertyId) const;
// Material asset generated from source data, used to get the final values for properties to be assigned to the document
AZ::Data::Asset<AZ::RPI::MaterialAsset> m_materialAsset; AZ::Data::Asset<AZ::RPI::MaterialAsset> m_materialAsset;
// Material instance being edited // Material instance is only needed to run editor functors and is assigned directly to the viewport model to reflect real time
// changes to material property values
AZ::Data::Instance<AZ::RPI::Material> m_materialInstance; AZ::Data::Instance<AZ::RPI::Material> m_materialInstance;
// If material instance value(s) were modified, do we need to recompile on next tick? // If material instance value(s) were modified, do we need to recompile on next tick?
bool m_compilePending = false; bool m_compilePending = false;
// Collection of all material's properties
PropertyMap m_properties;
// Collection of all material's property groups
PropertyGroupVisibilityMap m_propertyGroupVisibility;
// Material functors that run in editor. See MaterialFunctor.h for details. // Material functors that run in editor. See MaterialFunctor.h for details.
AZStd::vector<AZ::RPI::Ptr<AZ::RPI::MaterialFunctor>> m_editorFunctors; AZStd::vector<AZ::RPI::Ptr<AZ::RPI::MaterialFunctor>> m_editorFunctors;
// Source data for material type // Material type source data used to enumerate all properties and populate the document
AZ::RPI::MaterialTypeSourceData m_materialTypeSourceData; AZ::RPI::MaterialTypeSourceData m_materialTypeSourceData;
// Source data for material // Material source data with property values that override the material type
AZ::RPI::MaterialSourceData m_materialSourceData; AZ::RPI::MaterialSourceData m_materialSourceData;
// State of property values prior to an edit, used for restoration during undo // State of property values prior to an edit, used for restoration during undo
@ -126,5 +132,12 @@ namespace MaterialEditor
// State of property values prior to reopen // State of property values prior to reopen
PropertyValueMap m_propertyValuesBeforeReopen; PropertyValueMap m_propertyValuesBeforeReopen;
// A container of root level dynamic property groups that represents the reflected, editable data within the document.
// These groups will be mapped to document object info so they can populate and be edited directly in the inspector.
AZStd::vector<AZStd::shared_ptr<AtomToolsFramework::DynamicPropertyGroup>> m_groups;
// Dummy default value returned whenever a property cannot be located
AZStd::any m_invalidValue;
}; };
} // namespace MaterialEditor } // namespace MaterialEditor

@ -45,7 +45,14 @@ namespace MaterialEditor
//! Get the internal material type source data //! Get the internal material type source data
virtual const AZ::RPI::MaterialTypeSourceData* GetMaterialTypeSourceData() const = 0; virtual const AZ::RPI::MaterialTypeSourceData* GetMaterialTypeSourceData() const = 0;
};
//! Modify property value
virtual void SetPropertyValue(const AZ::Name& propertyFullName, const AZStd::any& value) = 0;
//! Return property value
//! If the document is not open or the id can't be found, an invalid value is returned instead.
virtual const AZStd::any& GetPropertyValue(const AZ::Name& propertyFullName) const = 0;
};
using MaterialDocumentRequestBus = AZ::EBus<MaterialDocumentRequests>; using MaterialDocumentRequestBus = AZ::EBus<MaterialDocumentRequests>;
} // namespace MaterialEditor } // namespace MaterialEditor

@ -65,7 +65,8 @@ namespace MaterialEditor
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Category, "Editor") ->Attribute(AZ::Script::Attributes::Category, "Editor")
->Attribute(AZ::Script::Attributes::Module, "materialeditor") ->Attribute(AZ::Script::Attributes::Module, "materialeditor")
; ->Event("SetPropertyValue", &MaterialDocumentRequestBus::Events::SetPropertyValue)
->Event("GetPropertyValue", &MaterialDocumentRequestBus::Events::GetPropertyValue);
} }
} }

@ -7,14 +7,10 @@
*/ */
#include <Atom/RPI.Edit/Common/AssetUtils.h> #include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <Atom/RPI.Edit/Material/MaterialPropertyId.h>
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
#include <Atom/RPI.Edit/Material/MaterialUtils.h>
#include <AtomToolsFramework/Document/AtomToolsDocumentRequestBus.h> #include <AtomToolsFramework/Document/AtomToolsDocumentRequestBus.h>
#include <AtomToolsFramework/DynamicProperty/DynamicPropertyGroup.h> #include <AtomToolsFramework/DynamicProperty/DynamicPropertyGroup.h>
#include <AtomToolsFramework/Inspector/InspectorPropertyGroupWidget.h> #include <AtomToolsFramework/Inspector/InspectorPropertyGroupWidget.h>
#include <AtomToolsFramework/Util/MaterialPropertyUtil.h> #include <AtomToolsFramework/Util/MaterialPropertyUtil.h>
#include <Document/MaterialDocumentRequestBus.h>
#include <Window/MaterialInspector/MaterialInspector.h> #include <Window/MaterialInspector/MaterialInspector.h>
namespace MaterialEditor namespace MaterialEditor
@ -38,7 +34,7 @@ namespace MaterialEditor
{ {
m_documentPath.clear(); m_documentPath.clear();
m_documentId = AZ::Uuid::CreateNull(); m_documentId = AZ::Uuid::CreateNull();
m_groups = {}; m_activeProperty = {};
AtomToolsFramework::InspectorRequestBus::Handler::BusDisconnect(); AtomToolsFramework::InspectorRequestBus::Handler::BusDisconnect();
AtomToolsFramework::InspectorWidget::Reset(); AtomToolsFramework::InspectorWidget::Reset();
@ -67,18 +63,31 @@ namespace MaterialEditor
m_documentId = documentId; m_documentId = documentId;
bool isOpen = false; bool isOpen = false;
AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(isOpen, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::IsOpen); AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(
isOpen, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::IsOpen);
AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(m_documentPath, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetAbsolutePath); AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(
m_documentPath, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetAbsolutePath);
if (!m_documentId.IsNull() && isOpen) if (!m_documentId.IsNull() && isOpen)
{ {
// Create the top group for displaying overview info about the material // This will automatically expose all document contents to an inspector with a collapsible group per object. In the case of the
AddOverviewGroup(); // material editor, this will be one inspector group per property group.
// Create groups for displaying editable UV names AZStd::vector<AtomToolsFramework::DocumentObjectInfo> objects;
AddUvNamesGroup(); AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(
// Create groups for displaying editable properties objects, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetObjectInfo);
AddPropertiesGroup();
for (auto& objectInfo : objects)
{
// Passing in same main and comparison instance to enable custom value comparison for highlighting modified properties
auto propertyGroupWidget = new AtomToolsFramework::InspectorPropertyGroupWidget(
objectInfo.m_objectPtr, objectInfo.m_objectPtr, objectInfo.m_objectType, this, this,
GetGroupSaveStateKey(objectInfo.m_name), {},
[this](const auto node) { return GetInstanceNodePropertyIndicator(node); }, 0);
AddGroup(objectInfo.m_name, objectInfo.m_displayName, objectInfo.m_description, propertyGroupWidget);
SetGroupVisible(objectInfo.m_name, objectInfo.m_visible);
}
AtomToolsFramework::InspectorRequestBus::Handler::BusConnect(m_documentId); AtomToolsFramework::InspectorRequestBus::Handler::BusConnect(m_documentId);
} }
@ -106,198 +115,42 @@ namespace MaterialEditor
return ":/Icons/blank.png"; return ":/Icons/blank.png";
} }
void MaterialInspector::AddOverviewGroup() void MaterialInspector::OnDocumentObjectInfoChanged(
{ [[maybe_unused]] const AZ::Uuid& documentId, const AtomToolsFramework::DocumentObjectInfo& objectInfo, bool rebuilt)
const AZ::RPI::MaterialTypeSourceData* materialTypeSourceData = nullptr;
MaterialDocumentRequestBus::EventResult(
materialTypeSourceData, m_documentId, &MaterialDocumentRequestBus::Events::GetMaterialTypeSourceData);
const AZStd::string groupName = "overview";
const AZStd::string groupDisplayName = "Overview";
const AZStd::string groupDescription = materialTypeSourceData->m_description;
auto& group = m_groups[groupName];
AtomToolsFramework::DynamicProperty property;
AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(
property, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetProperty, AZ::Name("overview.materialType"));
group.m_properties.push_back(property);
property = {};
AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(
property, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetProperty, AZ::Name("overview.parentMaterial"));
group.m_properties.push_back(property);
// Passing in same group as main and comparison instance to enable custom value comparison for highlighting modified properties
auto propertyGroupWidget = new AtomToolsFramework::InspectorPropertyGroupWidget(
&group, &group, group.TYPEINFO_Uuid(), this, this, GetGroupSaveStateKey(groupName), {},
[this](const auto node) { return GetInstanceNodePropertyIndicator(node); }, 0);
AddGroup(groupName, groupDisplayName, groupDescription, propertyGroupWidget);
}
void MaterialInspector::AddUvNamesGroup()
{
AZ::Data::Asset<AZ::RPI::MaterialAsset> materialAsset;
MaterialDocumentRequestBus::EventResult(materialAsset, m_documentId, &MaterialDocumentRequestBus::Events::GetAsset);
const AZStd::string groupName = UvGroupName;
const AZStd::string groupDisplayName = "UV Sets";
const AZStd::string groupDescription = "UV set names in this material, which can be renamed to match those in the model.";
auto& group = m_groups[groupName];
const auto& uvNameMap = materialAsset->GetMaterialTypeAsset()->GetUvNameMap();
group.m_properties.reserve(uvNameMap.size());
for (const auto& uvNamePair : uvNameMap)
{
AtomToolsFramework::DynamicProperty property;
AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(
property, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetProperty,
AZ::RPI::MaterialPropertyId(groupName, uvNamePair.m_shaderInput.ToString()));
group.m_properties.push_back(property);
property.SetValue(property.GetConfig().m_parentValue);
}
// Passing in same group as main and comparison instance to enable custom value comparison for highlighting modified properties
auto propertyGroupWidget = new AtomToolsFramework::InspectorPropertyGroupWidget(
&group, &group, group.TYPEINFO_Uuid(), this, this, GetGroupSaveStateKey(groupName), {},
[this](const auto node) { return GetInstanceNodePropertyIndicator(node); }, 0);
AddGroup(groupName, groupDisplayName, groupDescription, propertyGroupWidget);
}
void MaterialInspector::AddPropertiesGroup()
{
const AZ::RPI::MaterialTypeSourceData* materialTypeSourceData = nullptr;
MaterialDocumentRequestBus::EventResult(
materialTypeSourceData, m_documentId, &MaterialDocumentRequestBus::Events::GetMaterialTypeSourceData);
// TODO: Support populating the Material Editor with nested property groups, not just the top level.
for (const AZStd::unique_ptr<AZ::RPI::MaterialTypeSourceData::PropertyGroup>& propertyGroup : materialTypeSourceData->GetPropertyLayout().m_propertyGroups)
{
const AZStd::string& groupName = propertyGroup->GetName();
const AZStd::string& groupDisplayName = !propertyGroup->GetDisplayName().empty() ? propertyGroup->GetDisplayName() : groupName;
const AZStd::string& groupDescription = !propertyGroup->GetDescription().empty() ? propertyGroup->GetDescription() : groupDisplayName;
auto& group = m_groups[groupName];
group.m_properties.reserve(propertyGroup->GetProperties().size());
for (const auto& propertyDefinition : propertyGroup->GetProperties())
{
AtomToolsFramework::DynamicProperty property;
AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(
property, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetProperty,
AZ::RPI::MaterialPropertyId(groupName, propertyDefinition->GetName()));
group.m_properties.push_back(property);
}
// Passing in same group as main and comparison instance to enable custom value comparison for highlighting modified properties
auto propertyGroupWidget = new AtomToolsFramework::InspectorPropertyGroupWidget(
&group, &group, group.TYPEINFO_Uuid(), this, this, GetGroupSaveStateKey(groupName), {},
[this](const auto node) { return GetInstanceNodePropertyIndicator(node); }, 0);
AddGroup(groupName, groupDisplayName, groupDescription, propertyGroupWidget);
bool isGroupVisible = false;
AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(
isGroupVisible, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::IsPropertyGroupVisible, AZ::Name{groupName});
SetGroupVisible(groupName, isGroupVisible);
}
}
void MaterialInspector::OnDocumentPropertyValueModified(const AZ::Uuid& documentId, const AtomToolsFramework::DynamicProperty& property)
{ {
for (auto& groupPair : m_groups) SetGroupVisible(objectInfo.m_name, objectInfo.m_visible);
if (rebuilt)
{ {
for (auto& reflectedProperty : groupPair.second.m_properties) RebuildGroup(objectInfo.m_name);
{
if (reflectedProperty.GetId() == property.GetId())
{
if (!AtomToolsFramework::ArePropertyValuesEqual(reflectedProperty.GetValue(), property.GetValue()))
{
reflectedProperty.SetValue(property.GetValue());
AtomToolsFramework::InspectorRequestBus::Event(
documentId, &AtomToolsFramework::InspectorRequestBus::Events::RefreshGroup, groupPair.first);
}
return;
}
}
} }
} else
void MaterialInspector::OnDocumentPropertyConfigModified(const AZ::Uuid&, const AtomToolsFramework::DynamicProperty& property)
{
for (auto& groupPair : m_groups)
{ {
for (auto& reflectedProperty : groupPair.second.m_properties) RefreshGroup(objectInfo.m_name);
{
if (reflectedProperty.GetId() == property.GetId())
{
// Visibility changes require the entire reflected property editor tree for this group to be rebuilt
if (reflectedProperty.GetVisibility() != property.GetVisibility())
{
reflectedProperty.SetConfig(property.GetConfig());
RebuildGroup(groupPair.first);
}
else
{
reflectedProperty.SetConfig(property.GetConfig());
RefreshGroup(groupPair.first);
}
return;
}
}
} }
} }
void MaterialInspector::OnDocumentPropertyGroupVisibilityChanged(const AZ::Uuid&, const AZ::Name& groupId, bool visible)
{
SetGroupVisible(groupId.GetStringView(), visible);
}
void MaterialInspector::BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode) void MaterialInspector::BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode)
{ {
// For some reason the reflected property editor notifications are not symmetrical // This function is called before every single property change whether it's a button click or dragging a slider. We only want to
// This function is called continuously anytime a property changes until the edit has completed // begin tracking undo state for the first change in the sequence, when the user begins to drag the slider.
// Because of that, we have to track whether or not we are continuing to edit the same property to know when editing has started and
// ended
const AtomToolsFramework::DynamicProperty* property = AtomToolsFramework::FindDynamicPropertyForInstanceDataNode(pNode);
if (property)
{
if (m_activeProperty != property)
{
m_activeProperty = property;
AtomToolsFramework::AtomToolsDocumentRequestBus::Event(m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::BeginEdit);
}
}
}
void MaterialInspector::AfterPropertyModified(AzToolsFramework::InstanceDataNode* pNode)
{
const AtomToolsFramework::DynamicProperty* property = AtomToolsFramework::FindDynamicPropertyForInstanceDataNode(pNode); const AtomToolsFramework::DynamicProperty* property = AtomToolsFramework::FindDynamicPropertyForInstanceDataNode(pNode);
if (property) if (!m_activeProperty && property)
{ {
if (m_activeProperty == property) m_activeProperty = property;
{ AtomToolsFramework::AtomToolsDocumentRequestBus::Event(
AtomToolsFramework::AtomToolsDocumentRequestBus::Event( m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::BeginEdit);
m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::SetPropertyValue, property->GetId(), property->GetValue());
}
} }
} }
void MaterialInspector::SetPropertyEditingComplete(AzToolsFramework::InstanceDataNode* pNode) void MaterialInspector::SetPropertyEditingComplete(AzToolsFramework::InstanceDataNode* pNode)
{ {
// As above, there are symmetrical functions on the notification interface for when editing begins and ends and has been completed // If tracking has started and editing has completed then we can stop tracking undue state for this sequence of changes.
// but they are not being called following that pattern. when this function executes the changes to the property are ready to be
// committed or reverted
const AtomToolsFramework::DynamicProperty* property = AtomToolsFramework::FindDynamicPropertyForInstanceDataNode(pNode); const AtomToolsFramework::DynamicProperty* property = AtomToolsFramework::FindDynamicPropertyForInstanceDataNode(pNode);
if (property) if (m_activeProperty && m_activeProperty == property)
{ {
if (m_activeProperty == property) AtomToolsFramework::AtomToolsDocumentRequestBus::Event(
{ m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::EndEdit);
AtomToolsFramework::AtomToolsDocumentRequestBus::Event( m_activeProperty = {};
m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::SetPropertyValue, property->GetId(), property->GetValue());
AtomToolsFramework::AtomToolsDocumentRequestBus::Event(m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::EndEdit);
m_activeProperty = nullptr;
}
} }
} }
} // namespace MaterialEditor } // namespace MaterialEditor

@ -45,31 +45,25 @@ namespace MaterialEditor
bool IsInstanceNodePropertyModifed(const AzToolsFramework::InstanceDataNode* node) const; bool IsInstanceNodePropertyModifed(const AzToolsFramework::InstanceDataNode* node) const;
const char* GetInstanceNodePropertyIndicator(const AzToolsFramework::InstanceDataNode* node) const; const char* GetInstanceNodePropertyIndicator(const AzToolsFramework::InstanceDataNode* node) const;
void AddOverviewGroup();
void AddUvNamesGroup();
void AddPropertiesGroup();
// AtomToolsDocumentNotificationBus::Handler implementation // AtomToolsDocumentNotificationBus::Handler implementation
void OnDocumentOpened(const AZ::Uuid& documentId) override; void OnDocumentOpened(const AZ::Uuid& documentId) override;
void OnDocumentPropertyValueModified(const AZ::Uuid& documentId, const AtomToolsFramework::DynamicProperty& property) override; void OnDocumentObjectInfoChanged(
void OnDocumentPropertyConfigModified(const AZ::Uuid& documentId, const AtomToolsFramework::DynamicProperty& property) override; const AZ::Uuid& documentId, const AtomToolsFramework::DocumentObjectInfo& objectInfo, bool rebuilt) override;
void OnDocumentPropertyGroupVisibilityChanged(const AZ::Uuid& documentId, const AZ::Name& groupId, bool visible) override;
// AzToolsFramework::IPropertyEditorNotify overrides... // AzToolsFramework::IPropertyEditorNotify overrides...
void BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode) override; void BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode) override;
void AfterPropertyModified(AzToolsFramework::InstanceDataNode* pNode) override; void AfterPropertyModified([[maybe_unused]] AzToolsFramework::InstanceDataNode* pNode) override {}
void SetPropertyEditingActive([[maybe_unused]] AzToolsFramework::InstanceDataNode* pNode) override {} void SetPropertyEditingActive([[maybe_unused]] AzToolsFramework::InstanceDataNode* pNode) override {}
void SetPropertyEditingComplete(AzToolsFramework::InstanceDataNode* pNode) override; void SetPropertyEditingComplete(AzToolsFramework::InstanceDataNode* pNode) override;
void SealUndoStack() override {} void SealUndoStack() override {}
void RequestPropertyContextMenu(AzToolsFramework::InstanceDataNode*, const QPoint&) override {} void RequestPropertyContextMenu([[maybe_unused]] AzToolsFramework::InstanceDataNode* pNode, const QPoint&) override {}
void PropertySelectionChanged(AzToolsFramework::InstanceDataNode*, bool) override {} void PropertySelectionChanged([[maybe_unused]] AzToolsFramework::InstanceDataNode* pNode, bool) override {}
// Tracking the property that is activiley being edited in the inspector // Tracking the property that is activiley being edited in the inspector
const AtomToolsFramework::DynamicProperty* m_activeProperty = nullptr; const AtomToolsFramework::DynamicProperty* m_activeProperty = {};
AZ::Uuid m_documentId = AZ::Uuid::CreateNull(); AZ::Uuid m_documentId = AZ::Uuid::CreateNull();
AZStd::string m_documentPath; AZStd::string m_documentPath;
AZStd::unordered_map<AZStd::string, AtomToolsFramework::DynamicPropertyGroup> m_groups;
AZStd::intrusive_ptr<MaterialEditorWindowSettings> m_windowSettings; AZStd::intrusive_ptr<MaterialEditorWindowSettings> m_windowSettings;
}; };
} // namespace MaterialEditor } // namespace MaterialEditor

@ -78,6 +78,28 @@ namespace ShaderManagementConsole
return m_invalidDescriptor; return m_invalidDescriptor;
} }
AZStd::vector<AtomToolsFramework::DocumentObjectInfo> ShaderManagementConsoleDocument::GetObjectInfo() const
{
if (!IsOpen())
{
AZ_Error("ShaderManagementConsoleDocument", false, "Document is not open.");
return {};
}
AZStd::vector<AtomToolsFramework::DocumentObjectInfo> objects;
AtomToolsFramework::DocumentObjectInfo objectInfo;
objectInfo.m_visible = true;
objectInfo.m_name = "Shader Variant List";
objectInfo.m_displayName = "Shader Variant List";
objectInfo.m_description = "Shader Variant List";
objectInfo.m_objectType = azrtti_typeid<AZ::RPI::ShaderVariantListSourceData>();
objectInfo.m_objectPtr = const_cast<AZ::RPI::ShaderVariantListSourceData*>(&m_shaderVariantListSourceData);
objects.push_back(AZStd::move(objectInfo));
return objects;
}
bool ShaderManagementConsoleDocument::Open(AZStd::string_view loadPath) bool ShaderManagementConsoleDocument::Open(AZStd::string_view loadPath)
{ {
if (!AtomToolsDocument::Open(loadPath)) if (!AtomToolsDocument::Open(loadPath))

@ -32,6 +32,7 @@ namespace ShaderManagementConsole
~ShaderManagementConsoleDocument(); ~ShaderManagementConsoleDocument();
// AtomToolsFramework::AtomToolsDocument overrides... // AtomToolsFramework::AtomToolsDocument overrides...
AZStd::vector<AtomToolsFramework::DocumentObjectInfo> GetObjectInfo() const override;
bool Open(AZStd::string_view loadPath) override; bool Open(AZStd::string_view loadPath) override;
bool Save() override; bool Save() override;
bool SaveAsCopy(AZStd::string_view savePath) override; bool SaveAsCopy(AZStd::string_view savePath) override;

@ -0,0 +1,46 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/std/containers/span.h>
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Matrix3x3.h>
#include <AzCore/RTTI/TypeInfo.h>
//! This file holds useful material related utility functions.
namespace AZ
{
namespace Render
{
enum class TransformType
{
Invalid,
Scale,
Rotate,
Translate
};
struct UvTransformDescriptor
{
Vector2 m_center{ Vector2::CreateZero() };
float m_scale{ 1.0f };
float m_scaleX{ 1.0f };
float m_scaleY{ 1.0f };
float m_translateX{ 0.0f };
float m_translateY{ 0.0f };
float m_rotateDegrees{ 0.0f };
};
// Create a 3x3 uv transform matrix from a set of input properties.
Matrix3x3 CreateUvTransformMatrix(const UvTransformDescriptor& desc, const AZStd::span<const TransformType> transformOrder);
}
AZ_TYPE_INFO_SPECIALIZE(Render::TransformType, "{D8C15D33-CE3D-4297-A646-030B0625BF84}");
}

@ -0,0 +1,62 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Atom/Utils/MaterialUtils.h>
namespace AZ::Render
{
Matrix3x3 CreateUvTransformMatrix(const UvTransformDescriptor& desc, AZStd::span<const TransformType> transformOrder)
{
float translateX = desc.m_translateX;
float translateY = desc.m_translateY;
if (desc.m_scaleX != 0.0f)
{
translateX *= (1.0f / desc.m_scaleX);
}
if (desc.m_scaleY != 0.0f)
{
translateY *= (1.0f / desc.m_scaleY);
}
Matrix3x3 translateCenter2D = Matrix3x3::CreateIdentity();
translateCenter2D.SetBasisZ(-desc.m_center.GetX(), -desc.m_center.GetY(), 1.0f);
Matrix3x3 translateCenterInv2D = Matrix3x3::CreateIdentity();
translateCenterInv2D.SetBasisZ(desc.m_center.GetX(), desc.m_center.GetY(), 1.0f);
Matrix3x3 scale2D = Matrix3x3::CreateDiagonal(AZ::Vector3(desc.m_scaleX * desc.m_scale, desc.m_scaleY * desc.m_scale, 1.0f));
Matrix3x3 translate2D = Matrix3x3::CreateIdentity();
translate2D.SetBasisZ(translateX, translateY, 1.0f);
Matrix3x3 rotate2D = Matrix3x3::CreateRotationZ(AZ::DegToRad(desc.m_rotateDegrees));
Matrix3x3 transform = translateCenter2D;
for (auto transformType : transformOrder)
{
switch (transformType)
{
case TransformType::Scale:
transform = scale2D * transform;
break;
case TransformType::Rotate:
transform = rotate2D * transform;
break;
case TransformType::Translate:
transform = translate2D * transform;
break;
}
}
transform = translateCenterInv2D * transform;
return transform;
}
}

@ -21,6 +21,7 @@ set(FILES
Include/Atom/Utils/ImGuiShaderMetrics.inl Include/Atom/Utils/ImGuiShaderMetrics.inl
Include/Atom/Utils/ImGuiTransientAttachmentProfiler.h Include/Atom/Utils/ImGuiTransientAttachmentProfiler.h
Include/Atom/Utils/ImGuiTransientAttachmentProfiler.inl Include/Atom/Utils/ImGuiTransientAttachmentProfiler.inl
Include/Atom/Utils/MaterialUtils.h
Include/Atom/Utils/PngFile.h Include/Atom/Utils/PngFile.h
Include/Atom/Utils/PpmFile.h Include/Atom/Utils/PpmFile.h
Include/Atom/Utils/StableDynamicArray.h Include/Atom/Utils/StableDynamicArray.h
@ -30,6 +31,7 @@ set(FILES
Include/Atom/Utils/AssetCollectionAsyncLoader.h Include/Atom/Utils/AssetCollectionAsyncLoader.h
Source/DdsFile.cpp Source/DdsFile.cpp
Source/ImageComparison.cpp Source/ImageComparison.cpp
Source/MaterialUtils.cpp
Source/PngFile.cpp Source/PngFile.cpp
Source/PpmFile.cpp Source/PpmFile.cpp
Source/Utils.cpp Source/Utils.cpp

@ -95,7 +95,7 @@ namespace SurfaceData
m_refresh = false; m_refresh = false;
// Update the cached mesh data and bounds, then register the surface data provider // Update the cached mesh data and bounds, then register the surface data provider
AssignSurfaceTagWeights(m_configuration.m_tags, 1.0f, m_newPointWeights); m_newPointWeights.AssignSurfaceTagWeights(m_configuration.m_tags, 1.0f);
UpdateMeshData(); UpdateMeshData();
} }
@ -178,12 +178,7 @@ namespace SurfaceData
AZ::Vector3 hitNormal; AZ::Vector3 hitNormal;
if (DoRayTrace(inPosition, hitPosition, hitNormal)) if (DoRayTrace(inPosition, hitPosition, hitNormal))
{ {
SurfacePoint point; surfacePointList.AddSurfacePoint(GetEntityId(), hitPosition, hitNormal, m_newPointWeights);
point.m_entityId = GetEntityId();
point.m_position = hitPosition;
point.m_normal = hitNormal;
point.m_masks = m_newPointWeights;
surfacePointList.push_back(AZStd::move(point));
} }
} }

@ -103,6 +103,6 @@ namespace SurfaceData
AZ::Transform m_meshWorldTMInverse = AZ::Transform::CreateIdentity(); AZ::Transform m_meshWorldTMInverse = AZ::Transform::CreateIdentity();
AZ::Vector3 m_meshNonUniformScale = AZ::Vector3::CreateOne(); AZ::Vector3 m_meshNonUniformScale = AZ::Vector3::CreateOne();
AZ::Aabb m_meshBounds = AZ::Aabb::CreateNull(); AZ::Aabb m_meshBounds = AZ::Aabb::CreateNull();
SurfaceTagWeightMap m_newPointWeights; SurfaceTagWeights m_newPointWeights;
}; };
} }

@ -109,14 +109,14 @@ namespace GradientSignal
private: private:
static float CalculateAltitudeRatio(const SurfaceData::SurfacePointList& points, float altitudeMin, float altitudeMax) static float CalculateAltitudeRatio(const SurfaceData::SurfacePointList& points, float altitudeMin, float altitudeMax)
{ {
if (points.empty()) if (points.IsEmpty())
{ {
return 0.0f; return 0.0f;
} }
// GetSurfacePoints (which was used to populate the points list) always returns points in decreasing height order, so the // GetSurfacePoints (which was used to populate the points list) always returns points in decreasing height order, so the
// first point in the list contains the highest altitude. // first point in the list contains the highest altitude.
const float highestAltitude = points.front().m_position.GetZ(); const float highestAltitude = points.GetHighestSurfacePoint().m_position.GetZ();
// Turn the absolute altitude value into a 0-1 value by returning the % of the given altitude range that it falls at. // Turn the absolute altitude value into a 0-1 value by returning the % of the given altitude range that it falls at.
return GetRatio(altitudeMin, altitudeMax, highestAltitude); return GetRatio(altitudeMin, altitudeMax, highestAltitude);

@ -85,13 +85,18 @@ namespace GradientSignal
{ {
float result = 0.0f; float result = 0.0f;
for (const auto& point : points) points.EnumeratePoints([&result](
[[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& normal,
const SurfaceData::SurfaceTagWeights& masks) -> bool
{ {
for (const auto& [maskId, weight] : point.m_masks) masks.EnumerateWeights(
{ [&result]([[maybe_unused]] AZ::Crc32 surfaceType, float weight) -> bool
result = AZ::GetMax(AZ::GetClamp(weight, 0.0f, 1.0f), result); {
} result = AZ::GetMax(AZ::GetClamp(weight, 0.0f, 1.0f), result);
} return true;
});
return true;
});
return result; return result;
} }

@ -125,7 +125,7 @@ namespace GradientSignal
private: private:
float GetSlopeRatio(const SurfaceData::SurfacePointList& points, float angleMin, float angleMax) const float GetSlopeRatio(const SurfaceData::SurfacePointList& points, float angleMin, float angleMax) const
{ {
if (points.empty()) if (points.IsEmpty())
{ {
return 0.0f; return 0.0f;
} }
@ -133,9 +133,9 @@ namespace GradientSignal
// Assuming our surface normal vector is actually normalized, we can get the slope // Assuming our surface normal vector is actually normalized, we can get the slope
// by just grabbing the Z value. It's the same thing as normal.Dot(AZ::Vector3::CreateAxisZ()). // by just grabbing the Z value. It's the same thing as normal.Dot(AZ::Vector3::CreateAxisZ()).
AZ_Assert( AZ_Assert(
points.front().m_normal.GetNormalized().IsClose(points.front().m_normal), points.GetHighestSurfacePoint().m_normal.GetNormalized().IsClose(points.GetHighestSurfacePoint().m_normal),
"Surface normals are expected to be normalized"); "Surface normals are expected to be normalized");
const float slope = points.front().m_normal.GetZ(); const float slope = points.GetHighestSurfacePoint().m_normal.GetZ();
// Convert slope back to an angle so that we can lerp in "angular space", not "slope value space". // Convert slope back to an angle so that we can lerp in "angular space", not "slope value space".
// (We want our 0-1 range to be linear across the range of angles) // (We want our 0-1 range to be linear across the range of angles)
const float slopeAngle = acosf(slope); const float slopeAngle = acosf(slope);

@ -224,10 +224,9 @@ namespace GradientSignal
validShapeBounds = m_cachedShapeConstraintBounds.IsValid(); validShapeBounds = m_cachedShapeConstraintBounds.IsValid();
} }
const AZ::EntityId entityId = GetEntityId(); surfacePointList.ModifySurfaceWeights(
for (auto& point : surfacePointList) GetEntityId(),
{ [this, validShapeBounds, shapeConstraintBounds](const AZ::Vector3& position, SurfaceData::SurfaceTagWeights& weights)
if (point.m_entityId != entityId)
{ {
bool inBounds = true; bool inBounds = true;
@ -236,28 +235,26 @@ namespace GradientSignal
if (validShapeBounds) if (validShapeBounds)
{ {
inBounds = false; inBounds = false;
if (shapeConstraintBounds.Contains(point.m_position)) if (shapeConstraintBounds.Contains(position))
{ {
LmbrCentral::ShapeComponentRequestsBus::EventResult(inBounds, m_configuration.m_shapeConstraintEntityId, LmbrCentral::ShapeComponentRequestsBus::EventResult(
&LmbrCentral::ShapeComponentRequestsBus::Events::IsPointInside, point.m_position); inBounds, m_configuration.m_shapeConstraintEntityId,
&LmbrCentral::ShapeComponentRequestsBus::Events::IsPointInside, position);
} }
} }
// If the point is within our allowed shape bounds, verify that it meets the gradient thresholds. // If the point is within our allowed shape bounds, verify that it meets the gradient thresholds.
// If so, then add the value to the surface tags. // If so, then return the value to add to the surface tags.
if (inBounds) if (inBounds)
{ {
const GradientSampleParams sampleParams = { point.m_position }; const GradientSampleParams sampleParams = { position };
const float value = m_gradientSampler.GetValue(sampleParams); const float value = m_gradientSampler.GetValue(sampleParams);
if (value >= m_configuration.m_thresholdMin && if (value >= m_configuration.m_thresholdMin && value <= m_configuration.m_thresholdMax)
value <= m_configuration.m_thresholdMax)
{ {
SurfaceData::AddMaxValueForMasks(point.m_masks, m_configuration.m_modifierTags, value); weights.AddSurfaceWeightsIfGreater(m_configuration.m_modifierTags, value);
} }
} }
});
}
}
} }
} }

@ -234,7 +234,7 @@ namespace GradientSignal
// For each position, call GetSurfacePoints() and turn the height into a 0-1 value based on our min/max altitudes. // For each position, call GetSurfacePoints() and turn the height into a 0-1 value based on our min/max altitudes.
for (size_t index = 0; index < positions.size(); index++) for (size_t index = 0; index < positions.size(); index++)
{ {
points.clear(); points.Clear();
surfaceDataRequests->GetSurfacePoints(positions[index], m_configuration.m_surfaceTagsToSample, points); surfaceDataRequests->GetSurfacePoints(positions[index], m_configuration.m_surfaceTagsToSample, points);
outValues[index] = CalculateAltitudeRatio(points, m_configuration.m_altitudeMin, m_configuration.m_altitudeMax); outValues[index] = CalculateAltitudeRatio(points, m_configuration.m_altitudeMin, m_configuration.m_altitudeMax);
} }

@ -198,7 +198,7 @@ namespace GradientSignal
for (size_t index = 0; index < positions.size(); index++) for (size_t index = 0; index < positions.size(); index++)
{ {
points.clear(); points.Clear();
surfaceDataRequests->GetSurfacePoints(positions[index], m_configuration.m_surfaceTagList, points); surfaceDataRequests->GetSurfacePoints(positions[index], m_configuration.m_surfaceTagList, points);
outValues[index] = GetMaxSurfaceWeight(points); outValues[index] = GetMaxSurfaceWeight(points);
} }

@ -239,7 +239,7 @@ namespace GradientSignal
for (size_t index = 0; index < positions.size(); index++) for (size_t index = 0; index < positions.size(); index++)
{ {
points.clear(); points.Clear();
surfaceDataRequests->GetSurfacePoints(positions[index], m_configuration.m_surfaceTagsToSample, points); surfaceDataRequests->GetSurfacePoints(positions[index], m_configuration.m_surfaceTagsToSample, points);
outValues[index] = GetSlopeRatio(points, angleMin, angleMax); outValues[index] = GetSlopeRatio(points, angleMin, angleMax);
} }

@ -98,10 +98,9 @@ namespace GradientSignal
return [this]([[maybe_unused]] float sampleValue, const GradientSampleParams& params) return [this]([[maybe_unused]] float sampleValue, const GradientSampleParams& params)
{ {
// Create a fake surface point with the position we're sampling. // Create a fake surface point with the position we're sampling.
SurfaceData::SurfacePoint point; AzFramework::SurfaceData::SurfacePoint point;
point.m_position = params.m_position; point.m_position = params.m_position;
SurfaceData::SurfacePointList pointList; SurfaceData::SurfacePointList pointList = { { point } };
pointList.emplace_back(point);
// Send it into the component, see what emerges // Send it into the component, see what emerges
m_component.ModifySurfacePoints(pointList); m_component.ModifySurfacePoints(pointList);
@ -110,10 +109,18 @@ namespace GradientSignal
// Technically, they should all have the same value, but we'll grab the max from all of them in case // Technically, they should all have the same value, but we'll grab the max from all of them in case
// the underlying logic ever changes to allow separate ranges per tag. // the underlying logic ever changes to allow separate ranges per tag.
float result = 0.0f; float result = 0.0f;
for (auto& mask : pointList[0].m_masks) pointList.EnumeratePoints([&result](
[[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& normal,
const SurfaceData::SurfaceTagWeights& masks) -> bool
{ {
result = AZ::GetMax(result, mask.second); masks.EnumerateWeights(
} [&result]([[maybe_unused]] AZ::Crc32 surfaceType, float weight) -> bool
{
result = AZ::GetMax(result, weight);
return true;
});
return true;
});
return result; return result;
}; };
} }

@ -66,7 +66,7 @@ namespace UnitTest
float falloffMidpoint, float falloffRange, float falloffStrength) float falloffMidpoint, float falloffRange, float falloffStrength)
{ {
MockSurfaceDataSystem mockSurfaceDataSystem; MockSurfaceDataSystem mockSurfaceDataSystem;
SurfaceData::SurfacePoint point; AzFramework::SurfaceData::SurfacePoint point;
// Fill our mock surface with the correct normal value for each point based on our test angle set. // Fill our mock surface with the correct normal value for each point based on our test angle set.
for (int y = 0; y < dataSize; y++) for (int y = 0; y < dataSize; y++)
@ -539,10 +539,10 @@ namespace UnitTest
// Set a different altitude for each point we're going to test. We'll use 0, 2, 5, 10 to test various points along the range. // Set a different altitude for each point we're going to test. We'll use 0, 2, 5, 10 to test various points along the range.
MockSurfaceDataSystem mockSurfaceDataSystem; MockSurfaceDataSystem mockSurfaceDataSystem;
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 0.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3::CreateZero() } }; mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3::CreateZero() } };
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 0.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 2.0f), AZ::Vector3::CreateZero() } }; mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, 2.0f), AZ::Vector3::CreateZero() } };
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 1.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 5.0f), AZ::Vector3::CreateZero() } }; mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 5.0f), AZ::Vector3::CreateZero() } };
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 1.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 10.0f), AZ::Vector3::CreateZero() } }; mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 10.0f), AZ::Vector3::CreateZero() } };
// We set the min/max to values other than 0-10 to help validate that they aren't used in the case of the pinned shape. // We set the min/max to values other than 0-10 to help validate that they aren't used in the case of the pinned shape.
GradientSignal::SurfaceAltitudeGradientConfig config; GradientSignal::SurfaceAltitudeGradientConfig config;
@ -573,10 +573,10 @@ namespace UnitTest
// Set a different altitude for each point we're going to test. We'll use 0, 2, 5, 10 to test various points along the range. // Set a different altitude for each point we're going to test. We'll use 0, 2, 5, 10 to test various points along the range.
MockSurfaceDataSystem mockSurfaceDataSystem; MockSurfaceDataSystem mockSurfaceDataSystem;
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 0.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3::CreateZero() } }; mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3::CreateZero() } };
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 0.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 2.0f), AZ::Vector3::CreateZero() } }; mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, 2.0f), AZ::Vector3::CreateZero() } };
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 1.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 5.0f), AZ::Vector3::CreateZero() } }; mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 5.0f), AZ::Vector3::CreateZero() } };
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 1.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 10.0f), AZ::Vector3::CreateZero() } }; mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 10.0f), AZ::Vector3::CreateZero() } };
// We set the min/max to 0-10, but don't set a shape. // We set the min/max to 0-10, but don't set a shape.
GradientSignal::SurfaceAltitudeGradientConfig config; GradientSignal::SurfaceAltitudeGradientConfig config;
@ -634,13 +634,13 @@ namespace UnitTest
MockSurfaceDataSystem mockSurfaceDataSystem; MockSurfaceDataSystem mockSurfaceDataSystem;
// Altitude value below min - should result in 0.0f. // Altitude value below min - should result in 0.0f.
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 0.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, -10.0f), AZ::Vector3::CreateZero() } }; mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, -10.0f), AZ::Vector3::CreateZero() } };
// Altitude value at exactly min - should result in 0.0f. // Altitude value at exactly min - should result in 0.0f.
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 0.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, -5.0f), AZ::Vector3::CreateZero() } }; mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, -5.0f), AZ::Vector3::CreateZero() } };
// Altitude value at exactly max - should result in 1.0f. // Altitude value at exactly max - should result in 1.0f.
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 1.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 15.0f), AZ::Vector3::CreateZero() } }; mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 15.0f), AZ::Vector3::CreateZero() } };
// Altitude value above max - should result in 1.0f. // Altitude value above max - should result in 1.0f.
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 1.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 20.0f), AZ::Vector3::CreateZero() } }; mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 20.0f), AZ::Vector3::CreateZero() } };
// We set the min/max to -5 - 15. By using a range without 0 at either end, and not having 0 as the midpoint, // We set the min/max to -5 - 15. By using a range without 0 at either end, and not having 0 as the midpoint,
// it should be easier to verify that we're successfully clamping to 0 and 1. // it should be easier to verify that we're successfully clamping to 0 and 1.
@ -668,14 +668,15 @@ namespace UnitTest
}; };
MockSurfaceDataSystem mockSurfaceDataSystem; MockSurfaceDataSystem mockSurfaceDataSystem;
SurfaceData::SurfacePoint point; AzFramework::SurfaceData::SurfacePoint point;
// Fill our mock surface with the test_mask set and the expected gradient value at each point. // Fill our mock surface with the test_mask set and the expected gradient value at each point.
for (int y = 0; y < dataSize; y++) for (int y = 0; y < dataSize; y++)
{ {
for (int x = 0; x < dataSize; x++) for (int x = 0; x < dataSize; x++)
{ {
point.m_masks[AZ_CRC("test_mask", 0x7a16e9ff)] = expectedOutput[(y * dataSize) + x]; point.m_surfaceTags.clear();
point.m_surfaceTags.emplace_back(AZ_CRC_CE("test_mask"), expectedOutput[(y * dataSize) + x]);
mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(static_cast<float>(x), static_cast<float>(y))] = { { point } }; mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(static_cast<float>(x), static_cast<float>(y))] = { { point } };
} }
} }

@ -19,27 +19,53 @@ namespace UnitTest
struct GradientSignalSurfaceTestsFixture struct GradientSignalSurfaceTestsFixture
: public GradientSignalTest : public GradientSignalTest
{ {
void SetSurfacePoint(SurfaceData::SurfacePoint& point, AZ::EntityId id, AZ::Vector3 position, AZ::Vector3 normal, AZStd::vector<AZStd::pair<AZStd::string, float>> tags) void SetSurfacePoint(AzFramework::SurfaceData::SurfacePoint& point, AZ::Vector3 position, AZ::Vector3 normal, AZStd::vector<AZStd::pair<AZStd::string, float>> tags)
{ {
point.m_entityId = id;
point.m_position = position; point.m_position = position;
point.m_normal = normal; point.m_normal = normal;
for (auto& tag : tags) for (auto& tag : tags)
{ {
point.m_masks[SurfaceData::SurfaceTag(tag.first)] = tag.second; point.m_surfaceTags.emplace_back(SurfaceData::SurfaceTag(tag.first), tag.second);
} }
} }
bool SurfacePointsAreEqual(const SurfaceData::SurfacePoint& lhs, const SurfaceData::SurfacePoint& rhs) bool SurfacePointsAreEqual(const AzFramework::SurfaceData::SurfacePoint& lhs, const AzFramework::SurfaceData::SurfacePoint& rhs)
{ {
return (lhs.m_entityId == rhs.m_entityId) if ((lhs.m_position != rhs.m_position) || (lhs.m_normal != rhs.m_normal)
&& (lhs.m_position == rhs.m_position) || (lhs.m_surfaceTags.size() != rhs.m_surfaceTags.size()))
&& (lhs.m_normal == rhs.m_normal) {
&& (lhs.m_masks == rhs.m_masks); return false;
}
for (auto& mask : lhs.m_surfaceTags)
{
auto maskEntry = AZStd::find_if(
rhs.m_surfaceTags.begin(), rhs.m_surfaceTags.end(),
[mask](const AzFramework::SurfaceData::SurfaceTagWeight& weight) -> bool
{
return (mask.m_surfaceType == weight.m_surfaceType) && (mask.m_weight == weight.m_weight);
});
if (maskEntry == rhs.m_surfaceTags.end())
{
return false;
}
}
return true;
}
bool SurfacePointsAreEqual(
const AZ::Vector3& lhsPosition, const AZ::Vector3& lhsNormal, const SurfaceData::SurfaceTagWeights& lhsWeights,
const AzFramework::SurfaceData::SurfacePoint& rhs)
{
return ((lhsPosition == rhs.m_position)
&& (lhsNormal == rhs.m_normal)
&& (lhsWeights.SurfaceWeightsAreEqual(rhs.m_surfaceTags)));
} }
void TestGradientSurfaceDataComponent(float gradientValue, float thresholdMin, float thresholdMax, AZStd::vector<AZStd::string> tags, bool usesShape, void TestGradientSurfaceDataComponent(float gradientValue, float thresholdMin, float thresholdMax, AZStd::vector<AZStd::string> tags, bool usesShape,
const SurfaceData::SurfacePoint& input, const SurfaceData::SurfacePoint& expectedOutput) const AzFramework::SurfaceData::SurfacePoint& input,
const AzFramework::SurfaceData::SurfacePoint& expectedOutput)
{ {
// This lets our component register with surfaceData successfully. // This lets our component register with surfaceData successfully.
MockSurfaceDataSystem mockSurfaceDataSystem; MockSurfaceDataSystem mockSurfaceDataSystem;
@ -83,10 +109,17 @@ namespace UnitTest
EXPECT_TRUE(modifierHandle != SurfaceData::InvalidSurfaceDataRegistryHandle); EXPECT_TRUE(modifierHandle != SurfaceData::InvalidSurfaceDataRegistryHandle);
// Call ModifySurfacePoints and verify the results // Call ModifySurfacePoints and verify the results
SurfaceData::SurfacePointList pointList; SurfaceData::SurfacePointList pointList = { { input } };
pointList.emplace_back(input);
SurfaceData::SurfaceDataModifierRequestBus::Event(modifierHandle, &SurfaceData::SurfaceDataModifierRequestBus::Events::ModifySurfacePoints, pointList); SurfaceData::SurfaceDataModifierRequestBus::Event(modifierHandle, &SurfaceData::SurfaceDataModifierRequestBus::Events::ModifySurfacePoints, pointList);
EXPECT_TRUE(SurfacePointsAreEqual(pointList[0],expectedOutput)); ASSERT_EQ(pointList.GetSize(), 1);
pointList.EnumeratePoints(
[this, expectedOutput](
const AZ::Vector3& position, const AZ::Vector3& normal,
const SurfaceData::SurfaceTagWeights& masks)
{
EXPECT_TRUE(SurfacePointsAreEqual(position, normal, masks, expectedOutput));
return true;
});
} }
@ -97,17 +130,17 @@ namespace UnitTest
// Verify that for a gradient value within the threshold, the output point contains the // Verify that for a gradient value within the threshold, the output point contains the
// correct tag and gradient value. // correct tag and gradient value.
SurfaceData::SurfacePoint input; AzFramework::SurfaceData::SurfacePoint input;
SurfaceData::SurfacePoint expectedOutput; AzFramework::SurfaceData::SurfacePoint expectedOutput;
const char* tag = "test_mask"; const char* tag = "test_mask";
// Select a gradient value within the threshold range below // Select a gradient value within the threshold range below
float gradientValue = 0.5f; float gradientValue = 0.5f;
// Set arbitrary input data // Set arbitrary input data
SetSurfacePoint(input, AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), {}); SetSurfacePoint(input, AZ::Vector3(1.0f), AZ::Vector3(0.0f), {});
// Output should match the input, but with an added tag / value // Output should match the input, but with an added tag / value
SetSurfacePoint(expectedOutput, input.m_entityId, input.m_position, input.m_normal, { AZStd::make_pair<AZStd::string, float>(tag, gradientValue) }); SetSurfacePoint(expectedOutput, input.m_position, input.m_normal, { AZStd::make_pair<AZStd::string, float>(tag, gradientValue) });
TestGradientSurfaceDataComponent( TestGradientSurfaceDataComponent(
gradientValue, // constant gradient value gradientValue, // constant gradient value
@ -123,17 +156,17 @@ namespace UnitTest
{ {
// Verify that for a gradient value outside the threshold, the output point contains no tags / values. // Verify that for a gradient value outside the threshold, the output point contains no tags / values.
SurfaceData::SurfacePoint input; AzFramework::SurfaceData::SurfacePoint input;
SurfaceData::SurfacePoint expectedOutput; AzFramework::SurfaceData::SurfacePoint expectedOutput;
const char* tag = "test_mask"; const char* tag = "test_mask";
// Choose a value outside the threshold range // Choose a value outside the threshold range
float gradientValue = 0.05f; float gradientValue = 0.05f;
// Set arbitrary input data // Set arbitrary input data
SetSurfacePoint(input, AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), {}); SetSurfacePoint(input, AZ::Vector3(1.0f), AZ::Vector3(0.0f), {});
// Output should match the input - no extra tags / values should be added. // Output should match the input - no extra tags / values should be added.
SetSurfacePoint(expectedOutput, input.m_entityId, input.m_position, input.m_normal, {}); SetSurfacePoint(expectedOutput, input.m_position, input.m_normal, {});
TestGradientSurfaceDataComponent( TestGradientSurfaceDataComponent(
gradientValue, // constant gradient value gradientValue, // constant gradient value
@ -149,8 +182,8 @@ namespace UnitTest
{ {
// Verify that if the component has multiple tags, all of them get put on the output with the same gradient value. // Verify that if the component has multiple tags, all of them get put on the output with the same gradient value.
SurfaceData::SurfacePoint input; AzFramework::SurfaceData::SurfacePoint input;
SurfaceData::SurfacePoint expectedOutput; AzFramework::SurfaceData::SurfacePoint expectedOutput;
const char* tag1 = "test_mask1"; const char* tag1 = "test_mask1";
const char* tag2 = "test_mask2"; const char* tag2 = "test_mask2";
@ -158,9 +191,9 @@ namespace UnitTest
float gradientValue = 0.5f; float gradientValue = 0.5f;
// Set arbitrary input data // Set arbitrary input data
SetSurfacePoint(input, AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), {}); SetSurfacePoint(input, AZ::Vector3(1.0f), AZ::Vector3(0.0f), {});
// Output should match the input, but with two added tags // Output should match the input, but with two added tags
SetSurfacePoint(expectedOutput, input.m_entityId, input.m_position, input.m_normal, SetSurfacePoint(expectedOutput, input.m_position, input.m_normal,
{ AZStd::make_pair<AZStd::string, float>(tag1, gradientValue), AZStd::make_pair<AZStd::string, float>(tag2, gradientValue) }); { AZStd::make_pair<AZStd::string, float>(tag1, gradientValue), AZStd::make_pair<AZStd::string, float>(tag2, gradientValue) });
TestGradientSurfaceDataComponent( TestGradientSurfaceDataComponent(
@ -178,8 +211,8 @@ namespace UnitTest
// Verify that the output contains input tags that are NOT on the modification list and adds any // Verify that the output contains input tags that are NOT on the modification list and adds any
// new tags that weren't in the input // new tags that weren't in the input
SurfaceData::SurfacePoint input; AzFramework::SurfaceData::SurfacePoint input;
SurfaceData::SurfacePoint expectedOutput; AzFramework::SurfaceData::SurfacePoint expectedOutput;
const char* preservedTag = "preserved_tag"; const char* preservedTag = "preserved_tag";
const char* modifierTag = "modifier_tag"; const char* modifierTag = "modifier_tag";
@ -187,9 +220,9 @@ namespace UnitTest
float gradientValue = 0.5f; float gradientValue = 0.5f;
// Set arbitrary input data // Set arbitrary input data
SetSurfacePoint(input, AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::make_pair<AZStd::string, float>(preservedTag, 1.0f) }); SetSurfacePoint(input, AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::make_pair<AZStd::string, float>(preservedTag, 1.0f) });
// Output should match the input, but with two added tags // Output should match the input, but with two added tags
SetSurfacePoint(expectedOutput, input.m_entityId, input.m_position, input.m_normal, SetSurfacePoint(expectedOutput, input.m_position, input.m_normal,
{ AZStd::make_pair<AZStd::string, float>(preservedTag, 1.0f), AZStd::make_pair<AZStd::string, float>(modifierTag, gradientValue) }); { AZStd::make_pair<AZStd::string, float>(preservedTag, 1.0f), AZStd::make_pair<AZStd::string, float>(modifierTag, gradientValue) });
TestGradientSurfaceDataComponent( TestGradientSurfaceDataComponent(
@ -206,8 +239,8 @@ namespace UnitTest
{ {
// Verify that if the input has a higher value on the tag than the modifier, it keeps the higher value. // Verify that if the input has a higher value on the tag than the modifier, it keeps the higher value.
SurfaceData::SurfacePoint input; AzFramework::SurfaceData::SurfacePoint input;
SurfaceData::SurfacePoint expectedOutput; AzFramework::SurfaceData::SurfacePoint expectedOutput;
const char* tag = "test_mask"; const char* tag = "test_mask";
// Select a gradient value within the threshold range below // Select a gradient value within the threshold range below
@ -216,9 +249,9 @@ namespace UnitTest
float inputValue = 0.75f; float inputValue = 0.75f;
// Set arbitrary input data // Set arbitrary input data
SetSurfacePoint(input, AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::make_pair<AZStd::string, float>(tag, inputValue) }); SetSurfacePoint(input, AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::make_pair<AZStd::string, float>(tag, inputValue) });
// Output should match the input - the higher input value on the tag is preserved // Output should match the input - the higher input value on the tag is preserved
SetSurfacePoint(expectedOutput, input.m_entityId, input.m_position, input.m_normal, SetSurfacePoint(expectedOutput, input.m_position, input.m_normal,
{ AZStd::make_pair<AZStd::string, float>(tag, inputValue) }); { AZStd::make_pair<AZStd::string, float>(tag, inputValue) });
TestGradientSurfaceDataComponent( TestGradientSurfaceDataComponent(
@ -235,8 +268,8 @@ namespace UnitTest
{ {
// Verify that if the input has a lower value on the tag than the modifier, it keeps the higher value. // Verify that if the input has a lower value on the tag than the modifier, it keeps the higher value.
SurfaceData::SurfacePoint input; AzFramework::SurfaceData::SurfacePoint input;
SurfaceData::SurfacePoint expectedOutput; AzFramework::SurfaceData::SurfacePoint expectedOutput;
const char* tag = "test_mask"; const char* tag = "test_mask";
// Select a gradient value within the threshold range below // Select a gradient value within the threshold range below
@ -245,9 +278,9 @@ namespace UnitTest
float inputValue = 0.25f; float inputValue = 0.25f;
// Set arbitrary input data // Set arbitrary input data
SetSurfacePoint(input, AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::make_pair<AZStd::string, float>(tag, inputValue) }); SetSurfacePoint(input, AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::make_pair<AZStd::string, float>(tag, inputValue) });
// Output should match the input, except that the value on the tag gets the higher modifier value // Output should match the input, except that the value on the tag gets the higher modifier value
SetSurfacePoint(expectedOutput, input.m_entityId, input.m_position, input.m_normal, SetSurfacePoint(expectedOutput, input.m_position, input.m_normal,
{ AZStd::make_pair<AZStd::string, float>(tag, gradientValue) }); { AZStd::make_pair<AZStd::string, float>(tag, gradientValue) });
TestGradientSurfaceDataComponent( TestGradientSurfaceDataComponent(
@ -264,17 +297,17 @@ namespace UnitTest
{ {
// Verify that if no shape has been added, the component modifies points in unbounded space // Verify that if no shape has been added, the component modifies points in unbounded space
SurfaceData::SurfacePoint input; AzFramework::SurfaceData::SurfacePoint input;
SurfaceData::SurfacePoint expectedOutput; AzFramework::SurfaceData::SurfacePoint expectedOutput;
const char* tag = "test_mask"; const char* tag = "test_mask";
// Select a gradient value within the threshold range below // Select a gradient value within the threshold range below
float gradientValue = 0.5f; float gradientValue = 0.5f;
// Set arbitrary input data, but with a point that's extremely far away in space // Set arbitrary input data, but with a point that's extremely far away in space
SetSurfacePoint(input, AZ::EntityId(0x12345678), AZ::Vector3(-100000000.0f), AZ::Vector3(0.0f), {}); SetSurfacePoint(input, AZ::Vector3(-100000000.0f), AZ::Vector3(0.0f), {});
// Output should match the input but with the tag added, even though the point was far away. // Output should match the input but with the tag added, even though the point was far away.
SetSurfacePoint(expectedOutput, input.m_entityId, input.m_position, input.m_normal, SetSurfacePoint(expectedOutput, input.m_position, input.m_normal,
{ AZStd::make_pair<AZStd::string, float>(tag, gradientValue) }); { AZStd::make_pair<AZStd::string, float>(tag, gradientValue) });
TestGradientSurfaceDataComponent( TestGradientSurfaceDataComponent(
@ -292,17 +325,17 @@ namespace UnitTest
// Verify that if a shape constraint is added, points within the shape are still modified. // Verify that if a shape constraint is added, points within the shape are still modified.
// Our default mock shape is a cube that exists from -0.5 to 0.5 in space. // Our default mock shape is a cube that exists from -0.5 to 0.5 in space.
SurfaceData::SurfacePoint input; AzFramework::SurfaceData::SurfacePoint input;
SurfaceData::SurfacePoint expectedOutput; AzFramework::SurfaceData::SurfacePoint expectedOutput;
const char* tag = "test_mask"; const char* tag = "test_mask";
// Select a gradient value within the threshold range below // Select a gradient value within the threshold range below
float gradientValue = 0.5f; float gradientValue = 0.5f;
// Set arbitrary input data, but with a point that's within the mock shape cube (0.25 vs -0.5 to 0.5) // Set arbitrary input data, but with a point that's within the mock shape cube (0.25 vs -0.5 to 0.5)
SetSurfacePoint(input, AZ::EntityId(0x12345678), AZ::Vector3(0.25f), AZ::Vector3(0.0f), {}); SetSurfacePoint(input, AZ::Vector3(0.25f), AZ::Vector3(0.0f), {});
// Output should match the input but with the tag added, since the point is within the shape constraint. // Output should match the input but with the tag added, since the point is within the shape constraint.
SetSurfacePoint(expectedOutput, input.m_entityId, input.m_position, input.m_normal, SetSurfacePoint(expectedOutput, input.m_position, input.m_normal,
{ AZStd::make_pair<AZStd::string, float>(tag, gradientValue) }); { AZStd::make_pair<AZStd::string, float>(tag, gradientValue) });
TestGradientSurfaceDataComponent( TestGradientSurfaceDataComponent(
@ -320,17 +353,17 @@ namespace UnitTest
// Verify that if a shape constraint is added, points outside the shape are not modified. // Verify that if a shape constraint is added, points outside the shape are not modified.
// Our default mock shape is a cube that exists from -0.5 to 0.5 in space. // Our default mock shape is a cube that exists from -0.5 to 0.5 in space.
SurfaceData::SurfacePoint input; AzFramework::SurfaceData::SurfacePoint input;
SurfaceData::SurfacePoint expectedOutput; AzFramework::SurfaceData::SurfacePoint expectedOutput;
const char* tag = "test_mask"; const char* tag = "test_mask";
// Select a gradient value within the threshold range below // Select a gradient value within the threshold range below
float gradientValue = 0.5f; float gradientValue = 0.5f;
// Set arbitrary input data, but with a point that's outside the mock shape cube (10.0 vs -0.5 to 0.5) // Set arbitrary input data, but with a point that's outside the mock shape cube (10.0 vs -0.5 to 0.5)
SetSurfacePoint(input, AZ::EntityId(0x12345678), AZ::Vector3(10.0f), AZ::Vector3(0.0f), {}); SetSurfacePoint(input, AZ::Vector3(10.0f), AZ::Vector3(0.0f), {});
// Output should match the input with no tag added, since the point is outside the shape constraint // Output should match the input with no tag added, since the point is outside the shape constraint
SetSurfacePoint(expectedOutput, input.m_entityId, input.m_position, input.m_normal, {}); SetSurfacePoint(expectedOutput, input.m_position, input.m_normal, {});
TestGradientSurfaceDataComponent( TestGradientSurfaceDataComponent(
gradientValue, // constant gradient value gradientValue, // constant gradient value

@ -84,7 +84,7 @@ namespace UnitTest
AZStd::unique_ptr<MockSurfaceDataSystem> GradientSignalBaseFixture::CreateMockSurfaceDataSystem(const AZ::Aabb& spawnerBox) AZStd::unique_ptr<MockSurfaceDataSystem> GradientSignalBaseFixture::CreateMockSurfaceDataSystem(const AZ::Aabb& spawnerBox)
{ {
SurfaceData::SurfacePoint point; AzFramework::SurfaceData::SurfacePoint point;
AZStd::unique_ptr<MockSurfaceDataSystem> mockSurfaceDataSystem = AZStd::make_unique<MockSurfaceDataSystem>(); AZStd::unique_ptr<MockSurfaceDataSystem> mockSurfaceDataSystem = AZStd::make_unique<MockSurfaceDataSystem>();
// Give the mock surface data a bunch of fake point values to return. // Give the mock surface data a bunch of fake point values to return.
@ -101,7 +101,8 @@ namespace UnitTest
// Create an arbitrary normal value. // Create an arbitrary normal value.
point.m_normal = point.m_position.GetNormalized(); point.m_normal = point.m_position.GetNormalized();
// Create an arbitrary surface value. // Create an arbitrary surface value.
point.m_masks[AZ_CRC_CE("test_mask")] = arbitraryPercentage; point.m_surfaceTags.clear();
point.m_surfaceTags.emplace_back(AZ_CRC_CE("test_mask"), arbitraryPercentage);
mockSurfaceDataSystem->m_GetSurfacePoints[AZStd::make_pair(x, y)] = { { point } }; mockSurfaceDataSystem->m_GetSurfacePoints[AZStd::make_pair(x, y)] = { { point } };
} }

@ -37,7 +37,7 @@ ly_add_target(
PUBLIC PUBLIC
Include Include
BUILD_DEPENDENCIES BUILD_DEPENDENCIES
PRIVATE PUBLIC
Gem::SurfaceData.Static Gem::SurfaceData.Static
Gem::LmbrCentral Gem::LmbrCentral
RUNTIME_DEPENDENCIES RUNTIME_DEPENDENCIES

@ -9,31 +9,215 @@
#pragma once #pragma once
#include <AzCore/Math/Aabb.h> #include <AzCore/Math/Aabb.h>
#include <AzCore/Math/Crc.h>
#include <AzCore/Math/Vector3.h> #include <AzCore/Math/Vector3.h>
#include <AzCore/Memory/SystemAllocator.h> #include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/string/string.h> #include <AzCore/std/string/string.h>
#include <AzCore/std/containers/unordered_set.h> #include <AzCore/std/containers/unordered_set.h>
#include <AzFramework/SurfaceData/SurfaceData.h>
#include <SurfaceData/SurfaceTag.h> #include <SurfaceData/SurfaceTag.h>
namespace SurfaceData namespace SurfaceData
{ {
//map of id or crc to contribution factor //map of id or crc to contribution factor
using SurfaceTagWeightMap = AZStd::unordered_map<AZ::Crc32, float>;
using SurfaceTagNameSet = AZStd::unordered_set<AZStd::string>; using SurfaceTagNameSet = AZStd::unordered_set<AZStd::string>;
using SurfaceTagVector = AZStd::vector<SurfaceTag>; using SurfaceTagVector = AZStd::vector<SurfaceTag>;
struct SurfacePoint final //! SurfaceTagWeights stores a collection of surface tags and weights.
class SurfaceTagWeights
{ {
AZ_CLASS_ALLOCATOR(SurfacePoint, AZ::SystemAllocator, 0); public:
AZ_TYPE_INFO(SurfacePoint, "{0DC7E720-68D6-47D4-BB6D-B89EF23C5A5C}"); SurfaceTagWeights() = default;
AZ::EntityId m_entityId; //! Construct a collection of SurfaceTagWeights from the given SurfaceTagWeightList.
AZ::Vector3 m_position; //! @param weights - The list of weights to assign to the new instance.
AZ::Vector3 m_normal; SurfaceTagWeights(const AzFramework::SurfaceData::SurfaceTagWeightList& weights)
SurfaceTagWeightMap m_masks; {
AssignSurfaceTagWeights(weights);
}
//! Replace the existing surface tag weights with the given set.
//! @param weights - The list of weights to assign to this instance.
void AssignSurfaceTagWeights(const AzFramework::SurfaceData::SurfaceTagWeightList& weights);
//! Replace the existing surface tag weights with the given set.
//! @param tags - The list of tags to assign to this instance.
//! @param weight - The weight to assign to each tag.
void AssignSurfaceTagWeights(const SurfaceTagVector& tags, float weight);
//! Add a surface tag weight to this collection.
//! @param tag - The surface tag.
//! @param weight - The surface tag weight.
void AddSurfaceTagWeight(const AZ::Crc32 tag, const float weight);
//! Replace the surface tag weight with the new one if it's higher, or add it if the tag isn't found.
//! (This method is intentionally inlined for its performance impact)
//! @param tag - The surface tag.
//! @param weight - The surface tag weight.
void AddSurfaceWeightIfGreater(const AZ::Crc32 tag, const float weight)
{
const auto maskItr = m_weights.find(tag);
const float previousValue = maskItr != m_weights.end() ? maskItr->second : 0.0f;
m_weights[tag] = AZ::GetMax(weight, previousValue);
}
//! Replace the surface tag weight with the new one if it's higher, or add it if the tag isn't found.
//! (This method is intentionally inlined for its performance impact)
//! @param tags - The surface tags to replace/add.
//! @param weight - The surface tag weight to use for each tag.
void AddSurfaceWeightsIfGreater(const SurfaceTagVector& tags, const float weight)
{
for (const auto& tag : tags)
{
AddSurfaceWeightIfGreater(tag, weight);
}
}
//! Replace the surface tag weight with the new one if it's higher, or add it if the tag isn't found.
//! (This method is intentionally inlined for its performance impact)
//! @param weights - The surface tags and weights to replace/add.
void AddSurfaceWeightsIfGreater(const SurfaceTagWeights& weights)
{
for (const auto& [tag, weight] : weights.m_weights)
{
AddSurfaceWeightIfGreater(tag, weight);
}
}
//! Equality comparison operator for SurfaceTagWeights.
bool operator==(const SurfaceTagWeights& rhs) const;
//! Inequality comparison operator for SurfaceTagWeights.
bool operator!=(const SurfaceTagWeights& rhs) const
{
return !(*this == rhs);
}
//! Compares a SurfaceTagWeightList with a SurfaceTagWeights instance to look for equality.
//! They will be equal if they have the exact same set of tags and weights.
//! @param compareWeights - the set of weights to compare against.
bool SurfaceWeightsAreEqual(const AzFramework::SurfaceData::SurfaceTagWeightList& compareWeights) const;
//! Clear the surface tag weight collection.
void Clear();
//! Get the size of the surface tag weight collection.
//! @return The size of the collection.
size_t GetSize() const;
//! Get the collection of surface tag weights as a SurfaceTagWeightList.
//! @return SurfaceTagWeightList containing the same tags and weights as this collection.
AzFramework::SurfaceData::SurfaceTagWeightList GetSurfaceTagWeightList() const;
//! Enumerate every tag and weight and call a callback for each one found.
//! Callback params:
//! AZ::Crc32 - The surface tag.
//! float - The surface tag weight.
//! return - true to keep enumerating, false to stop.
//! @param weightCallback - the callback to use for each surface tag / weight found.
void EnumerateWeights(AZStd::function<bool(AZ::Crc32 tag, float weight)> weightCallback) const;
//! Check to see if the collection has any valid tags stored within it.
//! A tag of "Unassigned" is considered an invalid tag.
//! @return True if there is at least one valid tag, false if there isn't.
bool HasValidTags() const;
//! Check to see if the collection contains the given tag.
//! @param sampleTag - The tag to look for.
//! @return True if the tag is found, false if it isn't.
bool HasMatchingTag(const AZ::Crc32& sampleTag) const;
//! Check to see if the collection contains the given tag with the given weight range.
//! The range check is inclusive on both sides of the range: [weightMin, weightMax]
//! @param sampleTag - The tag to look for.
//! @param weightMin - The minimum weight for this tag.
//! @param weightMax - The maximum weight for this tag.
//! @return True if the tag is found, false if it isn't.
bool HasMatchingTag(const AZ::Crc32& sampleTag, float weightMin, float weightMax) const;
//! Check to see if the collection contains any of the given tags.
//! @param sampleTags - The tags to look for.
//! @return True if any of the tags is found, false if none are found.
bool HasAnyMatchingTags(const SurfaceTagVector& sampleTags) const;
//! Check to see if the collection contains the given tag with the given weight range.
//! The range check is inclusive on both sides of the range: [weightMin, weightMax]
//! @param sampleTags - The tags to look for.
//! @param weightMin - The minimum weight for this tag.
//! @param weightMax - The maximum weight for this tag.
//! @return True if any of the tags is found, false if none are found.
bool HasAnyMatchingTags(const SurfaceTagVector& sampleTags, float weightMin, float weightMax) const;
private:
AZStd::unordered_map<AZ::Crc32, float> m_weights;
};
//! SurfacePointList stores a collection of surface point data, which consists of positions, normals, and surface tag weights.
class SurfacePointList
{
public:
AZ_CLASS_ALLOCATOR(SurfacePointList, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(SurfacePointList, "{DBA02848-2131-4279-BDEF-3581B76AB736}");
SurfacePointList() = default;
~SurfacePointList() = default;
//! Constructor for creating a SurfacePointList from a list of SurfacePoint data.
//! Primarily used as a convenience for unit tests.
//! @param surfacePoints - An initial set of SurfacePoint points to store in the SurfacePointList.
SurfacePointList(AZStd::initializer_list<const AzFramework::SurfaceData::SurfacePoint> surfacePoints);
//! Add a surface point to the list.
//! @param entityId - The entity creating the surface point.
//! @param position - The position of the surface point.
//! @param normal - The normal for the surface point.
//! @param weights - The surface tags and weights for this surface point.
void AddSurfacePoint(const AZ::EntityId& entityId,
const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceTagWeights& weights);
//! Clear the surface point list.
void Clear();
//! Preallocate space in the list based on the maximum number of output points per input point we can generate.
//! @param maxPointsPerInput - The maximum number of output points per input point.
void ReserveSpace(size_t maxPointsPerInput);
//! Check if the surface point list is empty.
//! @return - true if empty, false if it contains points.
bool IsEmpty() const;
//! Get the size of the surface point list.
//! @return - The number of valid points in the list.
size_t GetSize() const;
//! Enumerate every surface point and call a callback for each point found.
void EnumeratePoints(AZStd::function<
bool(const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceTagWeights& surfaceWeights)> pointCallback) const;
//! Modify the surface weights for each surface point in the list.
void ModifySurfaceWeights(
const AZ::EntityId& currentEntityId,
AZStd::function<void(const AZ::Vector3& position, SurfaceTagWeights& surfaceWeights)> modificationWeightCallback);
//! Get the surface point with the highest Z value.
AzFramework::SurfaceData::SurfacePoint GetHighestSurfacePoint() const;
//! Remove any points that don't contain any of the provided surface tags.
void FilterPoints(const SurfaceTagVector& desiredTags);
protected:
// These are kept in separate parallel vectors instead of a single struct so that it's possible to pass just specific data
// "channels" into other methods as span<> without having to pass the full struct into the span<>. Specifically, we want to be
// able to pass spans of the positions down through nesting gradient/surface calls.
// A side benefit is that profiling showed the data access to be faster than packing all the fields into a single struct.
AZStd::vector<AZ::EntityId> m_surfaceCreatorIdList;
AZStd::vector<AZ::Vector3> m_surfacePositionList;
AZStd::vector<AZ::Vector3> m_surfaceNormalList;
AZStd::vector<SurfaceTagWeights> m_surfaceWeightsList;
AZ::Aabb m_pointBounds = AZ::Aabb::CreateNull();
}; };
using SurfacePointList = AZStd::vector<SurfacePoint>;
using SurfacePointLists = AZStd::vector<SurfacePointList>; using SurfacePointLists = AZStd::vector<SurfacePointList>;
struct SurfaceDataRegistryEntry struct SurfaceDataRegistryEntry

@ -88,48 +88,6 @@ namespace SurfaceData
const AZ::Vector3& rayStart, const AZ::Vector3& rayEnd, const AZ::Vector3& rayStart, const AZ::Vector3& rayEnd,
AZ::Vector3& outPosition, AZ::Vector3& outNormal); AZ::Vector3& outPosition, AZ::Vector3& outNormal);
AZ_INLINE void AssignSurfaceTagWeights(const SurfaceTagVector& tags, float weight, SurfaceTagWeightMap& weights)
{
weights.clear();
weights.reserve(tags.size());
for (auto& tag : tags)
{
weights[tag] = weight;
}
}
AZ_INLINE void AddMaxValueForMasks(SurfaceTagWeightMap& masks, const AZ::Crc32 tag, const float value)
{
const auto maskItr = masks.find(tag);
const float valueOld = maskItr != masks.end() ? maskItr->second : 0.0f;
masks[tag] = AZ::GetMax(value, valueOld);
}
AZ_INLINE void AddMaxValueForMasks(SurfaceTagWeightMap& masks, const SurfaceTagVector& tags, const float value)
{
for (const auto& tag : tags)
{
AddMaxValueForMasks(masks, tag, value);
}
}
AZ_INLINE void AddMaxValueForMasks(SurfaceTagWeightMap& outMasks, const SurfaceTagWeightMap& inMasks)
{
for (const auto& inMask : inMasks)
{
AddMaxValueForMasks(outMasks, inMask.first, inMask.second);
}
}
template<typename Container, typename Element>
AZ_INLINE void AddItemIfNotFound(Container& container, const Element& element)
{
if (AZStd::find(container.begin(), container.end(), element) == container.end())
{
container.insert(container.end(), element);
}
}
template<typename SourceContainer> template<typename SourceContainer>
AZ_INLINE bool HasMatchingTag(const SourceContainer& sourceTags, const AZ::Crc32& sampleTag) AZ_INLINE bool HasMatchingTag(const SourceContainer& sourceTags, const AZ::Crc32& sampleTag)
{ {
@ -137,26 +95,7 @@ namespace SurfaceData
} }
template<typename SourceContainer, typename SampleContainer> template<typename SourceContainer, typename SampleContainer>
AZ_INLINE bool HasMatchingTags(const SourceContainer& sourceTags, const SampleContainer& sampleTags) AZ_INLINE bool HasAnyMatchingTags(const SourceContainer& sourceTags, const SampleContainer& sampleTags)
{
for (const auto& sampleTag : sampleTags)
{
if (HasMatchingTag(sourceTags, sampleTag))
{
return true;
}
}
return false;
}
AZ_INLINE bool HasMatchingTag(const SurfaceTagWeightMap& sourceTags, const AZ::Crc32& sampleTag)
{
return sourceTags.find(sampleTag) != sourceTags.end();
}
template<typename SampleContainer>
AZ_INLINE bool HasMatchingTags(const SurfaceTagWeightMap& sourceTags, const SampleContainer& sampleTags)
{ {
for (const auto& sampleTag : sampleTags) for (const auto& sampleTag : sampleTags)
{ {
@ -168,36 +107,8 @@ namespace SurfaceData
return false; return false;
} }
AZ_INLINE bool HasMatchingTag(const SurfaceTagWeightMap& sourceTags, const AZ::Crc32& sampleTag, float valueMin, float valueMax) AZ_INLINE bool HasValidTags(const SurfaceTagVector& sourceTags)
{
auto maskItr = sourceTags.find(sampleTag);
return maskItr != sourceTags.end() && valueMin <= maskItr->second && valueMax >= maskItr->second;
}
template<typename SampleContainer>
AZ_INLINE bool HasMatchingTags(
const SurfaceTagWeightMap& sourceTags, const SampleContainer& sampleTags, float valueMin, float valueMax)
{
for (const auto& sampleTag : sampleTags)
{
if (HasMatchingTag(sourceTags, sampleTag, valueMin, valueMax))
{
return true;
}
}
return false;
}
template<typename SourceContainer>
AZ_INLINE void RemoveUnassignedTags(const SourceContainer& sourceTags)
{
sourceTags.erase(AZStd::remove(sourceTags.begin(), sourceTags.end(), Constants::s_unassignedTagCrc), sourceTags.end());
}
template<typename SourceContainer>
AZ_INLINE bool HasValidTags(const SourceContainer& sourceTags)
{ {
for (const auto& sourceTag : sourceTags) for (const auto& sourceTag : sourceTags)
{ {
@ -209,18 +120,6 @@ namespace SurfaceData
return false; return false;
} }
AZ_INLINE bool HasValidTags(const SurfaceTagWeightMap& sourceTags)
{
for (const auto& sourceTag : sourceTags)
{
if (sourceTag.first != Constants::s_unassignedTagCrc)
{
return true;
}
}
return false;
}
// Utility method to compare two AABBs for overlapping XY coordinates while ignoring the Z coordinates. // Utility method to compare two AABBs for overlapping XY coordinates while ignoring the Z coordinates.
AZ_INLINE bool AabbOverlaps2D(const AZ::Aabb& box1, const AZ::Aabb& box2) AZ_INLINE bool AabbOverlaps2D(const AZ::Aabb& box1, const AZ::Aabb& box2)
{ {

@ -132,7 +132,7 @@ namespace SurfaceData
Physics::ColliderComponentEventBus::Handler::BusConnect(GetEntityId()); Physics::ColliderComponentEventBus::Handler::BusConnect(GetEntityId());
// Update the cached collider data and bounds, then register the surface data provider / modifier // Update the cached collider data and bounds, then register the surface data provider / modifier
AssignSurfaceTagWeights(m_configuration.m_providerTags, 1.0f, m_newPointWeights); m_newPointWeights.AssignSurfaceTagWeights(m_configuration.m_providerTags, 1.0f);
UpdateColliderData(); UpdateColliderData();
} }
@ -237,12 +237,7 @@ namespace SurfaceData
if (DoRayTrace(inPosition, queryPointOnly, hitPosition, hitNormal)) if (DoRayTrace(inPosition, queryPointOnly, hitPosition, hitNormal))
{ {
SurfacePoint point; surfacePointList.AddSurfacePoint(GetEntityId(), hitPosition, hitNormal, m_newPointWeights);
point.m_entityId = GetEntityId();
point.m_position = hitPosition;
point.m_normal = hitNormal;
point.m_masks = m_newPointWeights;
surfacePointList.push_back(AZStd::move(point));
} }
} }
@ -252,20 +247,22 @@ namespace SurfaceData
if (m_colliderBounds.IsValid() && !m_configuration.m_modifierTags.empty()) if (m_colliderBounds.IsValid() && !m_configuration.m_modifierTags.empty())
{ {
const AZ::EntityId entityId = GetEntityId(); surfacePointList.ModifySurfaceWeights(
for (auto& point : surfacePointList) GetEntityId(),
{ [this](const AZ::Vector3& position, SurfaceData::SurfaceTagWeights& weights)
if (point.m_entityId != entityId && m_colliderBounds.Contains(point.m_position))
{ {
AZ::Vector3 hitPosition; if (m_colliderBounds.Contains(position))
AZ::Vector3 hitNormal;
constexpr bool queryPointOnly = true;
if (DoRayTrace(point.m_position, queryPointOnly, hitPosition, hitNormal))
{ {
AddMaxValueForMasks(point.m_masks, m_configuration.m_modifierTags, 1.0f); AZ::Vector3 hitPosition;
AZ::Vector3 hitNormal;
constexpr bool queryPointOnly = true;
if (DoRayTrace(position, queryPointOnly, hitPosition, hitNormal))
{
// If the query point collides with the volume, add all our modifier tags with a weight of 1.0f.
weights.AddSurfaceWeightsIfGreater(m_configuration.m_modifierTags, 1.0f);
}
} }
} });
}
} }
} }

@ -101,6 +101,6 @@ namespace SurfaceData
AZStd::atomic_bool m_refresh{ false }; AZStd::atomic_bool m_refresh{ false };
mutable AZStd::shared_mutex m_cacheMutex; mutable AZStd::shared_mutex m_cacheMutex;
AZ::Aabb m_colliderBounds = AZ::Aabb::CreateNull(); AZ::Aabb m_colliderBounds = AZ::Aabb::CreateNull();
SurfaceTagWeightMap m_newPointWeights; SurfaceTagWeights m_newPointWeights;
}; };
} }

@ -89,7 +89,7 @@ namespace SurfaceData
LmbrCentral::ShapeComponentNotificationsBus::Handler::BusConnect(GetEntityId()); LmbrCentral::ShapeComponentNotificationsBus::Handler::BusConnect(GetEntityId());
// Update the cached shape data and bounds, then register the surface data provider / modifier // Update the cached shape data and bounds, then register the surface data provider / modifier
AssignSurfaceTagWeights(m_configuration.m_providerTags, 1.0f, m_newPointWeights); m_newPointWeights.AssignSurfaceTagWeights(m_configuration.m_providerTags, 1.0f);
UpdateShapeData(); UpdateShapeData();
} }
@ -155,12 +155,8 @@ namespace SurfaceData
LmbrCentral::ShapeComponentRequestsBus::EventResult(hitShape, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::IntersectRay, rayOrigin, rayDirection, intersectionDistance); LmbrCentral::ShapeComponentRequestsBus::EventResult(hitShape, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::IntersectRay, rayOrigin, rayDirection, intersectionDistance);
if (hitShape) if (hitShape)
{ {
SurfacePoint point; AZ::Vector3 position = rayOrigin + intersectionDistance * rayDirection;
point.m_entityId = GetEntityId(); surfacePointList.AddSurfacePoint(GetEntityId(), position, AZ::Vector3::CreateAxisZ(), m_newPointWeights);
point.m_position = rayOrigin + intersectionDistance * rayDirection;
point.m_normal = AZ::Vector3::CreateAxisZ();
point.m_masks = m_newPointWeights;
surfacePointList.push_back(AZStd::move(point));
} }
} }
} }
@ -173,20 +169,19 @@ namespace SurfaceData
{ {
const AZ::EntityId entityId = GetEntityId(); const AZ::EntityId entityId = GetEntityId();
LmbrCentral::ShapeComponentRequestsBus::Event( LmbrCentral::ShapeComponentRequestsBus::Event(
GetEntityId(), entityId,
[entityId, this, &surfacePointList](LmbrCentral::ShapeComponentRequestsBus::Events* shape) [entityId, this, &surfacePointList](LmbrCentral::ShapeComponentRequestsBus::Events* shape)
{ {
for (auto& point : surfacePointList) surfacePointList.ModifySurfaceWeights(
entityId,
[this, shape](const AZ::Vector3& position, SurfaceData::SurfaceTagWeights& weights)
{ {
if (point.m_entityId != entityId && m_shapeBounds.Contains(point.m_position)) if (m_shapeBounds.Contains(position) && shape->IsPointInside(position))
{ {
bool inside = shape->IsPointInside(point.m_position); // If the point is inside our shape, add all our modifier tags with a weight of 1.0f.
if (inside) weights.AddSurfaceWeightsIfGreater(m_configuration.m_modifierTags, 1.0f);
{
AddMaxValueForMasks(point.m_masks, m_configuration.m_modifierTags, 1.0f);
}
} }
} });
}); });
} }
} }

@ -97,6 +97,6 @@ namespace SurfaceData
AZ::Aabb m_shapeBounds = AZ::Aabb::CreateNull(); AZ::Aabb m_shapeBounds = AZ::Aabb::CreateNull();
bool m_shapeBoundsIsValid = false; bool m_shapeBoundsIsValid = false;
static const float s_rayAABBHeightPadding; static const float s_rayAABBHeightPadding;
SurfaceTagWeightMap m_newPointWeights; SurfaceTagWeights m_newPointWeights;
}; };
} }

@ -45,14 +45,10 @@ namespace SurfaceData
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context)) if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{ {
behaviorContext->Class<SurfacePoint>() behaviorContext->Class<SurfacePointList>()
->Constructor() ->Constructor()
->Attribute(AZ::Script::Attributes::Category, "Vegetation") ->Attribute(AZ::Script::Attributes::Category, "Vegetation")
->Attribute(AZ::Script::Attributes::Module, "surface_data") ->Attribute(AZ::Script::Attributes::Module, "surface_data")
->Property("entityId", BehaviorValueProperty(&SurfacePoint::m_entityId))
->Property("position", BehaviorValueProperty(&SurfacePoint::m_position))
->Property("normal", BehaviorValueProperty(&SurfacePoint::m_normal))
->Property("masks", BehaviorValueProperty(&SurfacePoint::m_masks))
; ;
behaviorContext->Class<SurfaceDataSystemComponent>() behaviorContext->Class<SurfaceDataSystemComponent>()
@ -182,11 +178,12 @@ namespace SurfaceData
void SurfaceDataSystemComponent::GetSurfacePoints(const AZ::Vector3& inPosition, const SurfaceTagVector& desiredTags, SurfacePointList& surfacePointList) const void SurfaceDataSystemComponent::GetSurfacePoints(const AZ::Vector3& inPosition, const SurfaceTagVector& desiredTags, SurfacePointList& surfacePointList) const
{ {
const bool useTagFilters = HasValidTags(desiredTags); const bool useTagFilters = HasValidTags(desiredTags);
const bool hasModifierTags = useTagFilters && HasMatchingTags(desiredTags, m_registeredModifierTags); const bool hasModifierTags = useTagFilters && HasAnyMatchingTags(desiredTags, m_registeredModifierTags);
AZStd::shared_lock<decltype(m_registrationMutex)> registrationLock(m_registrationMutex); AZStd::shared_lock<decltype(m_registrationMutex)> registrationLock(m_registrationMutex);
surfacePointList.clear(); surfacePointList.Clear();
surfacePointList.ReserveSpace(m_registeredSurfaceDataProviders.size());
//gather all intersecting points //gather all intersecting points
for (const auto& entryPair : m_registeredSurfaceDataProviders) for (const auto& entryPair : m_registeredSurfaceDataProviders)
@ -195,14 +192,14 @@ namespace SurfaceData
const SurfaceDataRegistryEntry& entry = entryPair.second; const SurfaceDataRegistryEntry& entry = entryPair.second;
if (!entry.m_bounds.IsValid() || AabbContains2D(entry.m_bounds, inPosition)) if (!entry.m_bounds.IsValid() || AabbContains2D(entry.m_bounds, inPosition))
{ {
if (!useTagFilters || hasModifierTags || HasMatchingTags(desiredTags, entry.m_tags)) if (!useTagFilters || hasModifierTags || HasAnyMatchingTags(desiredTags, entry.m_tags))
{ {
SurfaceDataProviderRequestBus::Event(entryAddress, &SurfaceDataProviderRequestBus::Events::GetSurfacePoints, inPosition, surfacePointList); SurfaceDataProviderRequestBus::Event(entryAddress, &SurfaceDataProviderRequestBus::Events::GetSurfacePoints, inPosition, surfacePointList);
} }
} }
} }
if (!surfacePointList.empty()) if (!surfacePointList.IsEmpty())
{ {
//modify or annotate reported points //modify or annotate reported points
for (const auto& entryPair : m_registeredSurfaceDataModifiers) for (const auto& entryPair : m_registeredSurfaceDataModifiers)
@ -221,10 +218,8 @@ namespace SurfaceData
// doesn't add a desired tag, and a surface modifier has the *potential* to add it, but then doesn't. // doesn't add a desired tag, and a surface modifier has the *potential* to add it, but then doesn't.
if (useTagFilters) if (useTagFilters)
{ {
FilterPoints(surfacePointList, desiredTags); surfacePointList.FilterPoints(desiredTags);
} }
CombineAndSortNeighboringPoints(surfacePointList);
} }
} }
@ -260,8 +255,13 @@ namespace SurfaceData
surfacePointLists.clear(); surfacePointLists.clear();
surfacePointLists.resize(totalQueryPositions); surfacePointLists.resize(totalQueryPositions);
for (auto& surfacePointList : surfacePointLists)
{
surfacePointList.ReserveSpace(m_registeredSurfaceDataProviders.size());
}
const bool useTagFilters = HasValidTags(desiredTags); const bool useTagFilters = HasValidTags(desiredTags);
const bool hasModifierTags = useTagFilters && HasMatchingTags(desiredTags, m_registeredModifierTags); const bool hasModifierTags = useTagFilters && HasAnyMatchingTags(desiredTags, m_registeredModifierTags);
// Loop through each data provider, and query all the points for each one. This allows us to check the tags and the overall // Loop through each data provider, and query all the points for each one. This allows us to check the tags and the overall
// AABB bounds just once per provider, instead of once per point. It also allows for an eventual optimization in which we could // AABB bounds just once per provider, instead of once per point. It also allows for an eventual optimization in which we could
@ -270,7 +270,7 @@ namespace SurfaceData
{ {
bool hasInfiniteBounds = !provider.m_bounds.IsValid(); bool hasInfiniteBounds = !provider.m_bounds.IsValid();
if (!useTagFilters || hasModifierTags || HasMatchingTags(desiredTags, provider.m_tags)) if (!useTagFilters || hasModifierTags || HasAnyMatchingTags(desiredTags, provider.m_tags))
{ {
for (size_t index = 0; index < totalQueryPositions; index++) for (size_t index = 0; index < totalQueryPositions; index++)
{ {
@ -299,7 +299,7 @@ namespace SurfaceData
{ {
const auto& inPosition = inPositions[index]; const auto& inPosition = inPositions[index];
SurfacePointList& surfacePointList = surfacePointLists[index]; SurfacePointList& surfacePointList = surfacePointLists[index];
if (!surfacePointList.empty()) if (!surfacePointList.IsEmpty())
{ {
if (hasInfiniteBounds || AabbContains2D(entry.m_bounds, inPosition)) if (hasInfiniteBounds || AabbContains2D(entry.m_bounds, inPosition))
{ {
@ -315,82 +315,14 @@ namespace SurfaceData
// same XY coordinates and extremely similar Z values. This produces results that are sorted in decreasing Z order. // same XY coordinates and extremely similar Z values. This produces results that are sorted in decreasing Z order.
// Also, this filters out any remaining points that don't match the desired tag list. This can happen when a surface provider // Also, this filters out any remaining points that don't match the desired tag list. This can happen when a surface provider
// doesn't add a desired tag, and a surface modifier has the *potential* to add it, but then doesn't. // doesn't add a desired tag, and a surface modifier has the *potential* to add it, but then doesn't.
for (auto& surfacePointList : surfacePointLists) if (useTagFilters)
{ {
if (useTagFilters) for (auto& surfacePointList : surfacePointLists)
{ {
FilterPoints(surfacePointList, desiredTags); surfacePointList.FilterPoints(desiredTags);
} }
CombineAndSortNeighboringPoints(surfacePointList);
}
}
void SurfaceDataSystemComponent::FilterPoints(SurfacePointList& sourcePointList, const SurfaceTagVector& desiredTags) const
{
// Before sorting and combining, filter out any points that don't match our search tags.
sourcePointList.erase(
AZStd::remove_if(
sourcePointList.begin(), sourcePointList.end(),
[desiredTags](SurfacePoint& point) -> bool
{
return !HasMatchingTags(point.m_masks, desiredTags);
}),
sourcePointList.end());
}
void SurfaceDataSystemComponent::CombineAndSortNeighboringPoints(SurfacePointList& sourcePointList) const
{
// If there's only 0 or 1 point, there is no sorting or combining that needs to happen, so just return.
if (sourcePointList.size() <= 1)
{
return;
} }
// Efficient point consolidation requires the points to be pre-sorted so we are only comparing/combining neighbors.
// Sort XY points together, with decreasing Z.
AZStd::sort(sourcePointList.begin(), sourcePointList.end(), [](const SurfacePoint& a, const SurfacePoint& b)
{
// Our goal is to have identical XY values sorted adjacent to each other with decreasing Z.
// We sort increasing Y, then increasing X, then decreasing Z, because we need to compare all 3 values for a
// stable sort. The choice of increasing Y first is because we'll often generate the points as ranges of X values within
// ranges of Y values, so this will produce the most usable and expected output sort.
if (a.m_position.GetY() != b.m_position.GetY())
{
return a.m_position.GetY() < b.m_position.GetY();
}
if (a.m_position.GetX() != b.m_position.GetX())
{
return a.m_position.GetX() < b.m_position.GetX();
}
if (a.m_position.GetZ() != b.m_position.GetZ())
{
return a.m_position.GetZ() > b.m_position.GetZ();
}
// If we somehow ended up with two points with identical positions getting generated, use the entity ID as the tiebreaker
// to guarantee a stable sort. We should never have two identical positions generated from the same entity.
return a.m_entityId < b.m_entityId;
});
// iterate over subsequent source points for comparison and consolidation with the last added target/unique point
for (auto pointItr = sourcePointList.begin() + 1; pointItr < sourcePointList.end();)
{
auto prevPointItr = pointItr - 1;
// (Someday we should add a configurable tolerance for comparison)
if (pointItr->m_position.IsClose(prevPointItr->m_position) && pointItr->m_normal.IsClose(prevPointItr->m_normal))
{
// consolidate points with similar attributes by adding masks/weights to the previous point and deleting this point.
AddMaxValueForMasks(prevPointItr->m_masks, pointItr->m_masks);
pointItr = sourcePointList.erase(pointItr);
}
else
{
pointItr++;
}
}
} }
SurfaceDataRegistryHandle SurfaceDataSystemComponent::RegisterSurfaceDataProviderInternal(const SurfaceDataRegistryEntry& entry) SurfaceDataRegistryHandle SurfaceDataSystemComponent::RegisterSurfaceDataProviderInternal(const SurfaceDataRegistryEntry& entry)

@ -58,9 +58,6 @@ namespace SurfaceData
void RefreshSurfaceData(const AZ::Aabb& dirtyArea) override; void RefreshSurfaceData(const AZ::Aabb& dirtyArea) override;
private: private:
void FilterPoints(SurfacePointList& sourcePointList, const SurfaceTagVector& desiredTags) const;
void CombineAndSortNeighboringPoints(SurfacePointList& sourcePointList) const;
SurfaceDataRegistryHandle RegisterSurfaceDataProviderInternal(const SurfaceDataRegistryEntry& entry); SurfaceDataRegistryHandle RegisterSurfaceDataProviderInternal(const SurfaceDataRegistryEntry& entry);
SurfaceDataRegistryEntry UnregisterSurfaceDataProviderInternal(const SurfaceDataRegistryHandle& handle); SurfaceDataRegistryEntry UnregisterSurfaceDataProviderInternal(const SurfaceDataRegistryHandle& handle);
bool UpdateSurfaceDataProviderInternal(const SurfaceDataRegistryHandle& handle, const SurfaceDataRegistryEntry& entry, AZ::Aabb& oldBounds); bool UpdateSurfaceDataProviderInternal(const SurfaceDataRegistryHandle& handle, const SurfaceDataRegistryEntry& entry, AZ::Aabb& oldBounds);

@ -0,0 +1,319 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <SurfaceData/Utility/SurfaceDataUtility.h>
#include <Atom/RPI.Reflect/Model/ModelAssetCreator.h>
namespace SurfaceData
{
void SurfaceTagWeights::AssignSurfaceTagWeights(const AzFramework::SurfaceData::SurfaceTagWeightList& weights)
{
m_weights.clear();
m_weights.reserve(weights.size());
for (auto& weight : weights)
{
m_weights.emplace(weight.m_surfaceType, weight.m_weight);
}
}
void SurfaceTagWeights::AssignSurfaceTagWeights(const SurfaceTagVector& tags, float weight)
{
m_weights.clear();
m_weights.reserve(tags.size());
for (auto& tag : tags)
{
m_weights[tag] = weight;
}
}
void SurfaceTagWeights::AddSurfaceTagWeight(const AZ::Crc32 tag, const float value)
{
m_weights[tag] = value;
}
void SurfaceTagWeights::Clear()
{
m_weights.clear();
}
size_t SurfaceTagWeights::GetSize() const
{
return m_weights.size();
}
AzFramework::SurfaceData::SurfaceTagWeightList SurfaceTagWeights::GetSurfaceTagWeightList() const
{
AzFramework::SurfaceData::SurfaceTagWeightList weights;
weights.reserve(m_weights.size());
for (auto& weight : m_weights)
{
weights.emplace_back(weight.first, weight.second);
}
return weights;
}
bool SurfaceTagWeights::operator==(const SurfaceTagWeights& rhs) const
{
// If the lists are different sizes, they're not equal.
if (m_weights.size() != rhs.m_weights.size())
{
return false;
}
for (auto& weight : m_weights)
{
auto rhsWeight = rhs.m_weights.find(weight.first);
if ((rhsWeight == rhs.m_weights.end()) || (rhsWeight->second != weight.second))
{
return false;
}
}
// All the entries matched, and the lists are the same size, so they're equal.
return true;
}
bool SurfaceTagWeights::SurfaceWeightsAreEqual(const AzFramework::SurfaceData::SurfaceTagWeightList& compareWeights) const
{
// If the lists are different sizes, they're not equal.
if (m_weights.size() != compareWeights.size())
{
return false;
}
for (auto& weight : m_weights)
{
auto maskEntry = AZStd::find_if(
compareWeights.begin(), compareWeights.end(),
[weight](const AzFramework::SurfaceData::SurfaceTagWeight& compareWeight) -> bool
{
return (weight.first == compareWeight.m_surfaceType) && (weight.second == compareWeight.m_weight);
});
// If we didn't find a match, they're not equal.
if (maskEntry == compareWeights.end())
{
return false;
}
}
// All the entries matched, and the lists are the same size, so they're equal.
return true;
}
void SurfaceTagWeights::EnumerateWeights(AZStd::function<bool(AZ::Crc32 tag, float weight)> weightCallback) const
{
for (auto& [tag, weight] : m_weights)
{
if (!weightCallback(tag, weight))
{
break;
}
}
}
bool SurfaceTagWeights::HasValidTags() const
{
for (const auto& sourceTag : m_weights)
{
if (sourceTag.first != Constants::s_unassignedTagCrc)
{
return true;
}
}
return false;
}
bool SurfaceTagWeights::HasMatchingTag(const AZ::Crc32& sampleTag) const
{
return m_weights.find(sampleTag) != m_weights.end();
}
bool SurfaceTagWeights::HasAnyMatchingTags(const SurfaceTagVector& sampleTags) const
{
for (const auto& sampleTag : sampleTags)
{
if (HasMatchingTag(sampleTag))
{
return true;
}
}
return false;
}
bool SurfaceTagWeights::HasMatchingTag(const AZ::Crc32& sampleTag, float weightMin, float weightMax) const
{
auto maskItr = m_weights.find(sampleTag);
return maskItr != m_weights.end() && weightMin <= maskItr->second && weightMax >= maskItr->second;
}
bool SurfaceTagWeights::HasAnyMatchingTags(const SurfaceTagVector& sampleTags, float weightMin, float weightMax) const
{
for (const auto& sampleTag : sampleTags)
{
if (HasMatchingTag(sampleTag, weightMin, weightMax))
{
return true;
}
}
return false;
}
SurfacePointList::SurfacePointList(AZStd::initializer_list<const AzFramework::SurfaceData::SurfacePoint> surfacePoints)
{
ReserveSpace(surfacePoints.size());
for (auto& point : surfacePoints)
{
SurfaceTagWeights weights(point.m_surfaceTags);
AddSurfacePoint(AZ::EntityId(), point.m_position, point.m_normal, weights);
}
}
void SurfacePointList::AddSurfacePoint(const AZ::EntityId& entityId,
const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceTagWeights& masks)
{
// When adding a surface point, we'll either merge it with a similar existing point, or else add it in order of
// decreasing Z, so that our final results are sorted.
for (size_t index = 0; index < m_surfacePositionList.size(); ++index)
{
// (Someday we should add a configurable tolerance for comparison)
if (m_surfacePositionList[index].IsClose(position) && m_surfaceNormalList[index].IsClose(normal))
{
// consolidate points with similar attributes by adding masks/weights to the similar point instead of adding a new one.
m_surfaceWeightsList[index].AddSurfaceWeightsIfGreater(masks);
return;
}
else if (m_surfacePositionList[index].GetZ() < position.GetZ())
{
m_pointBounds.AddPoint(position);
m_surfacePositionList.insert(m_surfacePositionList.begin() + index, position);
m_surfaceNormalList.insert(m_surfaceNormalList.begin() + index, normal);
m_surfaceWeightsList.insert(m_surfaceWeightsList.begin() + index, masks);
m_surfaceCreatorIdList.insert(m_surfaceCreatorIdList.begin() + index, entityId);
return;
}
}
// The point wasn't merged and the sort puts it at the end, so just add the point to the end of the list.
m_pointBounds.AddPoint(position);
m_surfacePositionList.emplace_back(position);
m_surfaceNormalList.emplace_back(normal);
m_surfaceWeightsList.emplace_back(masks);
m_surfaceCreatorIdList.emplace_back(entityId);
}
void SurfacePointList::Clear()
{
m_surfacePositionList.clear();
m_surfaceNormalList.clear();
m_surfaceWeightsList.clear();
m_surfaceCreatorIdList.clear();
}
void SurfacePointList::ReserveSpace(size_t maxPointsPerInput)
{
AZ_Assert(
m_surfacePositionList.size() < maxPointsPerInput,
"Trying to reserve space on a list that is already using more points than requested.");
m_surfaceCreatorIdList.reserve(maxPointsPerInput);
m_surfacePositionList.reserve(maxPointsPerInput);
m_surfaceNormalList.reserve(maxPointsPerInput);
m_surfaceWeightsList.reserve(maxPointsPerInput);
}
bool SurfacePointList::IsEmpty() const
{
return m_surfacePositionList.empty();
}
size_t SurfacePointList::GetSize() const
{
return m_surfacePositionList.size();
}
void SurfacePointList::EnumeratePoints(
AZStd::function<bool(const AZ::Vector3&, const AZ::Vector3&, const SurfaceData::SurfaceTagWeights&)>
pointCallback) const
{
for (size_t index = 0; index < m_surfacePositionList.size(); index++)
{
if (!pointCallback(m_surfacePositionList[index], m_surfaceNormalList[index], m_surfaceWeightsList[index]))
{
break;
}
}
}
void SurfacePointList::ModifySurfaceWeights(
const AZ::EntityId& currentEntityId,
AZStd::function<void(const AZ::Vector3& position, SurfaceData::SurfaceTagWeights& surfaceWeights)> modificationWeightCallback)
{
for (size_t index = 0; index < m_surfacePositionList.size(); index++)
{
if (m_surfaceCreatorIdList[index] != currentEntityId)
{
modificationWeightCallback(m_surfacePositionList[index], m_surfaceWeightsList[index]);
}
}
}
AzFramework::SurfaceData::SurfacePoint SurfacePointList::GetHighestSurfacePoint() const
{
AzFramework::SurfaceData::SurfacePoint point;
point.m_position = m_surfacePositionList.front();
point.m_normal = m_surfaceNormalList.front();
point.m_surfaceTags = m_surfaceWeightsList.front().GetSurfaceTagWeightList();
return point;
}
void SurfacePointList::FilterPoints(const SurfaceTagVector& desiredTags)
{
// Filter out any points that don't match our search tags.
// This has to be done after the Surface Modifiers have processed the points, not at point insertion time, because
// Surface Modifiers add tags to existing points.
size_t listSize = m_surfacePositionList.size();
size_t index = 0;
for (; index < listSize; index++)
{
if (!m_surfaceWeightsList[index].HasAnyMatchingTags(desiredTags))
{
break;
}
}
if (index != listSize)
{
size_t next = index + 1;
for (; next < listSize; ++next)
{
if (m_surfaceWeightsList[index].HasAnyMatchingTags(desiredTags))
{
m_surfaceCreatorIdList[index] = m_surfaceCreatorIdList[next];
m_surfacePositionList[index] = m_surfacePositionList[next];
m_surfaceNormalList[index] = m_surfaceNormalList[next];
m_surfaceWeightsList[index] = m_surfaceWeightsList[next];
++index;
}
}
m_surfaceCreatorIdList.resize(index);
m_surfacePositionList.resize(index);
m_surfaceNormalList.resize(index);
m_surfaceWeightsList.resize(index);
}
}
}

@ -190,7 +190,7 @@ namespace UnitTest
for (float x = 0.0f; x < worldSize; x += 1.0f) for (float x = 0.0f; x < worldSize; x += 1.0f)
{ {
AZ::Vector3 queryPosition(x, y, 0.0f); AZ::Vector3 queryPosition(x, y, 0.0f);
points.clear(); points.Clear();
SurfaceData::SurfaceDataSystemRequestBus::Broadcast( SurfaceData::SurfaceDataSystemRequestBus::Broadcast(
&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, queryPosition, filterTags, points); &SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, queryPosition, filterTags, points);

@ -26,7 +26,8 @@ namespace UnitTest
: public AzPhysics::SimulatedBodyComponentRequestsBus::Handler : public AzPhysics::SimulatedBodyComponentRequestsBus::Handler
{ {
public: public:
MockPhysicsWorldBusProvider(const AZ::EntityId& id, AZ::Vector3 inPosition, bool setHitResult, const SurfaceData::SurfacePoint& hitResult) MockPhysicsWorldBusProvider(
const AZ::EntityId& id, AZ::Vector3 inPosition, bool setHitResult, const AzFramework::SurfaceData::SurfacePoint& hitResult)
{ {
AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusConnect(id); AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusConnect(id);
@ -77,51 +78,36 @@ namespace UnitTest
{ {
protected: protected:
// Create a new SurfacePoint with the given fields. // Create a new SurfacePoint with the given fields.
SurfaceData::SurfacePoint CreateSurfacePoint(AZ::EntityId id, AZ::Vector3 position, AZ::Vector3 normal, AZStd::vector<AZStd::pair<AZStd::string, float>> tags) AzFramework::SurfaceData::SurfacePoint CreateSurfacePoint(
AZ::Vector3 position, AZ::Vector3 normal, AZStd::vector<AZStd::pair<AZStd::string, float>> tags)
{ {
SurfaceData::SurfacePoint point; AzFramework::SurfaceData::SurfacePoint point;
point.m_entityId = id;
point.m_position = position; point.m_position = position;
point.m_normal = normal; point.m_normal = normal;
for (auto& tag : tags) for (auto& tag : tags)
{ {
point.m_masks[SurfaceData::SurfaceTag(tag.first)] = tag.second; point.m_surfaceTags.emplace_back(SurfaceData::SurfaceTag(tag.first), tag.second);
} }
return point; return point;
} }
// Compare two surface points. // Compare two surface points.
bool SurfacePointsAreEqual(const SurfaceData::SurfacePoint& lhs, const SurfaceData::SurfacePoint& rhs) bool SurfacePointsAreEqual(
const AZ::Vector3& lhsPosition,
const AZ::Vector3& lhsNormal,
const SurfaceData::SurfaceTagWeights& lhsMasks,
const AzFramework::SurfaceData::SurfacePoint& rhs)
{ {
if ((lhs.m_entityId != rhs.m_entityId) return ((lhsPosition == rhs.m_position)
|| (lhs.m_position != rhs.m_position) && (lhsNormal == rhs.m_normal)
|| (lhs.m_normal != rhs.m_normal) && (lhsMasks.SurfaceWeightsAreEqual(rhs.m_surfaceTags)));
|| (lhs.m_masks.size() != rhs.m_masks.size()))
{
return false;
}
for (auto& mask : lhs.m_masks)
{
auto maskEntry = rhs.m_masks.find(mask.first);
if (maskEntry == rhs.m_masks.end())
{
return false;
}
if (maskEntry->second != mask.second)
{
return false;
}
}
return true;
} }
// Common test function for testing the "Provider" functionality of the component. // Common test function for testing the "Provider" functionality of the component.
// Given a set of tags and an expected output, check to see if the component provides the // Given a set of tags and an expected output, check to see if the component provides the
// expected output point. // expected output point.
void TestSurfaceDataColliderProvider(AZStd::vector<AZStd::string> providerTags, bool pointOnProvider, void TestSurfaceDataColliderProvider(AZStd::vector<AZStd::string> providerTags, bool pointOnProvider,
AZ::Vector3 queryPoint, const SurfaceData::SurfacePoint& expectedOutput) AZ::Vector3 queryPoint, const AzFramework::SurfaceData::SurfacePoint& expectedOutput)
{ {
// This lets our component register with surfaceData successfully. // This lets our component register with surfaceData successfully.
MockSurfaceDataSystem mockSurfaceDataSystem; MockSurfaceDataSystem mockSurfaceDataSystem;
@ -135,8 +121,6 @@ namespace UnitTest
// Create the test entity with the SurfaceDataCollider component and the required physics collider dependency // Create the test entity with the SurfaceDataCollider component and the required physics collider dependency
auto entity = CreateEntity(); auto entity = CreateEntity();
// Initialize our Entity ID to the one passed in on the expectedOutput
entity->SetId(expectedOutput.m_entityId);
// Create the components // Create the components
CreateComponent<MockPhysicsColliderComponent>(entity.get()); CreateComponent<MockPhysicsColliderComponent>(entity.get());
CreateComponent<SurfaceData::SurfaceDataColliderComponent>(entity.get(), config); CreateComponent<SurfaceData::SurfaceDataColliderComponent>(entity.get(), config);
@ -155,17 +139,25 @@ namespace UnitTest
queryPoint, pointList); queryPoint, pointList);
if (pointOnProvider) if (pointOnProvider)
{ {
ASSERT_TRUE(pointList.size() == 1); ASSERT_EQ(pointList.GetSize(), 1);
EXPECT_TRUE(SurfacePointsAreEqual(pointList[0], expectedOutput)); pointList.EnumeratePoints(
[this, expectedOutput](
const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool
{
EXPECT_TRUE(SurfacePointsAreEqual(position, normal, masks, expectedOutput));
return true;
});
} }
else else
{ {
EXPECT_TRUE(pointList.empty()); EXPECT_TRUE(pointList.IsEmpty());
} }
} }
void TestSurfaceDataColliderModifier(AZStd::vector<AZStd::string> modifierTags, void TestSurfaceDataColliderModifier(AZStd::vector<AZStd::string> modifierTags,
const SurfaceData::SurfacePoint& input, bool pointInCollider, const SurfaceData::SurfacePoint& expectedOutput) const AzFramework::SurfaceData::SurfacePoint& input,
bool pointInCollider,
const AzFramework::SurfaceData::SurfacePoint& expectedOutput)
{ {
// This lets our component register with surfaceData successfully. // This lets our component register with surfaceData successfully.
MockSurfaceDataSystem mockSurfaceDataSystem; MockSurfaceDataSystem mockSurfaceDataSystem;
@ -191,11 +183,18 @@ namespace UnitTest
EXPECT_TRUE(modifierHandle != SurfaceData::InvalidSurfaceDataRegistryHandle); EXPECT_TRUE(modifierHandle != SurfaceData::InvalidSurfaceDataRegistryHandle);
// Call ModifySurfacePoints and verify the results // Call ModifySurfacePoints and verify the results
SurfaceData::SurfacePointList pointList; // Add the surface point with a different entity ID than the entity doing the modification, so that the point doesn't get
pointList.emplace_back(input); // filtered out.
SurfaceData::SurfacePointList pointList = { input };
SurfaceData::SurfaceDataModifierRequestBus::Event(modifierHandle, &SurfaceData::SurfaceDataModifierRequestBus::Events::ModifySurfacePoints, pointList); SurfaceData::SurfaceDataModifierRequestBus::Event(modifierHandle, &SurfaceData::SurfaceDataModifierRequestBus::Events::ModifySurfacePoints, pointList);
ASSERT_TRUE(pointList.size() == 1); ASSERT_EQ(pointList.GetSize(), 1);
EXPECT_TRUE(SurfacePointsAreEqual(pointList[0], expectedOutput)); pointList.EnumeratePoints(
[this, expectedOutput](
const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool
{
EXPECT_TRUE(SurfacePointsAreEqual(position, normal, masks, expectedOutput));
return true;
});
} }
}; };
@ -232,7 +231,8 @@ namespace UnitTest
// Set the expected output to an arbitrary entity ID, position, and normal. // Set the expected output to an arbitrary entity ID, position, and normal.
// We'll use this to initialize the mock physics, so the output of the query should match. // We'll use this to initialize the mock physics, so the output of the query should match.
const char* tag = "test_mask"; const char* tag = "test_mask";
SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3::CreateAxisZ(), AzFramework::SurfaceData::SurfacePoint expectedOutput =
CreateSurfacePoint(AZ::Vector3(1.0f), AZ::Vector3::CreateAxisZ(),
{ AZStd::make_pair<AZStd::string, float>(tag, 1.0f) }); { AZStd::make_pair<AZStd::string, float>(tag, 1.0f) });
// Query from the same XY, but one unit higher on Z, just so we can verify that the output returns the collision // Query from the same XY, but one unit higher on Z, just so we can verify that the output returns the collision
@ -248,7 +248,8 @@ namespace UnitTest
// Set the expected output to an arbitrary entity ID, position, and normal. // Set the expected output to an arbitrary entity ID, position, and normal.
// We'll use this to initialize the mock physics. // We'll use this to initialize the mock physics.
const char* tag = "test_mask"; const char* tag = "test_mask";
SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3::CreateAxisZ(), AzFramework::SurfaceData::SurfacePoint expectedOutput =
CreateSurfacePoint(AZ::Vector3(1.0f), AZ::Vector3::CreateAxisZ(),
{ AZStd::make_pair<AZStd::string, float>(tag, 1.0f) }); { AZStd::make_pair<AZStd::string, float>(tag, 1.0f) });
// Query from the same XY, but one unit higher on Z. However, we're also telling our test to provide // Query from the same XY, but one unit higher on Z. However, we're also telling our test to provide
@ -266,9 +267,9 @@ namespace UnitTest
// We'll use this to initialize the mock physics. // We'll use this to initialize the mock physics.
const char* tag1 = "test_mask1"; const char* tag1 = "test_mask1";
const char* tag2 = "test_mask2"; const char* tag2 = "test_mask2";
SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3::CreateAxisZ(), AzFramework::SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(
{ AZStd::make_pair<AZStd::string, float>(tag1, 1.0f), AZ::Vector3(1.0f), AZ::Vector3::CreateAxisZ(),
AZStd::make_pair<AZStd::string, float>(tag2, 1.0f) }); { AZStd::make_pair<AZStd::string, float>(tag1, 1.0f), AZStd::make_pair<AZStd::string, float>(tag2, 1.0f) });
// Query from the same XY, but one unit higher on Z, just so we can verify that the output returns the collision // Query from the same XY, but one unit higher on Z, just so we can verify that the output returns the collision
// result, not the input point. // result, not the input point.
@ -281,11 +282,12 @@ namespace UnitTest
// Verify that for a point inside the collider, the output point contains the correct tag and value. // Verify that for a point inside the collider, the output point contains the correct tag and value.
// Set arbitrary input data // Set arbitrary input data
SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), {}); AzFramework::SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::Vector3(1.0f), AZ::Vector3(0.0f), {});
// Output should match the input, but with an added tag / value // Output should match the input, but with an added tag / value
const char* tag = "test_mask"; const char* tag = "test_mask";
SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(input.m_entityId, input.m_position, input.m_normal, AzFramework::SurfaceData::SurfacePoint expectedOutput =
{ AZStd::make_pair<AZStd::string, float>(tag, 1.0f) }); CreateSurfacePoint(input.m_position, input.m_normal,
{ AZStd::make_pair<AZStd::string, float>(tag, 1.0f) });
constexpr bool pointInCollider = true; constexpr bool pointInCollider = true;
TestSurfaceDataColliderModifier({ tag }, input, pointInCollider, expectedOutput); TestSurfaceDataColliderModifier({ tag }, input, pointInCollider, expectedOutput);
@ -296,10 +298,10 @@ namespace UnitTest
// Verify that for a point outside the collider, the output point contains no tags / values. // Verify that for a point outside the collider, the output point contains no tags / values.
// Set arbitrary input data // Set arbitrary input data
SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), {}); AzFramework::SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::Vector3(1.0f), AZ::Vector3(0.0f), {});
// Output should match the input - no extra tags / values should be added. // Output should match the input - no extra tags / values should be added.
const char* tag = "test_mask"; const char* tag = "test_mask";
SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(input.m_entityId, input.m_position, input.m_normal, {}); AzFramework::SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(input.m_position, input.m_normal, {});
constexpr bool pointInCollider = true; constexpr bool pointInCollider = true;
TestSurfaceDataColliderModifier({ tag }, input, !pointInCollider, expectedOutput); TestSurfaceDataColliderModifier({ tag }, input, !pointInCollider, expectedOutput);
@ -310,11 +312,12 @@ namespace UnitTest
// Verify that if the component has multiple tags, all of them get put on the output with the same value. // Verify that if the component has multiple tags, all of them get put on the output with the same value.
// Set arbitrary input data // Set arbitrary input data
SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), {}); AzFramework::SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::Vector3(1.0f), AZ::Vector3(0.0f), {});
// Output should match the input, but with two added tags // Output should match the input, but with two added tags
const char* tag1 = "test_mask1"; const char* tag1 = "test_mask1";
const char* tag2 = "test_mask2"; const char* tag2 = "test_mask2";
SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(input.m_entityId, input.m_position, input.m_normal, AzFramework::SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(
input.m_position, input.m_normal,
{ AZStd::make_pair<AZStd::string, float>(tag1, 1.0f), AZStd::make_pair<AZStd::string, float>(tag2, 1.0f) }); { AZStd::make_pair<AZStd::string, float>(tag1, 1.0f), AZStd::make_pair<AZStd::string, float>(tag2, 1.0f) });
constexpr bool pointInCollider = true; constexpr bool pointInCollider = true;
@ -328,11 +331,13 @@ namespace UnitTest
// Set arbitrary input data // Set arbitrary input data
const char* preservedTag = "preserved_tag"; const char* preservedTag = "preserved_tag";
SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), AzFramework::SurfaceData::SurfacePoint input =
CreateSurfacePoint(AZ::Vector3(1.0f), AZ::Vector3(0.0f),
{ AZStd::make_pair<AZStd::string, float>(preservedTag, 1.0f) }); { AZStd::make_pair<AZStd::string, float>(preservedTag, 1.0f) });
// Output should match the input, but with two added tags // Output should match the input, but with two added tags
const char* modifierTag = "modifier_tag"; const char* modifierTag = "modifier_tag";
SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(input.m_entityId, input.m_position, input.m_normal, AzFramework::SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(
input.m_position, input.m_normal,
{ AZStd::make_pair<AZStd::string, float>(preservedTag, 1.0f), AZStd::make_pair<AZStd::string, float>(modifierTag, 1.0f) }); { AZStd::make_pair<AZStd::string, float>(preservedTag, 1.0f), AZStd::make_pair<AZStd::string, float>(modifierTag, 1.0f) });
constexpr bool pointInCollider = true; constexpr bool pointInCollider = true;
@ -349,10 +354,12 @@ namespace UnitTest
float inputValue = 0.25f; float inputValue = 0.25f;
// Set arbitrary input data // Set arbitrary input data
SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), AzFramework::SurfaceData::SurfacePoint input =
CreateSurfacePoint(AZ::Vector3(1.0f), AZ::Vector3(0.0f),
{ AZStd::make_pair<AZStd::string, float>(tag, inputValue) }); { AZStd::make_pair<AZStd::string, float>(tag, inputValue) });
// Output should match the input, except that the value on the tag gets the higher modifier value // Output should match the input, except that the value on the tag gets the higher modifier value
SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(input.m_entityId, input.m_position, input.m_normal, AzFramework::SurfaceData::SurfacePoint expectedOutput =
CreateSurfacePoint(input.m_position, input.m_normal,
{ AZStd::make_pair<AZStd::string, float>(tag, 1.0f) }); { AZStd::make_pair<AZStd::string, float>(tag, 1.0f) });
constexpr bool pointInCollider = true; constexpr bool pointInCollider = true;

@ -54,7 +54,7 @@ class MockSurfaceProvider
} }
private: private:
AZStd::unordered_map<AZStd::pair<float, float>, SurfaceData::SurfacePointList> m_GetSurfacePoints; AZStd::unordered_map<AZStd::pair<float, float>, AZStd::vector<AzFramework::SurfaceData::SurfacePoint>> m_GetSurfacePoints;
SurfaceData::SurfaceTagVector m_tags; SurfaceData::SurfaceTagVector m_tags;
ProviderType m_providerType; ProviderType m_providerType;
AZ::EntityId m_id; AZ::EntityId m_id;
@ -71,15 +71,16 @@ class MockSurfaceProvider
{ {
for (float x = start.GetX(); x < end.GetX(); x += stepSize.GetX()) for (float x = start.GetX(); x < end.GetX(); x += stepSize.GetX())
{ {
SurfaceData::SurfacePointList points; AZStd::vector<AzFramework::SurfaceData::SurfacePoint> points;
for (float z = start.GetZ(); z < end.GetZ(); z += stepSize.GetZ()) for (float z = start.GetZ(); z < end.GetZ(); z += stepSize.GetZ())
{ {
SurfaceData::SurfacePoint point; AzFramework::SurfaceData::SurfacePoint point;
point.m_entityId = m_id;
point.m_position = AZ::Vector3(x, y, z); point.m_position = AZ::Vector3(x, y, z);
point.m_normal = AZ::Vector3::CreateAxisZ(); point.m_normal = AZ::Vector3::CreateAxisZ();
AddMaxValueForMasks(point.m_masks, m_tags, 1.0f); for (auto& tag : m_tags)
{
point.m_surfaceTags.emplace_back(tag, 1.0f);
}
points.push_back(point); points.push_back(point);
} }
m_GetSurfacePoints[AZStd::pair<float, float>(x, y)] = points; m_GetSurfacePoints[AZStd::pair<float, float>(x, y)] = points;
@ -150,7 +151,8 @@ class MockSurfaceProvider
{ {
for (auto& point : surfacePoints->second) for (auto& point : surfacePoints->second)
{ {
surfacePointList.push_back(point); SurfaceData::SurfaceTagWeights weights(point.m_surfaceTags);
surfacePointList.AddSurfacePoint(m_id, point.m_position, point.m_normal, weights);
} }
} }
} }
@ -159,16 +161,17 @@ class MockSurfaceProvider
// SurfaceDataModifierRequestBus // SurfaceDataModifierRequestBus
void ModifySurfacePoints(SurfaceData::SurfacePointList& surfacePointList) const override void ModifySurfacePoints(SurfaceData::SurfacePointList& surfacePointList) const override
{ {
for (auto& point : surfacePointList) surfacePointList.ModifySurfaceWeights(
{ AZ::EntityId(),
auto surfacePoints = m_GetSurfacePoints.find(AZStd::make_pair(point.m_position.GetX(), point.m_position.GetY())); [this](const AZ::Vector3& position, SurfaceData::SurfaceTagWeights& weights)
if (surfacePoints != m_GetSurfacePoints.end())
{ {
AddMaxValueForMasks(point.m_masks, m_tags, 1.0f); auto surfacePoints = m_GetSurfacePoints.find(AZStd::make_pair(position.GetX(), position.GetY()));
}
}
if (surfacePoints != m_GetSurfacePoints.end())
{
weights.AddSurfaceWeightsIfGreater(m_tags, 1.0f);
}
});
} }
SurfaceData::SurfaceDataRegistryHandle m_providerHandle = SurfaceData::InvalidSurfaceDataRegistryHandle; SurfaceData::SurfaceDataRegistryHandle m_providerHandle = SurfaceData::InvalidSurfaceDataRegistryHandle;
@ -205,42 +208,49 @@ public:
} }
void CompareSurfacePointListWithGetSurfacePoints( void CompareSurfacePointListWithGetSurfacePoints(
const AZStd::vector<AZ::Vector3>& queryPositions, SurfaceData::SurfacePointLists surfacePointLists, const AZStd::vector<AZ::Vector3>& queryPositions, SurfaceData::SurfacePointLists& surfacePointLists,
const SurfaceData::SurfaceTagVector& testTags) const SurfaceData::SurfaceTagVector& testTags)
{ {
SurfaceData::SurfacePointLists singleQueryPointLists; AZStd::vector<AzFramework::SurfaceData::SurfacePoint> singleQueryResults;
for (auto& queryPosition : queryPositions) for (auto& queryPosition : queryPositions)
{ {
SurfaceData::SurfacePointList tempSingleQueryPointList; SurfaceData::SurfacePointList tempSingleQueryPointList;
SurfaceData::SurfaceDataSystemRequestBus::Broadcast( SurfaceData::SurfaceDataSystemRequestBus::Broadcast(
&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, queryPosition, testTags, tempSingleQueryPointList); &SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, queryPosition, testTags, tempSingleQueryPointList);
singleQueryPointLists.push_back(tempSingleQueryPointList); tempSingleQueryPointList.EnumeratePoints(
[&singleQueryResults](
const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool
{
AzFramework::SurfaceData::SurfacePoint point;
point.m_position = position;
point.m_normal = normal;
point.m_surfaceTags = masks.GetSurfaceTagWeightList();
singleQueryResults.emplace_back(AZStd::move(point));
return true;
});
} }
// Verify the two point lists are the same size, then verify that each point in each list is equal. // Verify that each point in each list is equal.
ASSERT_EQ(singleQueryPointLists.size(), surfacePointLists.size()); AzFramework::SurfaceData::SurfacePoint* singleQueryPoint = singleQueryResults.begin();
for (size_t listIndex = 0; listIndex < surfacePointLists.size(); listIndex++) for (size_t listIndex = 0; listIndex < surfacePointLists.size(); listIndex++)
{ {
auto& surfacePointList = surfacePointLists[listIndex]; auto& surfacePointList = surfacePointLists[listIndex];
auto& singleQueryPointList = singleQueryPointLists[listIndex]; surfacePointList.EnumeratePoints(
[&singleQueryPoint, singleQueryResults](
ASSERT_EQ(singleQueryPointList.size(), surfacePointList.size()); const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool
for (size_t index = 0; index < surfacePointList.size(); index++)
{
SurfaceData::SurfacePoint& point1 = surfacePointList[index];
SurfaceData::SurfacePoint& point2 = singleQueryPointList[index];
EXPECT_EQ(point1.m_entityId, point2.m_entityId);
EXPECT_EQ(point1.m_position, point2.m_position);
EXPECT_EQ(point1.m_normal, point2.m_normal);
ASSERT_EQ(point1.m_masks.size(), point2.m_masks.size());
for (auto& mask : point1.m_masks)
{ {
EXPECT_EQ(mask.second, point2.m_masks[mask.first]); EXPECT_NE(singleQueryPoint, singleQueryResults.end());
}
} EXPECT_EQ(position, singleQueryPoint->m_position);
EXPECT_EQ(normal, singleQueryPoint->m_normal);
EXPECT_TRUE(masks.SurfaceWeightsAreEqual(singleQueryPoint->m_surfaceTags));
++singleQueryPoint;
return true;
});
} }
EXPECT_EQ(singleQueryPoint, singleQueryResults.end());
} }
@ -488,13 +498,18 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion)
// We *could* check every mask as well for completeness, but that seems like overkill. // We *could* check every mask as well for completeness, but that seems like overkill.
for (auto& pointList : availablePointsPerPosition) for (auto& pointList : availablePointsPerPosition)
{ {
EXPECT_EQ(pointList.size(), 2); EXPECT_EQ(pointList.GetSize(), 2);
EXPECT_EQ(pointList[0].m_position.GetZ(), 4.0f); float expectedZ = 4.0f;
EXPECT_EQ(pointList[1].m_position.GetZ(), 0.0f); pointList.EnumeratePoints(
for (auto& point : pointList) [providerTags,
{ &expectedZ](const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& normal,
EXPECT_EQ(point.m_masks.size(), providerTags.size()); const SurfaceData::SurfaceTagWeights& masks) -> bool
} {
EXPECT_EQ(position.GetZ(), expectedZ);
EXPECT_EQ(masks.GetSize(), providerTags.size());
expectedZ = (expectedZ == 4.0f) ? 0.0f : 4.0f;
return true;
});
} }
} }
@ -523,7 +538,7 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_NoMatchingMas
// any of the masks from our mock surface provider. // any of the masks from our mock surface provider.
for (auto& queryPosition : availablePointsPerPosition) for (auto& queryPosition : availablePointsPerPosition)
{ {
EXPECT_TRUE(queryPosition.empty()); EXPECT_TRUE(queryPosition.IsEmpty());
} }
} }
@ -551,7 +566,7 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_NoMatchingReg
// our surface provider. // our surface provider.
for (auto& pointList : availablePointsPerPosition) for (auto& pointList : availablePointsPerPosition)
{ {
EXPECT_TRUE(pointList.empty()); EXPECT_TRUE(pointList.IsEmpty());
} }
} }
@ -601,14 +616,17 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_ProviderModif
// and each point should have both the "test_surface1" and "test_surface2" tag. // and each point should have both the "test_surface1" and "test_surface2" tag.
for (auto& pointList : availablePointsPerPosition) for (auto& pointList : availablePointsPerPosition)
{ {
EXPECT_EQ(pointList.size(), 2); EXPECT_EQ(pointList.GetSize(), 2);
float expectedZ = 4.0f; float expectedZ = 4.0f;
for (auto& point : pointList) pointList.EnumeratePoints(
{ [&expectedZ](const AZ::Vector3& position,
EXPECT_EQ(point.m_position.GetZ(), expectedZ); [[maybe_unused]] const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool
EXPECT_EQ(point.m_masks.size(), 2); {
expectedZ = (expectedZ == 4.0f) ? 0.0f : 4.0f; EXPECT_EQ(position.GetZ(), expectedZ);
} EXPECT_EQ(masks.GetSize(), 2);
expectedZ = (expectedZ == 4.0f) ? 0.0f : 4.0f;
return true;
});
} }
} }
} }
@ -648,14 +666,20 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_SimilarPoints
// should have both surface tags on them. // should have both surface tags on them.
for (auto& pointList : availablePointsPerPosition) for (auto& pointList : availablePointsPerPosition)
{ {
EXPECT_EQ(pointList.size(), 2); EXPECT_EQ(pointList.GetSize(), 2);
float expectedZ = 4.0005f; float expectedZ = 4.0f;
for (auto& point : pointList) pointList.EnumeratePoints(
{ [&expectedZ](
EXPECT_EQ(point.m_position.GetZ(), expectedZ); const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& normal,
EXPECT_EQ(point.m_masks.size(), 2); const SurfaceData::SurfaceTagWeights& masks) -> bool
expectedZ = (expectedZ == 4.0005f) ? 0.0005f : 4.0005f; {
} // Similar points get merged, but there's no guarantee which value will be kept, so we set our comparison tolerance
// high enough to allow both x.0 and x.0005 to pass.
EXPECT_NEAR(position.GetZ(), expectedZ, 0.001f);
EXPECT_EQ(masks.GetSize(), 2);
expectedZ = (expectedZ == 4.0f) ? 0.0f : 4.0f;
return true;
});
} }
} }
@ -693,11 +717,14 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_DissimilarPoi
// because the points are far enough apart that they won't merge. // because the points are far enough apart that they won't merge.
for (auto& pointList : availablePointsPerPosition) for (auto& pointList : availablePointsPerPosition)
{ {
EXPECT_EQ(pointList.size(), 4); EXPECT_EQ(pointList.GetSize(), 4);
for (auto& point : pointList) pointList.EnumeratePoints(
{ []([[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& normal,
EXPECT_EQ(point.m_masks.size(), 1); const SurfaceData::SurfaceTagWeights& masks) -> bool
} {
EXPECT_EQ(masks.GetSize(), 1);
return true;
});
} }
} }

@ -19,6 +19,7 @@ set(FILES
Include/SurfaceData/Utility/SurfaceDataUtility.h Include/SurfaceData/Utility/SurfaceDataUtility.h
Source/SurfaceDataSystemComponent.cpp Source/SurfaceDataSystemComponent.cpp
Source/SurfaceDataSystemComponent.h Source/SurfaceDataSystemComponent.h
Source/SurfaceDataTypes.cpp
Source/SurfaceTag.cpp Source/SurfaceTag.cpp
Source/Components/SurfaceDataColliderComponent.cpp Source/Components/SurfaceDataColliderComponent.cpp
Source/Components/SurfaceDataColliderComponent.h Source/Components/SurfaceDataColliderComponent.h

@ -204,16 +204,33 @@ void GetDetailSurfaceForMaterial(inout DetailSurface surface, uint materialId, f
{ {
TerrainSrg::DetailMaterialData detailMaterialData = TerrainSrg::m_detailMaterialData[materialId]; TerrainSrg::DetailMaterialData detailMaterialData = TerrainSrg::m_detailMaterialData[materialId];
float3x3 uvTransform = (float3x3)detailMaterialData.m_uvTransform;
float2 transformedUv = mul(uvTransform, float3(uv, 1.0)).xy;
// With different materials in the quad, we can't rely on ddx/ddy of the transformed uv because
// the materials may have different uv transforms. This would create visible seams where the wrong
// mip was being used. Instead, manually calculate the transformed ddx/ddy using the ddx/ddy of the
// original uv.
float2 uvDdx = ddx(uv); float2 uvDdx = ddx(uv);
float2 uvDdy = ddy(uv); float2 uvDdy = ddy(uv);
surface.m_color = GetDetailColor(detailMaterialData, uv, uvDdx, uvDdy); float2 uvX = uv + uvDdx;
surface.m_normal = GetDetailNormal(detailMaterialData, uv, uvDdx, uvDdy); float2 uvY = uv + uvDdy;
surface.m_roughness = GetDetailRoughness(detailMaterialData, uv, uvDdx, uvDdy);
surface.m_specularF0 = GetDetailSpecularF0(detailMaterialData, uv, uvDdx, uvDdy); float2 transformedUvX = mul(uvTransform, float3(uvX, 1.0)).xy;
surface.m_metalness = GetDetailMetalness(detailMaterialData, uv, uvDdx, uvDdy); float2 transformedUvY = mul(uvTransform, float3(uvY, 1.0)).xy;
surface.m_occlusion = GetDetailOcclusion(detailMaterialData, uv, uvDdx, uvDdy);
surface.m_height = GetDetailHeight(detailMaterialData, uv, uvDdx, uvDdy); float2 transformedUvDdx = transformedUvX - transformedUv;
float2 transformedUvDdy = transformedUvY - transformedUv;
surface.m_color = GetDetailColor(detailMaterialData, transformedUv, transformedUvDdx, transformedUvDdy);
surface.m_normal = GetDetailNormal(detailMaterialData, transformedUv, transformedUvDdx, transformedUvDdy);
surface.m_roughness = GetDetailRoughness(detailMaterialData, transformedUv, transformedUvDdx, transformedUvDdy);
surface.m_specularF0 = GetDetailSpecularF0(detailMaterialData, transformedUv, transformedUvDdx, transformedUvDdy);
surface.m_metalness = GetDetailMetalness(detailMaterialData, transformedUv, transformedUvDdx, transformedUvDdy);
surface.m_occlusion = GetDetailOcclusion(detailMaterialData, transformedUv, transformedUvDdx, transformedUvDdy);
surface.m_height = GetDetailHeight(detailMaterialData, transformedUv, transformedUvDdx, transformedUvDdy);
} }
// Debugs the detail material by choosing a random color per material ID and rendering it without blending. // Debugs the detail material by choosing a random color per material ID and rendering it without blending.
@ -279,7 +296,7 @@ bool GetDetailSurface(inout DetailSurface surface, float2 detailMaterialIdCoord,
float2 textureSize; float2 textureSize;
TerrainSrg::m_detailMaterialIdImage.GetDimensions(textureSize.x, textureSize.y); TerrainSrg::m_detailMaterialIdImage.GetDimensions(textureSize.x, textureSize.y);
// The detail material id texture wraps since the "center" point can be anywhere in the texture, so mod by texturesize // The detail material id texture wraps since the "center" point can be anywhere in the texture, so mod by textureSize
int2 detailMaterialIdTopLeft = ((int2(detailMaterialIdCoord) % textureSize) + textureSize) % textureSize; int2 detailMaterialIdTopLeft = ((int2(detailMaterialIdCoord) % textureSize) + textureSize) % textureSize;
int2 detailMaterialIdBottomRight = (detailMaterialIdTopLeft + 1) % textureSize; int2 detailMaterialIdBottomRight = (detailMaterialIdTopLeft + 1) % textureSize;

@ -159,25 +159,13 @@ namespace Terrain
const bool isHole = !isTerrainValidAtPoint; const bool isHole = !isTerrainValidAtPoint;
SurfaceData::SurfacePoint point; SurfaceData::SurfaceTagWeights weights(terrainSurfacePoint.m_surfaceTags);
point.m_entityId = GetEntityId();
point.m_position = terrainSurfacePoint.m_position;
point.m_normal = terrainSurfacePoint.m_normal;
// Preallocate enough space for all of our terrain's surface tags, plus the default "terrain" / "terrainHole" tag.
point.m_masks.reserve(terrainSurfacePoint.m_surfaceTags.size() + 1);
// Add all of the surface tags that the terrain has at this point.
for (auto& tag : terrainSurfacePoint.m_surfaceTags)
{
point.m_masks[tag.m_surfaceType] = tag.m_weight;
}
// Always add a "terrain" or "terrainHole" tag. // Always add a "terrain" or "terrainHole" tag.
const AZ::Crc32 terrainTag = isHole ? Constants::s_terrainHoleTagCrc : Constants::s_terrainTagCrc; const AZ::Crc32 terrainTag = isHole ? Constants::s_terrainHoleTagCrc : Constants::s_terrainTagCrc;
point.m_masks[terrainTag] = 1.0f; weights.AddSurfaceTagWeight(terrainTag, 1.0f);
surfacePointList.push_back(AZStd::move(point)); surfacePointList.AddSurfacePoint(GetEntityId(), terrainSurfacePoint.m_position, terrainSurfacePoint.m_normal, weights);
} }
AZ::Aabb TerrainSurfaceDataSystemComponent::GetSurfaceAabb() const AZ::Aabb TerrainSurfaceDataSystemComponent::GetSurfaceAabb() const

@ -15,6 +15,8 @@
#include <Atom/RPI.Public/Image/ImageSystemInterface.h> #include <Atom/RPI.Public/Image/ImageSystemInterface.h>
#include <Atom/RPI.Public/Shader/ShaderSystemInterface.h> #include <Atom/RPI.Public/Shader/ShaderSystemInterface.h>
#include <Atom/Utils/MaterialUtils.h>
#include <SurfaceData/SurfaceDataSystemRequestBus.h> #include <SurfaceData/SurfaceDataSystemRequestBus.h>
namespace Terrain namespace Terrain
@ -56,6 +58,13 @@ namespace Terrain
static const char* const HeightFactor("parallax.factor"); static const char* const HeightFactor("parallax.factor");
static const char* const HeightOffset("parallax.offset"); static const char* const HeightOffset("parallax.offset");
static const char* const HeightBlendFactor("parallax.blendFactor"); static const char* const HeightBlendFactor("parallax.blendFactor");
static const char* const UvCenter("uv.center");
static const char* const UvScale("uv.scale");
static const char* const UvTileU("uv.tileU");
static const char* const UvTileV("uv.tileV");
static const char* const UvOffsetU("uv.offsetU");
static const char* const UvOffsetV("uv.offsetV");
static const char* const UvRotateDegrees("uv.rotateDegrees");
} }
namespace TerrainSrgInputs namespace TerrainSrgInputs
@ -548,6 +557,36 @@ namespace Terrain
applyProperty(HeightOffset, shaderData.m_heightOffset); applyProperty(HeightOffset, shaderData.m_heightOffset);
applyProperty(HeightBlendFactor, shaderData.m_heightBlendFactor); applyProperty(HeightBlendFactor, shaderData.m_heightBlendFactor);
AZ::Render::UvTransformDescriptor transformDescriptor;
applyProperty(UvCenter, transformDescriptor.m_center);
applyProperty(UvScale, transformDescriptor.m_scale);
applyProperty(UvTileU, transformDescriptor.m_scaleX);
applyProperty(UvTileV, transformDescriptor.m_scaleY);
applyProperty(UvOffsetU, transformDescriptor.m_translateX);
applyProperty(UvOffsetV, transformDescriptor.m_translateY);
applyProperty(UvRotateDegrees, transformDescriptor.m_rotateDegrees);
AZStd::array<AZ::Render::TransformType, 3> order =
{
AZ::Render::TransformType::Rotate,
AZ::Render::TransformType::Translate,
AZ::Render::TransformType::Scale,
};
AZ::Matrix3x3 uvTransformMatrix = AZ::Render::CreateUvTransformMatrix(transformDescriptor, order);
uvTransformMatrix.GetRow(0).StoreToFloat3(&shaderData.m_uvTransform[0]);
uvTransformMatrix.GetRow(1).StoreToFloat3(&shaderData.m_uvTransform[4]);
uvTransformMatrix.GetRow(2).StoreToFloat3(&shaderData.m_uvTransform[8]);
// Store a hash of the matrix in element in an unused portion for quick comparisons in the shader
size_t hash64 = 0;
for (float value : shaderData.m_uvTransform)
{
AZStd::hash_combine(hash64, value);
}
uint32_t hash32 = uint32_t((hash64 ^ (hash64 >> 32)) & 0xFFFFFFFF);
shaderData.m_uvTransform[3] = *reinterpret_cast<float*>(&hash32);
m_detailMaterialBufferNeedsUpdate = true; m_detailMaterialBufferNeedsUpdate = true;
} }

@ -77,7 +77,7 @@ namespace Terrain
struct DetailMaterialShaderData struct DetailMaterialShaderData
{ {
// Uv // Uv (data is 3x3, padding each row for explicit alignment)
AZStd::array<float, 12> m_uvTransform AZStd::array<float, 12> m_uvTransform
{ {
1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0,

@ -56,12 +56,12 @@ namespace Vegetation
ClaimHandle m_handle; ClaimHandle m_handle;
AZ::Vector3 m_position; AZ::Vector3 m_position;
AZ::Vector3 m_normal; AZ::Vector3 m_normal;
SurfaceData::SurfaceTagWeightMap m_masks; SurfaceData::SurfaceTagWeights m_masks;
}; };
struct ClaimContext struct ClaimContext
{ {
SurfaceData::SurfaceTagWeightMap m_masks; SurfaceData::SurfaceTagWeights m_masks;
AZStd::vector<ClaimPoint> m_availablePoints; AZStd::vector<ClaimPoint> m_availablePoints;
AZStd::function<bool(const ClaimPoint&, const InstanceData&)> m_existedCallback; AZStd::function<bool(const ClaimPoint&, const InstanceData&)> m_existedCallback;
AZStd::function<void(const ClaimPoint&, const InstanceData&)> m_createdCallback; AZStd::function<void(const ClaimPoint&, const InstanceData&)> m_createdCallback;

@ -34,7 +34,7 @@ namespace Vegetation
AZ::Quaternion m_rotation = AZ::Quaternion::CreateIdentity(); AZ::Quaternion m_rotation = AZ::Quaternion::CreateIdentity();
AZ::Quaternion m_alignment = AZ::Quaternion::CreateIdentity(); AZ::Quaternion m_alignment = AZ::Quaternion::CreateIdentity();
float m_scale = 1.0f; float m_scale = 1.0f;
SurfaceData::SurfaceTagWeightMap m_masks; //[LY-90908] remove when surface mask filtering is done in area SurfaceData::SurfaceTagWeights m_masks; //[LY-90908] remove when surface mask filtering is done in area
DescriptorPtr m_descriptorPtr; DescriptorPtr m_descriptorPtr;
// Determine if two different sets of instance data are similar enough to be considered the same when placing // Determine if two different sets of instance data are similar enough to be considered the same when placing

@ -1091,7 +1091,7 @@ namespace Vegetation
const float vegStep = sectorSizeInMeters / static_cast<float>(sectorDensity); const float vegStep = sectorSizeInMeters / static_cast<float>(sectorDensity);
//build a free list of all points in the sector for areas to consume //build a free list of all points in the sector for areas to consume
sectorInfo.m_baseContext.m_masks.clear(); sectorInfo.m_baseContext.m_masks.Clear();
sectorInfo.m_baseContext.m_availablePoints.clear(); sectorInfo.m_baseContext.m_availablePoints.clear();
sectorInfo.m_baseContext.m_availablePoints.reserve(sectorDensity * sectorDensity); sectorInfo.m_baseContext.m_availablePoints.reserve(sectorDensity * sectorDensity);
@ -1127,16 +1127,19 @@ namespace Vegetation
uint claimIndex = 0; uint claimIndex = 0;
for (auto& availablePoints : availablePointsPerPosition) for (auto& availablePoints : availablePointsPerPosition)
{ {
for (auto& surfacePoint : availablePoints) availablePoints.EnumeratePoints(
{ [this, &sectorInfo,
sectorInfo.m_baseContext.m_availablePoints.push_back(); &claimIndex](const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool
ClaimPoint& claimPoint = sectorInfo.m_baseContext.m_availablePoints.back(); {
claimPoint.m_handle = CreateClaimHandle(sectorInfo, ++claimIndex); sectorInfo.m_baseContext.m_availablePoints.push_back();
claimPoint.m_position = surfacePoint.m_position; ClaimPoint& claimPoint = sectorInfo.m_baseContext.m_availablePoints.back();
claimPoint.m_normal = surfacePoint.m_normal; claimPoint.m_handle = CreateClaimHandle(sectorInfo, ++claimIndex);
claimPoint.m_masks = surfacePoint.m_masks; claimPoint.m_position = position;
SurfaceData::AddMaxValueForMasks(sectorInfo.m_baseContext.m_masks, surfacePoint.m_masks); claimPoint.m_normal = normal;
} claimPoint.m_masks = masks;
sectorInfo.m_baseContext.m_masks.AddSurfaceWeightsIfGreater(masks);
return true;
});
} }
} }

@ -306,31 +306,40 @@ namespace Vegetation
m_surfaceTagsToSnapToCombined.clear(); m_surfaceTagsToSnapToCombined.clear();
m_surfaceTagsToSnapToCombined.reserve( m_surfaceTagsToSnapToCombined.reserve(
m_configuration.m_surfaceTagsToSnapTo.size() + m_configuration.m_surfaceTagsToSnapTo.size() +
instanceData.m_masks.size()); instanceData.m_masks.GetSize());
m_surfaceTagsToSnapToCombined.insert(m_surfaceTagsToSnapToCombined.end(), m_surfaceTagsToSnapToCombined.insert(m_surfaceTagsToSnapToCombined.end(),
m_configuration.m_surfaceTagsToSnapTo.begin(), m_configuration.m_surfaceTagsToSnapTo.end()); m_configuration.m_surfaceTagsToSnapTo.begin(), m_configuration.m_surfaceTagsToSnapTo.end());
for (const auto& maskPair : instanceData.m_masks) instanceData.m_masks.EnumerateWeights(
{ [this](AZ::Crc32 surfaceType, [[maybe_unused]] float weight)
m_surfaceTagsToSnapToCombined.push_back(maskPair.first); {
} m_surfaceTagsToSnapToCombined.push_back(surfaceType);
return true;
});
//get the intersection data at the new position //get the intersection data at the new position
m_points.clear(); m_points.Clear();
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, instanceData.m_position, m_surfaceTagsToSnapToCombined, m_points); SurfaceData::SurfaceDataSystemRequestBus::Broadcast(&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, instanceData.m_position, m_surfaceTagsToSnapToCombined, m_points);
if (!m_points.empty())
{ // Get the point with the closest distance from the new position in case there are multiple intersections at different or
//sort the intersection data by distance from the new position in case there are multiple intersections at different or unrelated heights // unrelated heights
AZStd::sort(m_points.begin(), m_points.end(), [&instanceData](const SurfaceData::SurfacePoint& a, const SurfaceData::SurfacePoint& b) float closestPointDistanceSq = AZStd::numeric_limits<float>::max();
AZ::Vector3 originalInstanceDataPosition = instanceData.m_position;
m_points.EnumeratePoints(
[&instanceData, originalInstanceDataPosition, &closestPointDistanceSq](
const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool
{ {
return a.m_position.GetDistanceSq(instanceData.m_position) < b.m_position.GetDistanceSq(instanceData.m_position); float distanceSq = position.GetDistanceSq(originalInstanceDataPosition);
if (distanceSq < closestPointDistanceSq)
{
instanceData.m_position = position;
instanceData.m_normal = normal;
instanceData.m_masks = masks;
closestPointDistanceSq = distanceSq;
}
return true;
}); });
instanceData.m_position = m_points[0].m_position;
instanceData.m_normal = m_points[0].m_normal;
instanceData.m_masks = m_points[0].m_masks;
}
} }
instanceData.m_position.SetZ(instanceData.m_position.GetZ() + delta.GetZ()); instanceData.m_position.SetZ(instanceData.m_position.GetZ() + delta.GetZ());

@ -416,9 +416,9 @@ namespace Vegetation
AZ_PROFILE_FUNCTION(Entity); AZ_PROFILE_FUNCTION(Entity);
//reject entire spawner if there are inclusion tags to consider that don't exist in the context //reject entire spawner if there are inclusion tags to consider that don't exist in the context
if (SurfaceData::HasValidTags(context.m_masks) && if (context.m_masks.HasValidTags() &&
SurfaceData::HasValidTags(m_inclusiveTagsToConsider) && SurfaceData::HasValidTags(m_inclusiveTagsToConsider) &&
!SurfaceData::HasMatchingTags(context.m_masks, m_inclusiveTagsToConsider)) !context.m_masks.HasAnyMatchingTags(m_inclusiveTagsToConsider))
{ {
VEG_PROFILE_METHOD(DebugNotificationBus::TryQueueBroadcast(&DebugNotificationBus::Events::MarkAreaRejectedByMask, GetEntityId())); VEG_PROFILE_METHOD(DebugNotificationBus::TryQueueBroadcast(&DebugNotificationBus::Events::MarkAreaRejectedByMask, GetEntityId()));
return; return;

@ -210,26 +210,38 @@ namespace Vegetation
float lowerZDistanceRange = useOverrides ? instanceData.m_descriptorPtr->m_surfaceTagDistance.m_lowerDistanceInMeters : m_configuration.m_lowerDistance; float lowerZDistanceRange = useOverrides ? instanceData.m_descriptorPtr->m_surfaceTagDistance.m_lowerDistanceInMeters : m_configuration.m_lowerDistance;
float upperZDistanceRange = useOverrides ? instanceData.m_descriptorPtr->m_surfaceTagDistance.m_upperDistanceInMeters : m_configuration.m_upperDistance; float upperZDistanceRange = useOverrides ? instanceData.m_descriptorPtr->m_surfaceTagDistance.m_upperDistanceInMeters : m_configuration.m_upperDistance;
bool passesFilter = false;
if (!surfaceTagsToCompare.empty()) if (!surfaceTagsToCompare.empty())
{ {
m_points.clear(); m_points.Clear();
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, instanceData.m_position, surfaceTagsToCompare, m_points); SurfaceData::SurfaceDataSystemRequestBus::Broadcast(&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, instanceData.m_position, surfaceTagsToCompare, m_points);
float instanceZ = instanceData.m_position.GetZ(); float instanceZ = instanceData.m_position.GetZ();
for (auto& point : m_points) m_points.EnumeratePoints(
{ [instanceZ, lowerZDistanceRange, upperZDistanceRange, &passesFilter](
float pointZ = point.m_position.GetZ(); const AZ::Vector3& position,
float zDistance = instanceZ - pointZ; [[maybe_unused]] const AZ::Vector3& normal, [[maybe_unused]] const SurfaceData::SurfaceTagWeights& masks) -> bool
if (lowerZDistanceRange <= zDistance && zDistance <= upperZDistanceRange)
{ {
float pointZ = position.GetZ();
float zDistance = instanceZ - pointZ;
if (lowerZDistanceRange <= zDistance && zDistance <= upperZDistanceRange)
{
passesFilter = true;
return false;
}
return true; return true;
} });
}
} }
// if we get here instance is marked filtered if (!passesFilter)
VEG_PROFILE_METHOD(DebugNotificationBus::TryQueueBroadcast(&DebugNotificationBus::Events::FilterInstance, instanceData.m_id, AZStd::string_view("SurfaceDepthMaskFilter"))); {
return false; // if we get here instance is marked filtered
VEG_PROFILE_METHOD(DebugNotificationBus::TryQueueBroadcast(
&DebugNotificationBus::Events::FilterInstance, instanceData.m_id, AZStd::string_view("SurfaceDepthMaskFilter")));
}
return passesFilter;
} }
FilterStage SurfaceMaskDepthFilterComponent::GetFilterStage() const FilterStage SurfaceMaskDepthFilterComponent::GetFilterStage() const

@ -281,14 +281,15 @@ namespace Vegetation
const float exclusiveWeightMax = AZ::GetMax(m_configuration.m_exclusiveWeightMin, m_configuration.m_exclusiveWeightMax); const float exclusiveWeightMax = AZ::GetMax(m_configuration.m_exclusiveWeightMin, m_configuration.m_exclusiveWeightMax);
if (useCompTags && if (useCompTags &&
SurfaceData::HasMatchingTags(instanceData.m_masks, m_configuration.m_exclusiveSurfaceMasks, exclusiveWeightMin, exclusiveWeightMax)) instanceData.m_masks.HasAnyMatchingTags(m_configuration.m_exclusiveSurfaceMasks, exclusiveWeightMin, exclusiveWeightMax))
{ {
VEG_PROFILE_METHOD(DebugNotificationBus::TryQueueBroadcast(&DebugNotificationBus::Events::FilterInstance, instanceData.m_id, AZStd::string_view("SurfaceMaskFilter"))); VEG_PROFILE_METHOD(DebugNotificationBus::TryQueueBroadcast(&DebugNotificationBus::Events::FilterInstance, instanceData.m_id, AZStd::string_view("SurfaceMaskFilter")));
return false; return false;
} }
if (useDescTags && if (useDescTags &&
SurfaceData::HasMatchingTags(instanceData.m_masks, instanceData.m_descriptorPtr->m_exclusiveSurfaceFilterTags, exclusiveWeightMin, exclusiveWeightMax)) instanceData.m_masks.HasAnyMatchingTags(
instanceData.m_descriptorPtr->m_exclusiveSurfaceFilterTags, exclusiveWeightMin, exclusiveWeightMax))
{ {
VEG_PROFILE_METHOD(DebugNotificationBus::TryQueueBroadcast(&DebugNotificationBus::Events::FilterInstance, instanceData.m_id, AZStd::string_view("SurfaceMaskFilter"))); VEG_PROFILE_METHOD(DebugNotificationBus::TryQueueBroadcast(&DebugNotificationBus::Events::FilterInstance, instanceData.m_id, AZStd::string_view("SurfaceMaskFilter")));
return false; return false;
@ -299,13 +300,14 @@ namespace Vegetation
const float inclusiveWeightMax = AZ::GetMax(m_configuration.m_inclusiveWeightMin, m_configuration.m_inclusiveWeightMax); const float inclusiveWeightMax = AZ::GetMax(m_configuration.m_inclusiveWeightMin, m_configuration.m_inclusiveWeightMax);
if (useCompTags && if (useCompTags &&
SurfaceData::HasMatchingTags(instanceData.m_masks, m_configuration.m_inclusiveSurfaceMasks, inclusiveWeightMin, inclusiveWeightMax)) instanceData.m_masks.HasAnyMatchingTags(m_configuration.m_inclusiveSurfaceMasks, inclusiveWeightMin, inclusiveWeightMax))
{ {
return true; return true;
} }
if (useDescTags && if (useDescTags &&
SurfaceData::HasMatchingTags(instanceData.m_masks, instanceData.m_descriptorPtr->m_inclusiveSurfaceFilterTags, inclusiveWeightMin, inclusiveWeightMax)) instanceData.m_masks.HasAnyMatchingTags(
instanceData.m_descriptorPtr->m_inclusiveSurfaceFilterTags, inclusiveWeightMin, inclusiveWeightMax))
{ {
return true; return true;
} }

@ -862,7 +862,7 @@ void DebugComponent::PrepareNextReport()
SurfaceData::SurfacePointList points; SurfaceData::SurfacePointList points;
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, pos, SurfaceData::SurfaceTagVector(), points); SurfaceData::SurfaceDataSystemRequestBus::Broadcast(&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, pos, SurfaceData::SurfaceTagVector(), points);
timing.m_worldPosition = points.empty() ? pos : points.front().m_position; timing.m_worldPosition = points.IsEmpty() ? pos : points.GetHighestSurfacePoint().m_position;
return timing; return timing;
}, },
[](const SectorTracker& sectorTracker, SectorTiming& sectorTiming) [](const SectorTracker& sectorTracker, SectorTiming& sectorTiming)

@ -80,7 +80,7 @@ namespace UnitTest
}); });
Vegetation::InstanceData vegInstance; Vegetation::InstanceData vegInstance;
vegInstance.m_masks[maskValue] = 1.0f; vegInstance.m_masks.AddSurfaceTagWeight(maskValue, 1.0f);
// passes // passes
{ {
@ -119,8 +119,7 @@ namespace UnitTest
MockSurfaceHandler mockSurfaceHandler; MockSurfaceHandler mockSurfaceHandler;
mockSurfaceHandler.m_outPosition = AZ::Vector3::CreateZero(); mockSurfaceHandler.m_outPosition = AZ::Vector3::CreateZero();
mockSurfaceHandler.m_outNormal = AZ::Vector3::CreateAxisZ(); mockSurfaceHandler.m_outNormal = AZ::Vector3::CreateAxisZ();
mockSurfaceHandler.m_outMasks.clear(); mockSurfaceHandler.m_outMasks.AddSurfaceTagWeight(SurfaceData::Constants::s_unassignedTagCrc, 1.0f);
mockSurfaceHandler.m_outMasks[SurfaceData::Constants::s_unassignedTagCrc] = 1.0f;
// passes // passes
{ {

@ -71,7 +71,7 @@ namespace UnitTest
MockSurfaceHandler mockSurfaceHandler; MockSurfaceHandler mockSurfaceHandler;
mockSurfaceHandler.m_outPosition = AZ::Vector3(vegInstance.m_position.GetX(), vegInstance.m_position.GetY(), 6.0f); mockSurfaceHandler.m_outPosition = AZ::Vector3(vegInstance.m_position.GetX(), vegInstance.m_position.GetY(), 6.0f);
mockSurfaceHandler.m_outNormal = AZ::Vector3(0.0f, 0.0f, 1.0f); mockSurfaceHandler.m_outNormal = AZ::Vector3(0.0f, 0.0f, 1.0f);
mockSurfaceHandler.m_outMasks[crcMask] = 1.0f; mockSurfaceHandler.m_outMasks.AddSurfaceTagWeight(crcMask, 1.0f);
entity->Deactivate(); entity->Deactivate();
config.m_autoSnapToSurface = true; config.m_autoSnapToSurface = true;

@ -329,15 +329,11 @@ namespace UnitTest
AZ::Vector3 m_outPosition = {}; AZ::Vector3 m_outPosition = {};
AZ::Vector3 m_outNormal = {}; AZ::Vector3 m_outNormal = {};
SurfaceData::SurfaceTagWeightMap m_outMasks; SurfaceData::SurfaceTagWeights m_outMasks;
void GetSurfacePoints([[maybe_unused]] const AZ::Vector3& inPosition, [[maybe_unused]] const SurfaceData::SurfaceTagVector& masks, SurfaceData::SurfacePointList& surfacePointList) const override void GetSurfacePoints([[maybe_unused]] const AZ::Vector3& inPosition, [[maybe_unused]] const SurfaceData::SurfaceTagVector& masks, SurfaceData::SurfacePointList& surfacePointList) const override
{ {
++m_count; ++m_count;
SurfaceData::SurfacePoint outPoint; surfacePointList.AddSurfacePoint(AZ::EntityId(), m_outPosition, m_outNormal, m_outMasks);
outPoint.m_position = m_outPosition;
outPoint.m_normal = m_outNormal;
SurfaceData::AddMaxValueForMasks(outPoint.m_masks, m_outMasks);
surfacePointList.push_back(outPoint);
} }
void GetSurfacePointsFromRegion([[maybe_unused]] const AZ::Aabb& inRegion, [[maybe_unused]] const AZ::Vector2 stepSize, [[maybe_unused]] const SurfaceData::SurfaceTagVector& desiredTags, void GetSurfacePointsFromRegion([[maybe_unused]] const AZ::Aabb& inRegion, [[maybe_unused]] const AZ::Vector2 stepSize, [[maybe_unused]] const SurfaceData::SurfaceTagVector& desiredTags,

@ -38,7 +38,7 @@ ly_associate_package(PACKAGE_NAME OpenMesh-8.1-rev3-linux
ly_associate_package(PACKAGE_NAME OpenSSL-1.1.1b-rev2-linux TARGETS OpenSSL PACKAGE_HASH b779426d1e9c5ddf71160d5ae2e639c3b956e0fb5e9fcaf9ce97c4526024e3bc) ly_associate_package(PACKAGE_NAME OpenSSL-1.1.1b-rev2-linux TARGETS OpenSSL PACKAGE_HASH b779426d1e9c5ddf71160d5ae2e639c3b956e0fb5e9fcaf9ce97c4526024e3bc)
ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxc-1.6.2104-o3de-rev3-linux TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 88c4a359325d749bc34090b9ac466424847f3b71ba0de15045cf355c17c07099) ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxc-1.6.2104-o3de-rev3-linux TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 88c4a359325d749bc34090b9ac466424847f3b71ba0de15045cf355c17c07099)
ly_associate_package(PACKAGE_NAME SPIRVCross-2021.04.29-rev1-linux TARGETS SPIRVCross PACKAGE_HASH 7889ee5460a688e9b910c0168b31445c0079d363affa07b25d4c8aeb608a0b80) ly_associate_package(PACKAGE_NAME SPIRVCross-2021.04.29-rev1-linux TARGETS SPIRVCross PACKAGE_HASH 7889ee5460a688e9b910c0168b31445c0079d363affa07b25d4c8aeb608a0b80)
ly_associate_package(PACKAGE_NAME azslc-1.7.34-rev1-linux TARGETS azslc PACKAGE_HASH 6d7dc671936c34ff70d2632196107ca1b8b2b41acdd021bfbc69a9fd56215c22) ly_associate_package(PACKAGE_NAME azslc-1.7.35-rev1-linux TARGETS azslc PACKAGE_HASH 273484be06dfc25e8da6a6e17937ae69a2efdb0b4c5f105efa83d6ad54d756e5)
ly_associate_package(PACKAGE_NAME zlib-1.2.11-rev5-linux TARGETS ZLIB PACKAGE_HASH 9be5ea85722fc27a8645a9c8a812669d107c68e6baa2ca0740872eaeb6a8b0fc) ly_associate_package(PACKAGE_NAME zlib-1.2.11-rev5-linux TARGETS ZLIB PACKAGE_HASH 9be5ea85722fc27a8645a9c8a812669d107c68e6baa2ca0740872eaeb6a8b0fc)
ly_associate_package(PACKAGE_NAME squish-ccr-deb557d-rev1-linux TARGETS squish-ccr PACKAGE_HASH 85fecafbddc6a41a27c5f59ed4a5dfb123a94cb4666782cf26e63c0a4724c530) ly_associate_package(PACKAGE_NAME squish-ccr-deb557d-rev1-linux TARGETS squish-ccr PACKAGE_HASH 85fecafbddc6a41a27c5f59ed4a5dfb123a94cb4666782cf26e63c0a4724c530)
ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev2-linux TARGETS astc-encoder PACKAGE_HASH 71549d1ca9e4d48391b92a89ea23656d3393810e6777879f6f8a9def2db1610c) ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev2-linux TARGETS astc-encoder PACKAGE_HASH 71549d1ca9e4d48391b92a89ea23656d3393810e6777879f6f8a9def2db1610c)

@ -41,6 +41,6 @@ ly_associate_package(PACKAGE_NAME squish-ccr-deb557d-rev1-mac
ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev5-mac TARGETS astc-encoder PACKAGE_HASH bdb1146cc6bbacc07901564fe884529d7cacc9bb44895597327341d3b9833ab0) ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev5-mac TARGETS astc-encoder PACKAGE_HASH bdb1146cc6bbacc07901564fe884529d7cacc9bb44895597327341d3b9833ab0)
ly_associate_package(PACKAGE_NAME ISPCTexComp-36b80aa-rev1-mac TARGETS ISPCTexComp PACKAGE_HASH 8a4e93277b8face6ea2fd57c6d017bdb55643ed3d6387110bc5f6b3b884dd169) ly_associate_package(PACKAGE_NAME ISPCTexComp-36b80aa-rev1-mac TARGETS ISPCTexComp PACKAGE_HASH 8a4e93277b8face6ea2fd57c6d017bdb55643ed3d6387110bc5f6b3b884dd169)
ly_associate_package(PACKAGE_NAME lz4-1.9.3-vcpkg-rev4-mac TARGETS lz4 PACKAGE_HASH 891ff630bf34f7ab1d8eaee2ea0a8f1fca89dbdc63fca41ee592703dd488a73b) ly_associate_package(PACKAGE_NAME lz4-1.9.3-vcpkg-rev4-mac TARGETS lz4 PACKAGE_HASH 891ff630bf34f7ab1d8eaee2ea0a8f1fca89dbdc63fca41ee592703dd488a73b)
ly_associate_package(PACKAGE_NAME azslc-1.7.34-rev1-mac TARGETS azslc PACKAGE_HASH a9d81946b42ffa55c0d14d6a9249b3340e59a8fb8835e7a96c31df80f14723bc) ly_associate_package(PACKAGE_NAME azslc-1.7.35-rev1-mac TARGETS azslc PACKAGE_HASH 03cb1ea8c47d4c80c893e2e88767272d5d377838f5ba94b777a45902dd85052e)
ly_associate_package(PACKAGE_NAME SQLite-3.37.2-rev1-mac TARGETS SQLite PACKAGE_HASH f9101023f99cf32fc5867284ceb28c0761c23d2c5a4b1748349c69f976a2fbea) ly_associate_package(PACKAGE_NAME SQLite-3.37.2-rev1-mac TARGETS SQLite PACKAGE_HASH f9101023f99cf32fc5867284ceb28c0761c23d2c5a4b1748349c69f976a2fbea)
ly_associate_package(PACKAGE_NAME AwsIotDeviceSdkCpp-1.15.2-rev2-mac TARGETS AwsIotDeviceSdkCpp PACKAGE_HASH 4854edb7b88fa6437b4e69e87d0ee111a25313ac2a2db5bb2f8b674ba0974f95) ly_associate_package(PACKAGE_NAME AwsIotDeviceSdkCpp-1.15.2-rev2-mac TARGETS AwsIotDeviceSdkCpp PACKAGE_HASH 4854edb7b88fa6437b4e69e87d0ee111a25313ac2a2db5bb2f8b674ba0974f95)

@ -47,6 +47,6 @@ ly_associate_package(PACKAGE_NAME squish-ccr-deb557d-rev1-windows
ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev2-windows TARGETS astc-encoder PACKAGE_HASH 17249bfa438afb34e21449865d9c9297471174ae0cea9b2f9def2ee206038295) ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev2-windows TARGETS astc-encoder PACKAGE_HASH 17249bfa438afb34e21449865d9c9297471174ae0cea9b2f9def2ee206038295)
ly_associate_package(PACKAGE_NAME ISPCTexComp-36b80aa-rev1-windows TARGETS ISPCTexComp PACKAGE_HASH b6fa6ea28a2808a9a5524c72c37789c525925e435770f2d94eb2d387360fa2d0) ly_associate_package(PACKAGE_NAME ISPCTexComp-36b80aa-rev1-windows TARGETS ISPCTexComp PACKAGE_HASH b6fa6ea28a2808a9a5524c72c37789c525925e435770f2d94eb2d387360fa2d0)
ly_associate_package(PACKAGE_NAME lz4-1.9.3-vcpkg-rev4-windows TARGETS lz4 PACKAGE_HASH 4ea457b833cd8cfaf8e8e06ed6df601d3e6783b606bdbc44a677f77e19e0db16) ly_associate_package(PACKAGE_NAME lz4-1.9.3-vcpkg-rev4-windows TARGETS lz4 PACKAGE_HASH 4ea457b833cd8cfaf8e8e06ed6df601d3e6783b606bdbc44a677f77e19e0db16)
ly_associate_package(PACKAGE_NAME azslc-1.7.34-rev1-windows TARGETS azslc PACKAGE_HASH 44eb2e0fc4b0f1c75d0fb6f24c93a5753655b84dbc3e6ad45389ed3b9cf7a4b0) ly_associate_package(PACKAGE_NAME azslc-1.7.35-rev1-windows TARGETS azslc PACKAGE_HASH 606aea611f2f20afcd8467ddabeecd3661e946eac3c843756c7df2871c1fb8a0)
ly_associate_package(PACKAGE_NAME SQLite-3.37.2-rev1-windows TARGETS SQLite PACKAGE_HASH c1658c8ed5cf0e45d4a5da940c6a6d770b76e0f4f57313b70d0fd306885f015e) ly_associate_package(PACKAGE_NAME SQLite-3.37.2-rev1-windows TARGETS SQLite PACKAGE_HASH c1658c8ed5cf0e45d4a5da940c6a6d770b76e0f4f57313b70d0fd306885f015e)
ly_associate_package(PACKAGE_NAME AwsIotDeviceSdkCpp-1.15.2-rev1-windows TARGETS AwsIotDeviceSdkCpp PACKAGE_HASH b03475a9f0f7a7e7c90619fba35f1a74fb2b8f4cd33fa07af99f2ae9e0c079dd) ly_associate_package(PACKAGE_NAME AwsIotDeviceSdkCpp-1.15.2-rev1-windows TARGETS AwsIotDeviceSdkCpp PACKAGE_HASH b03475a9f0f7a7e7c90619fba35f1a74fb2b8f4cd33fa07af99f2ae9e0c079dd)

@ -55,7 +55,6 @@ ly_append_configurations_options(
-Wno-parentheses -Wno-parentheses
-Wno-reorder -Wno-reorder
-Wno-restrict -Wno-restrict
-Wno-return-local-addr
-Wno-sequence-point -Wno-sequence-point
-Wno-sign-compare -Wno-sign-compare
-Wno-strict-aliasing -Wno-strict-aliasing

@ -203,6 +203,9 @@ def download_file(parsed_uri, download_path: pathlib.Path, force_overwrite: bool
except urllib.error.HTTPError as e: except urllib.error.HTTPError as e:
logger.error(f'HTTP Error {e.code} opening {parsed_uri.geturl()}') logger.error(f'HTTP Error {e.code} opening {parsed_uri.geturl()}')
return 1 return 1
except urllib.error.URLError as e:
logger.error(f'URL Error {e.reason} opening {parsed_uri.geturl()}')
return 1
else: else:
origin_file = pathlib.Path(parsed_uri.geturl()).resolve() origin_file = pathlib.Path(parsed_uri.geturl()).resolve()
if not origin_file.is_file(): if not origin_file.is_file():

Loading…
Cancel
Save