diff --git a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt index 7931aa7178..31afab87ed 100644 --- a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt @@ -51,7 +51,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) TEST_SUITE sandbox TEST_SERIAL PATH ${CMAKE_CURRENT_LIST_DIR}/physics/TestSuite_Sandbox.py - TIMEOUT 3600 + TIMEOUT 1500 RUNTIME_DEPENDENCIES Legacy::Editor AZ::AssetProcessor diff --git a/AutomatedTesting/Gem/PythonTests/editor/test_AssetPicker.py b/AutomatedTesting/Gem/PythonTests/editor/test_AssetPicker.py index ac499d734f..90ca3690e4 100644 --- a/AutomatedTesting/Gem/PythonTests/editor/test_AssetPicker.py +++ b/AutomatedTesting/Gem/PythonTests/editor/test_AssetPicker.py @@ -42,6 +42,7 @@ class TestAssetPicker(object): @pytest.mark.test_case_id("C13751579", "C1508814") @pytest.mark.SUITE_periodic + @pytest.mark.xfail # ATOM-15493 def test_AssetPicker_UI_UX(self, request, editor, level, launcher_platform): expected_lines = [ "TestEntity Entity successfully created", diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/ArraySerializer.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/ArraySerializer.cpp index 13a25e5aa6..d5a1730364 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/ArraySerializer.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/ArraySerializer.cpp @@ -74,7 +74,7 @@ namespace AZ "Unable to retrieve the correct container information for AZStd::array instance."); } - Flags flags = Flags::None; + ContinuationFlags flags = ContinuationFlags::None; Uuid elementTypeId = Uuid::CreateNull(); auto typeEnumCallback = [&elementTypeId, &flags](const Uuid&, const SerializeContext::ClassElement* genericClassElement) { @@ -82,7 +82,7 @@ namespace AZ elementTypeId = genericClassElement->m_typeId; if (genericClassElement->m_flags & SerializeContext::ClassElement::Flags::FLG_POINTER) { - flags = Flags::ResolvePointer; + flags = ContinuationFlags::ResolvePointer; } return false; }; @@ -161,7 +161,7 @@ namespace AZ "Not enough entries in JSON array to load an AZStd::array from."); } - Flags flags = Flags::None; + ContinuationFlags flags = ContinuationFlags::None; Uuid elementTypeId = Uuid::CreateNull(); auto typeEnumCallback = [&elementTypeId, &flags](const Uuid&, const SerializeContext::ClassElement* genericClassElement) { @@ -169,7 +169,7 @@ namespace AZ elementTypeId = genericClassElement->m_typeId; if (genericClassElement->m_flags & SerializeContext::ClassElement::Flags::FLG_POINTER) { - flags = Flags::ResolvePointer; + flags = ContinuationFlags::ResolvePointer; } return false; }; diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/BaseJsonSerializer.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/BaseJsonSerializer.cpp index 6c67fd284a..9a426a1e59 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/BaseJsonSerializer.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/BaseJsonSerializer.cpp @@ -208,22 +208,28 @@ namespace AZ // BaseJsonSerializer // - JsonSerializationResult::ResultCode BaseJsonSerializer::ContinueLoading(void* object, const Uuid& typeId, const rapidjson::Value& value, - JsonDeserializerContext& context, Flags flags) + BaseJsonSerializer::OperationFlags BaseJsonSerializer::GetOperationsFlags() const { - return flags & Flags::ResolvePointer ? - JsonDeserializer::LoadToPointer(object, typeId, value, context) : - JsonDeserializer::Load(object, typeId, value, context); + return OperationFlags::None; } - JsonSerializationResult::ResultCode BaseJsonSerializer::ContinueStoring(rapidjson::Value& output, const void* object, - const void* defaultObject, const Uuid& typeId, JsonSerializerContext& context, Flags flags) + JsonSerializationResult::ResultCode BaseJsonSerializer::ContinueLoading( + void* object, const Uuid& typeId, const rapidjson::Value& value, JsonDeserializerContext& context, ContinuationFlags flags) + { + return (flags & ContinuationFlags::ResolvePointer) == ContinuationFlags::ResolvePointer + ? JsonDeserializer::LoadToPointer(object, typeId, value, context) + : JsonDeserializer::Load(object, typeId, value, context); + } + + JsonSerializationResult::ResultCode BaseJsonSerializer::ContinueStoring( + rapidjson::Value& output, const void* object, const void* defaultObject, const Uuid& typeId, JsonSerializerContext& context, + ContinuationFlags flags) { using namespace JsonSerializationResult; - if (flags & Flags::ReplaceDefault && !context.ShouldKeepDefaults()) + if ((flags & ContinuationFlags::ReplaceDefault) == ContinuationFlags::ReplaceDefault && !context.ShouldKeepDefaults()) { - if (flags & Flags::ResolvePointer) + if ((flags & ContinuationFlags::ResolvePointer) == ContinuationFlags::ResolvePointer) { return JsonSerializer::StoreFromPointer(output, object, nullptr, typeId, context); } @@ -248,7 +254,7 @@ namespace AZ } } - return flags & Flags::ResolvePointer ? + return (flags & ContinuationFlags::ResolvePointer) == ContinuationFlags::ResolvePointer ? JsonSerializer::StoreFromPointer(output, object, defaultObject, typeId, context) : JsonSerializer::Store(output, object, defaultObject, typeId, context); } @@ -265,8 +271,9 @@ namespace AZ return JsonSerializer::StoreTypeName(output, typeId, context); } - JsonSerializationResult::ResultCode BaseJsonSerializer::ContinueLoadingFromJsonObjectField(void* object, const Uuid& typeId, const rapidjson::Value& value, - rapidjson::Value::StringRefType memberName, JsonDeserializerContext& context, Flags flags) + JsonSerializationResult::ResultCode BaseJsonSerializer::ContinueLoadingFromJsonObjectField( + void* object, const Uuid& typeId, const rapidjson::Value& value, rapidjson::Value::StringRefType memberName, + JsonDeserializerContext& context, ContinuationFlags flags) { using namespace JsonSerializationResult; @@ -291,7 +298,7 @@ namespace AZ JsonSerializationResult::ResultCode BaseJsonSerializer::ContinueStoringToJsonObjectField(rapidjson::Value& output, rapidjson::Value::StringRefType newMemberName, const void* object, const void* defaultObject, - const Uuid& typeId, JsonSerializerContext& context, Flags flags) + const Uuid& typeId, JsonSerializerContext& context, ContinuationFlags flags) { using namespace JsonSerializationResult; diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/BaseJsonSerializer.h b/Code/Framework/AzCore/AzCore/Serialization/Json/BaseJsonSerializer.h index f6ced44583..06c5eda6de 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/BaseJsonSerializer.h +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/BaseJsonSerializer.h @@ -161,13 +161,19 @@ namespace AZ public: AZ_RTTI(BaseJsonSerializer, "{7291FFDC-D339-40B5-BB26-EA067A327B21}"); - enum Flags + enum class ContinuationFlags { - None = 0, //! No extra flags. + None = 0, //! No extra flags. ResolvePointer = 1 << 0, //! The pointer passed in contains a pointer. The (de)serializer will attempt to resolve to an instance. ReplaceDefault = 1 << 1 //! The default value provided for storing will be replaced with a newly created one. }; + enum class OperationFlags + { + None = 0, //! No flags that control how the custom json serializer is used. + ManualDefault = 1 << 0 //! Even if an (explicit) default is found the custom json serializer will still be called. + }; + virtual ~BaseJsonSerializer() = default; //! Transforms the data from the rapidjson Value to outputValue, if the conversion is possible and supported. @@ -180,6 +186,9 @@ namespace AZ virtual JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue, const Uuid& valueTypeId, JsonSerializerContext& context) = 0; + //! Returns the operation flags which tells the Json Serialization how this custom json serializer can be used. + virtual OperationFlags GetOperationsFlags() const; + protected: //! Continues loading of a (sub)value. Use this function to load member variables for instance. This is more optimal than //! directly calling the json serialization. @@ -187,8 +196,9 @@ namespace AZ //! @param typeId Type id of the object passed in. //! @param value The value in the JSON document where the deserializer will start reading data from. //! @param context The context used during deserialization. Use the value passed in from Load. - JsonSerializationResult::ResultCode ContinueLoading(void* object, const Uuid& typeId, const rapidjson::Value& value, - JsonDeserializerContext& context, Flags flags = Flags::None); + JsonSerializationResult::ResultCode ContinueLoading( + void* object, const Uuid& typeId, const rapidjson::Value& value, JsonDeserializerContext& context, + ContinuationFlags flags = ContinuationFlags::None); //! Continues storing of a (sub)value. Use this function to store member variables for instance. This is more optimal than //! directly calling the json serialization. @@ -200,8 +210,9 @@ namespace AZ //! the settings. //! @param typeId The type id of the object and default object. //! @param context The context used during serialization. Use the value passed in from Store. - JsonSerializationResult::ResultCode ContinueStoring(rapidjson::Value& output, const void* object, const void* defaultObject, - const Uuid& typeId, JsonSerializerContext& context, Flags flags = Flags::None); + JsonSerializationResult::ResultCode ContinueStoring( + rapidjson::Value& output, const void* object, const void* defaultObject, const Uuid& typeId, JsonSerializerContext& context, + ContinuationFlags flags = ContinuationFlags::None); //! Retrieves the type id from a json object or json string. //! @param typeId The retrieved type id. @@ -222,12 +233,14 @@ namespace AZ const Uuid& typeId, JsonSerializerContext& context); //! Helper function similar to ContinueLoading, but loads the data as a member of 'value' rather than 'value' itself, if it exists. - JsonSerializationResult::ResultCode ContinueLoadingFromJsonObjectField(void* object, const Uuid& typeId, const rapidjson::Value& value, - rapidjson::Value::StringRefType memberName, JsonDeserializerContext& context, Flags flags = Flags::None); + JsonSerializationResult::ResultCode ContinueLoadingFromJsonObjectField( + void* object, const Uuid& typeId, const rapidjson::Value& value, rapidjson::Value::StringRefType memberName, + JsonDeserializerContext& context, ContinuationFlags flags = ContinuationFlags::None); //! Helper function similar to ContinueStoring, but stores the data as a member of 'output' rather than overwriting 'output'. - JsonSerializationResult::ResultCode ContinueStoringToJsonObjectField(rapidjson::Value& output, rapidjson::Value::StringRefType newMemberName, - const void* object, const void* defaultObject, const Uuid& typeId, JsonSerializerContext& context, Flags flags = Flags::None); + JsonSerializationResult::ResultCode ContinueStoringToJsonObjectField( + rapidjson::Value& output, rapidjson::Value::StringRefType newMemberName, const void* object, const void* defaultObject, + const Uuid& typeId, JsonSerializerContext& context, ContinuationFlags flags = ContinuationFlags::None); //! Checks if a value is an explicit default. This useful for containers where not storing anything as a default would mean //! a slot wouldn't be used so something has to be added to represent the fully default target. @@ -238,6 +251,7 @@ namespace AZ rapidjson::Value GetExplicitDefault(); }; - AZ_DEFINE_ENUM_BITWISE_OPERATORS(AZ::BaseJsonSerializer::Flags) + AZ_DEFINE_ENUM_BITWISE_OPERATORS(AZ::BaseJsonSerializer::ContinuationFlags) + AZ_DEFINE_ENUM_BITWISE_OPERATORS(AZ::BaseJsonSerializer::OperationFlags) } // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/BasicContainerSerializer.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/BasicContainerSerializer.cpp index c15cb9ef54..400a3b7949 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/BasicContainerSerializer.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/BasicContainerSerializer.cpp @@ -75,9 +75,10 @@ namespace AZ auto elementCallback = [this, &array, &retVal, &index, &context] (void* elementPtr, const Uuid& elementId, const SerializeContext::ClassData*, const SerializeContext::ClassElement* classElement) { - Flags flags = classElement->m_flags & SerializeContext::ClassElement::Flags::FLG_POINTER ? - Flags::ResolvePointer : Flags::None; - flags |= Flags::ReplaceDefault; + ContinuationFlags flags = classElement->m_flags & SerializeContext::ClassElement::Flags::FLG_POINTER + ? ContinuationFlags::ResolvePointer + : ContinuationFlags::None; + flags |= ContinuationFlags::ReplaceDefault; ScopedContextPath subPath(context, index); index++; @@ -161,8 +162,9 @@ namespace AZ container->EnumTypes(typeEnumCallback); AZ_Assert(classElement, "No class element found for the type in the basic container."); - Flags flags = classElement->m_flags & SerializeContext::ClassElement::Flags::FLG_POINTER ? - Flags::ResolvePointer : Flags::None; + ContinuationFlags flags = classElement->m_flags & SerializeContext::ClassElement::Flags::FLG_POINTER + ? ContinuationFlags::ResolvePointer + : ContinuationFlags::None; const size_t capacity = container->IsFixedCapacity() ? container->Capacity(outputValue) : std::numeric_limits::max(); diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonDeserializer.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonDeserializer.cpp index 8d0da9e54a..93d12acba3 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonDeserializer.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonDeserializer.cpp @@ -22,6 +22,19 @@ namespace AZ { + JsonSerializationResult::ResultCode JsonDeserializer::DeserializerDefaultCheck(BaseJsonSerializer* serializer, void* object, + const Uuid& typeId, const rapidjson::Value& value, JsonDeserializerContext& context) + { + using namespace AZ::JsonSerializationResult; + + bool isExplicitDefault = IsExplicitDefault(value); + bool manuallyDefaults = (serializer->GetOperationsFlags() & BaseJsonSerializer::OperationFlags::ManualDefault) == + BaseJsonSerializer::OperationFlags::ManualDefault; + return !isExplicitDefault || (isExplicitDefault && manuallyDefaults) + ? serializer->Load(object, typeId, value, context) + : context.Report(Tasks::ReadField, Outcomes::DefaultsUsed, "Value has an explicit default."); + } + JsonSerializationResult::ResultCode JsonDeserializer::Load(void* object, const Uuid& typeId, const rapidjson::Value& value, JsonDeserializerContext& context) { @@ -33,17 +46,12 @@ namespace AZ "Target object for Json Serialization is pointing to nothing during loading."); } - if (IsExplicitDefault(value)) - { - return context.Report(Tasks::ReadField, Outcomes::DefaultsUsed, "Value has an explicit default."); - } - BaseJsonSerializer* serializer = context.GetRegistrationContext()->GetSerializerForType(typeId); if (serializer) { - return serializer->Load(object, typeId, value, context); + return DeserializerDefaultCheck(serializer, object, typeId, value, context); } - + const SerializeContext::ClassData* classData = context.GetSerializeContext()->FindClassData(typeId); if (!classData) { @@ -56,9 +64,14 @@ namespace AZ serializer = context.GetRegistrationContext()->GetSerializerForType(classData->m_azRtti->GetGenericTypeId()); if (serializer) { - return serializer->Load(object, typeId, value, context); + return DeserializerDefaultCheck(serializer, object, typeId, value, context); } } + + if (IsExplicitDefault(value)) + { + return context.Report(Tasks::ReadField, Outcomes::DefaultsUsed, "Value has an explicit default."); + } if (classData->m_azRtti && (classData->m_azRtti->GetTypeTraits() & AZ::TypeTraits::is_enum) == AZ::TypeTraits::is_enum) { diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonDeserializer.h b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonDeserializer.h index 89e527b9ae..5954082ee0 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonDeserializer.h +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonDeserializer.h @@ -113,5 +113,13 @@ namespace AZ //! Checks if a value is an explicit default. This means the value is an object with no members. static bool IsExplicitDefault(const rapidjson::Value& value); + + private: + static JsonSerializationResult::ResultCode DeserializerDefaultCheck( + BaseJsonSerializer* serializer, + void* object, + const Uuid& typeId, + const rapidjson::Value& value, + JsonDeserializerContext& context); }; } // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/MapSerializer.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/MapSerializer.cpp index fa244d3dae..437a648e1e 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/MapSerializer.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/MapSerializer.cpp @@ -215,10 +215,10 @@ namespace AZ // Load key void* keyAddress = pairContainer->GetElementByIndex(address, pairElement, 0); AZ_Assert(keyAddress, "Element reserved for associative container, but unable to retrieve address of the key."); - Flags keyLoadFlags = Flags::None; + ContinuationFlags keyLoadFlags = ContinuationFlags::None; if (keyElement->m_flags & SerializeContext::ClassElement::Flags::FLG_POINTER) { - keyLoadFlags = Flags::ResolvePointer; + keyLoadFlags = ContinuationFlags::ResolvePointer; *reinterpret_cast(keyAddress) = nullptr; } JSR::ResultCode keyResult = ContinueLoading(keyAddress, keyElement->m_typeId, key, context, keyLoadFlags); @@ -231,10 +231,10 @@ namespace AZ // Load value void* valueAddress = pairContainer->GetElementByIndex(address, pairElement, 1); AZ_Assert(valueAddress, "Element reserved for associative container, but unable to retrieve address of the value."); - Flags valueLoadFlags = Flags::None; + ContinuationFlags valueLoadFlags = ContinuationFlags::None; if (valueElement->m_flags & SerializeContext::ClassElement::Flags::FLG_POINTER) { - valueLoadFlags = Flags::ResolvePointer; + valueLoadFlags = ContinuationFlags::ResolvePointer; *reinterpret_cast(valueAddress) = nullptr; } JSR::ResultCode valueResult = ContinueLoading(valueAddress, valueElement->m_typeId, value, context, valueLoadFlags); diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/SmartPointerSerializer.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/SmartPointerSerializer.cpp index 9e707f8644..0ab32ac08d 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/SmartPointerSerializer.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/SmartPointerSerializer.cpp @@ -82,7 +82,7 @@ namespace AZ { // If the target type is the same as the type already stored in the smart pointer than no new // instance is created and the existing instance will be updated with the data in the json document. - result = ContinueLoading(instance, elementClassId, inputValue, context, Flags::ResolvePointer); + result = ContinueLoading(instance, elementClassId, inputValue, context, ContinuationFlags::ResolvePointer); return false; } } @@ -93,7 +93,7 @@ namespace AZ // the wrong address. In these cases explicitly reset the smart pointer. This will erase the existing // data but that's fine as it's not being used. void* element = nullptr; - result = ContinueLoading(&element, elementClassId, inputValue, context, Flags::ResolvePointer); + result = ContinueLoading(&element, elementClassId, inputValue, context, ContinuationFlags::ResolvePointer); if (result.GetProcessing() != JSR::Processing::Halted && result.GetProcessing() != JSR::Processing::Altered) { void* elementPtr = container->ReserveElement(instance, nullptr); @@ -155,8 +155,14 @@ namespace AZ container->EnumElements(const_cast(defaultValue), defaultInputCallback); } - JSR::ResultCode result = ContinueStoring(outputValue, inputValue, defaultValue, inputPtrType, context, Flags::ResolvePointer); + JSR::ResultCode result = + ContinueStoring(outputValue, inputValue, defaultValue, inputPtrType, context, ContinuationFlags::ResolvePointer); return context.Report(result, result.GetProcessing() != JSR::Processing::Halted ? "Successfully processed smart pointer." : "A problem occurred while processing a smart pointer."); } + + BaseJsonSerializer::OperationFlags JsonSmartPointerSerializer::GetOperationsFlags() const + { + return OperationFlags::ManualDefault; + } } // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/SmartPointerSerializer.h b/Code/Framework/AzCore/AzCore/Serialization/Json/SmartPointerSerializer.h index 8c550cb824..9a0cf61be9 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/SmartPointerSerializer.h +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/SmartPointerSerializer.h @@ -28,5 +28,7 @@ namespace AZ JsonDeserializerContext& context) override; JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue, const Uuid& valueTypeId, JsonSerializerContext& context) override; + + OperationFlags GetOperationsFlags() const override; }; } // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/TupleSerializer.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/TupleSerializer.cpp index 9ea22592bc..5b43cec817 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/TupleSerializer.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/TupleSerializer.cpp @@ -99,8 +99,9 @@ namespace AZ ScopedContextPath subPath(context, i); - Flags flags = classElements[i]->m_flags & SerializeContext::ClassElement::Flags::FLG_POINTER ? - Flags::ResolvePointer : Flags::None; + ContinuationFlags flags = classElements[i]->m_flags & SerializeContext::ClassElement::Flags::FLG_POINTER + ? ContinuationFlags::ResolvePointer + : ContinuationFlags::None; JSR::ResultCode result = ContinueStoring(elementValues[i], elementAddress, defaultElementAddress, classElements[i]->m_typeId, context, flags); @@ -179,8 +180,9 @@ namespace AZ void* elementAddress = container->GetElementByIndex(outputValue, nullptr, i); AZ_Assert(elementAddress, "Address of AZStd::pair or AZStd::tuple element %zu could not be retrieved.", i); - Flags flags = classElements[i]->m_flags & SerializeContext::ClassElement::Flags::FLG_POINTER ? - Flags::ResolvePointer : Flags::None; + ContinuationFlags flags = classElements[i]->m_flags & SerializeContext::ClassElement::Flags::FLG_POINTER + ? ContinuationFlags::ResolvePointer + : ContinuationFlags::None; while (arrayIndex < inputValue.Size()) { diff --git a/Code/Framework/AzCore/Tests/AssetJsonSerializerTests.cpp b/Code/Framework/AzCore/Tests/AssetJsonSerializerTests.cpp index a494207850..e44f77b119 100644 --- a/Code/Framework/AzCore/Tests/AssetJsonSerializerTests.cpp +++ b/Code/Framework/AzCore/Tests/AssetJsonSerializerTests.cpp @@ -104,6 +104,11 @@ namespace JsonSerializationTests AZ::AllocatorInstance::Destroy(); } + void Reflect(AZStd::unique_ptr& context) override + { + context->RegisterGenericType(); + } + AZStd::shared_ptr CreateSerializer() override { return AZStd::make_shared(); diff --git a/Code/Framework/AzCore/Tests/Serialization/Json/BaseJsonSerializerTests.cpp b/Code/Framework/AzCore/Tests/Serialization/Json/BaseJsonSerializerTests.cpp index 08de21b54f..47e05997fc 100644 --- a/Code/Framework/AzCore/Tests/Serialization/Json/BaseJsonSerializerTests.cpp +++ b/Code/Framework/AzCore/Tests/Serialization/Json/BaseJsonSerializerTests.cpp @@ -119,7 +119,8 @@ namespace JsonSerializationTests int value = 0; int* ptrValue = &value; - ResultCode result = ContinueLoading(&ptrValue, azrtti_typeid(), json, *m_jsonDeserializationContext, Flags::ResolvePointer); + ResultCode result = + ContinueLoading(&ptrValue, azrtti_typeid(), json, *m_jsonDeserializationContext, ContinuationFlags::ResolvePointer); EXPECT_EQ(Processing::Completed, result.GetProcessing()); ASSERT_NE(nullptr, ptrValue); @@ -134,7 +135,8 @@ namespace JsonSerializationTests json.Set(42); int* ptrValue = nullptr; - ResultCode result = ContinueLoading(&ptrValue, azrtti_typeid(), json, *m_jsonDeserializationContext, Flags::ResolvePointer); + ResultCode result = + ContinueLoading(&ptrValue, azrtti_typeid(), json, *m_jsonDeserializationContext, ContinuationFlags::ResolvePointer); EXPECT_EQ(Processing::Completed, result.GetProcessing()); ASSERT_NE(nullptr, ptrValue); @@ -150,7 +152,8 @@ namespace JsonSerializationTests rapidjson::Value json(rapidjson::kObjectType); int* ptrValue = nullptr; - ResultCode result = ContinueLoading(&ptrValue, azrtti_typeid(), json, *m_jsonDeserializationContext, Flags::ResolvePointer); + ResultCode result = + ContinueLoading(&ptrValue, azrtti_typeid(), json, *m_jsonDeserializationContext, ContinuationFlags::ResolvePointer); EXPECT_EQ(Processing::Completed, result.GetProcessing()); ASSERT_NE(nullptr, ptrValue); @@ -165,7 +168,8 @@ namespace JsonSerializationTests rapidjson::Value json(rapidjson::kNullType); int* ptrValue = reinterpret_cast(azmalloc(sizeof(int), alignof(int), AZ::SystemAllocator)); - ResultCode result = ContinueLoading(&ptrValue, azrtti_typeid(), json, *m_jsonDeserializationContext, Flags::ResolvePointer); + ResultCode result = + ContinueLoading(&ptrValue, azrtti_typeid(), json, *m_jsonDeserializationContext, ContinuationFlags::ResolvePointer); EXPECT_EQ(Processing::Completed, result.GetProcessing()); ASSERT_EQ(nullptr, ptrValue); @@ -194,8 +198,8 @@ namespace JsonSerializationTests int value = 42; int* ptrValue = &value; - ResultCode result = ContinueStoring(*m_jsonDocument, &ptrValue, nullptr, azrtti_typeid(), *m_jsonSerializationContext, - Flags::ResolvePointer); + ResultCode result = ContinueStoring( + *m_jsonDocument, &ptrValue, nullptr, azrtti_typeid(), *m_jsonSerializationContext, ContinuationFlags::ResolvePointer); EXPECT_EQ(Processing::Completed, result.GetProcessing()); Expect_DocStrEq("42"); @@ -210,8 +214,9 @@ namespace JsonSerializationTests int value2 = 42; int* defaultPtrValue = &value2; - ResultCode result = - ContinueStoring(*m_jsonDocument, &ptrValue, &defaultPtrValue, azrtti_typeid(), *m_jsonSerializationContext, Flags::ResolvePointer); + ResultCode result = ContinueStoring( + *m_jsonDocument, &ptrValue, &defaultPtrValue, azrtti_typeid(), *m_jsonSerializationContext, + ContinuationFlags::ResolvePointer); EXPECT_EQ(Processing::Completed, result.GetProcessing()); Expect_DocStrEq("{}"); @@ -224,7 +229,7 @@ namespace JsonSerializationTests int* ptrValue = nullptr; ResultCode result = ContinueStoring( - *m_jsonDocument, &ptrValue, nullptr, azrtti_typeid(), *m_jsonSerializationContext, Flags::ResolvePointer); + *m_jsonDocument, &ptrValue, nullptr, azrtti_typeid(), *m_jsonSerializationContext, ContinuationFlags::ResolvePointer); EXPECT_EQ(Processing::Completed, result.GetProcessing()); Expect_DocStrEq("null"); @@ -238,8 +243,9 @@ namespace JsonSerializationTests int value2 = 42; int* defaultPtrValue = &value2; - ResultCode result = - ContinueStoring(*m_jsonDocument, &ptrValue, &defaultPtrValue, azrtti_typeid(), *m_jsonSerializationContext, Flags::ResolvePointer); + ResultCode result = ContinueStoring( + *m_jsonDocument, &ptrValue, &defaultPtrValue, azrtti_typeid(), *m_jsonSerializationContext, + ContinuationFlags::ResolvePointer); EXPECT_EQ(Processing::Completed, result.GetProcessing()); Expect_DocStrEq("null"); @@ -252,8 +258,9 @@ namespace JsonSerializationTests int* ptrValue = nullptr; int* defaultPtrValue = nullptr; - ResultCode result = - ContinueStoring(*m_jsonDocument, &ptrValue, &defaultPtrValue, azrtti_typeid(), *m_jsonSerializationContext, Flags::ResolvePointer); + ResultCode result = ContinueStoring( + *m_jsonDocument, &ptrValue, &defaultPtrValue, azrtti_typeid(), *m_jsonSerializationContext, + ContinuationFlags::ResolvePointer); EXPECT_EQ(Processing::Completed, result.GetProcessing()); Expect_DocStrEq("null"); @@ -265,8 +272,8 @@ namespace JsonSerializationTests int value = 42; - ResultCode result = ContinueStoring(*m_jsonDocument, &value, nullptr, azrtti_typeid(), *m_jsonSerializationContext, - Flags::ReplaceDefault); + ResultCode result = ContinueStoring( + *m_jsonDocument, &value, nullptr, azrtti_typeid(), *m_jsonSerializationContext, ContinuationFlags::ReplaceDefault); EXPECT_EQ(Processing::Completed, result.GetProcessing()); Expect_DocStrEq("42"); @@ -280,7 +287,7 @@ namespace JsonSerializationTests int* ptrValue = &value; ResultCode result = ContinueStoring(*m_jsonDocument, &ptrValue, nullptr, azrtti_typeid(), *m_jsonSerializationContext, - Flags::ResolvePointer | Flags::ReplaceDefault); + ContinuationFlags::ResolvePointer | ContinuationFlags::ReplaceDefault); EXPECT_EQ(Processing::Completed, result.GetProcessing()); Expect_DocStrEq("42"); @@ -293,8 +300,8 @@ namespace JsonSerializationTests int value = 42; AZ::Uuid unknownType("{09AE3CEC-EBFC-41EC-A7F6-949721521716}"); - ResultCode result = ContinueStoring(*m_jsonDocument, &value, nullptr, unknownType, *m_jsonSerializationContext, - Flags::ReplaceDefault); + ResultCode result = + ContinueStoring(*m_jsonDocument, &value, nullptr, unknownType, *m_jsonSerializationContext, ContinuationFlags::ReplaceDefault); EXPECT_EQ(Processing::Halted, result.GetProcessing()); } diff --git a/Code/Framework/AzCore/Tests/Serialization/Json/JsonSerializerConformityTests.h b/Code/Framework/AzCore/Tests/Serialization/Json/JsonSerializerConformityTests.h index 4f0825ff1b..c0f470378c 100644 --- a/Code/Framework/AzCore/Tests/Serialization/Json/JsonSerializerConformityTests.h +++ b/Code/Framework/AzCore/Tests/Serialization/Json/JsonSerializerConformityTests.h @@ -90,9 +90,14 @@ namespace JsonSerializationTests virtual ~JsonSerializerConformityTestDescriptor() = default; virtual AZStd::shared_ptr CreateSerializer() = 0; - + //! Create an instance of the target type with all values set to default. virtual AZStd::shared_ptr CreateDefaultInstance() = 0; + //! Create an instance of the target type that constructed with default constructor. + //! This will be the same instance that Json Serialization creates for dynamic types. Typically it's the same + //! as from CreateDefaultInstance(), except of types, such as pointers, that need to do minimal (de)serialization + //! to initialize an object. + virtual AZStd::shared_ptr CreateDefaultConstructedInstance() { return CreateDefaultInstance(); } //! Create an instance of the target type with some values set and some kept on defaults. //! If the target type doesn't support partial specialization this can be ignored and //! tests for partial support will be skipped. @@ -316,10 +321,10 @@ namespace JsonSerializationTests ASSERT_FALSE(this->m_jsonDocument->HasParseError()); auto serializer = this->m_description.CreateSerializer(); - auto instance = this->m_description.CreateDefaultInstance(); + auto instance = this->m_description.CreateDefaultConstructedInstance(); auto original = this->m_description.CreateDefaultInstance(); - ResultCode result = serializer->Load(instance.get(), azrtti_typeid(*original), + ResultCode result = serializer->Load(instance.get(), azrtti_typeid(*instance), *this->m_jsonDocument, *this->m_jsonDeserializationContext); if (this->m_features.m_mandatoryFields.empty()) @@ -339,6 +344,42 @@ namespace JsonSerializationTests } } + TYPED_TEST_P(JsonSerializerConformityTests, Load_DeserializeEmptyObjectThroughMainLoad_SucceedsAndObjectMatchesDefaults) + { + using namespace AZ::JsonSerializationResult; + + if (this->m_features.SupportsJsonType(rapidjson::kObjectType)) + { + this->m_jsonDocument->Parse("{}"); + ASSERT_FALSE(this->m_jsonDocument->HasParseError()); + + auto serializer = this->m_description.CreateSerializer(); + auto instance = this->m_description.CreateDefaultConstructedInstance(); + auto original = this->m_description.CreateDefaultInstance(); + + AZ::JsonDeserializerSettings settings; + settings.m_serializeContext = this->m_jsonDeserializationContext->GetSerializeContext(); + settings.m_registrationContext = this->m_jsonDeserializationContext->GetRegistrationContext(); + ResultCode result = AZ::JsonSerialization::Load( + instance.get(), azrtti_typeid(*instance), *this->m_jsonDocument, settings); + + if (this->m_features.m_mandatoryFields.empty()) + { + EXPECT_EQ(Outcomes::DefaultsUsed, result.GetOutcome()); + EXPECT_EQ(Processing::Completed, result.GetProcessing()); + } + else + { + EXPECT_EQ(Outcomes::Unsupported, result.GetOutcome()); + bool validProcessing = + result.GetProcessing() == Processing::Altered || + result.GetProcessing() == Processing::PartialAlter; + EXPECT_TRUE(validProcessing); + } + EXPECT_TRUE(this->m_description.AreEqual(*original, *instance)); + } + } + TYPED_TEST_P(JsonSerializerConformityTests, Load_DeserializeEmptyArray_SucceedsAndObjectMatchesDefaults) { using namespace AZ::JsonSerializationResult; @@ -349,7 +390,7 @@ namespace JsonSerializationTests ASSERT_FALSE(this->m_jsonDocument->HasParseError()); auto serializer = this->m_description.CreateSerializer(); - auto instance = this->m_description.CreateDefaultInstance(); + auto instance = this->m_description.CreateDefaultConstructedInstance(); auto original = this->m_description.CreateDefaultInstance(); this->m_deserializationSettings->m_clearContainers = false; @@ -384,7 +425,7 @@ namespace JsonSerializationTests ASSERT_FALSE(this->m_jsonDocument->HasParseError()); auto serializer = this->m_description.CreateSerializer(); - auto instance = this->m_description.CreateDefaultInstance(); + auto instance = this->m_description.CreateDefaultConstructedInstance(); auto original = this->m_description.CreateDefaultInstance(); this->m_deserializationSettings->m_clearContainers = true; @@ -488,7 +529,7 @@ namespace JsonSerializationTests ASSERT_FALSE(this->m_jsonDocument->HasParseError()); auto serializer = this->m_description.CreateSerializer(); - auto instance = this->m_description.CreateDefaultInstance(); + auto instance = this->m_description.CreateDefaultConstructedInstance(); auto compare = this->m_description.CreateFullySetInstance(); ResultCode result = serializer->Load(instance.get(), azrtti_typeid(*instance), @@ -499,6 +540,28 @@ namespace JsonSerializationTests EXPECT_TRUE(this->m_description.AreEqual(*instance, *compare)); } + TYPED_TEST_P(JsonSerializerConformityTests, Load_DeserializeFullySetInstanceThroughMainLoad_SucceedsAndObjectMatchesFullySetInstance) + { + using namespace AZ::JsonSerializationResult; + + AZStd::string_view json = this->m_description.GetJsonFor_Load_DeserializeFullySetInstance(); + this->m_jsonDocument->Parse(json.data()); + ASSERT_FALSE(this->m_jsonDocument->HasParseError()); + + auto serializer = this->m_description.CreateSerializer(); + auto instance = this->m_description.CreateDefaultConstructedInstance(); + auto compare = this->m_description.CreateFullySetInstance(); + + AZ::JsonDeserializerSettings settings; + settings.m_serializeContext = this->m_jsonDeserializationContext->GetSerializeContext(); + settings.m_registrationContext = this->m_jsonDeserializationContext->GetRegistrationContext(); + ResultCode result = AZ::JsonSerialization::Load(instance.get(), azrtti_typeid(*instance), *this->m_jsonDocument, settings); + + EXPECT_EQ(Outcomes::Success, result.GetOutcome()); + EXPECT_EQ(Processing::Completed, result.GetProcessing()); + EXPECT_TRUE(this->m_description.AreEqual(*instance, *compare)); + } + TYPED_TEST_P(JsonSerializerConformityTests, Load_DeserializeWithMissingMandatoryField_LoadFailedAndUnsupportedReported) { using namespace AZ::JsonSerializationResult; @@ -518,7 +581,7 @@ namespace JsonSerializationTests ASSERT_NE(this->m_jsonDocument->MemberEnd(), memberToErase); this->m_jsonDocument->RemoveMember(memberToErase); - auto instance = this->m_description.CreateDefaultInstance(); + auto instance = this->m_description.CreateDefaultConstructedInstance(); ResultCode result = serializer->Load(instance.get(), azrtti_typeid(*instance), *this->m_jsonDocument, *this->m_jsonDeserializationContext); @@ -546,7 +609,7 @@ namespace JsonSerializationTests ASSERT_FALSE(this->m_jsonDocument->HasParseError()); auto serializer = this->m_description.CreateSerializer(); - auto instance = this->m_description.CreateDefaultInstance(); + auto instance = this->m_description.CreateDefaultConstructedInstance(); auto compare = this->m_description.CreatePartialDefaultInstance(); ASSERT_NE(nullptr, compare); @@ -567,7 +630,7 @@ namespace JsonSerializationTests ASSERT_FALSE(this->m_jsonDocument->HasParseError()); auto serializer = this->m_description.CreateSerializer(); - auto instance = this->m_description.CreateDefaultInstance(); + auto instance = this->m_description.CreateDefaultConstructedInstance(); AZ::ScopedContextReporter reporter(*this->m_jsonDeserializationContext, [](AZStd::string_view message, ResultCode result, AZStd::string_view path) -> ResultCode @@ -604,7 +667,7 @@ namespace JsonSerializationTests } auto serializer = this->m_description.CreateSerializer(); - auto instance = this->m_description.CreateDefaultInstance(); + auto instance = this->m_description.CreateDefaultConstructedInstance(); auto compare = this->m_description.CreateFullySetInstance(); ResultCode result = serializer->Load(instance.get(), azrtti_typeid(*instance), @@ -635,7 +698,7 @@ namespace JsonSerializationTests } auto serializer = this->m_description.CreateSerializer(); - auto instance = this->m_description.CreateDefaultInstance(); + auto instance = this->m_description.CreateDefaultConstructedInstance(); ResultCode result = serializer->Load(instance.get(), azrtti_typeid(*instance), *this->m_jsonDocument, *this->m_jsonDeserializationContext); @@ -693,6 +756,36 @@ namespace JsonSerializationTests } } + TYPED_TEST_P(JsonSerializerConformityTests, Store_SerializeDefaultInstanceThroughMainStore_EmptyJsonReturned) + { + using namespace AZ::JsonSerializationResult; + + auto serializer = this->m_description.CreateSerializer(); + auto instance = this->m_description.CreateDefaultInstance(); + rapidjson::Value convertedValue = this->CreateExplicitDefault(); + + AZ::JsonSerializerSettings settings; + settings.m_serializeContext = this->m_jsonDeserializationContext->GetSerializeContext(); + settings.m_registrationContext = this->m_jsonDeserializationContext->GetRegistrationContext(); + ResultCode result = AZ::JsonSerialization::Store( + convertedValue, this->m_jsonDocument->GetAllocator(), instance.get(), instance.get(), azrtti_typeid(*instance), settings); + + EXPECT_EQ(Processing::Completed, result.GetProcessing()); + if (convertedValue.IsObject() && !this->m_features.m_mandatoryFields.empty()) + { + ASSERT_EQ(convertedValue.MemberCount(), this->m_features.m_mandatoryFields.size()); + for (const AZStd::string& mandatoryField : this->m_features.m_mandatoryFields) + { + EXPECT_NE(convertedValue.MemberEnd(), convertedValue.FindMember(mandatoryField.c_str())); + } + } + else + { + EXPECT_EQ(Outcomes::DefaultsUsed, result.GetOutcome()); + this->Expect_ExplicitDefault(convertedValue); + } + } + TYPED_TEST_P(JsonSerializerConformityTests, Store_SerializeWithDefaultsKept_FullyWrittenJson) { using namespace AZ::JsonSerializationResult; @@ -924,6 +1017,20 @@ namespace JsonSerializationTests } } + TYPED_TEST_P(JsonSerializerConformityTests, GetOperationsFlags_ManualDefaultSetIfNeeded_ManualDefaultOperationSetIfMandatoryFieldsAreDeclared) + { + if (this->m_features.SupportsJsonType(rapidjson::kObjectType)) + { + if (!this->m_features.m_mandatoryFields.empty()) + { + auto serializer = this->m_description.CreateSerializer(); + bool manuallyHandlesDefaults = (serializer->GetOperationsFlags() & AZ::BaseJsonSerializer::OperationFlags::ManualDefault) == + AZ::BaseJsonSerializer::OperationFlags::ManualDefault; + EXPECT_TRUE(manuallyHandlesDefaults); + } + } + } + REGISTER_TYPED_TEST_CASE_P(JsonSerializerConformityTests, Registration_SerializerIsRegisteredWithContext_SerializerFound, @@ -934,14 +1041,16 @@ namespace JsonSerializationTests Load_InvalidTypeOfArrayType_ReturnsUnsupported, Load_InvalidTypeOfStringType_ReturnsUnsupported, Load_InvalidTypeOfNumberType_ReturnsUnsupported, - + Load_DeserializeUnreflectedType_ReturnsUnsupported, Load_DeserializeEmptyObject_SucceedsAndObjectMatchesDefaults, + Load_DeserializeEmptyObjectThroughMainLoad_SucceedsAndObjectMatchesDefaults, Load_DeserializeEmptyArray_SucceedsAndObjectMatchesDefaults, Load_DeserializeEmptyArrayWithClearEnabled_SucceedsAndObjectMatchesDefaults, Load_DeserializeEmptyArrayWithClearedTarget_SucceedsAndObjectMatchesDefaults, Load_InterruptClearingTarget_ContainerIsNotCleared, Load_DeserializeFullySetInstance_SucceedsAndObjectMatchesFullySetInstance, + Load_DeserializeFullySetInstanceThroughMainLoad_SucceedsAndObjectMatchesFullySetInstance, Load_DeserializePartialInstance_SucceedsAndObjectMatchesParialInstance, Load_DeserializeWithMissingMandatoryField_LoadFailedAndUnsupportedReported, Load_InsertAdditionalData_SucceedsAndObjectMatchesFullySetInstance, @@ -950,6 +1059,7 @@ namespace JsonSerializationTests Store_SerializeUnreflectedType_ReturnsUnsupported, Store_SerializeDefaultInstance_EmptyJsonReturned, + Store_SerializeDefaultInstanceThroughMainStore_EmptyJsonReturned, Store_SerializeWithDefaultsKept_FullyWrittenJson, Store_SerializeFullySetInstance_StoredSuccessfullyAndJsonMatches, Store_SerializeWithoutDefault_StoredSuccessfullyAndJsonMatches, @@ -957,10 +1067,12 @@ namespace JsonSerializationTests Store_SerializePartialInstance_StoredSuccessfullyAndJsonMatches, Store_SerializeEmptyArray_StoredSuccessfullyAndJsonMatches, Store_HaltedThroughCallback_StoreFailsAndHaltReported, - + StoreLoad_RoundTripWithPartialDefault_IdenticalInstances, StoreLoad_RoundTripWithFullSet_IdenticalInstances, - StoreLoad_RoundTripWithDefaultsKept_IdenticalInstances); + StoreLoad_RoundTripWithDefaultsKept_IdenticalInstances, + + GetOperationsFlags_ManualDefaultSetIfNeeded_ManualDefaultOperationSetIfMandatoryFieldsAreDeclared); } // namespace JsonSerializationTests namespace AZ diff --git a/Code/Framework/AzCore/Tests/Serialization/Json/SmartPointerSerializerTests.cpp b/Code/Framework/AzCore/Tests/Serialization/Json/SmartPointerSerializerTests.cpp index 2fc131aae2..75391d81d4 100644 --- a/Code/Framework/AzCore/Tests/Serialization/Json/SmartPointerSerializerTests.cpp +++ b/Code/Framework/AzCore/Tests/Serialization/Json/SmartPointerSerializerTests.cpp @@ -32,6 +32,11 @@ namespace JsonSerializationTests return AZStd::make_shared(); } + AZStd::shared_ptr CreateDefaultConstructedInstance() override + { + return AZStd::make_shared(); + } + void Reflect(AZStd::unique_ptr& context) override { context->RegisterGenericType(); @@ -228,13 +233,19 @@ namespace JsonSerializationTests public: using SmartPointer = typename SmartPointerSimpleDerivedClassTestDescription::SmartPointer; - AZStd::shared_ptr CreateDefaultInstance() override + // This test is specific for derived classes being used as a default value. + AZStd::shared_ptr CreateDefaultConstructedInstance() override { auto result = AZStd::make_shared(); *result = SmartPointer(aznew SimpleInheritence()); return result; } + AZStd::shared_ptr CreateDefaultInstance() override + { + return CreateDefaultConstructedInstance(); + } + AZStd::string_view GetJsonForPartialDefaultInstance() override { return R"( @@ -386,13 +397,19 @@ namespace JsonSerializationTests public: using SmartPointer = typename SmartPointerComplexDerivedClassTestDescription::SmartPointer; - AZStd::shared_ptr CreateDefaultInstance() override + // This test is specific for derived classes being used as a default value. + AZStd::shared_ptr CreateDefaultConstructedInstance() override { auto result = AZStd::make_shared(); *result = SmartPointer(aznew MultipleInheritence()); return result; } + AZStd::shared_ptr CreateDefaultInstance() override + { + return CreateDefaultConstructedInstance(); + } + AZStd::string_view GetJsonForPartialDefaultInstance() override { return R"( diff --git a/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.cpp b/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.cpp index 52eae22fee..a535f5f65d 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.cpp +++ b/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.cpp @@ -48,6 +48,9 @@ namespace Physics { if (auto serializeContext = azrtti_cast(context)) { + serializeContext + ->RegisterGenericType>(); + serializeContext->Class() ->Version(1) ->Field("Radius", &SphereShapeConfiguration::m_radius) @@ -76,6 +79,9 @@ namespace Physics { if (auto serializeContext = azrtti_cast(context)) { + serializeContext + ->RegisterGenericType>(); + serializeContext->Class() ->Version(1) ->Field("Configuration", &BoxShapeConfiguration::m_dimensions) @@ -104,6 +110,9 @@ namespace Physics { if (auto serializeContext = azrtti_cast(context)) { + serializeContext + ->RegisterGenericType>(); + serializeContext->Class() ->Version(1) ->Field("Height", &CapsuleShapeConfiguration::m_height) @@ -153,6 +162,9 @@ namespace Physics { if (auto serializeContext = azrtti_cast(context)) { + serializeContext + ->RegisterGenericType>(); + serializeContext->Class() ->Version(1) ->Field("PhysicsAsset", &PhysicsAssetShapeConfiguration::m_asset) @@ -185,6 +197,9 @@ namespace Physics { if (auto serializeContext = azrtti_cast(context)) { + serializeContext + ->RegisterGenericType>(); + serializeContext->Class() ->Version(1) ->Field("Scale", &NativeShapeConfiguration::m_nativeShapeScale) @@ -208,6 +223,9 @@ namespace Physics { if (auto serializeContext = azrtti_cast(context)) { + serializeContext + ->RegisterGenericType>(); + serializeContext->Class() ->Version(1) ->Field("CookedData", &CookedMeshShapeConfiguration::m_cookedData) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityModel.cpp index 9caca69b34..ef6d0fe1f8 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityModel.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityModel.cpp @@ -117,8 +117,6 @@ namespace AzToolsFramework { EditorEntityModel::EditorEntityModel() { - AzFramework::ApplicationRequests::Bus::BroadcastResult(m_isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); - EntityCompositionNotificationBus::Handler::BusConnect(); EditorOnlyEntityComponentNotificationBus::Handler::BusConnect(); EditorEntityRuntimeActivationChangeNotificationBus::Handler::BusConnect(); @@ -565,7 +563,7 @@ namespace AzToolsFramework { //retrieve or add an entity entry to the table //the entry must exist, even if not connected, so children and other data can be assigned - [[maybe_unused]] auto [it, inserted] = m_entityInfoTable.try_emplace(entityId, m_isPrefabEnabled); + [[maybe_unused]] auto [it, inserted] = m_entityInfoTable.try_emplace(entityId); auto& entityInfo = it->second; //the entity id defaults to invalid and must be set to match the requested id @@ -882,11 +880,6 @@ namespace AzToolsFramework } } - EditorEntityModel::EditorEntityModelEntry::EditorEntityModelEntry(bool isPrefabEnabled) - : m_isPrefabEnabled(isPrefabEnabled) - { - } - EditorEntityModel::EditorEntityModelEntry::~EditorEntityModelEntry() { Disconnect(); @@ -1213,29 +1206,15 @@ namespace AzToolsFramework auto childItr = m_childIndexCache.find(childId); if (childItr != m_childIndexCache.end()) { - if (m_isPrefabEnabled) - { - // Take the last entry and move it into the removed spot instead of deleting the entry and having to move all - // following entries one step down. - AZ::EntityId backEntity = m_children.back(); - m_children[childItr->second] = backEntity; - // Update cached index for the moved id to the new index. - m_childIndexCache[backEntity] = childItr->second; - // Now remove the deleted id from the children and cache. - m_childIndexCache.erase(childId); - m_children.erase(m_children.end() - 1); - } - else - { - m_children.erase(m_children.begin() + childItr->second); - - // rebuild index cache for faster lookup - m_childIndexCache.clear(); - for (auto childIdToCache : m_children) - { - m_childIndexCache[childIdToCache] = static_cast(m_childIndexCache.size()); - } - } + // Take the last entry and move it into the removed spot instead of deleting the entry and having to move all + // following entries one step down. + AZ::EntityId backEntity = m_children.back(); + m_children[childItr->second] = backEntity; + // Update cached index for the moved id to the new index. + m_childIndexCache[backEntity] = childItr->second; + // Now remove the deleted id from the children and cache. + m_childIndexCache.erase(childId); + m_children.erase(m_children.end() - 1); } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityModel.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityModel.h index b6cceb85fe..72965b4017 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityModel.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityModel.h @@ -171,7 +171,6 @@ namespace AzToolsFramework , public PropertyEditorEntityChangeNotificationBus::Handler { public: - explicit EditorEntityModelEntry(bool isPrefabEnabled); ~EditorEntityModelEntry(); // Separately connect to EditorEntityInfoRequestBus and refresh Entity @@ -336,7 +335,6 @@ namespace AzToolsFramework bool m_visible = true; bool m_locked = false; bool m_connected = false; - bool m_isPrefabEnabled = false; AZStd::string m_name; AZStd::string m_sliceAssetName; AZStd::unordered_map m_childIndexCache; @@ -375,6 +373,5 @@ namespace AzToolsFramework AZ::EntityId m_postInstantiateBeforeEntity; AZ::EntityId m_postInstantiateSliceParent; bool m_gotInstantiateSliceDetails = false; - bool m_isPrefabEnabled = false; }; } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h index 6ceb175fe4..4e7a520698 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h @@ -244,6 +244,8 @@ namespace AzToolsFramework /// from ViewportCursorScreenPosition. This method will always return the correct position to generate a mouse /// position delta. virtual AZStd::optional PreviousViewportCursorScreenPosition() = 0; + /// Is mouse over viewport. + virtual bool IsMouseOver() const = 0; protected: ~ViewportMouseCursorRequests() = default; diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/PrefabUpdateInstancesBenchmarks.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/PrefabUpdateInstancesBenchmarks.cpp index c81db58c95..d1bd2a488e 100644 --- a/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/PrefabUpdateInstancesBenchmarks.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/PrefabUpdateInstancesBenchmarks.cpp @@ -78,7 +78,7 @@ namespace Benchmark } BENCHMARK_REGISTER_F(BM_PrefabUpdateInstances, UpdateInstances_SingeEntityInstances) ->RangeMultiplier(10) - ->Range(100, 1000) + ->Range(100, 10000) ->Unit(benchmark::kMillisecond) ->Complexity(); diff --git a/Code/Sandbox/Editor/MainWindow.cpp b/Code/Sandbox/Editor/MainWindow.cpp index 895df4214f..e9bfd96a1d 100644 --- a/Code/Sandbox/Editor/MainWindow.cpp +++ b/Code/Sandbox/Editor/MainWindow.cpp @@ -1327,7 +1327,7 @@ QToolButton* MainWindow::CreateDebugModeButton() QWidget* MainWindow::CreateSpacerRightWidget() { - QWidget* spacer = new QWidget(); + QWidget* spacer = new QWidget(this); spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); spacer->setVisible(true); return spacer; diff --git a/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/UI/Outliner/OutlinerListModel.cpp b/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/UI/Outliner/OutlinerListModel.cpp index cd423535f2..ac9b92adce 100644 --- a/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/UI/Outliner/OutlinerListModel.cpp +++ b/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/UI/Outliner/OutlinerListModel.cpp @@ -1473,14 +1473,15 @@ void OutlinerListModel::OnEntityInfoUpdatedRemoveChildBegin(AZ::EntityId parentI emit EnableSelectionUpdates(false); auto parentIndex = GetIndexFromEntity(parentId); auto childIndex = GetIndexFromEntity(childId); - beginRemoveRows(parentIndex, childIndex.row(), childIndex.row()); + beginResetModel(); } void OutlinerListModel::OnEntityInfoUpdatedRemoveChildEnd(AZ::EntityId parentId, AZ::EntityId childId) { (void)childId; AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework); - endRemoveRows(); + + endResetModel(); //must refresh partial lock/visibility of parents m_isFilterDirty = true; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp new file mode 100644 index 0000000000..b11c7a7a2c --- /dev/null +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp @@ -0,0 +1,25 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include "GemInfo.h" + +namespace O3DE::ProjectManager +{ + GemInfo::GemInfo(const QString& name, const QString& creator, const QString& summary, Platforms platforms, bool isAdded) + : m_name(name) + , m_creator(creator) + , m_summary(summary) + , m_platforms(platforms) + , m_isAdded(isAdded) + { + } +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h new file mode 100644 index 0000000000..e5aa9c41f7 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h @@ -0,0 +1,55 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#include +#include +#include +#endif + +namespace O3DE::ProjectManager +{ + class GemInfo + { + public: + enum Platform + { + Android = 0x0, + iOS = 0x1, + Linux = 0x2, + macOS = 0x3, + Windows = 0x4 + }; + Q_DECLARE_FLAGS(Platforms, Platform) + + GemInfo(const QString& name, const QString& creator, const QString& summary, Platforms platforms, bool isAdded); + + QString m_name; + QString m_displayName; + AZ::Uuid m_uuid; + QString m_creator; + bool m_isAdded = false; //! Is the gem currently added and enabled in the project? + QString m_summary; + Platforms m_platforms; + QStringList m_features; + QString m_version; + QString m_lastUpdatedDate; + QString m_documentationUrl; + QVector m_dependingGemUuids; + QVector m_conflictingGemUuids; + }; +} // namespace O3DE::ProjectManager + +Q_DECLARE_OPERATORS_FOR_FLAGS(O3DE::ProjectManager::GemInfo::Platforms) diff --git a/Code/Tools/ProjectManager/project_manager_files.cmake b/Code/Tools/ProjectManager/project_manager_files.cmake index 333180ea78..a97ff692b7 100644 --- a/Code/Tools/ProjectManager/project_manager_files.cmake +++ b/Code/Tools/ProjectManager/project_manager_files.cmake @@ -37,4 +37,6 @@ set(FILES Source/EngineSettings.h Source/EngineSettings.cpp Source/EngineSettings.ui + Source/GemCatalog/GemInfo.h + Source/GemCatalog/GemInfo.cpp ) diff --git a/Gems/AWSCore/Code/Include/Private/Configuration/AWSCoreConfiguration.h b/Gems/AWSCore/Code/Include/Private/Configuration/AWSCoreConfiguration.h index f3d474f053..bd30af3ce7 100644 --- a/Gems/AWSCore/Code/Include/Private/Configuration/AWSCoreConfiguration.h +++ b/Gems/AWSCore/Code/Include/Private/Configuration/AWSCoreConfiguration.h @@ -25,13 +25,24 @@ namespace AWSCore : AWSCoreInternalRequestBus::Handler { public: - static constexpr const char AWSCORE_CONFIGURATION_FILENAME[] = "awscoreconfiguration.setreg"; + static constexpr const char AWSCoreConfigurationName[] = "AWSCoreConfiguration"; + static constexpr const char AWSCoreConfigurationFileName[] = "awscoreconfiguration.setreg"; - static constexpr const char AWSCORE_RESOURCE_MAPPING_CONFIG_FOLDERNAME[] = "Config"; - static constexpr const char AWSCORE_RESOURCE_MAPPING_CONFIG_FILENAME_KEY[] = "/AWSCore/ResourceMappingConfigFileName"; + static constexpr const char AWSCoreResourceMappingConfigFolderName[] = "Config"; + static constexpr const char AWSCoreResourceMappingConfigFileNameKey[] = "/AWSCore/ResourceMappingConfigFileName"; + + static constexpr const char AWSCoreDefaultProfileName[] = "default"; + static constexpr const char AWSCoreProfileNameKey[] = "/AWSCore/ProfileName"; + + static constexpr const char ProjectSourceFolderNotFoundErrorMessage[] = + "Failed to get project source folder path."; + static constexpr const char ProfileNameNotFoundErrorMessage[] = + "Failed to get profile name, return default value instead."; + static constexpr const char ResourceMappingFileNameNotFoundErrorMessage[] = + "Failed to get resource mapping config file name, return empty value instead."; + static constexpr const char SettingsRegistryLoadFailureErrorMessage[] = + "Failed to load AWSCore settings registry file."; - static constexpr const char AWSCORE_DEFAULT_PROFILE_NAME[] = "default"; - static constexpr const char AWSCORE_PROFILENAME_KEY[] = "/AWSCore/ProfileName"; AWSCoreConfiguration(); ~AWSCoreConfiguration() = default; @@ -55,6 +66,9 @@ namespace AWSCore // Parse values from project .setreg file void ParseSettingsRegistryValues(); + // Reset settings registry data + void ResetSettingsRegistryData(); + AZStd::string m_sourceProjectFolder; AZ::SettingsRegistryImpl m_settingsRegistry; AZStd::string m_profileName; diff --git a/Gems/AWSCore/Code/Include/Private/ResourceMapping/AWSResourceMappingConstants.h b/Gems/AWSCore/Code/Include/Private/ResourceMapping/AWSResourceMappingConstants.h index 2ca203bb96..55360f1c76 100644 --- a/Gems/AWSCore/Code/Include/Private/ResourceMapping/AWSResourceMappingConstants.h +++ b/Gems/AWSCore/Code/Include/Private/ResourceMapping/AWSResourceMappingConstants.h @@ -14,20 +14,20 @@ namespace AWSCore { - static constexpr const char AWS_CHINA_REGION_PREFIX[] = "cn-"; + static constexpr const char AWSChinaRegionPrefix[] = "cn-"; - static constexpr const char AWS_FEATURE_GEM_RESTAPI_ID_KEYNAME_SUFFIX[] = ".RESTApiId"; - static constexpr const char AWS_FEATURE_GEM_RESTAPI_STAGE_KEYNAME_SUFFIX[] = ".RESTApiStage"; + static constexpr const char AWSFeatureGemRESTApiIdKeyNameSuffix[] = ".RESTApiId"; + static constexpr const char AWSFeatureGemRESTApiStageKeyNameSuffix[] = ".RESTApiStage"; - static constexpr const char RESOURCE_MAPPING_ACCOUNTID_KEYNAME[] = "AccountId"; - static constexpr const char RESOURCE_MAPPING_RESOURCES_KEYNAME[] = "AWSResourceMappings"; - static constexpr const char RESOURCE_MAPPING_NAMEID_KEYNAME[] = "Name/ID"; - static constexpr const char RESOURCE_MAPPING_REGION_KEYNAME[] = "Region"; - static constexpr const char RESOURCE_MAPPING_TYPE_KEYNAME[] = "Type"; - static constexpr const char RESOURCE_MAPPING_VERSION_KEYNAME[] = "Version"; + static constexpr const char ResourceMappingAccountIdKeyName[] = "AccountId"; + static constexpr const char ResourceMappingResourcesKeyName[] = "AWSResourceMappings"; + static constexpr const char ResourceMappingNameIdKeyName[] = "Name/ID"; + static constexpr const char ResourceMappingRegionKeyName[] = "Region"; + static constexpr const char ResourceMappingTypeKeyName[] = "Type"; + static constexpr const char ResourceMappingVersionKeyName[] = "Version"; // TODO: move this into an independent file under AWSCore gem, if resource mapping tool can reuse it - static constexpr const char RESOURCE_MAPPING_JSON_SCHEMA[] = + static constexpr const char ResourceMappingJsonSchema[] = R"({ "$schema": "http://json-schema.org/draft-04/schema", "type": "object", diff --git a/Gems/AWSCore/Code/Include/Private/ResourceMapping/AWSResourceMappingManager.h b/Gems/AWSCore/Code/Include/Private/ResourceMapping/AWSResourceMappingManager.h index 9200a9c4ff..6d22c55d2c 100644 --- a/Gems/AWSCore/Code/Include/Private/ResourceMapping/AWSResourceMappingManager.h +++ b/Gems/AWSCore/Code/Include/Private/ResourceMapping/AWSResourceMappingManager.h @@ -45,6 +45,35 @@ namespace AWSCore }; public: + static constexpr const char AWSResourceMappingManagerName[] = "AWSResourceMappingManager"; + static constexpr const char ManagerUnexpectedStatusErrorMessage[] = + "AWSResourceMappingManager is in unexpected status."; + static constexpr const char ResourceMappingFileInvalidPathErrorMessage[] = + "Failed to get resource mapping config file path."; + static constexpr const char ResourceMappingKeyNotFoundErrorMessage[] = + "Failed to find resource mapping key: %s"; + static constexpr const char ResourceMappingFileNotLoadedErrorMessage[] = + "Resource mapping config file is not loaded, please confirm %s is setup correctly."; + static constexpr const char ResourceMappingFileLoadFailureErrorMessage[] = + "Resource mapping config file failed to load, please confirm file is present and in correct format."; + static constexpr const char ResourceMappingRESTApiIdAndStageInconsistentErrorMessage[] = + "Resource mapping %s and %s have inconsistent region value, return empty service url."; + static constexpr const char ResourceMappingRESTApiInvalidServiceUrlErrorMessage[] = + "Unable to format REST Api url with RESTApiId=%s, RESTApiRegion=%s, RESTApiStage=%s, return empty service url."; + static constexpr const char ResourceMappingFileInvalidJsonFormatErrorMessage[] = + "Failed to read resource mapping config file: %s"; + static constexpr const char ResourceMappingFileInvalidSchemaErrorMessage[] = + "Failed to load resource mapping config file json schema."; + static constexpr const char ResourceMappingFileInvalidContentErrorMessage[] = + "Failed to parse resource mapping config file: %s"; + + enum class Status : AZ::u8 + { + NotLoaded = 0, + Ready = 1, + Error = 2 + }; + AWSResourceMappingManager(); ~AWSResourceMappingManager() = default; @@ -63,7 +92,12 @@ namespace AWSCore const AZStd::string& restApiIdKeyName, const AZStd::string& restApiStageKeyName) const override; void ReloadConfigFile(bool reloadConfigFileName = false) override; + Status GetStatus() const; + private: + // Get resource attribute error message based on the status + AZStd::string GetResourceAttributeErrorMessageByStatus(const AZStd::string& resourceKeyName) const; + // Get resource attribute from resource mappings AZStd::string GetResourceAttribute( AZStd::function getAttributeFunction, @@ -83,6 +117,7 @@ namespace AWSCore // Validate JSON document against schema bool ValidateJsonDocumentAgainstSchema(const rapidjson::Document& jsonDocument); + Status m_status; // Resource mapping related data AZStd::string m_defaultAccountId; AZStd::string m_defaultRegion; diff --git a/Gems/AWSCore/Code/Source/Configuration/AWSCoreConfiguration.cpp b/Gems/AWSCore/Code/Source/Configuration/AWSCoreConfiguration.cpp index 0445ea830e..3c0f48c058 100644 --- a/Gems/AWSCore/Code/Source/Configuration/AWSCoreConfiguration.cpp +++ b/Gems/AWSCore/Code/Source/Configuration/AWSCoreConfiguration.cpp @@ -20,7 +20,7 @@ namespace AWSCore { AWSCoreConfiguration::AWSCoreConfiguration() : m_sourceProjectFolder("") - , m_profileName(AWSCORE_DEFAULT_PROFILE_NAME) + , m_profileName(AWSCoreDefaultProfileName) , m_resourceMappingConfigFileName("") { } @@ -44,16 +44,16 @@ namespace AWSCore { if (m_sourceProjectFolder.empty()) { - AZ_Warning("AWSCoreConfiguration", false, "Failed to get source project folder path."); + AZ_Warning(AWSCoreConfigurationName, false, ProjectSourceFolderNotFoundErrorMessage); return ""; } if (m_resourceMappingConfigFileName.empty()) { - AZ_Warning("AWSCoreConfiguration", false, "Failed to get resource mapping config file name."); + AZ_Warning(AWSCoreConfigurationName, false, ResourceMappingFileNameNotFoundErrorMessage); return ""; } AZStd::string configFilePath = AZStd::string::format("%s/%s/%s", - m_sourceProjectFolder.c_str(), AWSCORE_RESOURCE_MAPPING_CONFIG_FOLDERNAME, m_resourceMappingConfigFileName.c_str()); + m_sourceProjectFolder.c_str(), AWSCoreResourceMappingConfigFolderName, m_resourceMappingConfigFileName.c_str()); AzFramework::StringFunc::Path::Normalize(configFilePath); return configFilePath; } @@ -68,17 +68,17 @@ namespace AWSCore { if (m_sourceProjectFolder.empty()) { - AZ_Warning("AWSCoreConfiguration", false, "Failed to get source project folder path."); + AZ_Warning(AWSCoreConfigurationName, false, ProjectSourceFolderNotFoundErrorMessage); return; } AZStd::string settingsRegistryPath = AZStd::string::format("%s/%s/%s", - m_sourceProjectFolder.c_str(), AZ::SettingsRegistryInterface::RegistryFolder, AWSCoreConfiguration::AWSCORE_CONFIGURATION_FILENAME); + m_sourceProjectFolder.c_str(), AZ::SettingsRegistryInterface::RegistryFolder, AWSCoreConfiguration::AWSCoreConfigurationFileName); AzFramework::StringFunc::Path::Normalize(settingsRegistryPath); if (!m_settingsRegistry.MergeSettingsFile(settingsRegistryPath, AZ::SettingsRegistryInterface::Format::JsonMergePatch, "")) { - AZ_Warning("AWSCoreConfiguration", false, "Failed to merge AWS core settings registry."); + AZ_Warning(AWSCoreConfigurationName, false, SettingsRegistryLoadFailureErrorMessage); return; } @@ -90,7 +90,7 @@ namespace AWSCore auto sourceProjectFolder = AZ::IO::FileIOBase::GetInstance()->GetAlias("@devassets@"); if (!sourceProjectFolder) { - AZ_Error("AWSCoreConfiguration", false, "Failed to initialize source project folder path."); + AZ_Error(AWSCoreConfigurationName, false, ProjectSourceFolderNotFoundErrorMessage); } else { @@ -102,24 +102,38 @@ namespace AWSCore { m_resourceMappingConfigFileName.clear(); auto resourceMappingConfigFileNamePath = AZStd::string::format("%s%s", - AZ::SettingsRegistryMergeUtils::OrganizationRootKey, AWSCORE_RESOURCE_MAPPING_CONFIG_FILENAME_KEY); + AZ::SettingsRegistryMergeUtils::OrganizationRootKey, AWSCoreResourceMappingConfigFileNameKey); if (!m_settingsRegistry.Get(m_resourceMappingConfigFileName, resourceMappingConfigFileNamePath)) { - AZ_Warning("AWSCoreConfiguration", false, "Failed to get resource mapping config file name from settings registry."); + AZ_Warning(AWSCoreConfigurationName, false, ResourceMappingFileNameNotFoundErrorMessage); } m_profileName.clear(); auto profileNamePath = AZStd::string::format( - "%s%s", AZ::SettingsRegistryMergeUtils::OrganizationRootKey, AWSCORE_PROFILENAME_KEY); + "%s%s", AZ::SettingsRegistryMergeUtils::OrganizationRootKey, AWSCoreProfileNameKey); if (!m_settingsRegistry.Get(m_profileName, profileNamePath)) { - AZ_Warning("AWSCoreConfiguration", false, "Failed to get profile name from settings registry, using default value instead."); - m_profileName = AWSCORE_DEFAULT_PROFILE_NAME; + AZ_Warning(AWSCoreConfigurationName, false, ProfileNameNotFoundErrorMessage); + m_profileName = AWSCoreDefaultProfileName; } } + void AWSCoreConfiguration::ResetSettingsRegistryData() + { + auto profileNamePath = AZStd::string::format("%s%s", + AZ::SettingsRegistryMergeUtils::OrganizationRootKey, AWSCoreProfileNameKey); + m_settingsRegistry.Remove(profileNamePath); + m_profileName.clear(); + + auto resourceMappingConfigFileNamePath = AZStd::string::format("%s%s", + AZ::SettingsRegistryMergeUtils::OrganizationRootKey, AWSCoreResourceMappingConfigFileNameKey); + m_settingsRegistry.Remove(resourceMappingConfigFileNamePath); + m_resourceMappingConfigFileName.clear(); + } + void AWSCoreConfiguration::ReloadConfiguration() { + ResetSettingsRegistryData(); InitSettingsRegistry(); } } // namespace AWSCore diff --git a/Gems/AWSCore/Code/Source/Credential/AWSDefaultCredentialHandler.cpp b/Gems/AWSCore/Code/Source/Credential/AWSDefaultCredentialHandler.cpp index ba7a8c72dd..bb195e487a 100644 --- a/Gems/AWSCore/Code/Source/Credential/AWSDefaultCredentialHandler.cpp +++ b/Gems/AWSCore/Code/Source/Credential/AWSDefaultCredentialHandler.cpp @@ -84,7 +84,7 @@ namespace AWSCore { AZ_Warning("AWSDefaultCredentialHandler", false, "Failed to get profile name, use default profile name instead"); SetProfileCredentialsProvider(Aws::MakeShared( - AWSDEFAULTCREDENTIALHANDLER_ALLOC_TAG, AWSCoreConfiguration::AWSCORE_DEFAULT_PROFILE_NAME)); + AWSDEFAULTCREDENTIALHANDLER_ALLOC_TAG, AWSCoreConfiguration::AWSCoreDefaultProfileName)); } else { diff --git a/Gems/AWSCore/Code/Source/ResourceMapping/AWSResourceMappingManager.cpp b/Gems/AWSCore/Code/Source/ResourceMapping/AWSResourceMappingManager.cpp index c87a7f99ba..efdba0e9e0 100644 --- a/Gems/AWSCore/Code/Source/ResourceMapping/AWSResourceMappingManager.cpp +++ b/Gems/AWSCore/Code/Source/ResourceMapping/AWSResourceMappingManager.cpp @@ -12,12 +12,14 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -25,7 +27,8 @@ namespace AWSCore { AWSResourceMappingManager::AWSResourceMappingManager() - : m_defaultAccountId("") + : m_status(Status::NotLoaded) + , m_defaultAccountId("") , m_defaultRegion("") , m_resourceMappings() { @@ -43,11 +46,27 @@ namespace AWSCore ResetResourceMappingsData(); } + AZStd::string AWSResourceMappingManager::GetResourceAttributeErrorMessageByStatus(const AZStd::string& resourceKeyName) const + { + switch (m_status) + { + case Status::NotLoaded: + return AZStd::string::format(ResourceMappingFileNotLoadedErrorMessage, AWSCoreConfiguration::AWSCoreConfigurationFileName); + case Status::Ready: + return AZStd::string::format(ResourceMappingKeyNotFoundErrorMessage, resourceKeyName.c_str()); + case Status::Error: + return ResourceMappingFileLoadFailureErrorMessage; + default: + return ManagerUnexpectedStatusErrorMessage; + } + } + AZStd::string AWSResourceMappingManager::GetDefaultAccountId() const { if (m_defaultAccountId.empty()) { - AZ_Warning("AWSResourceMappingManager", false, "Account Id should not be empty, please make sure config file is valid."); + AZ_Warning(AWSResourceMappingManagerName, false, + GetResourceAttributeErrorMessageByStatus(ResourceMappingAccountIdKeyName).c_str()); } return m_defaultAccountId; } @@ -56,7 +75,8 @@ namespace AWSCore { if (m_defaultRegion.empty()) { - AZ_Warning("AWSResourceMappingManager", false, "Region should not be empty, please make sure config file is valid."); + AZ_Warning(AWSResourceMappingManagerName, false, + GetResourceAttributeErrorMessageByStatus(ResourceMappingRegionKeyName).c_str()); } return m_defaultRegion; } @@ -100,8 +120,8 @@ namespace AWSCore AZStd::string AWSResourceMappingManager::GetServiceUrlByServiceName(const AZStd::string& serviceName) const { return GetServiceUrlByRESTApiIdAndStage( - AZStd::string::format("%s%s", serviceName.c_str(), AWS_FEATURE_GEM_RESTAPI_ID_KEYNAME_SUFFIX), - AZStd::string::format("%s%s", serviceName.c_str(), AWS_FEATURE_GEM_RESTAPI_STAGE_KEYNAME_SUFFIX)); + AZStd::string::format("%s%s", serviceName.c_str(), AWSFeatureGemRESTApiIdKeyNameSuffix), + AZStd::string::format("%s%s", serviceName.c_str(), AWSFeatureGemRESTApiStageKeyNameSuffix)); } AZStd::string AWSResourceMappingManager::GetServiceUrlByRESTApiIdAndStage( @@ -113,16 +133,13 @@ namespace AWSCore AZStd::string serviceRegion = GetResourceRegion(restApiIdKeyName); if (serviceRegion != GetResourceRegion(restApiStageKeyName)) { - AZ_Warning( - "AWSResourceMappingManager", false, "%s and %s have inconsistent region value, return empty service url.", + AZ_Warning(AWSResourceMappingManagerName, false, ResourceMappingRESTApiIdAndStageInconsistentErrorMessage, restApiIdKeyName.c_str(), restApiStageKeyName.c_str()); return ""; } AZStd::string serviceRESTApiUrl = AWSResourceMappingUtils::FormatRESTApiUrl(serviceRESTApiId, serviceRegion, serviceRESTApiStage); - AZ_Warning( - "AWSResourceMappingManager", !serviceRESTApiUrl.empty(), - "Unable to format REST Api url with RESTApiId=%s, RESTApiRegion=%s, RESTApiStage=%s, return empty service url.", + AZ_Warning(AWSResourceMappingManagerName, !serviceRESTApiUrl.empty(), ResourceMappingRESTApiInvalidServiceUrlErrorMessage, serviceRESTApiId.c_str(), serviceRegion.c_str(), serviceRESTApiStage.c_str()); return serviceRESTApiUrl; } @@ -136,16 +153,21 @@ namespace AWSCore return getAttributeFunction(iter->second); } - AZ_Warning("AWSResourceMappingManager", false, "Failed to find resource mapping key: %s.", resourceKeyName.c_str()); + AZ_Warning(AWSResourceMappingManagerName, false, GetResourceAttributeErrorMessageByStatus(resourceKeyName).c_str()); return ""; } + AWSResourceMappingManager::Status AWSResourceMappingManager::GetStatus() const + { + return m_status; + } + void AWSResourceMappingManager::ParseJsonDocument(const rapidjson::Document& jsonDocument) { - m_defaultAccountId = jsonDocument.FindMember(RESOURCE_MAPPING_ACCOUNTID_KEYNAME)->value.GetString(); - m_defaultRegion = jsonDocument.FindMember(RESOURCE_MAPPING_REGION_KEYNAME)->value.GetString(); + m_defaultAccountId = jsonDocument.FindMember(ResourceMappingAccountIdKeyName)->value.GetString(); + m_defaultRegion = jsonDocument.FindMember(ResourceMappingRegionKeyName)->value.GetString(); - auto resourceMappings = jsonDocument.FindMember(RESOURCE_MAPPING_RESOURCES_KEYNAME)->value.GetObject(); + auto resourceMappings = jsonDocument.FindMember(ResourceMappingResourcesKeyName)->value.GetObject(); for (auto mappingIter = resourceMappings.MemberBegin(); mappingIter != resourceMappings.MemberEnd(); mappingIter++) { auto mappingValue = mappingIter->value.GetObject(); @@ -162,16 +184,16 @@ namespace AWSCore const JsonObject& jsonObject) { AWSResourceMappingAttributes attributes; - if (jsonObject.HasMember(RESOURCE_MAPPING_ACCOUNTID_KEYNAME)) + if (jsonObject.HasMember(ResourceMappingAccountIdKeyName)) { - attributes.resourceAccountId = jsonObject.FindMember(RESOURCE_MAPPING_ACCOUNTID_KEYNAME)->value.GetString(); + attributes.resourceAccountId = jsonObject.FindMember(ResourceMappingAccountIdKeyName)->value.GetString(); } - attributes.resourceNameId = jsonObject.FindMember(RESOURCE_MAPPING_NAMEID_KEYNAME)->value.GetString(); - if (jsonObject.HasMember(RESOURCE_MAPPING_REGION_KEYNAME)) + attributes.resourceNameId = jsonObject.FindMember(ResourceMappingNameIdKeyName)->value.GetString(); + if (jsonObject.HasMember(ResourceMappingRegionKeyName)) { - attributes.resourceRegion = jsonObject.FindMember(RESOURCE_MAPPING_REGION_KEYNAME)->value.GetString(); + attributes.resourceRegion = jsonObject.FindMember(ResourceMappingRegionKeyName)->value.GetString(); } - attributes.resourceType = jsonObject.FindMember(RESOURCE_MAPPING_TYPE_KEYNAME)->value.GetString(); + attributes.resourceType = jsonObject.FindMember(ResourceMappingTypeKeyName)->value.GetString(); return attributes; } @@ -188,7 +210,7 @@ namespace AWSCore AWSCoreInternalRequestBus::BroadcastResult(configJsonPath, &AWSCoreInternalRequests::GetResourceMappingConfigFilePath); if (configJsonPath.empty()) { - AZ_Warning("AWSResourceMappingManager", false, "Failed to get resource mapping config file path."); + AZ_Warning(AWSResourceMappingManagerName, false, ResourceMappingFileInvalidPathErrorMessage); return; } @@ -201,20 +223,26 @@ namespace AWSCore if (!ValidateJsonDocumentAgainstSchema(jsonDocument)) { // Failed to satisfy the validation against json schema + m_status = Status::Error; return; } ParseJsonDocument(jsonDocument); } else { - AZ_Warning( - "AWSResourceMappingManager", false, "Failed to get read resource mapping config file: %s\n Error: %s", - configJsonPath.c_str(), readJsonOutcome.GetError().c_str()); + m_status = Status::Error; + AZ_Warning(AWSResourceMappingManagerName, false, + ResourceMappingFileInvalidJsonFormatErrorMessage, readJsonOutcome.GetError().c_str()); + return; } + + // Resource mapping config file gets loaded successfully + m_status = Status::Ready; } void AWSResourceMappingManager::ResetResourceMappingsData() { + m_status = Status::NotLoaded; m_defaultAccountId = ""; m_defaultRegion = ""; m_resourceMappings.clear(); @@ -223,9 +251,9 @@ namespace AWSCore bool AWSResourceMappingManager::ValidateJsonDocumentAgainstSchema(const rapidjson::Document& jsonDocument) { rapidjson::Document jsonSchemaDocument; - if (jsonSchemaDocument.Parse(RESOURCE_MAPPING_JSON_SCHEMA).HasParseError()) + if (jsonSchemaDocument.Parse(ResourceMappingJsonSchema).HasParseError()) { - AZ_Error("AWSResourceMappingManager", false, "Invalid resource mapping json schema."); + AZ_Error(AWSResourceMappingManagerName, false, ResourceMappingFileInvalidSchemaErrorMessage); return false; } @@ -235,12 +263,10 @@ namespace AWSCore if (!jsonDocument.Accept(validator)) { rapidjson::StringBuffer error; - validator.GetInvalidSchemaPointer().StringifyUriFragment(error); - AZ_Warning("AWSResourceMappingManager", false, "Failed to load config file, invalid schema: %s.", error.GetString()); - AZ_Warning("AWSResourceMappingManager", false, "Failed to load config file, invalid keyword: %s.", validator.GetInvalidSchemaKeyword()); - error.Clear(); - validator.GetInvalidDocumentPointer().StringifyUriFragment(error); - AZ_Warning("AWSResourceMappingManager", false, "Failed to load config file, invalid document: %s.", error.GetString()); + rapidjson::PrettyWriter writer(error); + validator.GetError().Accept(writer); + AZ_Warning(AWSResourceMappingManagerName, false, ResourceMappingFileInvalidContentErrorMessage, error.GetString()); + return false; } return true; diff --git a/Gems/AWSCore/Code/Source/ResourceMapping/AWSResourceMappingUtils.cpp b/Gems/AWSCore/Code/Source/ResourceMapping/AWSResourceMappingUtils.cpp index 74be3bf914..7d17a86909 100644 --- a/Gems/AWSCore/Code/Source/ResourceMapping/AWSResourceMappingUtils.cpp +++ b/Gems/AWSCore/Code/Source/ResourceMapping/AWSResourceMappingUtils.cpp @@ -18,8 +18,8 @@ namespace AWSCore namespace AWSResourceMappingUtils { // https://docs.aws.amazon.com/general/latest/gr/apigateway.html - static constexpr char RESTAPI_URL_FORMAT[] = "https://%s.execute-api.%s.amazonaws.com/%s"; - static constexpr char RESTAPI_CHINA_URL_FORMAT[] = "https://%s.execute-api.%s.amazonaws.com.cn/%s"; + static constexpr char RESTApiUrlFormat[] = "https://%s.execute-api.%s.amazonaws.com/%s"; + static constexpr char RESTApiChinaUrlFormat[] = "https://%s.execute-api.%s.amazonaws.com.cn/%s"; AZStd::string FormatRESTApiUrl( const AZStd::string& restApiId, const AZStd::string& restApiRegion, const AZStd::string& restApiStage) @@ -27,14 +27,14 @@ namespace AWSCore // https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-call-api.html if (!restApiId.empty() && !restApiRegion.empty() && !restApiStage.empty()) { - if (restApiRegion.rfind(AWS_CHINA_REGION_PREFIX, 0) == 0) + if (restApiRegion.rfind(AWSChinaRegionPrefix, 0) == 0) { - return AZStd::string::format(RESTAPI_CHINA_URL_FORMAT, + return AZStd::string::format(RESTApiChinaUrlFormat, restApiId.c_str(), restApiRegion.c_str(), restApiStage.c_str()); } else { - return AZStd::string::format(RESTAPI_URL_FORMAT, + return AZStd::string::format(RESTApiUrlFormat, restApiId.c_str(), restApiRegion.c_str(), restApiStage.c_str()); } } diff --git a/Gems/AWSCore/Code/Tests/Configuration/AWSCoreConfigurationTest.cpp b/Gems/AWSCore/Code/Tests/Configuration/AWSCoreConfigurationTest.cpp index dab405dee4..b3c2b8d2ca 100644 --- a/Gems/AWSCore/Code/Tests/Configuration/AWSCoreConfigurationTest.cpp +++ b/Gems/AWSCore/Code/Tests/Configuration/AWSCoreConfigurationTest.cpp @@ -46,7 +46,7 @@ public: void CreateTestSetRegFile(const AZStd::string& setregContent) { m_normalizedSetRegFilePath = AZStd::string::format("%s/%s", - m_normalizedSetRegFolderPath.c_str(), AWSCore::AWSCoreConfiguration::AWSCORE_CONFIGURATION_FILENAME); + m_normalizedSetRegFolderPath.c_str(), AWSCore::AWSCoreConfiguration::AWSCoreConfigurationFileName); AzFramework::StringFunc::Path::Normalize(m_normalizedSetRegFilePath); CreateTestFile(m_normalizedSetRegFilePath, setregContent); } @@ -177,7 +177,7 @@ TEST_F(AWSCoreConfigurationTest, ReloadConfiguration_LoadValidSettingsRegistryAf auto actualConfigFilePath = m_awsCoreConfiguration->GetResourceMappingConfigFilePath(); auto actualProfileName = m_awsCoreConfiguration->GetProfileName(); EXPECT_TRUE(actualConfigFilePath.empty()); - EXPECT_TRUE(actualProfileName == AWSCoreConfiguration::AWSCORE_DEFAULT_PROFILE_NAME); + EXPECT_TRUE(actualProfileName == AWSCoreConfiguration::AWSCoreDefaultProfileName); CreateTestSetRegFile(TEST_VALID_RESOURCE_MAPPING_SETREG); m_awsCoreConfiguration->ReloadConfiguration(); @@ -185,5 +185,24 @@ TEST_F(AWSCoreConfigurationTest, ReloadConfiguration_LoadValidSettingsRegistryAf actualConfigFilePath = m_awsCoreConfiguration->GetResourceMappingConfigFilePath(); actualProfileName = m_awsCoreConfiguration->GetProfileName(); EXPECT_FALSE(actualConfigFilePath.empty()); - EXPECT_TRUE(actualProfileName != AWSCoreConfiguration::AWSCORE_DEFAULT_PROFILE_NAME); + EXPECT_TRUE(actualProfileName != AWSCoreConfiguration::AWSCoreDefaultProfileName); +} + +TEST_F(AWSCoreConfigurationTest, ReloadConfiguration_LoadInvalidSettingsRegistryAfterValidOne_ReturnEmptyConfigFilePath) +{ + CreateTestSetRegFile(TEST_VALID_RESOURCE_MAPPING_SETREG); + m_awsCoreConfiguration->InitConfig(); + + auto actualConfigFilePath = m_awsCoreConfiguration->GetResourceMappingConfigFilePath(); + auto actualProfileName = m_awsCoreConfiguration->GetProfileName(); + EXPECT_FALSE(actualConfigFilePath.empty()); + EXPECT_TRUE(actualProfileName != AWSCoreConfiguration::AWSCoreDefaultProfileName); + + CreateTestSetRegFile(TEST_INVALID_RESOURCE_MAPPING_SETREG); + m_awsCoreConfiguration->ReloadConfiguration(); + + actualConfigFilePath = m_awsCoreConfiguration->GetResourceMappingConfigFilePath(); + actualProfileName = m_awsCoreConfiguration->GetProfileName(); + EXPECT_TRUE(actualConfigFilePath.empty()); + EXPECT_TRUE(actualProfileName == AWSCoreConfiguration::AWSCoreDefaultProfileName); } diff --git a/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp b/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp index 73a28f97cb..3adebc9a24 100644 --- a/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp +++ b/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp @@ -98,7 +98,7 @@ public: "AWSResourceMappingManager", AZ::Uuid::CreateRandom().ToString(false, false).c_str()); AzFramework::StringFunc::Path::Normalize(m_normalizedSourceProjectFolder); m_normalizedConfigFolderPath = AZStd::string::format("%s/%s/", - m_normalizedSourceProjectFolder.c_str(), AWSCore::AWSCoreConfiguration::AWSCORE_RESOURCE_MAPPING_CONFIG_FOLDERNAME); + m_normalizedSourceProjectFolder.c_str(), AWSCore::AWSCoreConfiguration::AWSCoreResourceMappingConfigFolderName); AzFramework::StringFunc::Path::Normalize(m_normalizedConfigFolderPath); AWSCoreInternalRequestBus::Handler::BusConnect(); } @@ -178,6 +178,7 @@ TEST_F(AWSResourceMappingManagerTest, ActivateManager_ParseInvalidConfigFile_Con EXPECT_EQ(m_reloadConfigurationCounter, 1); EXPECT_TRUE(actualAccountId.empty()); EXPECT_TRUE(actualRegion.empty()); + EXPECT_TRUE(m_resourceMappingManager->GetStatus() == AWSResourceMappingManager::Status::Error); } TEST_F(AWSResourceMappingManagerTest, ActivateManager_ParseValidConfigFile_ConfigDataIsNotEmpty) @@ -192,6 +193,7 @@ TEST_F(AWSResourceMappingManagerTest, ActivateManager_ParseValidConfigFile_Confi EXPECT_EQ(m_reloadConfigurationCounter, 1); EXPECT_FALSE(actualAccountId.empty()); EXPECT_FALSE(actualRegion.empty()); + EXPECT_TRUE(m_resourceMappingManager->GetStatus() == AWSResourceMappingManager::Status::Ready); } TEST_F(AWSResourceMappingManagerTest, ActivateManager_ParseValidConfigFile_ConfigDataIsNotEmptyWithMultithreadCalls) @@ -230,11 +232,13 @@ TEST_F(AWSResourceMappingManagerTest, DeactivateManager_AfterActivatingWithValid AWSResourceMappingRequestBus::BroadcastResult(actualRegion, &AWSResourceMappingRequests::GetDefaultRegion); EXPECT_FALSE(actualAccountId.empty()); EXPECT_FALSE(actualRegion.empty()); + EXPECT_TRUE(m_resourceMappingManager->GetStatus() == AWSResourceMappingManager::Status::Ready); m_resourceMappingManager->DeactivateManager(); EXPECT_TRUE(m_resourceMappingManager->GetDefaultAccountId().empty()); EXPECT_TRUE(m_resourceMappingManager->GetDefaultRegion().empty()); + EXPECT_TRUE(m_resourceMappingManager->GetStatus() == AWSResourceMappingManager::Status::NotLoaded); } TEST_F(AWSResourceMappingManagerTest, GetDefaultAccountId_AfterParsingValidConfigFile_GetExpectedDefaultAccountId) @@ -416,6 +420,7 @@ TEST_F(AWSResourceMappingManagerTest, ReloadConfigFile_ParseValidConfigFileAfter EXPECT_EQ(m_reloadConfigurationCounter, 1); EXPECT_TRUE(actualAccountId.empty()); EXPECT_TRUE(actualRegion.empty()); + EXPECT_TRUE(m_resourceMappingManager->GetStatus() == AWSResourceMappingManager::Status::Error); CreateTestConfigFile(TEST_VALID_RESOURCE_MAPPING_CONFIG_FILE); m_resourceMappingManager->ReloadConfigFile(); @@ -425,6 +430,7 @@ TEST_F(AWSResourceMappingManagerTest, ReloadConfigFile_ParseValidConfigFileAfter EXPECT_EQ(m_reloadConfigurationCounter, 1); EXPECT_FALSE(actualAccountId.empty()); EXPECT_FALSE(actualRegion.empty()); + EXPECT_TRUE(m_resourceMappingManager->GetStatus() == AWSResourceMappingManager::Status::Ready); } TEST_F(AWSResourceMappingManagerTest, ReloadConfigFile_ReloadConfigFileNameAndParseValidConfigFile_ConfigDataGetParsed) @@ -435,6 +441,7 @@ TEST_F(AWSResourceMappingManagerTest, ReloadConfigFile_ReloadConfigFileNameAndPa EXPECT_EQ(m_reloadConfigurationCounter, 1); EXPECT_FALSE(m_resourceMappingManager->GetDefaultAccountId().empty()); EXPECT_FALSE(m_resourceMappingManager->GetDefaultRegion().empty()); + EXPECT_TRUE(m_resourceMappingManager->GetStatus() == AWSResourceMappingManager::Status::Ready); } TEST_F(AWSResourceMappingManagerTest, ReloadConfigFile_MissingSetRegFile_ConfigDataIsNotParsed) @@ -444,4 +451,5 @@ TEST_F(AWSResourceMappingManagerTest, ReloadConfigFile_MissingSetRegFile_ConfigD EXPECT_EQ(m_reloadConfigurationCounter, 1); EXPECT_TRUE(m_resourceMappingManager->GetDefaultAccountId().empty()); EXPECT_TRUE(m_resourceMappingManager->GetDefaultRegion().empty()); + EXPECT_TRUE(m_resourceMappingManager->GetStatus() == AWSResourceMappingManager::Status::NotLoaded); } diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.cpp index f63ae4ca77..7b6ec518bc 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.cpp @@ -851,7 +851,9 @@ namespace ImageProcessingAtom if (preset == nullptr) { - AZ_Assert(false, "preset should always exist"); + AZStd::string uuidStr; + textureSettings.m_preset.ToString(uuidStr); + AZ_Assert(false, "%s cannot find image preset with ID %s.", imageFilePath.c_str(), uuidStr.c_str()); return nullptr; } diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype index cb66a987ae..954e01c592 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype @@ -946,19 +946,6 @@ "type": "Bool", "defaultValue": false }, - { - "id": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the depth values", - "type": "Float", - "defaultValue": 0.0, - "min": 0.0, - "softMax": 0.1, - "connection": { - "type": "ShaderInput", - "id": "m_depthFactor" - } - }, { "id": "textureMap", "displayName": "Texture Map", @@ -981,6 +968,32 @@ "id": "m_parallaxUvIndex" } }, + { + "id": "factor", + "displayName": "Heightmap Scale", + "description": "The total height of the heightmap in local model units.", + "type": "Float", + "defaultValue": 0.0, + "min": 0.0, + "softMax": 0.1, + "connection": { + "type": "ShaderInput", + "id": "m_depthFactor" + } + }, + { + "id": "offset", + "displayName": "Offset", + "description": "Adjusts the overall displacement amount in local model units.", + "type": "Float", + "defaultValue": 0.0, + "softMin": -0.1, + "softMax": 0.1, + "connection": { + "type": "ShaderInput", + "id": "m_depthOffset" + } + }, { "id": "invert", "displayName": "Invert", @@ -1026,6 +1039,17 @@ "type": "ShaderOption", "id": "o_parallax_enablePixelDepthOffset" } + }, + { + "id": "showClipping", + "displayName": "Show Clipping", + "description": "Highlight areas where the heightmap is clipped by the mesh surface.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "id": "o_parallax_highlightClipping" + } } ], "subsurfaceScattering": [ @@ -1714,22 +1738,6 @@ "shaderOption": "o_emissive_useTexture" } }, - { - // See the comment above for details. - "type": "UseTexture", - "args": { - "textureProperty": "parallax.textureMap", - "dependentProperties": ["parallax.textureMapUv"], - "useTextureProperty": "parallax.enable", - "shaderTags": [ - "ForwardPass", - "ForwardPass_EDS", - "Shadowmap_WithPS", - "DepthPass_WithPS" - ], - "shaderOption": "o_parallax_feature_enabled" - } - }, { // See the comment above for details. "type": "UseTexture", @@ -1813,34 +1821,6 @@ ] } }, - { - // Controls visibility for properties in the editor. - // @param actions - a list of actions that are executed in order. visibility will be set when triggerProperty hits the triggerValue. - // @param affectedProperties - the properties that are affected by actions. - "type": "UpdatePropertyVisibility", - "args": { - "actions": [ - { - "triggerProperty": "parallax.enable", - "triggerValue": true, - "visibility": "Enabled" - }, - { - "triggerProperty": "parallax.enable", - "triggerValue": false, - "visibility": "Hidden" - } - ], - "affectedProperties": [ - "parallax.factor", - "parallax.textureMap", - "parallax.invert", - "parallax.algorithm", - "parallax.quality", - "parallax.pdo" - ] - } - }, { "type": "UpdatePropertyVisibility", "args": { @@ -2076,6 +2056,12 @@ ] } }, + { + "type": "Lua", + "args": { + "file": "StandardPBR_ParallaxState.lua" + } + }, { "type": "Lua", "args": { diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Common.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Common.azsli index f9bfb75fea..34af9229c2 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Common.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Common.azsli @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include "MaterialInputs/BaseColorInput.azsli" #include "MaterialInputs/RoughnessInput.azsli" @@ -104,7 +106,22 @@ ShaderResourceGroup MaterialSrg : SRG_PerMaterial } // Callback function for ParallaxMapping.azsli -float GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy) +DepthResult GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy) { return SampleDepthOrHeightMap(MaterialSrg::m_depthInverted, MaterialSrg::m_depthMap, MaterialSrg::m_sampler, uv, uv_ddx, uv_ddy); } + +COMMON_OPTIONS_PARALLAX() + +bool ShouldHandleParallax() +{ + // Parallax mapping's non uniform uv transformations break screen space subsurface scattering, disable it when subsurface scattering is enabled. + return !o_enableSubsurfaceScattering && o_parallax_feature_enabled && o_useDepthMap; +} + +bool ShouldHandleParallaxInDepthShaders() +{ + // The depth pass shaders need to calculate parallax when the result could affect the depth buffer, or when + // parallax could affect texel clipping. + return ShouldHandleParallax() && (o_parallax_enablePixelDepthOffset || o_opacity_mode == OpacityMode::Cutout); +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl index 28968b5941..db99ee7e24 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl @@ -10,7 +10,6 @@ * */ -#include #include "./EnhancedPBR_Common.azsli" #include #include @@ -55,7 +54,7 @@ VSDepthOutput MainVS(VSInput IN) OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy; OUT.m_uv[1] = IN.m_uv1; - if(o_parallax_feature_enabled && o_parallax_enablePixelDepthOffset) + if(ShouldHandleParallaxInDepthShaders()) { OUT.m_worldPosition = worldPosition.xyz; @@ -74,45 +73,28 @@ PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace) { PSDepthOutput OUT; - // Clip Alpha - float2 baseColorUV = IN.m_uv[MaterialSrg::m_baseColorMapUvIndex]; - float2 opacityUV = IN.m_uv[MaterialSrg::m_opacityMapUvIndex]; - float alpha = SampleAlpha(MaterialSrg::m_baseColorMap, MaterialSrg::m_opacityMap, baseColorUV, opacityUV, MaterialSrg::m_sampler, o_opacity_source); - CheckClipping(alpha, MaterialSrg::m_opacityFactor); - OUT.m_depth = IN.m_position.z; - - if(o_parallax_feature_enabled && o_parallax_enablePixelDepthOffset) + + if(ShouldHandleParallaxInDepthShaders()) { // We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space. float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) }; float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) }; PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1); - float3 tangent = tangents[MaterialSrg::m_parallaxUvIndex]; - float3 bitangent = bitangents[MaterialSrg::m_parallaxUvIndex]; - float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); - - float3 tangentOffset = GetParallaxOffset( MaterialSrg::m_depthFactor, - IN.m_uv[MaterialSrg::m_parallaxUvIndex], - ViewSrg::m_worldPosition.xyz - IN.m_worldPosition, - tangent, - bitangent, - IN.m_normal, - uvMatrix); - - PixelDepthOffset pdo = CalcPixelDepthOffset(MaterialSrg::m_depthFactor, - tangentOffset, - IN.m_worldPosition, - tangent, - bitangent, - IN.m_normal, - uvMatrixInverse, - ObjectSrg::GetWorldMatrix(), - ViewSrg::m_viewProjectionMatrix); - OUT.m_depth = pdo.m_depth; + + GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_depthFactor, MaterialSrg::m_depthOffset, + ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse, + IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, OUT.m_depth); } + + // Clip Alpha + float2 baseColorUV = IN.m_uv[MaterialSrg::m_baseColorMapUvIndex]; + float2 opacityUV = IN.m_uv[MaterialSrg::m_opacityMapUvIndex]; + float alpha = SampleAlpha(MaterialSrg::m_baseColorMap, MaterialSrg::m_opacityMap, baseColorUV, opacityUV, MaterialSrg::m_sampler, o_opacity_source); + CheckClipping(alpha, MaterialSrg::m_opacityFactor); + return OUT; } diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl index 36c8e81a94..23e28e8742 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl @@ -40,8 +40,8 @@ COMMON_OPTIONS_NORMAL() COMMON_OPTIONS_CLEAR_COAT() COMMON_OPTIONS_OCCLUSION() COMMON_OPTIONS_EMISSIVE() -COMMON_OPTIONS_PARALLAX() COMMON_OPTIONS_DETAIL_MAPS() +// Note COMMON_OPTIONS_PARALLAX is in StandardPBR_Common.azsli because it's needed by all StandardPBR shaders. // Alpha #include "MaterialInputs/AlphaInput.azsli" @@ -102,8 +102,11 @@ VSOutput EnhancedPbr_ForwardPassVS(VSInput IN) // but we would need to address how it works with the parallax code below that indexes into the m_detailUV array. OUT.m_detailUv[0] = mul(MaterialSrg::m_detailUvMatrix, float3(IN.m_uv0, 1.0)).xy; OUT.m_detailUv[1] = mul(MaterialSrg::m_detailUvMatrix, float3(IN.m_uv1, 1.0)).xy; + + // Shadow coords will be calculated in the pixel shader in this case + bool skipShadowCoords = ShouldHandleParallax() && o_parallax_enablePixelDepthOffset; - VertexHelper(IN, OUT, worldPosition, o_parallax_feature_enabled && o_parallax_enablePixelDepthOffset); + VertexHelper(IN, OUT, worldPosition, skipShadowCoords); return OUT; } @@ -132,9 +135,11 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // ------- Depth & Parallax ------- depth = IN.m_position.z; + + bool displacementIsClipped = false; // Parallax mapping's non uniform uv transformations break screen space subsurface scattering, disable it when subsurface scatteirng is enabled - if(!o_enableSubsurfaceScattering && o_parallax_feature_enabled && o_useDepthMap) + if(ShouldHandleParallax()) { // GetParallaxInput applies an tangent offset to the UV. We want to apply the same offset to the detailUv (note: this needs to be tested with content) // The math is: offset = newUv - oldUv; detailUv += offset; @@ -143,9 +148,9 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); - GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_depthFactor, + GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_depthFactor, MaterialSrg::m_depthOffset, ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse, - IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depth); + IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depth, displacementIsClipped); // Apply second part of the offset to the detail UV (see comment above) IN.m_detailUv[MaterialSrg::m_parallaxUvIndex] -= IN.m_uv[MaterialSrg::m_parallaxUvIndex]; @@ -206,6 +211,11 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float float3 baseColor = GetDetailedBaseColorInput( MaterialSrg::m_baseColorMap, MaterialSrg::m_sampler, baseColorUv, o_baseColor_useTexture, MaterialSrg::m_baseColor, MaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode, MaterialSrg::m_detail_baseColor_texture, MaterialSrg::m_sampler, detailUv, o_detail_baseColor_useTexture, detailLayerBaseColorFactor); + + if(o_parallax_highlightClipping && displacementIsClipped) + { + ApplyParallaxClippingHighlight(baseColor); + } // ------- Metallic ------- diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl index 15b58c4deb..80aedcd6f4 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl @@ -12,7 +12,6 @@ #include #include "EnhancedPBR_Common.azsli" -#include #include #include #include @@ -54,8 +53,8 @@ VertexOutput MainVS(VertexInput IN) // By design, only UV0 is allowed to apply transforms. OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy; OUT.m_uv[1] = IN.m_uv1; - - if(o_parallax_feature_enabled && o_parallax_enablePixelDepthOffset) + + if(ShouldHandleParallaxInDepthShaders()) { OUT.m_worldPosition = worldPosition.xyz; @@ -75,57 +74,32 @@ PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace) { PSDepthOutput OUT; + OUT.m_depth = IN.m_position.z; + + if(ShouldHandleParallaxInDepthShaders()) + { + static const float ShadowMapDepthBias = 0.000001; + + // We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space. + float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) }; + float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) }; + PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1); + + float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); + float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); + + GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_depthFactor, MaterialSrg::m_depthOffset, + ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse, + IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, OUT.m_depth); + + OUT.m_depth += ShadowMapDepthBias; + } + // Clip Alpha float2 baseColorUV = IN.m_uv[MaterialSrg::m_baseColorMapUvIndex]; float2 opacityUV = IN.m_uv[MaterialSrg::m_opacityMapUvIndex]; float alpha = SampleAlpha(MaterialSrg::m_baseColorMap, MaterialSrg::m_opacityMap, baseColorUV, opacityUV, MaterialSrg::m_sampler, o_opacity_source); CheckClipping(alpha, MaterialSrg::m_opacityFactor); - OUT.m_depth = IN.m_position.z; - - float3 dirToCamera; - if(ViewSrg::m_projectionMatrix[0].w) - { - // orthographic projection (directional light) - // No view position, use light direction - dirToCamera = ViewSrg::m_viewMatrix[2].xyz; - } - else - { - dirToCamera = ViewSrg::m_worldPosition.xyz - IN.m_worldPosition; - } - - if(o_parallax_feature_enabled && o_parallax_enablePixelDepthOffset) - { - // We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space. - float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) }; - float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) }; - PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1); - - float3 tangent = tangents[MaterialSrg::m_parallaxUvIndex]; - float3 bitangent = bitangents[MaterialSrg::m_parallaxUvIndex]; - - float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); - float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); - - float3 tangentOffset = GetParallaxOffset( MaterialSrg::m_depthFactor, - IN.m_uv[MaterialSrg::m_parallaxUvIndex], - dirToCamera, - tangent, - bitangent, - IN.m_normal, - uvMatrix); - - PixelDepthOffset pdo = CalcPixelDepthOffset(MaterialSrg::m_depthFactor, - tangentOffset, - IN.m_worldPosition, - tangent, - bitangent, - IN.m_normal, - uvMatrixInverse, - ObjectSrg::GetWorldMatrix(), - ViewSrg::m_viewProjectionMatrix); - OUT.m_depth = pdo.m_depth; - } return OUT; } diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialInputs/ParallaxInput.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialInputs/ParallaxInput.azsli index edee4c3c07..ab601429e8 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialInputs/ParallaxInput.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialInputs/ParallaxInput.azsli @@ -24,16 +24,15 @@ #define COMMON_SRG_INPUTS_PARALLAX(prefix) \ Texture2D prefix##m_depthMap; \ float prefix##m_depthFactor; \ +float prefix##m_depthOffset; \ bool prefix##m_depthInverted; #define COMMON_OPTIONS_PARALLAX(prefix) \ option bool prefix##o_useDepthMap; -option bool o_parallax_feature_enabled; - -void GetParallaxInput(float3 normal, float3 tangent, float3 bitangent, float depthFactor, +void GetParallaxInput(float3 normal, float3 tangent, float3 bitangent, float depthFactor, float depthOffset, float4x4 objectWorldMatrix, float3x3 uvMatrix, float3x3 uvMatrixInverse, - inout float2 uv, inout float3 worldPosition, inout float depth) + inout float2 uv, inout float3 worldPosition, inout float depth, out bool isClipped) { if(o_parallax_feature_enabled) { @@ -49,20 +48,22 @@ void GetParallaxInput(float3 normal, float3 tangent, float3 bitangent, float dep dirToCamera = ViewSrg::m_worldPosition.xyz - worldPosition; } - float3 tangentOffset = GetParallaxOffset( depthFactor, - uv, - dirToCamera, - tangent, - bitangent, - normal, - uvMatrix); + ParallaxOffset tangentOffset = GetParallaxOffset( depthFactor, + depthOffset, + uv, + dirToCamera, + tangent, + bitangent, + normal, + uvMatrix); - uv += tangentOffset.xy; + uv += tangentOffset.m_offsetTS.xy; + isClipped = tangentOffset.m_isClipped; if(o_parallax_enablePixelDepthOffset) { PixelDepthOffset pdo = CalcPixelDepthOffset(depthFactor, - tangentOffset, + tangentOffset.m_offsetTS, worldPosition, tangent, bitangent, @@ -70,9 +71,20 @@ void GetParallaxInput(float3 normal, float3 tangent, float3 bitangent, float dep uvMatrixInverse, objectWorldMatrix, ViewSrg::m_viewProjectionMatrix); + depth = pdo.m_depth; + worldPosition = pdo.m_worldPosition; } + } } +void GetParallaxInput(float3 normal, float3 tangent, float3 bitangent, float depthFactor, float depthOffset, + float4x4 objectWorldMatrix, float3x3 uvMatrix, float3x3 uvMatrixInverse, + inout float2 uv, inout float3 worldPosition, inout float depth) +{ + bool isClipped; + GetParallaxInput(normal, tangent, bitangent, depthFactor, depthOffset, objectWorldMatrix, uvMatrix, uvMatrixInverse, uv, worldPosition, depth, isClipped); +} + diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype index 7610a4c9db..e2119dcf12 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype @@ -18,11 +18,6 @@ "displayName": "Parallax Settings", "description": "Properties for configuring the parallax effect, applied to all layers." }, - { - "id": "opacity", - "displayName": "Opacity", - "description": "Properties for configuring the materials transparency." - }, { "id": "uv", "displayName": "UVs", @@ -361,19 +356,6 @@ "id": "m_parallaxUvIndex" } }, - { - "id": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the depth values for all layers.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "id": "m_parallaxMainDepthFactor" - } - }, { "id": "algorithm", "displayName": "Algorithm", @@ -408,73 +390,17 @@ "type": "ShaderOption", "id": "o_parallax_enablePixelDepthOffset" } - } - ], - "opacity": [ + }, { - "id": "mode", - "displayName": "Opacity Mode", - "description": "Opacity mode for this texture.", - "type": "Enum", - "enumValues": [ "Opaque", "Cutout", "Blended" ], - "defaultValue": "Opaque", + "id": "showClipping", + "displayName": "Show Clipping", + "description": "Highlight areas where the heightmap is clipped by the mesh surface.", + "type": "Bool", + "defaultValue": false, "connection": { "type": "ShaderOption", - "id": "o_opacity_mode" + "id": "o_parallax_highlightClipping" } - }, - { - "id": "alphaSource", - "displayName": "Alpha Source", - "description": "Source texture of alpha value.", - "type": "Enum", - "enumValues": [ "Packed", "Split", "None" ], - "defaultValue": "Packed", - "connection": { - "type": "ShaderOption", - "id": "o_opacity_source" - } - }, - { - "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface opacity.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "id": "m_opacityMap" - } - }, - { - "id": "textureMapUv", - "displayName": "UV", - "description": "Opacity texture map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "id": "m_opacityMapUvIndex" - } - }, - { - "id": "factor", - "displayName": "Factor", - "description": "Factor for cutout threshold and blending", - "type": "Float", - "min": 0.0, - "max": 1.0, - "defaultValue": 0.5, - "connection": { - "type": "ShaderInput", - "id": "m_opacityFactor" - } - }, - { - "id": "doubleSided", - "displayName": "Double-sided", - "description": "Whether to render back-faces or just front-faces.", - "type": "Bool" } ], "uv": [ @@ -1343,8 +1269,8 @@ }, { "id": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the depth values", + "displayName": "Heightmap Scale", + "description": "The total height of the heightmap in local model units.", "type": "Float", "defaultValue": 0.0, "min": 0.0, @@ -1354,6 +1280,19 @@ "id": "m_layer1_m_depthFactor" } }, + { + "id": "offset", + "displayName": "Offset", + "description": "Adjusts the overall displacement amount in local model units.", + "type": "Float", + "defaultValue": 0.0, + "softMin": -0.1, + "softMax": 0.1, + "connection": { + "type": "ShaderInput", + "id": "m_layer1_m_depthOffset" + } + }, { "id": "invert", "displayName": "Invert", @@ -2036,8 +1975,8 @@ }, { "id": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the depth values", + "displayName": "Heightmap Scale", + "description": "The total height of the heightmap in local model units.", "type": "Float", "defaultValue": 0.0, "min": 0.0, @@ -2047,6 +1986,19 @@ "id": "m_layer2_m_depthFactor" } }, + { + "id": "offset", + "displayName": "Offset", + "description": "Adjusts the overall displacement amount in local model units.", + "type": "Float", + "defaultValue": 0.0, + "softMin": -0.1, + "softMax": 0.1, + "connection": { + "type": "ShaderInput", + "id": "m_layer2_m_depthOffset" + } + }, { "id": "invert", "displayName": "Invert", @@ -2729,8 +2681,8 @@ }, { "id": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the depth values", + "displayName": "Heightmap Scale", + "description": "The total height of the heightmap in local model units.", "type": "Float", "defaultValue": 0.0, "min": 0.0, @@ -2740,6 +2692,19 @@ "id": "m_layer3_m_depthFactor" } }, + { + "id": "offset", + "displayName": "Offset", + "description": "Adjusts the overall displacement amount in local model units.", + "type": "Float", + "defaultValue": 0.0, + "softMin": -0.1, + "softMax": 0.1, + "connection": { + "type": "ShaderInput", + "id": "m_layer3_m_depthOffset" + } + }, { "id": "invert", "displayName": "Invert", @@ -2851,16 +2816,7 @@ { "file": "Shaders/MotionVector/SkinnedMeshMotionVector.shader", "tag": "SkinnedMeshMotionVector" - }, - // Used by the light culling system to produce accurate depth bounds for this object when it uses blended transparency - { - "file": "Shaders/Depth/DepthPassTransparentMin.shader", - "tag": "DepthPassTransparentMin" - }, - { - "file": "Shaders/Depth/DepthPassTransparentMax.shader", - "tag": "DepthPassTransparentMax" - } + } ], "functors": [ //############################################################################################## @@ -2885,7 +2841,7 @@ { "type": "Lua", "args": { - "file": "StandardPBR_ShaderEnable.lua" + "file": "StandardMultilayerPBR_ShaderEnable.lua" } }, { @@ -2925,27 +2881,6 @@ "file": "StandardPBR_SubsurfaceState.lua" } }, - { - "type": "Lua", - "args": { - "file": "StandardPBR_HandleOpacityDoubleSided.lua" - } - }, - { - "type": "OverrideDrawList", - "args": { - "triggerProperty": "opacity.mode", - "triggerValue": "Blended", - "shaderIndex": 1, - "drawList": "transparent" - } - }, - { - "type": "Lua", - "args": { - "file": "StandardPBR_HandleOpacityMode.lua" - } - }, //############################################################################################## // Layer 1 Functors //############################################################################################## diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli index db3ec45e0c..ba0eaf2ac1 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli @@ -14,6 +14,7 @@ #include #include +#include #include "MaterialInputs/BaseColorInput.azsli" #include "MaterialInputs/RoughnessInput.azsli" @@ -26,6 +27,8 @@ #include "MaterialInputs/ParallaxInput.azsli" #include "MaterialInputs/UvSetCount.azsli" +// ------ ShaderResourceGroup ---------------------------------------- + #define DEFINE_LAYER_SRG_INPUTS(prefix) \ COMMON_SRG_INPUTS_BASE_COLOR(prefix) \ COMMON_SRG_INPUTS_ROUGHNESS(prefix) \ @@ -57,17 +60,16 @@ ShaderResourceGroup MaterialSrg : SRG_PerMaterial float4 m_pad3; // [GFX TODO][ATOM-14595] This is a workaround for a data stomping bug. Remove once it's fixed. uint m_parallaxUvIndex; - float m_parallaxMainDepthFactor; + + // These are used to limit the heightmap intersection search range to the narrowest band possible, to give the best quality result. + float m_displacementMin; // The lowest displacement value possible from all layers combined + float m_displacementMax; // The highest displacement value possible from all layers combined float3x3 m_uvMatrix; float4 m_pad4; // [GFX TODO][ATOM-14595] This is a workaround for a data stomping bug. Remove once it's fixed. float3x3 m_uvMatrixInverse; float4 m_pad5; // [GFX TODO][ATOM-14595] This is a workaround for a data stomping bug. Remove once it's fixed. - float m_opacityFactor; - Texture2D m_opacityMap; - uint m_opacityMapUvIndex; - Sampler m_sampler { AddressU = Wrap; @@ -109,6 +111,8 @@ ShaderResourceGroup MaterialSrg : SRG_PerMaterial uint m_transmissionThicknessMapUvIndex; } +// ------ Shader Options ---------------------------------------- + enum class DebugDrawMode { None, BlendMaskValues, DepthMaps }; option DebugDrawMode o_debugDrawMode; @@ -121,6 +125,8 @@ option BlendMaskSource o_blendSource; // [GFX TODO][ATOM-14475]: Come up with a more elegant way to associate the isBound flag with the input stream. option bool o_blendMask_isBound; +// ------ Blend Utilities ---------------------------------------- + //! Returns the BlendMaskSource that will actually be used when rendering (not necessarily the same BlendMaskSource specified by the user) BlendMaskSource GetFinalBlendMaskSource() { @@ -181,6 +187,22 @@ float3 BlendLayers(float3 layer1, float3 layer2, float3 layer3, float3 blendMask return layer1 * blendMaskValues.r + layer2 * blendMaskValues.g + layer3 * blendMaskValues.b; } +// ------ Parallax Utilities ---------------------------------------- + +bool ShouldHandleParallax() +{ + // Parallax mapping's non uniform uv transformations break screen space subsurface scattering, disable it when subsurface scattering is enabled. + // Also, all the debug draw modes avoid parallax (they early-return before parallax code actually) so you can see exactly where the various maps appear on the surface UV space. + return !o_enableSubsurfaceScattering && o_parallax_feature_enabled && o_debugDrawMode == DebugDrawMode::None; +} + +bool ShouldHandleParallaxInDepthShaders() +{ + // The depth pass shaders need to calculate parallax when the result could affect the depth buffer (or when + // parallax could affect texel clipping but we don't have alpha/clipping support in multilayer PBR). + return ShouldHandleParallax() && o_parallax_enablePixelDepthOffset; +} + // These static values are used to pass extra data to the GetDepth callback function during the parallax depth search. static float3 s_blendMaskFromVertexStream; @@ -192,7 +214,7 @@ void GetDepth_Setup(float3 vertexBlendMask) } // Callback function for ParallaxMapping.azsli -float GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy) +DepthResult GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy) { float3 layerDepthValues = float3(0,0,0); @@ -204,8 +226,9 @@ float GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy) layerUv = mul(MaterialSrg::m_layer1_m_uvMatrix, float3(uv, 1.0)).xy; } - layerDepthValues.r = SampleDepthOrHeightMap(MaterialSrg::m_layer1_m_depthInverted, MaterialSrg::m_layer1_m_depthMap, MaterialSrg::m_sampler, layerUv, uv_ddx, uv_ddy); + layerDepthValues.r = SampleDepthOrHeightMap(MaterialSrg::m_layer1_m_depthInverted, MaterialSrg::m_layer1_m_depthMap, MaterialSrg::m_sampler, layerUv, uv_ddx, uv_ddy).m_depth; layerDepthValues.r *= MaterialSrg::m_layer1_m_depthFactor; + layerDepthValues.r -= MaterialSrg::m_layer1_m_depthOffset; } if(o_layer2_o_useDepthMap) @@ -216,8 +239,9 @@ float GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy) layerUv = mul(MaterialSrg::m_layer2_m_uvMatrix, float3(uv, 1.0)).xy; } - layerDepthValues.g = SampleDepthOrHeightMap(MaterialSrg::m_layer2_m_depthInverted, MaterialSrg::m_layer2_m_depthMap, MaterialSrg::m_sampler, layerUv, uv_ddx, uv_ddy); + layerDepthValues.g = SampleDepthOrHeightMap(MaterialSrg::m_layer2_m_depthInverted, MaterialSrg::m_layer2_m_depthMap, MaterialSrg::m_sampler, layerUv, uv_ddx, uv_ddy).m_depth; layerDepthValues.g *= MaterialSrg::m_layer2_m_depthFactor; + layerDepthValues.g -= MaterialSrg::m_layer2_m_depthOffset; } if(o_layer3_o_useDepthMap) @@ -228,8 +252,9 @@ float GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy) layerUv = mul(MaterialSrg::m_layer3_m_uvMatrix, float3(uv, 1.0)).xy; } - layerDepthValues.b = SampleDepthOrHeightMap(MaterialSrg::m_layer3_m_depthInverted, MaterialSrg::m_layer3_m_depthMap, MaterialSrg::m_sampler, layerUv, uv_ddx, uv_ddy); + layerDepthValues.b = SampleDepthOrHeightMap(MaterialSrg::m_layer3_m_depthInverted, MaterialSrg::m_layer3_m_depthMap, MaterialSrg::m_sampler, layerUv, uv_ddx, uv_ddy).m_depth; layerDepthValues.b *= MaterialSrg::m_layer3_m_depthFactor; + layerDepthValues.b -= MaterialSrg::m_layer3_m_depthOffset; } // Note, when the blend source is BlendMaskSource::VertexColors, parallax will not be able to blend correctly between layers. It will end up using the same blend mask values @@ -237,7 +262,6 @@ float GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy) // you have a small depth factor relative to the size of the blend transition. float3 blendMaskValues = GetBlendMaskValues(uv, s_blendMaskFromVertexStream); - float3 depth = BlendLayers(layerDepthValues.r, layerDepthValues.g, layerDepthValues.b, blendMaskValues); - - return depth; + float depth = BlendLayers(layerDepthValues.r, layerDepthValues.g, layerDepthValues.b, blendMaskValues); + return DepthResultAbsolute(depth); } diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl index 074b9dce26..ae156d7313 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl @@ -11,12 +11,10 @@ */ #include -#include #include #include #include -#include "MaterialInputs/AlphaInput.azsli" #include "MaterialInputs/ParallaxInput.azsli" @@ -72,7 +70,7 @@ VSDepthOutput MainVS(VSInput IN) OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy; OUT.m_uv[1] = IN.m_uv1; - if(o_parallax_feature_enabled && o_parallax_enablePixelDepthOffset) + if(ShouldHandleParallaxInDepthShaders()) { OUT.m_worldPosition = worldPosition.xyz; @@ -101,18 +99,9 @@ PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace) { PSDepthOutput OUT; - // Alpha - float2 layer1_baseColorUV = IN.m_uv[MaterialSrg::m_layer1_m_baseColorMapUvIndex]; - float2 layer2_baseColorUV = IN.m_uv[MaterialSrg::m_layer2_m_baseColorMapUvIndex]; - float2 opacityUV = IN.m_uv[MaterialSrg::m_opacityMapUvIndex]; - // [GFX TODO][ATOM-14589] Figure out how to deal with opacity, instead of just hard-coding to layer1 - float alpha = SampleAlpha(MaterialSrg::m_layer1_m_baseColorMap, MaterialSrg::m_opacityMap, layer1_baseColorUV, opacityUV, MaterialSrg::m_sampler, o_opacity_source); - - CheckClipping(alpha, MaterialSrg::m_opacityFactor); - OUT.m_depth = IN.m_position.z; - if(o_debugDrawMode == DebugDrawMode::None && o_parallax_feature_enabled && o_parallax_enablePixelDepthOffset) + if(ShouldHandleParallaxInDepthShaders()) { // We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space. float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) }; @@ -126,7 +115,9 @@ PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace) float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); - GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_parallaxMainDepthFactor, + float parallaxOverallOffset = MaterialSrg::m_displacementMax; + float parallaxOverallFactor = MaterialSrg::m_displacementMax - MaterialSrg::m_displacementMin; + GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], parallaxOverallFactor, parallaxOverallOffset, ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse, IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depth); diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl index 560c7ab7eb..a83ea629e4 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl @@ -55,7 +55,6 @@ DEFINE_LAYER_OPTIONS(o_layer1_) DEFINE_LAYER_OPTIONS(o_layer2_) DEFINE_LAYER_OPTIONS(o_layer3_) -#include "MaterialInputs/AlphaInput.azsli" #include "MaterialInputs/SubsurfaceInput.azsli" #include "MaterialInputs/TransmissionInput.azsli" #include "StandardMultilayerPBR_Common.azsli" @@ -121,9 +120,8 @@ VSOutput ForwardPassVS(VSInput IN) OUT.m_blendMask = float3(1,1,1); } - // We can skip per-vertex shadow coords when parallax is enabled because we need to calculate per-pixel shadow coords anyway. - // We cannot skip shadow coords when o_debugDrawMode is on because some debug draw modes return before parallax. - bool skipShadowCoords = o_debugDrawMode == DebugDrawMode::None && o_parallax_feature_enabled && o_parallax_enablePixelDepthOffset; + // Shadow coords will be calculated in the pixel shader in this case + bool skipShadowCoords = ShouldHandleParallax() && o_parallax_enablePixelDepthOffset; VertexHelper(IN, OUT, worldPosition, skipShadowCoords); return OUT; @@ -167,23 +165,27 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float if(o_debugDrawMode == DebugDrawMode::DepthMaps) { GetDepth_Setup(IN.m_blendMask); - float depth = GetDepth(IN.m_uv[MaterialSrg::m_parallaxUvIndex], float2(0,0), float2(0,0)); + float depth = GetNormalizedDepth(-MaterialSrg::m_displacementMax, -MaterialSrg::m_displacementMin, IN.m_uv[MaterialSrg::m_parallaxUvIndex], float2(0,0), float2(0,0)); return DebugOutput(float3(depth,depth,depth)); } // ------- Parallax ------- + bool displacementIsClipped = false; + // Parallax mapping's non uniform uv transformations break screen space subsurface scattering, disable it when subsurface scatteirng is enabled - if(!o_enableSubsurfaceScattering && o_parallax_feature_enabled) + if(ShouldHandleParallax()) { GetDepth_Setup(IN.m_blendMask); float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); - - GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_parallaxMainDepthFactor, + + float parallaxOverallOffset = MaterialSrg::m_displacementMax; + float parallaxOverallFactor = MaterialSrg::m_displacementMax - MaterialSrg::m_displacementMin; + GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], parallaxOverallFactor, parallaxOverallOffset, ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse, - IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depth); + IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depth, displacementIsClipped); // Adjust directional light shadow coorinates for parallax correction if(o_parallax_enablePixelDepthOffset) @@ -218,15 +220,6 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // Now that any parallax has been calculated, we calculate the blend factors for any layers that are impacted by the parallax. float3 blendMaskValues = GetBlendMaskValues(IN.m_uv[MaterialSrg::m_blendMaskUvIndex], IN.m_blendMask); - // ------- Alpha & Clip ------- - - float2 layer1_baseColorUv = uvLayer1[MaterialSrg::m_layer1_m_baseColorMapUvIndex]; - float2 layer2_baseColorUv = uvLayer2[MaterialSrg::m_layer2_m_baseColorMapUvIndex]; - float2 layer3_baseColorUv = uvLayer3[MaterialSrg::m_layer3_m_baseColorMapUvIndex]; - float2 opacityUv = IN.m_uv[MaterialSrg::m_opacityMapUvIndex]; - // [GFX TODO][ATOM-14589] Figure out how to deal with opacity, instead of just hard-coding to layer1 - float alpha = GetAlphaInputAndClip(MaterialSrg::m_layer1_m_baseColorMap, MaterialSrg::m_opacityMap, layer1_baseColorUv, opacityUv, MaterialSrg::m_sampler, MaterialSrg::m_opacityFactor, o_opacity_source); - // ------- Normal ------- float3 layer1_normalFactor = MaterialSrg::m_layer1_m_normalFactor * blendMaskValues.r; @@ -245,6 +238,10 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float surface.normal = normalize(TangentSpaceToWorld(normalTS, IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex])); // ------- Base Color ------- + + float2 layer1_baseColorUv = uvLayer1[MaterialSrg::m_layer1_m_baseColorMapUvIndex]; + float2 layer2_baseColorUv = uvLayer2[MaterialSrg::m_layer2_m_baseColorMapUvIndex]; + float2 layer3_baseColorUv = uvLayer3[MaterialSrg::m_layer3_m_baseColorMapUvIndex]; float3 layer1_sampledColor = GetBaseColorInput(MaterialSrg::m_layer1_m_baseColorMap, MaterialSrg::m_sampler, layer1_baseColorUv, MaterialSrg::m_layer1_m_baseColor.rgb, o_layer1_o_baseColor_useTexture); float3 layer2_sampledColor = GetBaseColorInput(MaterialSrg::m_layer2_m_baseColorMap, MaterialSrg::m_sampler, layer2_baseColorUv, MaterialSrg::m_layer2_m_baseColor.rgb, o_layer2_o_baseColor_useTexture); @@ -253,6 +250,11 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float float3 layer2_baseColor = BlendBaseColor(layer2_sampledColor, MaterialSrg::m_layer2_m_baseColor.rgb, MaterialSrg::m_layer2_m_baseColorFactor, o_layer2_o_baseColorTextureBlendMode, o_layer2_o_baseColor_useTexture); float3 layer3_baseColor = BlendBaseColor(layer3_sampledColor, MaterialSrg::m_layer3_m_baseColor.rgb, MaterialSrg::m_layer3_m_baseColorFactor, o_layer3_o_baseColorTextureBlendMode, o_layer3_o_baseColor_useTexture); float3 baseColor = BlendLayers(layer1_baseColor, layer2_baseColor, layer3_baseColor, blendMaskValues); + + if(o_parallax_highlightClipping && displacementIsClipped) + { + ApplyParallaxClippingHighlight(baseColor); + } // ------- Metallic ------- @@ -427,32 +429,13 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float lightingData.FinalizeLighting(surface.transmission.tint); - if (o_opacity_mode == OpacityMode::Blended || o_opacity_mode == OpacityMode::TintedTransparent) - { - alpha = FresnelSchlickWithRoughness(lightingData.NdotV, alpha, surface.roughnessLinear).x; // Increase opacity at grazing angles. - } + const float alpha = 1.0; PbrLightingOutput lightingOutput = GetPbrLightingOutput(surface, lightingData, alpha); - // ------- Opacity ------- - - if (o_opacity_mode == OpacityMode::Blended) - { - // [GFX_TODO ATOM-13187] PbrLighting shouldn't be writing directly to render targets. It's confusing when - // specular is being added to diffuse just because we're calling render target 0 "diffuse". - - // For blended mode, we do (dest * alpha) + (source * 1.0). This allows the specular - // to be added on top of the diffuse, but then the diffuse must be pre-multiplied. - // It's done this way because surface transparency doesn't really change specular response (eg, glass). - lightingOutput.m_diffuseColor.rgb *= lightingOutput.m_diffuseColor.w; // pre-multiply diffuse - lightingOutput.m_diffuseColor.rgb += lightingOutput.m_specularColor.rgb; // add specular - } - else - { - // Pack factor and quality, drawback: because of precision limit of float16 cannot represent exact 1, maximum representable value is 0.9961 - uint factorAndQuality = dot(round(float2(saturate(surfaceScatteringFactor), MaterialSrg::m_subsurfaceScatteringQuality) * 255), float2(256, 1)); - lightingOutput.m_diffuseColor.w = factorAndQuality * (o_enableSubsurfaceScattering ? 1.0 : -1.0); - } + // Pack factor and quality, drawback: because of precision limit of float16 cannot represent exact 1, maximum representable value is 0.9961 + uint factorAndQuality = dot(round(float2(saturate(surfaceScatteringFactor), MaterialSrg::m_subsurfaceScatteringQuality) * 255), float2(256, 1)); + lightingOutput.m_diffuseColor.w = factorAndQuality * (o_enableSubsurfaceScattering ? 1.0 : -1.0); return lightingOutput; diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Parallax.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Parallax.lua index 522121c96f..8880a4b842 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Parallax.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Parallax.lua @@ -20,10 +20,12 @@ function GetMaterialPropertyDependencies() "layer1_parallax.enable", "layer2_parallax.enable", "layer3_parallax.enable", - "parallax.factor", "layer1_parallax.factor", "layer2_parallax.factor", - "layer3_parallax.factor" + "layer3_parallax.factor", + "layer1_parallax.offset", + "layer2_parallax.offset", + "layer3_parallax.offset" } end @@ -31,6 +33,23 @@ function GetShaderOptionDependencies() return {"o_parallax_feature_enabled"} end +function MergeRange(heightMinMax, offset, factor) + top = offset + bottom = offset - factor + + if(heightMinMax[1] == nil) then + heightMinMax[1] = top + else + heightMinMax[1] = math.max(heightMinMax[1], top) + end + + if(heightMinMax[0] == nil) then + heightMinMax[0] = bottom + else + heightMinMax[0] = math.min(heightMinMax[0], bottom) + end +end + function Process(context) local enableParallax = context:GetMaterialPropertyValue_bool("parallax.enable") local enable1 = context:GetMaterialPropertyValue_bool("layer1_parallax.enable") @@ -39,30 +58,25 @@ function Process(context) enableParallax = enableParallax and (enable1 or enable2 or enable3) context:SetShaderOptionValue_bool("o_parallax_feature_enabled", enableParallax) - -- Smaller values for the main parallax factor used in GetParallaxOffset() give better quality. - -- So increase the per-layer parallax factors by normalizing them, and reduce the main factor accordingly. if(enableParallax) then local factorLayer1 = context:GetMaterialPropertyValue_float("layer1_parallax.factor") local factorLayer2 = context:GetMaterialPropertyValue_float("layer2_parallax.factor") local factorLayer3 = context:GetMaterialPropertyValue_float("layer3_parallax.factor") - local mainFactor = context:GetMaterialPropertyValue_float("parallax.factor") - maxLayerFactor = 0.0 - if(enable1) then maxLayerFactor = math.max(maxLayerFactor, factorLayer1) end - if(enable2) then maxLayerFactor = math.max(maxLayerFactor, factorLayer2) end - if(enable3) then maxLayerFactor = math.max(maxLayerFactor, factorLayer3) end + local offsetLayer1 = context:GetMaterialPropertyValue_float("layer1_parallax.offset") + local offsetLayer2 = context:GetMaterialPropertyValue_float("layer2_parallax.offset") + local offsetLayer3 = context:GetMaterialPropertyValue_float("layer3_parallax.offset") - if(maxLayerFactor < 0.0001) then + local heightMinMax = {nil, nil} + if(enable1) then MergeRange(heightMinMax, offsetLayer1, factorLayer1) end + if(enable2) then MergeRange(heightMinMax, offsetLayer2, factorLayer2) end + if(enable3) then MergeRange(heightMinMax, offsetLayer3, factorLayer3) end + + if(heightMinMax[1] - heightMinMax[0] < 0.0001) then context:SetShaderOptionValue_bool("o_parallax_feature_enabled", false) else - factorLayer1 = factorLayer1 / maxLayerFactor - factorLayer2 = factorLayer2 / maxLayerFactor - factorLayer3 = factorLayer3 / maxLayerFactor - mainFactor = mainFactor * maxLayerFactor; - context:SetShaderConstant_float("m_layer1_m_depthFactor", factorLayer1) - context:SetShaderConstant_float("m_layer2_m_depthFactor", factorLayer2) - context:SetShaderConstant_float("m_layer3_m_depthFactor", factorLayer3) - context:SetShaderConstant_float("m_parallaxMainDepthFactor", mainFactor) + context:SetShaderConstant_float("m_displacementMin", heightMinMax[0]) + context:SetShaderConstant_float("m_displacementMax", heightMinMax[1]) end end end @@ -76,8 +90,8 @@ function ProcessEditor(context) end context:SetMaterialPropertyVisibility("parallax.parallaxUv", visibility) - context:SetMaterialPropertyVisibility("parallax.factor", visibility) context:SetMaterialPropertyVisibility("parallax.algorithm", visibility) context:SetMaterialPropertyVisibility("parallax.quality", visibility) context:SetMaterialPropertyVisibility("parallax.pdo", visibility) + context:SetMaterialPropertyVisibility("parallax.showClipping", visibility) end diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ParallaxPerLayer.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ParallaxPerLayer.lua index a1695e827b..119dfed436 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ParallaxPerLayer.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ParallaxPerLayer.lua @@ -42,7 +42,8 @@ function ProcessEditor(context) if(not enable or textureMap == nil) then visibility = MaterialPropertyVisibility_Hidden end - + context:SetMaterialPropertyVisibility("parallax.factor", visibility) + context:SetMaterialPropertyVisibility("parallax.offset", visibility) context:SetMaterialPropertyVisibility("parallax.invert", visibility) end diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ShaderEnable.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ShaderEnable.lua new file mode 100644 index 0000000000..69df610ab2 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ShaderEnable.lua @@ -0,0 +1,39 @@ +-------------------------------------------------------------------------------------- +-- +-- All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +-- its licensors. +-- +-- For complete copyright and license terms please see the LICENSE at the root of this +-- distribution (the "License"). All use of this software is governed by the License, +-- or, if provided, by the license below or the license accompanying this file. Do not +-- remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- +-- +---------------------------------------------------------------------------------------------------- + +function GetMaterialPropertyDependencies() + return {"parallax.enable", "parallax.pdo"} +end + +function Process(context) + local parallaxEnabled = context:GetMaterialPropertyValue_bool("parallax.enable") + local parallaxPdoEnabled = context:GetMaterialPropertyValue_bool("parallax.pdo") + + local depthPass = context:GetShaderByTag("DepthPass") + local shadowMap = context:GetShaderByTag("Shadowmap") + local forwardPassEDS = context:GetShaderByTag("ForwardPass_EDS") + local depthPassWithPS = context:GetShaderByTag("DepthPass_WithPS") + local shadowMapWitPS = context:GetShaderByTag("Shadowmap_WithPS") + local forwardPass = context:GetShaderByTag("ForwardPass") + + local shadingAffectsDepth = parallaxEnabled and parallaxPdoEnabled; + + depthPass:SetEnabled(not shadingAffectsDepth) + shadowMap:SetEnabled(not shadingAffectsDepth) + forwardPassEDS:SetEnabled(not shadingAffectsDepth) + + depthPassWithPS:SetEnabled(shadingAffectsDepth) + shadowMapWitPS:SetEnabled(shadingAffectsDepth) + forwardPass:SetEnabled(shadingAffectsDepth) +end diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl index 1274e07f7d..325937b228 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl @@ -12,12 +12,10 @@ #include #include -#include #include #include #include -#include "MaterialInputs/AlphaInput.azsli" #include "MaterialInputs/ParallaxInput.azsli" #include "MaterialInputs/ParallaxInput.azsli" @@ -71,7 +69,7 @@ VertexOutput MainVS(VertexInput IN) OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy; OUT.m_uv[1] = IN.m_uv1; - if(o_parallax_feature_enabled && o_parallax_enablePixelDepthOffset) + if(ShouldHandleParallaxInDepthShaders()) { OUT.m_worldPosition = worldPosition.xyz; @@ -100,18 +98,9 @@ PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace) { PSDepthOutput OUT; - // Alpha - float2 layer1_baseColorUV = IN.m_uv[MaterialSrg::m_layer1_m_baseColorMapUvIndex]; - float2 layer2_baseColorUV = IN.m_uv[MaterialSrg::m_layer2_m_baseColorMapUvIndex]; - float2 opacityUV = IN.m_uv[MaterialSrg::m_opacityMapUvIndex]; - // [GFX TODO][ATOM-14589] Figure out how to deal with opacity, instead of just hard-coding to layer1 - float alpha = SampleAlpha(MaterialSrg::m_layer1_m_baseColorMap, MaterialSrg::m_opacityMap, layer1_baseColorUV, opacityUV, MaterialSrg::m_sampler, o_opacity_source); - - CheckClipping(alpha, MaterialSrg::m_opacityFactor); - OUT.m_depth = IN.m_position.z; - if(o_debugDrawMode == DebugDrawMode::None && o_parallax_feature_enabled && o_parallax_enablePixelDepthOffset) + if(ShouldHandleParallaxInDepthShaders()) { // We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space. float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) }; @@ -124,13 +113,15 @@ PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace) float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); - - GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_parallaxMainDepthFactor, + + float parallaxOverallOffset = MaterialSrg::m_displacementMax; + float parallaxOverallFactor = MaterialSrg::m_displacementMax - MaterialSrg::m_displacementMin; + GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], parallaxOverallFactor, parallaxOverallOffset, ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse, IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depth); OUT.m_depth = depth; } - + return OUT; } diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype index e071a793a5..a74ceb1783 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype @@ -910,8 +910,8 @@ }, { "id": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the depth values", + "displayName": "Heightmap Scale", + "description": "The total height of the heightmap in local model units.", "type": "Float", "defaultValue": 0.0, "min": 0.0, @@ -921,6 +921,19 @@ "id": "m_depthFactor" } }, + { + "id": "offset", + "displayName": "Offset", + "description": "Adjusts the overall displacement amount in local model units.", + "type": "Float", + "defaultValue": 0.0, + "softMin": -0.1, + "softMax": 0.1, + "connection": { + "type": "ShaderInput", + "id": "m_depthOffset" + } + }, { "id": "invert", "displayName": "Invert", @@ -966,6 +979,17 @@ "type": "ShaderOption", "id": "o_parallax_enablePixelDepthOffset" } + }, + { + "id": "showClipping", + "displayName": "Show Clipping", + "description": "Highlight areas where the heightmap is clipped by the mesh surface.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "id": "o_parallax_highlightClipping" + } } ], "subsurfaceScattering": [ diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Common.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Common.azsli index 1a7e039da3..5723a6cd1e 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Common.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Common.azsli @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include "MaterialInputs/BaseColorInput.azsli" #include "MaterialInputs/RoughnessInput.azsli" @@ -93,8 +95,23 @@ ShaderResourceGroup MaterialSrg : SRG_PerMaterial } // Callback function for ParallaxMapping.azsli -float GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy) +DepthResult GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy) { return SampleDepthOrHeightMap(MaterialSrg::m_depthInverted, MaterialSrg::m_depthMap, MaterialSrg::m_sampler, uv, uv_ddx, uv_ddy); } + +COMMON_OPTIONS_PARALLAX() + +bool ShouldHandleParallax() +{ + // Parallax mapping's non uniform uv transformations break screen space subsurface scattering, disable it when subsurface scattering is enabled. + return !o_enableSubsurfaceScattering && o_parallax_feature_enabled && o_useDepthMap; +} + +bool ShouldHandleParallaxInDepthShaders() +{ + // The depth pass shaders need to calculate parallax when the result could affect the depth buffer, or when + // parallax could affect texel clipping. + return ShouldHandleParallax() && (o_parallax_enablePixelDepthOffset || o_opacity_mode == OpacityMode::Cutout); +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl index 4d4f7b195a..28708c12d5 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl @@ -10,7 +10,6 @@ * */ -#include #include "./StandardPBR_Common.azsli" #include #include @@ -56,7 +55,7 @@ VSDepthOutput MainVS(VSInput IN) OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy; OUT.m_uv[1] = IN.m_uv1; - if(o_parallax_feature_enabled && o_parallax_enablePixelDepthOffset) + if(ShouldHandleParallaxInDepthShaders()) { OUT.m_worldPosition = worldPosition.xyz; @@ -75,6 +74,23 @@ PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace) { PSDepthOutput OUT; + OUT.m_depth = IN.m_position.z; + + if(ShouldHandleParallaxInDepthShaders()) + { + // We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space. + float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) }; + float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) }; + PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1); + + float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); + float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); + + GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_depthFactor, MaterialSrg::m_depthOffset, + ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse, + IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, OUT.m_depth); + } + // Alpha float2 baseColorUV = IN.m_uv[MaterialSrg::m_baseColorMapUvIndex]; float2 opacityUV = IN.m_uv[MaterialSrg::m_opacityMapUvIndex]; @@ -82,39 +98,5 @@ PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace) CheckClipping(alpha, MaterialSrg::m_opacityFactor); - OUT.m_depth = IN.m_position.z; - - if(o_parallax_feature_enabled && o_parallax_enablePixelDepthOffset) - { - // We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space. - float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) }; - float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) }; - PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1); - - float3 tangent = tangents[MaterialSrg::m_parallaxUvIndex]; - float3 bitangent = bitangents[MaterialSrg::m_parallaxUvIndex]; - - float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); - float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); - - float3 tangentOffset = GetParallaxOffset( MaterialSrg::m_depthFactor, - IN.m_uv[MaterialSrg::m_parallaxUvIndex], - ViewSrg::m_worldPosition.xyz - IN.m_worldPosition, - tangent, - bitangent, - IN.m_normal, - uvMatrix); - - PixelDepthOffset pdo = CalcPixelDepthOffset(MaterialSrg::m_depthFactor, - tangentOffset, - IN.m_worldPosition, - tangent, - bitangent, - IN.m_normal, - uvMatrixInverse, - ObjectSrg::GetWorldMatrix(), - ViewSrg::m_viewProjectionMatrix); - OUT.m_depth = pdo.m_depth; - } return OUT; } diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl index d3bc72d162..22751b49bc 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl @@ -40,7 +40,7 @@ COMMON_OPTIONS_NORMAL() COMMON_OPTIONS_CLEAR_COAT() COMMON_OPTIONS_OCCLUSION() COMMON_OPTIONS_EMISSIVE() -COMMON_OPTIONS_PARALLAX() +// Note COMMON_OPTIONS_PARALLAX is in StandardPBR_Common.azsli because it's needed by all StandardPBR shaders. // Alpha #include "MaterialInputs/AlphaInput.azsli" @@ -94,7 +94,10 @@ VSOutput StandardPbr_ForwardPassVS(VSInput IN) OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy; OUT.m_uv[1] = IN.m_uv1; - VertexHelper(IN, OUT, worldPosition, o_parallax_feature_enabled && o_parallax_enablePixelDepthOffset); + // Shadow coords will be calculated in the pixel shader in this case + bool skipShadowCoords = ShouldHandleParallax() && o_parallax_enablePixelDepthOffset; + + VertexHelper(IN, OUT, worldPosition, skipShadowCoords); return OUT; } @@ -123,15 +126,18 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // ------- Depth & Parallax ------- depth = IN.m_position.z; + + bool displacementIsClipped = false; // Parallax mapping's non uniform uv transformations break screen space subsurface scattering, disable it when subsurface scatteirng is enabled - if(!o_enableSubsurfaceScattering && o_parallax_feature_enabled && o_useDepthMap) + if(ShouldHandleParallax()) { + float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); - GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_depthFactor, + GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_depthFactor, MaterialSrg::m_depthOffset, ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse, - IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depth); + IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depth, displacementIsClipped); // Adjust directional light shadow coorinates for parallax correction if(o_parallax_enablePixelDepthOffset) @@ -166,6 +172,11 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float float3 sampledColor = GetBaseColorInput(MaterialSrg::m_baseColorMap, MaterialSrg::m_sampler, baseColorUv, MaterialSrg::m_baseColor.rgb, o_baseColor_useTexture); float3 baseColor = BlendBaseColor(sampledColor, MaterialSrg::m_baseColor.rgb, MaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode, o_baseColor_useTexture); + if(o_parallax_highlightClipping && displacementIsClipped) + { + ApplyParallaxClippingHighlight(baseColor); + } + // ------- Metallic ------- float metallic = 0; diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ParallaxState.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ParallaxState.lua index bf1b59616a..0287e1105e 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ParallaxState.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ParallaxState.lua @@ -41,8 +41,10 @@ function ProcessEditor(context) if(not enable or textureMap == nil) then visibility = MaterialPropertyVisibility_Hidden end - + context:SetMaterialPropertyVisibility("parallax.factor", visibility) + context:SetMaterialPropertyVisibility("parallax.offset", visibility) + context:SetMaterialPropertyVisibility("parallax.showClipping", visibility) context:SetMaterialPropertyVisibility("parallax.invert", visibility) context:SetMaterialPropertyVisibility("parallax.algorithm", visibility) context:SetMaterialPropertyVisibility("parallax.quality", visibility) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl index e792c25778..7f24b29700 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl @@ -12,7 +12,6 @@ #include #include "StandardPBR_Common.azsli" -#include #include #include #include @@ -56,7 +55,7 @@ VertexOutput MainVS(VertexInput IN) OUT.m_uv[0] = mul(MaterialSrg::m_uvMatrix, float3(IN.m_uv0, 1.0)).xy; OUT.m_uv[1] = IN.m_uv1; - if(o_parallax_feature_enabled && o_parallax_enablePixelDepthOffset) + if(ShouldHandleParallaxInDepthShaders()) { OUT.m_worldPosition = worldPosition.xyz; @@ -76,28 +75,9 @@ PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace) { PSDepthOutput OUT; - // Alpha - float2 baseColorUV = IN.m_uv[MaterialSrg::m_baseColorMapUvIndex]; - float2 opacityUV = IN.m_uv[MaterialSrg::m_opacityMapUvIndex]; - float alpha = SampleAlpha(MaterialSrg::m_baseColorMap, MaterialSrg::m_opacityMap, baseColorUV, opacityUV, MaterialSrg::m_sampler, o_opacity_source); - - CheckClipping(alpha, MaterialSrg::m_opacityFactor); - OUT.m_depth = IN.m_position.z; - float3 dirToCamera; - if(ViewSrg::m_projectionMatrix[0].w) - { - // orthographic projection (directional light) - // No view position, use light direction - dirToCamera = ViewSrg::m_viewMatrix[2].xyz; - } - else - { - dirToCamera = ViewSrg::m_worldPosition.xyz - IN.m_worldPosition; - } - - if(o_parallax_feature_enabled && o_parallax_enablePixelDepthOffset) + if(ShouldHandleParallaxInDepthShaders()) { static const float ShadowMapDepthBias = 0.000001; @@ -106,31 +86,22 @@ PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace) float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) }; PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1); - float3 tangent = tangents[MaterialSrg::m_parallaxUvIndex]; - float3 bitangent = bitangents[MaterialSrg::m_parallaxUvIndex]; - float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); - float3 tangentOffset = GetParallaxOffset( MaterialSrg::m_depthFactor, - IN.m_uv[MaterialSrg::m_parallaxUvIndex], - dirToCamera, - tangent, - bitangent, - IN.m_normal, - uvMatrix); + GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_depthFactor, MaterialSrg::m_depthOffset, + ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse, + IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, OUT.m_depth); - PixelDepthOffset pdo = CalcPixelDepthOffset(MaterialSrg::m_depthFactor, - tangentOffset, - IN.m_worldPosition, - tangent, - bitangent, - IN.m_normal, - uvMatrixInverse, - ObjectSrg::GetWorldMatrix(), - ViewSrg::m_viewProjectionMatrix); - - OUT.m_depth = pdo.m_depth + ShadowMapDepthBias; + OUT.m_depth += ShadowMapDepthBias; } + + // Alpha + float2 baseColorUV = IN.m_uv[MaterialSrg::m_baseColorMapUvIndex]; + float2 opacityUV = IN.m_uv[MaterialSrg::m_opacityMapUvIndex]; + float alpha = SampleAlpha(MaterialSrg::m_baseColorMap, MaterialSrg::m_opacityMap, baseColorUV, opacityUV, MaterialSrg::m_sampler, o_opacity_source); + + CheckClipping(alpha, MaterialSrg::m_opacityFactor); + return OUT; } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ParallaxMapping.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ParallaxMapping.azsli index 8a6b5c479a..1d2beb0f10 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ParallaxMapping.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ParallaxMapping.azsli @@ -18,39 +18,147 @@ option bool o_parallax_enablePixelDepthOffset; option enum class ParallaxAlgorithm {Basic, Steep, POM, Relief, Contact} o_parallax_algorithm; option enum class ParallaxQuality {Low, Medium, High, Ultra} o_parallax_quality; + +option bool o_parallax_feature_enabled; +option bool o_parallax_highlightClipping; option bool o_parallax_shadow; +// I tried to make this an enum class, but ran into some DXC bug when compiling to SPIRV. +enum DepthResultCode +{ + DepthResultCode_Invalid, + DepthResultCode_Normalized, //!< The result is in range [0,1], where 0 is the top of the heightmap and 1 is the bottom of the heightmap. + DepthResultCode_Absolute //!< The result is tangent space units (the same as world units if there's no mesh scaling), where 0 is at the mesh surface and positive values are below the surface. +}; + +//! The return value for the GetDepth() callback function below. +struct DepthResult +{ + DepthResultCode m_resultCode; + float m_depth; +}; + +//! Convenience function for making a DepthResult with Code::Normalized +DepthResult DepthResultNormalized(float depth) +{ + DepthResult result; + result.m_resultCode = DepthResultCode_Normalized; + result.m_depth = depth; + return result; +} + +//! Convenience function for making a DepthResult with Code::Absolute +DepthResult DepthResultAbsolute(float depth) +{ + DepthResult result; + result.m_resultCode = DepthResultCode_Absolute; + result.m_depth = depth; + return result; +} + //! The client shader must define this function. //! This allows the client shader to implement special depth map sampling, for example procedurally generating or blending depth maps. +//! In simple cases though, the implementation of GetDepth() can simply call SampleDepthOrHeightMap(). //! @param uv the UV coordinates to use for sampling //! @param uv_ddx will be set to ddx_fine(uv) //! @param uv_ddy will be set to ddy_fine(uv) -float GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy); +//! @return see struct DepthResult +DepthResult GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy); //! Convenience function that can be used to implement GetDepth(). //! @param isHeightmap indicates whether to sample the map is a height map rather than a depth map. -float SampleDepthOrHeightMap(bool isHeightmap, Texture2D map, sampler mapSampler, float2 uv, float2 uv_ddx, float2 uv_ddy) +//! @return see struct DepthResult. In this case it will always contain a Code::Normalized result. +DepthResult SampleDepthOrHeightMap(bool isHeightmap, Texture2D map, sampler mapSampler, float2 uv, float2 uv_ddx, float2 uv_ddy) { - return abs((isHeightmap * 1.0) - map.SampleGrad(mapSampler, uv, uv_ddx, uv_ddy).r); + DepthResult result; + result.m_resultCode = DepthResultCode_Normalized; + result.m_depth = abs((isHeightmap * 1.0) - map.SampleGrad(mapSampler, uv, uv_ddx, uv_ddy).r); + return result; } +//! Calls GetDepth() and then normalizes the result if it isn't normalized already. +//! @param startDepth is the high point, which corresponds to a normalized depth value of 0. +//! @param stopDepth is the low point, which corresponds to a normalized depth value of 1. +//! @param inverseDepthRange is an optimization, and must be set to "1.0 / (stopDepth - startDepth)". +//! @param uv the UV coordinates to use for sampling +//! @param uv_ddx must be set to ddx_fine(uv) +//! @param uv_ddy must be set to ddy_fine(uv) +//! @param a depth value in the range [0,1] +float GetNormalizedDepth(float startDepth, float stopDepth, float inverseDepthRange, float2 uv, float2 uv_ddx, float2 uv_ddy) +{ + // startDepth can be less than 0, representing a displacement above the mesh surface. + // But since we don't currently support any vertex displacement, negative depth values would cause various + // problems especially when PDO is enabled, like parallax surfaces clipping through foreground geometry, and parallax + // surfaces disappearing at low angles. So we clamp all depth values to a minimum of 0. + + float normalizedDepth = 0.0; + + DepthResult depthResult = GetDepth(uv, uv_ddx, uv_ddy); + + if(stopDepth - startDepth > 0.0001) + { + if(DepthResultCode_Normalized == depthResult.m_resultCode) + { + float minNormalizedDepth = -startDepth * inverseDepthRange; + normalizedDepth = max(depthResult.m_depth, minNormalizedDepth); + } + else if(DepthResultCode_Absolute == depthResult.m_resultCode) + { + float clampedAbsoluteDepth = max(depthResult.m_depth, 0.0); + normalizedDepth = (clampedAbsoluteDepth - startDepth) * inverseDepthRange; + } + } + + return normalizedDepth; +} + +float GetNormalizedDepth(float startDepth, float stopDepth, float2 uv, float2 uv_ddx, float2 uv_ddy) +{ + float inverseDepthRange = 1.0 / (stopDepth - startDepth); + return GetNormalizedDepth(startDepth, stopDepth, inverseDepthRange, uv, uv_ddx, uv_ddy); +} + +void ApplyParallaxClippingHighlight(inout float3 baseColor) +{ + baseColor = lerp(baseColor, float3(1.0, 0.0, 1.0), 0.5); +} + +struct ParallaxOffset +{ + float3 m_offsetTS; //!< represents the intersection point relative to the geometry surface, in tangent space. + bool m_isClipped; //!< Indicates whether the result is being clipped by the geometry surface, mainly for debug rendering. Only set when o_parallax_highlightClipping is true. +}; + // dirToCameraTS should be in tangent space and normalized // From Reat-Time Rendering 3rd edition, p.192 -float3 BasicParallaxMapping(float depthFactor, float2 uv, float3 dirToCameraTS) +ParallaxOffset BasicParallaxMapping(float depthFactor, float2 uv, float3 dirToCameraTS) { // the amount to shift - float2 delta = dirToCameraTS.xy * GetDepth(uv, ddx_fine(uv), ddy_fine(uv)) * depthFactor; + float2 delta = dirToCameraTS.xy * GetNormalizedDepth(0, depthFactor, uv, ddx_fine(uv), ddy_fine(uv)) * depthFactor; - float3 offset = float3(0,0,0); - offset.xy -= delta; - return offset; + ParallaxOffset result; + + result.m_offsetTS = float3(0,0,0); + result.m_offsetTS.xy -= delta; + result.m_isClipped = false; + return result; } -// dirToCameraTS and dirToLightTS should be in tangent space and normalized -// Adapt from CryEngine shader shadelib.cfi and POM function in https://github.com/a-riccardi/shader-toy +// Performs ray intersection against a surface with a heightmap. +// Adapted from CryEngine shader shadelib.cfi and POM function in https://github.com/a-riccardi/shader-toy // check https://github.com/UPBGE/blender/issues/1009 for more details. -float3 AdvancedParallaxMapping(float depthFactor, float2 uv, float3 dirToCameraTS, float3 dirToLightTS, int numSteps, inout float parallaxShadowAttenuation) +// @param depthFactor - scales the heightmap in tangent space units (which normally ends up being world units). +// @param depthOffset - offsets the heighmap up or down in tangent space units (which normally ends up being world units). +// @param uv - the UV coordinates on the surface, where the search will begin, used to sample the heightmap. +// @param dirToCameraTS - normalized direction to the camera, in tangent space. +// @param dirToLightTS - normalized direction to a light source, in tangent space, for self-shadowing (if enabled via o_parallax_shadow). +// @param numSteps - the number of steps to take when marching along the ray searching for intersection. +// @param parallaxShadowAttenuation - returns a factor for attenuating a light source, for self-shadowing (if enabled via o_parallax_shadow). +ParallaxOffset AdvancedParallaxMapping(float depthFactor, float depthOffset, float2 uv, float3 dirToCameraTS, float3 dirToLightTS, int numSteps, inout float parallaxShadowAttenuation) { + ParallaxOffset result; + result.m_isClipped = false; + float dirToCameraZInverse = 1.0 / dirToCameraTS.z; float step = 1.0 / numSteps; float currentStep = 0.0; @@ -61,21 +169,38 @@ float3 AdvancedParallaxMapping(float depthFactor, float2 uv, float3 dirToCameraT float2 ddx_uv = ddx_fine(uv); float2 ddy_uv = ddy_fine(uv); - float currentSample = GetDepth(uv, ddx_uv, ddy_uv); - float prevSample; - float3 parallaxOffset = float3(0,0,0); + float depthSearchStart = -depthOffset; + float depthSearchEnd = depthSearchStart + depthFactor; + + float inverseDepthFactor = 1.0 / depthFactor; - // find the intersect step + // This is the relative position at which we begin searching for intersection. + // It is adjusted according to the depthOffset, raising or lowering the whole surface by depthOffset units. + float3 parallaxOffset = dirToCameraTS.xyz * dirToCameraZInverse * depthOffset; + + // Get an initial heightmap sample to start the intersection search, starting at our initial parallaxOffset position. + float currentSample = GetNormalizedDepth(depthSearchStart, depthSearchEnd, inverseDepthFactor, uv + parallaxOffset.xy, ddx_uv, ddy_uv); + float prevSample; + + // Note that when depthOffset < 0, we could actually narrow the search so that instead of going through the entire [depthSearchStart,depthSearchEnd] range + // of the heightmap, we could go through the range [0,depthSearchEnd]. This would give more accurate results and fewer artifacts + // in case where the magnitude of depthOffset is significant. But for the sake of simplicity we currently search the whole range in all cases. + + // Do a basic search for the intersect step while(currentSample > currentStep) { currentStep += step; parallaxOffset += delta; + prevSample = currentSample; - currentSample = GetDepth(uv + parallaxOffset.xy, ddx_uv, ddy_uv); + currentSample = GetNormalizedDepth(depthSearchStart, depthSearchEnd, inverseDepthFactor, uv + parallaxOffset.xy, ddx_uv, ddy_uv); } + // Depending on the algorithm, we refine the result of the above search switch(o_parallax_algorithm) { + case ParallaxAlgorithm::Steep: + break; // This algorithm just relies on the course intersection test loop above case ParallaxAlgorithm::POM: { if(currentStep > 0.0) @@ -108,7 +233,7 @@ float3 AdvancedParallaxMapping(float depthFactor, float2 uv, float3 dirToCameraT parallaxOffset += reliefDelta * depthSign; currentStep += reliefStep * depthSign; - currentSample = GetDepth(uv + parallaxOffset.xy, ddx_uv, ddy_uv); + currentSample = GetNormalizedDepth(depthSearchStart, depthSearchEnd, inverseDepthFactor, uv + parallaxOffset.xy, ddx_uv, ddy_uv); } } break; @@ -136,7 +261,7 @@ float3 AdvancedParallaxMapping(float depthFactor, float2 uv, float3 dirToCameraT parallaxOffset += adjustedDelta; prevSample = currentSample; - currentSample = GetDepth(uv + parallaxOffset.xy, ddx_uv, ddy_uv); + currentSample = GetNormalizedDepth(depthSearchStart, depthSearchEnd, inverseDepthFactor, uv + parallaxOffset.xy, ddx_uv, ddy_uv); } } break; @@ -144,6 +269,30 @@ float3 AdvancedParallaxMapping(float depthFactor, float2 uv, float3 dirToCameraT default: break; } + + // Even though we do a bunch of clamping above when calling GetClampedDepth(), there are still cases where the parallax offset + // can be noticeably above the surface and still needs to be clamped here. The main case is when depthFactor==0 and depthOffset>1. + if(parallaxOffset.z > 0.0) + { + parallaxOffset = float3(0,0,0); + } + + if (o_parallax_highlightClipping) + { + // The most accurate way to report clipping is to sample the heightmap one last time at the final adjusted UV. + // (trying to do it based on parallaxOffset.z values just leads to too many edge cases) + + DepthResult depthResult = GetDepth(uv + parallaxOffset.xy, ddx_uv, ddy_uv); + + if(DepthResultCode_Normalized == depthResult.m_resultCode) + { + result.m_isClipped = lerp(depthSearchStart, depthSearchEnd, depthResult.m_depth) < 0; + } + else if(DepthResultCode_Absolute == depthResult.m_resultCode) + { + result.m_isClipped = depthResult.m_depth < 0.0; + } + } if(o_parallax_shadow && any(dirToLightTS)) { @@ -168,7 +317,7 @@ float3 AdvancedParallaxMapping(float depthFactor, float2 uv, float3 dirToCameraT } shadowUV += shadowDelta; - currentSample = GetDepth(shadowUV, ddx_uv, ddy_uv); + currentSample = GetNormalizedDepth(depthSearchStart, depthSearchEnd, inverseDepthFactor, shadowUV, ddx_uv, ddy_uv); currentStep -= step; } @@ -181,12 +330,13 @@ float3 AdvancedParallaxMapping(float depthFactor, float2 uv, float3 dirToCameraT parallaxShadowAttenuation = 1; } } - - return parallaxOffset; + + result.m_offsetTS = parallaxOffset; + return result; } // return offset in tangent space -float3 CalculateParallaxOffset(float depthFactor, float2 uv, float3 dirToCameraTS, float3 dirToLightTS, inout float parallaxShadowAttenuation) +ParallaxOffset CalculateParallaxOffset(float depthFactor, float depthOffset, float2 uv, float3 dirToCameraTS, float3 dirToLightTS, inout float parallaxShadowAttenuation) { if(o_parallax_algorithm == ParallaxAlgorithm::Basic) { @@ -194,27 +344,34 @@ float3 CalculateParallaxOffset(float depthFactor, float2 uv, float3 dirToCameraT } else { - float3 parallaxOffset; + ParallaxOffset parallaxOffset; switch(o_parallax_quality) { case ParallaxQuality::Low: - parallaxOffset = AdvancedParallaxMapping(depthFactor, uv, dirToCameraTS, dirToLightTS, 16, parallaxShadowAttenuation); + parallaxOffset = AdvancedParallaxMapping(depthFactor, depthOffset, uv, dirToCameraTS, dirToLightTS, 16, parallaxShadowAttenuation); break; case ParallaxQuality::Medium: - parallaxOffset = AdvancedParallaxMapping(depthFactor, uv, dirToCameraTS, dirToLightTS, 32, parallaxShadowAttenuation); + parallaxOffset = AdvancedParallaxMapping(depthFactor, depthOffset, uv, dirToCameraTS, dirToLightTS, 32, parallaxShadowAttenuation); break; case ParallaxQuality::High: - parallaxOffset = AdvancedParallaxMapping(depthFactor, uv, dirToCameraTS, dirToLightTS, 64, parallaxShadowAttenuation); + parallaxOffset = AdvancedParallaxMapping(depthFactor, depthOffset, uv, dirToCameraTS, dirToLightTS, 64, parallaxShadowAttenuation); break; case ParallaxQuality::Ultra: - parallaxOffset = AdvancedParallaxMapping(depthFactor, uv, dirToCameraTS, dirToLightTS, 128, parallaxShadowAttenuation); + parallaxOffset = AdvancedParallaxMapping(depthFactor, depthOffset, uv, dirToCameraTS, dirToLightTS, 128, parallaxShadowAttenuation); break; } return parallaxOffset; } } -float3 GetParallaxOffset( float depthFactor, +// Performs ray intersection against a surface with a heightmap, to determine an offset amount required for a parallax effect. +// @param depthFactor - scales the heightmap in tangent space units (which normally ends up being world units). +// @param depthOffset - offsets the heighmap up or down in tangent space units (which normally ends up being world units). +// @param uv - the UV coordinates on the surface, where the search will begin, used to sample the heightmap. +// @param dirToCameraTS - normalized direction to the camera, in tangent space. +// @param dirToLightTS - normalized direction to a light source, in tangent space, for self-shadowing (if enabled via o_parallax_shadow). +ParallaxOffset GetParallaxOffset( float depthFactor, + float depthOffset, float2 uv, float3 dirToCameraWS, float3 tangentWS, @@ -236,7 +393,7 @@ float3 GetParallaxOffset( float depthFactor, float4 dirToCameraTransformed = mul(uv3DTransform, float4(dirToCameraTS, 0.0)); float dummy = 1; - return CalculateParallaxOffset(depthFactor, uv, normalize(dirToCameraTransformed.xyz), float3(0,0,0), dummy); + return CalculateParallaxOffset(depthFactor, depthOffset, uv, normalize(dirToCameraTransformed.xyz), float3(0,0,0), dummy); } struct PixelDepthOffset diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Decals/DecalFeatureProcessorInterface.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Decals/DecalFeatureProcessorInterface.h index 214feb2040..a7aebf5aa4 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Decals/DecalFeatureProcessorInterface.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Decals/DecalFeatureProcessorInterface.h @@ -88,7 +88,10 @@ namespace AZ //! Sets the transform of the decal //! Equivalent to calling SetDecalPosition() + SetDecalOrientation() + SetDecalHalfSize() + //! @{ virtual void SetDecalTransform(DecalHandle handle, const AZ::Transform& world) = 0; + virtual void SetDecalTransform(DecalHandle handle, const AZ::Transform& world, const AZ::Vector3& nonUniformScale) = 0; + //! @} //! Sets the material information for this decal virtual void SetDecalMaterial(DecalHandle handle, const AZ::Data::AssetId) = 0; diff --git a/Gems/Atom/Feature/Common/Code/Source/Decals/DecalFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Decals/DecalFeatureProcessor.cpp index 7abc6698fa..55fa633e5d 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Decals/DecalFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Decals/DecalFeatureProcessor.cpp @@ -264,7 +264,12 @@ namespace AZ void DecalFeatureProcessor::SetDecalTransform(DecalHandle handle, const AZ::Transform& world) { - // https://jira.agscollab.com/browse/ATOM-4330 + SetDecalTransform(handle, world, AZ::Vector3::CreateOne()); + } + + void DecalFeatureProcessor::SetDecalTransform(DecalHandle handle, const AZ::Transform& world, const AZ::Vector3& nonUniformScale) + { + // ATOM-4330 // Original Open 3D Engine uploads a 4x4 matrix rather than quaternion, rotation, scale. // That is more memory but less calculation because it is doing a matrix inverse rather than a polar decomposition // I've done some experiments and uploading a 3x4 transform matrix with 3x3 matrix inverse should be possible @@ -274,7 +279,7 @@ namespace AZ if (handle.IsValid()) { Quaternion orientation = world.GetRotation(); - Vector3 scale = world.GetScale(); + Vector3 scale = world.GetScale() * nonUniformScale; SetDecalHalfSize(handle, scale); SetDecalPosition(handle, world.GetTranslation()); diff --git a/Gems/Atom/Feature/Common/Code/Source/Decals/DecalFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/Decals/DecalFeatureProcessor.h index 9c4acf6322..3b99715d5a 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Decals/DecalFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/Decals/DecalFeatureProcessor.h @@ -73,7 +73,10 @@ namespace AZ //! Sets the transform of the decal //! Equivalent to calling SetDecalPosition() + SetDecalOrientation() + SetDecalHalfSize() + //! @{ void SetDecalTransform(DecalHandle handle, const AZ::Transform& world) override; + void SetDecalTransform(DecalHandle handle, const AZ::Transform& world, const AZ::Vector3& nonUniformScale) override; + //! @} //! Sets the material information for this decal void SetDecalMaterial(DecalHandle handle, const AZ::Data::AssetId) override; diff --git a/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArrayFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArrayFeatureProcessor.cpp index 9d1ea4e680..4000df646d 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArrayFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArrayFeatureProcessor.cpp @@ -270,10 +270,16 @@ namespace AZ } void DecalTextureArrayFeatureProcessor::SetDecalTransform(DecalHandle handle, const AZ::Transform& world) + { + SetDecalTransform(handle, world, AZ::Vector3::CreateOne()); + } + + void DecalTextureArrayFeatureProcessor::SetDecalTransform(DecalHandle handle, const AZ::Transform& world, + const AZ::Vector3& nonUniformScale) { if (handle.IsValid()) { - SetDecalHalfSize(handle, world.GetScale()); + SetDecalHalfSize(handle, nonUniformScale * world.GetScale()); SetDecalPosition(handle, world.GetTranslation()); SetDecalOrientation(handle, world.GetRotation()); diff --git a/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArrayFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArrayFeatureProcessor.h index 7ab073c7f9..825e461fc2 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArrayFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArrayFeatureProcessor.h @@ -82,7 +82,10 @@ namespace AZ //! Sets the transform of the decal //! Equivalent to calling SetDecalPosition() + SetDecalOrientation() + SetDecalHalfSize() + //! @{ void SetDecalTransform(const DecalHandle handle, const AZ::Transform& world) override; + void SetDecalTransform(const DecalHandle handle, const AZ::Transform& world, const AZ::Vector3& nonUniformScale) override; + //! @} //! Sets the material information for this decal void SetDecalMaterial(const DecalHandle handle, const AZ::Data::AssetId id) override; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialFunctorSourceDataSerializer.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialFunctorSourceDataSerializer.h index 59f129b52a..a10e773891 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialFunctorSourceDataSerializer.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialFunctorSourceDataSerializer.h @@ -33,6 +33,8 @@ namespace AZ JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue, const Uuid& valueTypeId, JsonSerializerContext& context) override; + private: + BaseJsonSerializer::OperationFlags GetOperationsFlags() const override; }; } // namespace RPI diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialFunctorSourceDataSerializer.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialFunctorSourceDataSerializer.cpp index a31d97c913..7ac8240faa 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialFunctorSourceDataSerializer.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialFunctorSourceDataSerializer.cpp @@ -127,5 +127,10 @@ namespace AZ return context.Report(result, "Successfully processed MaterialFunctorSourceData."); } + + BaseJsonSerializer::OperationFlags JsonMaterialFunctorSourceDataSerializer::GetOperationsFlags() const + { + return OperationFlags::ManualDefault; + } } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/012_Parallax_POM_Cutout.material b/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/012_Parallax_POM_Cutout.material new file mode 100644 index 0000000000..fb862dc5d3 --- /dev/null +++ b/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/012_Parallax_POM_Cutout.material @@ -0,0 +1,26 @@ +{ + "description": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "parentMaterial": "", + "propertyLayoutVersion": 3, + "properties": { + "baseColor": { + "textureMap": "TestData/Textures/TextureHaven/4k_castle_brick_02_red/4k_castle_brick_02_red_bc.png" + }, + "opacity": { + "alphaSource": "Split", + "mode": "Cutout", + "textureMap": "TestData/Textures/checker8x8_512.png" + }, + "parallax": { + "algorithm": "POM", + "enable": true, + "factor": 0.10000000149011612, + "quality": "High", + "textureMap": "TestData/Textures/TextureHaven/4k_castle_brick_02_red/4k_castle_brick_02_red_disp.png" + }, + "uv": { + "scale": 0.5 + } + } +} \ No newline at end of file diff --git a/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick_ForwardPass.azsl b/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick_ForwardPass.azsl index 793b71b3af..424de7749d 100644 --- a/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick_ForwardPass.azsl +++ b/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick_ForwardPass.azsl @@ -121,12 +121,12 @@ void GetSurfaceShape(float2 uv, out float depth, out float3 normal) } // Callback function for ParallaxMapping.azsli -float GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy) +DepthResult GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy) { float depth; float3 normal; GetSurfaceShape(uv, depth, normal); - return depth; + return DepthResultNormalized(depth); } ForwardPassOutput AutoBrick_ForwardPassPS(VSOutput IN) @@ -136,15 +136,18 @@ ForwardPassOutput AutoBrick_ForwardPassPS(VSOutput IN) 0,1,0, 0,0,1 }; - float3 tangentOffset = GetParallaxOffset( AutoBrickSrg::m_lineDepth, - IN.m_uv, - ViewSrg::m_worldPosition.xyz - IN.m_worldPosition, - IN.m_tangent, - IN.m_bitangent, - IN.m_normal, - identityUvMatrix); + float depthOffset = 0.0; + + ParallaxOffset tangentOffset = GetParallaxOffset( AutoBrickSrg::m_lineDepth, + depthOffset, + IN.m_uv, + ViewSrg::m_worldPosition.xyz - IN.m_worldPosition, + IN.m_tangent, + IN.m_bitangent, + IN.m_normal, + identityUvMatrix); - IN.m_uv += tangentOffset.xy; + IN.m_uv += tangentOffset.m_offsetTS.xy; float3 baseColor = float3(1,1,1); const float noise = AutoBrickSrg::m_noise.Sample(AutoBrickSrg::m_sampler, IN.m_uv).r; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorGroupHeaderWidget.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorGroupHeaderWidget.h index d1beed1a65..bba299187b 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorGroupHeaderWidget.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorGroupHeaderWidget.h @@ -15,6 +15,7 @@ #if !defined(Q_MOC_RUN) #include #include +#include #include #endif @@ -31,7 +32,11 @@ namespace AtomToolsFramework void SetExpanded(bool expanded); bool IsExpanded() const; + Q_SIGNALS: + void clicked(QMouseEvent* event); + protected: + void mousePressEvent(QMouseEvent* event) override; void paintEvent(QPaintEvent* event) override; private: diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorRequestBus.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorRequestBus.h index 5f16894a0c..81b512faf2 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorRequestBus.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorRequestBus.h @@ -55,6 +55,12 @@ namespace AtomToolsFramework //! Calls Rebuild for all InspectorGroupWidget, allowing for destructive UI changes virtual void RebuildAll() = 0; + + //! Expands all groups and headers + virtual void ExpandAll() = 0; + + //! Collapses all groups and headers + virtual void CollapseAll() = 0; }; using InspectorRequestBus = AZ::EBus; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorWidget.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorWidget.h index 9f8df2da71..b0932b3b04 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorWidget.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorWidget.h @@ -29,11 +29,8 @@ namespace Ui namespace AtomToolsFramework { - class InspectorPropertyGroupWidget; -} + class InspectorGroupHeaderWidget; -namespace AtomToolsFramework -{ //! Provides controls for viewing and editing object settings. //! The settings can be divided into groups, with each one showing a subset of properties. class InspectorWidget @@ -66,8 +63,15 @@ namespace AtomToolsFramework void RefreshAll() override; void RebuildAll() override; + void ExpandAll() override; + void CollapseAll() override; + private: + void OnHeaderClicked(QMouseEvent* event, InspectorGroupHeaderWidget* groupHeader, QWidget* groupWidget); + QVBoxLayout* m_layout = nullptr; QScopedPointer m_ui; + AZStd::vector m_headers; + AZStd::vector m_groups; }; } // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h index 8e64c1467e..f31d130ee6 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h @@ -104,6 +104,7 @@ namespace AtomToolsFramework void EndCursorCapture() override; AzFramework::ScreenPoint ViewportCursorScreenPosition() override; AZStd::optional PreviousViewportCursorScreenPosition() override; + bool IsMouseOver() const override; // AzFramework::WindowRequestBus::Handler ... void SetWindowTitle(const AZStd::string& title) override; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorGroupHeaderWidget.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorGroupHeaderWidget.cpp index 2928d745c1..41cc1e4a50 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorGroupHeaderWidget.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorGroupHeaderWidget.cpp @@ -14,10 +14,10 @@ #include #include -#include -#include #include +#include #include +#include #include namespace AtomToolsFramework @@ -44,6 +44,11 @@ namespace AtomToolsFramework return m_expanded; } + void InspectorGroupHeaderWidget::mousePressEvent(QMouseEvent* event) + { + emit clicked(event); + } + void InspectorGroupHeaderWidget::paintEvent([[maybe_unused]] QPaintEvent* event) { QPainter painter(this); @@ -52,19 +57,10 @@ namespace AtomToolsFramework auto& icon = m_expanded ? m_iconExpanded : m_iconCollapsed; const QRect iconRect(5, (geometry().height() / 2) - (iconSize.height() / 2), iconSize.width(), iconSize.height()); - style->drawItemPixmap(&painter, - iconRect, - Qt::AlignLeft | Qt::AlignVCenter, - icon.scaledToWidth(iconSize.width())); + style->drawItemPixmap(&painter, iconRect, Qt::AlignLeft | Qt::AlignVCenter, icon.scaledToWidth(iconSize.width())); const auto textRect = QRect(25, 0, geometry().width() - 21, geometry().height()); - style->drawItemText(&painter, - textRect, - Qt::AlignLeft | Qt::AlignVCenter, - QPalette(), - true, - text(), - QPalette::HighlightedText); + style->drawItemText(&painter, textRect, Qt::AlignLeft | Qt::AlignVCenter, QPalette(), true, text(), QPalette::HighlightedText); } } // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp index 31a291c845..ec5f9893bd 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp @@ -10,12 +10,13 @@ * */ +#include #include #include #include -#include #include +#include #include #include @@ -38,6 +39,8 @@ namespace AtomToolsFramework m_layout = new QVBoxLayout(m_ui->m_propertyContent); m_layout->setContentsMargins(0, 0, 0, 0); m_layout->setSpacing(0); + m_headers.clear(); + m_groups.clear(); } void InspectorWidget::AddGroupsBegin() @@ -52,8 +55,7 @@ namespace AtomToolsFramework m_layout->addStretch(); // Scroll to top whenever there is new content - m_ui->m_propertyScrollArea->verticalScrollBar()->setValue( - m_ui->m_propertyScrollArea->verticalScrollBar()->minimum()); + m_ui->m_propertyScrollArea->verticalScrollBar()->setValue(m_ui->m_propertyScrollArea->verticalScrollBar()->minimum()); setUpdatesEnabled(true); } @@ -68,15 +70,15 @@ namespace AtomToolsFramework groupHeader->setText(groupDisplayName.c_str()); groupHeader->setToolTip(groupDescription.c_str()); m_layout->addWidget(groupHeader); + m_headers.push_back(groupHeader); groupWidget->setObjectName(groupNameId.c_str()); groupWidget->setParent(m_ui->m_propertyContent); m_layout->addWidget(groupWidget); + m_groups.push_back(groupWidget); - connect(groupHeader, &AzQtComponents::ExtendedLabel::clicked, this, [groupHeader, groupWidget]() - { - groupHeader->SetExpanded(!groupHeader->IsExpanded()); - groupWidget->setVisible(groupHeader->IsExpanded()); + connect(groupHeader, &InspectorGroupHeaderWidget::clicked, this, [this, groupHeader, groupWidget](QMouseEvent* event) { + OnHeaderClicked(event, groupHeader, groupWidget); }); } @@ -111,6 +113,57 @@ namespace AtomToolsFramework groupWidget->Rebuild(); } } + + void InspectorWidget::ExpandAll() + { + for (auto headerWidget : m_headers) + { + headerWidget->SetExpanded(true); + } + for (auto groupWidget : m_groups) + { + groupWidget->setVisible(true); + } + } + + void InspectorWidget::CollapseAll() + { + for (auto headerWidget : m_headers) + { + headerWidget->SetExpanded(false); + } + for (auto groupWidget : m_groups) + { + groupWidget->setVisible(false); + } + } + + void InspectorWidget::OnHeaderClicked(QMouseEvent* event, InspectorGroupHeaderWidget* groupHeader, QWidget* groupWidget) + { + if (event->button() == Qt::MouseButton::LeftButton) + { + groupHeader->SetExpanded(!groupHeader->IsExpanded()); + groupWidget->setVisible(groupHeader->IsExpanded()); + return; + } + + if (event->button() == Qt::MouseButton::RightButton) + { + QMenu menu; + menu.addAction("Expand", [groupHeader, groupWidget]() { + groupHeader->SetExpanded(true); + groupWidget->setVisible(true); + })->setEnabled(!groupHeader->IsExpanded()); + menu.addAction("Collapse", [groupHeader, groupWidget]() { + groupHeader->SetExpanded(false); + groupWidget->setVisible(false); + })->setEnabled(groupHeader->IsExpanded()); + menu.addAction("Expand All", [this]() { ExpandAll(); }); + menu.addAction("Collapse All", [this]() { CollapseAll(); }); + menu.exec(event->globalPos()); + return; + } + } } // namespace AtomToolsFramework #include diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp index 5f3c553601..94038d1977 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp @@ -472,6 +472,11 @@ namespace AtomToolsFramework : AZStd::optional{}; } + bool RenderViewportWidget::IsMouseOver() const + { + return m_mouseOver; + } + void RenderViewportWidget::BeginCursorCapture() { if (m_capturingCursor) diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/InputController/MaterialEditorViewportInputController.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/InputController/MaterialEditorViewportInputController.cpp index 83ec5a41c5..36e4b76cec 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/InputController/MaterialEditorViewportInputController.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/InputController/MaterialEditorViewportInputController.cpp @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include #include @@ -136,6 +138,11 @@ namespace MaterialEditor const InputChannel::State state = event.m_inputChannel.GetState(); const KeyMask keysOld = m_keys; + bool mouseOver = false; + AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::EventResult( + mouseOver, GetViewportId(), + &AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Events::IsMouseOver); + if (!m_behavior) { EvaluateControlBehavior(); @@ -178,7 +185,10 @@ namespace MaterialEditor } else if (inputChannelId == InputDeviceMouse::Movement::Z) { - m_behavior->MoveZ(event.m_inputChannel.GetValue()); + if (mouseOver) + { + m_behavior->MoveZ(event.m_inputChannel.GetValue()); + } } break; case InputChannel::State::Ended: @@ -222,7 +232,10 @@ namespace MaterialEditor } else if (inputChannelId == InputDeviceMouse::Movement::Z) { - m_behavior->MoveZ(event.m_inputChannel.GetValue()); + if (mouseOver) + { + m_behavior->MoveZ(event.m_inputChannel.GetValue()); + } } break; } diff --git a/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp b/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp index d36307e4ec..40684640d6 100644 --- a/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp +++ b/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp @@ -101,6 +101,11 @@ AZ::RPI::WindowContextSharedPtr AZ::FFont::GetDefaultWindowContext() const bool AZ::FFont::InitFont(AZ::RPI::Scene* renderScene) { + if (!renderScene) + { + return false; + } + auto initializationState = InitializationState::Uninitialized; // Do an atomic transition to Initializing if we're in the Uninitialized state. // Otherwise, check the current state. @@ -111,11 +116,6 @@ bool AZ::FFont::InitFont(AZ::RPI::Scene* renderScene) return initializationState == InitializationState::Initialized; } - if (!renderScene) - { - return false; - } - // Create and initialize DynamicDrawContext for font draw AZ::RPI::Ptr dynamicDraw = m_atomFont->GetOrCreateDynamicDrawForScene(renderScene); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Decals/DecalComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Decals/DecalComponentController.cpp index 22a7e1c6c0..3c748e352e 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Decals/DecalComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Decals/DecalComponentController.cpp @@ -75,6 +75,12 @@ namespace AZ incompatible.push_back(AZ_CRC_CE("DecalService")); } + void DecalComponentController::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent) + { + dependent.push_back(AZ_CRC_CE("TransformService")); + dependent.push_back(AZ_CRC_CE("NonUniformScaleService")); + } + DecalComponentController::DecalComponentController(const DecalComponentConfig& config) : m_configuration(config) { @@ -90,6 +96,11 @@ namespace AZ m_handle = m_featureProcessor->AcquireDecal(); } + m_cachedNonUniformScale = AZ::Vector3::CreateOne(); + AZ::NonUniformScaleRequestBus::EventResult(m_cachedNonUniformScale, m_entityId, &AZ::NonUniformScaleRequests::GetScale); + AZ::NonUniformScaleRequestBus::Event(m_entityId, &AZ::NonUniformScaleRequests::RegisterScaleChangedEvent, + m_nonUniformScaleChangedHandler); + AZ::Transform local, world; AZ::TransformBus::Event(entityId, &AZ::TransformBus::Events::GetLocalAndWorld, local, world); OnTransformChanged(local, world); @@ -103,6 +114,7 @@ namespace AZ { DecalRequestBus::Handler::BusDisconnect(m_entityId); TransformNotificationBus::Handler::BusDisconnect(m_entityId); + m_nonUniformScaleChangedHandler.Disconnect(); if (m_featureProcessor) { m_featureProcessor->ReleaseDecal(m_handle); @@ -125,7 +137,18 @@ namespace AZ { if (m_featureProcessor) { - m_featureProcessor->SetDecalTransform(m_handle, world); + m_featureProcessor->SetDecalTransform(m_handle, world, m_cachedNonUniformScale); + } + } + + void DecalComponentController::HandleNonUniformScaleChange(const AZ::Vector3& nonUniformScale) + { + m_cachedNonUniformScale = nonUniformScale; + if (m_featureProcessor) + { + AZ::Transform world = AZ::Transform::CreateIdentity(); + AZ::TransformBus::EventResult(world, m_entityId, &AZ::TransformBus::Events::GetWorldTM); + m_featureProcessor->SetDecalTransform(m_handle, world, nonUniformScale); } } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Decals/DecalComponentController.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Decals/DecalComponentController.h index dc6b6e70ea..14204c43dc 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Decals/DecalComponentController.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Decals/DecalComponentController.h @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -33,6 +34,7 @@ namespace AZ static void Reflect(AZ::ReflectContext* context); static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); DecalComponentController() = default; DecalComponentController(const DecalComponentConfig& config); @@ -64,11 +66,18 @@ namespace AZ void OpacityChanged(); void SortKeyChanged(); void MaterialChanged(); + void HandleNonUniformScaleChange(const AZ::Vector3& nonUniformScale); DecalComponentConfig m_configuration; DecalFeatureProcessorInterface* m_featureProcessor = nullptr; DecalFeatureProcessorInterface::DecalHandle m_handle; EntityId m_entityId; + AZ::Vector3 m_cachedNonUniformScale = AZ::Vector3::CreateOne(); + + AZ::NonUniformScaleChangedEvent::Handler m_nonUniformScaleChangedHandler + { + [&](const AZ::Vector3& nonUniformScale) { HandleNonUniformScaleChange(nonUniformScale); } + }; }; } // namespace Render } // AZ namespace diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp index deab9d2715..70627fd2f1 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp @@ -131,6 +131,7 @@ namespace AZ void MeshComponentController::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent) { dependent.push_back(AZ_CRC("TransformService", 0x8ee22c50)); + dependent.push_back(AZ_CRC_CE("NonUniformScaleService")); } void MeshComponentController::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/ReflectionProbeComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/ReflectionProbeComponentController.cpp index 59d1f7afa7..772a995584 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/ReflectionProbeComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/ReflectionProbeComponentController.cpp @@ -164,6 +164,7 @@ namespace AZ if (m_featureProcessor) { m_featureProcessor->RemoveProbe(m_handle); + m_handle = nullptr; } LmbrCentral::ShapeComponentNotificationsBus::Handler::BusDisconnect(); diff --git a/Gems/ImageProcessing/Code/Source/Processing/ImageConvert.cpp b/Gems/ImageProcessing/Code/Source/Processing/ImageConvert.cpp index aec56af8d8..b5fc329a03 100644 --- a/Gems/ImageProcessing/Code/Source/Processing/ImageConvert.cpp +++ b/Gems/ImageProcessing/Code/Source/Processing/ImageConvert.cpp @@ -739,7 +739,9 @@ namespace ImageProcessing if (preset == nullptr) { - AZ_Assert(false, "preset should always exist"); + AZStd::string uuidStr; + textureSettings.m_preset.ToString(uuidStr); + AZ_Assert(false, "%s cannot find image preset with ID %s.", imageFilePath.c_str(), uuidStr.c_str()); return nullptr; } diff --git a/Gems/LandscapeCanvas/Code/Source/Editor/MainWindow.cpp b/Gems/LandscapeCanvas/Code/Source/Editor/MainWindow.cpp index 64d908bdb2..3879cd2597 100644 --- a/Gems/LandscapeCanvas/Code/Source/Editor/MainWindow.cpp +++ b/Gems/LandscapeCanvas/Code/Source/Editor/MainWindow.cpp @@ -206,7 +206,7 @@ namespace LandscapeCanvasEditor static const QStringList preferredCategories = { "Vegetation", - "Rendering" + "Atom" }; // There are a couple of cases where we prefer certain categories of Components diff --git a/Gems/LmbrCentral/Code/Source/Unhandled/Material/MaterialAssetTypeInfo.cpp b/Gems/LmbrCentral/Code/Source/Unhandled/Material/MaterialAssetTypeInfo.cpp index 907bd66cb8..4d5ec5e2f7 100644 --- a/Gems/LmbrCentral/Code/Source/Unhandled/Material/MaterialAssetTypeInfo.cpp +++ b/Gems/LmbrCentral/Code/Source/Unhandled/Material/MaterialAssetTypeInfo.cpp @@ -54,11 +54,6 @@ namespace LmbrCentral return "Icons/Components/Decal.svg"; } - AZ::Uuid MaterialAssetTypeInfo::GetComponentTypeId() const - { - return AZ::Uuid("{BA3890BD-D2E7-4DB6-95CD-7E7D5525567A}"); - } - // DccMaterialAssetTypeInfo DccMaterialAssetTypeInfo::~DccMaterialAssetTypeInfo() diff --git a/Gems/LmbrCentral/Code/Source/Unhandled/Material/MaterialAssetTypeInfo.h b/Gems/LmbrCentral/Code/Source/Unhandled/Material/MaterialAssetTypeInfo.h index b7f3294e89..7e9c9e5fb4 100644 --- a/Gems/LmbrCentral/Code/Source/Unhandled/Material/MaterialAssetTypeInfo.h +++ b/Gems/LmbrCentral/Code/Source/Unhandled/Material/MaterialAssetTypeInfo.h @@ -30,7 +30,6 @@ namespace LmbrCentral const char* GetAssetTypeDisplayName() const override; const char* GetGroup() const override; const char* GetBrowserIcon() const override; - AZ::Uuid GetComponentTypeId() const override; ////////////////////////////////////////////////////////////////////////////////////////////// void Register(); diff --git a/Gems/ScriptCanvas/Code/Editor/Components/EditorUtils.cpp b/Gems/ScriptCanvas/Code/Editor/Components/EditorUtils.cpp index e4596b5334..92aa1dddc7 100644 --- a/Gems/ScriptCanvas/Code/Editor/Components/EditorUtils.cpp +++ b/Gems/ScriptCanvas/Code/Editor/Components/EditorUtils.cpp @@ -58,7 +58,7 @@ namespace ScriptCanvasEditor } else { - resultHash = ScriptCanvas::NodeUtils::ConstructMethodNodeIdentifier(classMethodTreeItem->GetClassMethodName(), classMethodTreeItem->GetMethodName()); + resultHash = ScriptCanvas::NodeUtils::ConstructMethodNodeIdentifier(classMethodTreeItem->GetClassMethodName(), classMethodTreeItem->GetMethodName(), classMethodTreeItem->GetPropertyStatus()); } } else if (auto globalMethodTreeItem = azrtti_cast(treeItem); globalMethodTreeItem != nullptr) diff --git a/Gems/ScriptCanvas/Code/Editor/Nodes/NodeCreateUtils.cpp b/Gems/ScriptCanvas/Code/Editor/Nodes/NodeCreateUtils.cpp index 62905947a8..07c96aba82 100644 --- a/Gems/ScriptCanvas/Code/Editor/Nodes/NodeCreateUtils.cpp +++ b/Gems/ScriptCanvas/Code/Editor/Nodes/NodeCreateUtils.cpp @@ -167,7 +167,7 @@ namespace ScriptCanvasEditor::Nodes return nodeIdPair; } - NodeIdPair CreateObjectMethodNode(AZStd::string_view className, AZStd::string_view methodName, const ScriptCanvas::ScriptCanvasId& scriptCanvasId) + NodeIdPair CreateObjectMethodNode(AZStd::string_view className, AZStd::string_view methodName, const ScriptCanvas::ScriptCanvasId& scriptCanvasId, ScriptCanvas::PropertyStatus propertyStatus) { AZ_PROFILE_TIMER("ScriptCanvas", __FUNCTION__); NodeIdPair nodeIds; @@ -181,7 +181,7 @@ namespace ScriptCanvasEditor::Nodes auto* methodNode = azrtti_cast(node); ScriptCanvas::NamespacePath emptyNamespacePath; - methodNode->InitializeBehaviorMethod(emptyNamespacePath, className, methodName); + methodNode->InitializeBehaviorMethod(emptyNamespacePath, className, methodName, propertyStatus); AZStd::string_view displayName = methodNode->GetName(); scriptCanvasEntity->SetName(AZStd::string::format("SC-Node(%.*s)", aznumeric_cast(displayName.size()), displayName.data())); @@ -208,7 +208,7 @@ namespace ScriptCanvasEditor::Nodes auto* methodNode = azrtti_cast(node); ScriptCanvas::NamespacePath emptyNamespacePath; - methodNode->InitializeBehaviorMethod(emptyNamespacePath, className, methodName); + methodNode->InitializeBehaviorMethod(emptyNamespacePath, className, methodName, ScriptCanvas::PropertyStatus::None); AZStd::string_view displayName = methodNode->GetName(); scriptCanvasEntity->SetName(AZStd::string::format("SC-Node(%.*s)", aznumeric_cast(displayName.size()), displayName.data())); diff --git a/Gems/ScriptCanvas/Code/Editor/Nodes/NodeCreateUtils.h b/Gems/ScriptCanvas/Code/Editor/Nodes/NodeCreateUtils.h index fa81ccfe8e..f4f0378333 100644 --- a/Gems/ScriptCanvas/Code/Editor/Nodes/NodeCreateUtils.h +++ b/Gems/ScriptCanvas/Code/Editor/Nodes/NodeCreateUtils.h @@ -34,7 +34,7 @@ namespace ScriptCanvasEditor::Nodes AZStd::pair CreateAndGetNode(const AZ::Uuid& classData, const ScriptCanvas::ScriptCanvasId& scriptCanvasId, const StyleConfiguration& styleConfiguration, AZStd::function = nullptr); NodeIdPair CreateNode(const AZ::Uuid& classData, const ScriptCanvas::ScriptCanvasId& scriptCanvasId, const StyleConfiguration& styleConfiguration); NodeIdPair CreateEntityNode(const AZ::EntityId& sourceId, const ScriptCanvas::ScriptCanvasId& scriptCanvasId); - NodeIdPair CreateObjectMethodNode(AZStd::string_view className, AZStd::string_view methodName, const ScriptCanvas::ScriptCanvasId& scriptCanvasId); + NodeIdPair CreateObjectMethodNode(AZStd::string_view className, AZStd::string_view methodName, const ScriptCanvas::ScriptCanvasId& scriptCanvasId, ScriptCanvas::PropertyStatus propertyStatus); NodeIdPair CreateObjectMethodOverloadNode(AZStd::string_view className, AZStd::string_view methodName, const ScriptCanvas::ScriptCanvasId& scriptCanvasGraphId); NodeIdPair CreateGlobalMethodNode(AZStd::string_view methodName, const ScriptCanvas::ScriptCanvasId& scriptCanvasId); NodeIdPair CreateEbusWrapperNode(AZStd::string_view busName, const ScriptCanvas::ScriptCanvasId& scriptCanvasId); diff --git a/Gems/ScriptCanvas/Code/Editor/Nodes/NodeDisplayUtils.cpp b/Gems/ScriptCanvas/Code/Editor/Nodes/NodeDisplayUtils.cpp index a903c9f885..c3929e6bc4 100644 --- a/Gems/ScriptCanvas/Code/Editor/Nodes/NodeDisplayUtils.cpp +++ b/Gems/ScriptCanvas/Code/Editor/Nodes/NodeDisplayUtils.cpp @@ -327,12 +327,14 @@ namespace ScriptCanvasEditor::Nodes contextGroup = TranslationContextGroup::EbusSender; break; case ScriptCanvas::MethodType::Member: + case ScriptCanvas::MethodType::Getter: + case ScriptCanvas::MethodType::Setter: case ScriptCanvas::MethodType::Free: graphCanvasEntity->CreateComponent(); contextGroup = TranslationContextGroup::ClassMethod; break; default: - AZ_Error("ScriptCanvas", false, "Invalid method node type, node creation failed. This node nodes to be deleted."); + AZ_Error("ScriptCanvas", false, "Invalid method node type, node creation failed. This node needs to be deleted."); break; } diff --git a/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/EBusNodePaletteTreeItemTypes.cpp b/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/EBusNodePaletteTreeItemTypes.cpp index 4ae7d94560..474af058d3 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/EBusNodePaletteTreeItemTypes.cpp +++ b/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/EBusNodePaletteTreeItemTypes.cpp @@ -52,14 +52,16 @@ namespace ScriptCanvasEditor ->Field("BusName", &CreateEBusSenderMimeEvent::m_busName) ->Field("EventName", &CreateEBusSenderMimeEvent::m_eventName) ->Field("IsOverload", &CreateEBusSenderMimeEvent::m_isOverload) + ->Field("propertyStatus", &CreateEBusSenderMimeEvent::m_propertyStatus) ; } } - CreateEBusSenderMimeEvent::CreateEBusSenderMimeEvent(AZStd::string_view busName, AZStd::string_view eventName, bool isOverload) + CreateEBusSenderMimeEvent::CreateEBusSenderMimeEvent(AZStd::string_view busName, AZStd::string_view eventName, bool isOverload, ScriptCanvas::PropertyStatus propertyStatus) : m_busName(busName.data()) , m_eventName(eventName.data()) , m_isOverload(isOverload) + , m_propertyStatus(propertyStatus) { } @@ -71,7 +73,7 @@ namespace ScriptCanvasEditor } else { - return Nodes::CreateObjectMethodNode(m_busName, m_eventName, scriptCanvasId); + return Nodes::CreateObjectMethodNode(m_busName, m_eventName, scriptCanvasId, m_propertyStatus); } } @@ -91,13 +93,14 @@ namespace ScriptCanvasEditor return defaultIcon; } - EBusSendEventPaletteTreeItem::EBusSendEventPaletteTreeItem(AZStd::string_view busName, AZStd::string_view eventName, const ScriptCanvas::EBusBusId& busIdentifier, const ScriptCanvas::EBusEventId& eventIdentifier, bool isOverload) + EBusSendEventPaletteTreeItem::EBusSendEventPaletteTreeItem(AZStd::string_view busName, AZStd::string_view eventName, const ScriptCanvas::EBusBusId& busIdentifier, const ScriptCanvas::EBusEventId& eventIdentifier, bool isOverload, ScriptCanvas::PropertyStatus propertyStatus) : DraggableNodePaletteTreeItem(eventName, ScriptCanvasEditor::AssetEditorId) , m_busName(busName.data()) , m_eventName(eventName.data()) , m_busId(busIdentifier) , m_eventId(eventIdentifier) , m_isOverload(isOverload) + , m_propertyStatus(propertyStatus) { AZStd::string displayEventName = TranslationHelper::GetKeyTranslation(TranslationContextGroup::EbusSender, m_busName.toUtf8().data(), m_eventName.toUtf8().data(), TranslationItemType::Node, TranslationKeyId::Name); @@ -122,7 +125,7 @@ namespace ScriptCanvasEditor GraphCanvas::GraphCanvasMimeEvent* EBusSendEventPaletteTreeItem::CreateMimeEvent() const { - return aznew CreateEBusSenderMimeEvent(m_busName.toUtf8().data(), m_eventName.toUtf8().data(), m_isOverload); + return aznew CreateEBusSenderMimeEvent(m_busName.toUtf8().data(), m_eventName.toUtf8().data(), m_isOverload, ScriptCanvas::PropertyStatus::None); } AZStd::string EBusSendEventPaletteTreeItem::GetBusName() const @@ -145,6 +148,11 @@ namespace ScriptCanvasEditor return m_eventId; } + ScriptCanvas::PropertyStatus EBusSendEventPaletteTreeItem::GetPropertyStatus() const + { + return m_propertyStatus; + } + bool EBusSendEventPaletteTreeItem::IsOverload() const { return m_isOverload; diff --git a/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/EBusNodePaletteTreeItemTypes.h b/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/EBusNodePaletteTreeItemTypes.h index b5181754c4..b3bd771109 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/EBusNodePaletteTreeItemTypes.h +++ b/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/EBusNodePaletteTreeItemTypes.h @@ -28,7 +28,7 @@ namespace ScriptCanvasEditor static void Reflect(AZ::ReflectContext* reflectContext); CreateEBusSenderMimeEvent() = default; - CreateEBusSenderMimeEvent(AZStd::string_view busName, AZStd::string_view eventName, bool isOverload); + CreateEBusSenderMimeEvent(AZStd::string_view busName, AZStd::string_view eventName, bool isOverload, ScriptCanvas::PropertyStatus propertyStatus); ~CreateEBusSenderMimeEvent() = default; protected: @@ -36,6 +36,7 @@ namespace ScriptCanvasEditor private: bool m_isOverload; + ScriptCanvas::PropertyStatus m_propertyStatus = ScriptCanvas::PropertyStatus::None; AZStd::string m_busName; AZStd::string m_eventName; }; @@ -50,7 +51,7 @@ namespace ScriptCanvasEditor AZ_CLASS_ALLOCATOR(EBusSendEventPaletteTreeItem, AZ::SystemAllocator, 0); AZ_RTTI(EBusSendEventPaletteTreeItem, "{26258B0A-8E2C-434D-ACAD-3DE85E64A4F8}", GraphCanvas::DraggableNodePaletteTreeItem); - EBusSendEventPaletteTreeItem(AZStd::string_view busName, AZStd::string_view eventName, const ScriptCanvas::EBusBusId& busId, const ScriptCanvas::EBusEventId& eventIdentifier, bool isOverload); + EBusSendEventPaletteTreeItem(AZStd::string_view busName, AZStd::string_view eventName, const ScriptCanvas::EBusBusId& busId, const ScriptCanvas::EBusEventId& eventIdentifier, bool isOverload, ScriptCanvas::PropertyStatus propertyStatus); ~EBusSendEventPaletteTreeItem() = default; GraphCanvas::GraphCanvasMimeEvent* CreateMimeEvent() const override; @@ -63,6 +64,8 @@ namespace ScriptCanvasEditor bool IsOverload() const; + ScriptCanvas::PropertyStatus GetPropertyStatus() const; + private: bool m_isOverload; QString m_busName; @@ -70,6 +73,7 @@ namespace ScriptCanvasEditor ScriptCanvas::EBusBusId m_busId; ScriptCanvas::EBusEventId m_eventId; + ScriptCanvas::PropertyStatus m_propertyStatus = ScriptCanvas::PropertyStatus::None; }; // diff --git a/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/GeneralNodePaletteTreeItemTypes.cpp b/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/GeneralNodePaletteTreeItemTypes.cpp index 912a76df22..8341303ff4 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/GeneralNodePaletteTreeItemTypes.cpp +++ b/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/GeneralNodePaletteTreeItemTypes.cpp @@ -51,14 +51,16 @@ namespace ScriptCanvasEditor ->Field("ClassName", &CreateClassMethodMimeEvent::m_className) ->Field("MethodName", &CreateClassMethodMimeEvent::m_methodName) ->Field("IsOverload", &CreateClassMethodMimeEvent::m_isOverload) + ->Field("propertyStatus", &CreateClassMethodMimeEvent::m_propertyStatus) ; } } - CreateClassMethodMimeEvent::CreateClassMethodMimeEvent(const QString& className, const QString& methodName, bool isOverload) + CreateClassMethodMimeEvent::CreateClassMethodMimeEvent(const QString& className, const QString& methodName, bool isOverload, ScriptCanvas::PropertyStatus propertyStatus) : m_className(className.toUtf8().data()) , m_methodName(methodName.toUtf8().data()) , m_isOverload(isOverload) + , m_propertyStatus(propertyStatus) { } @@ -70,7 +72,7 @@ namespace ScriptCanvasEditor } else { - return Nodes::CreateObjectMethodNode(m_className, m_methodName, scriptCanvasId); + return Nodes::CreateObjectMethodNode(m_className, m_methodName, scriptCanvasId, m_propertyStatus); } } @@ -78,11 +80,12 @@ namespace ScriptCanvasEditor // ClassMethodEventPaletteTreeItem //////////////////////////////////// - ClassMethodEventPaletteTreeItem::ClassMethodEventPaletteTreeItem(AZStd::string_view className, AZStd::string_view methodName, bool isOverload) + ClassMethodEventPaletteTreeItem::ClassMethodEventPaletteTreeItem(AZStd::string_view className, AZStd::string_view methodName, bool isOverload, ScriptCanvas::PropertyStatus propertyStatus) : DraggableNodePaletteTreeItem(methodName, ScriptCanvasEditor::AssetEditorId) , m_className(className.data()) , m_methodName(methodName.data()) , m_isOverload(isOverload) + , m_propertyStatus(propertyStatus) { AZStd::string displayMethodName = TranslationHelper::GetKeyTranslation(TranslationContextGroup::ClassMethod, m_className.toUtf8().data(), m_methodName.toUtf8().data(), TranslationItemType::Node, TranslationKeyId::Name); @@ -95,6 +98,15 @@ namespace ScriptCanvasEditor SetName(displayMethodName.c_str()); } + if (propertyStatus == ScriptCanvas::PropertyStatus::Getter) + { + SetName(AZStd::string::format("Get %s", GetName().toUtf8().data()).data()); + } + else if (propertyStatus == ScriptCanvas::PropertyStatus::Setter) + { + SetName(AZStd::string::format("Set %s", GetName().toUtf8().data()).data()); + } + AZStd::string displayEventTooltip = TranslationHelper::GetKeyTranslation(TranslationContextGroup::ClassMethod, m_className.toUtf8().data(), m_methodName.toUtf8().data(), TranslationItemType::Node, TranslationKeyId::Tooltip); if (!displayEventTooltip.empty()) @@ -107,7 +119,7 @@ namespace ScriptCanvasEditor GraphCanvas::GraphCanvasMimeEvent* ClassMethodEventPaletteTreeItem::CreateMimeEvent() const { - return aznew CreateClassMethodMimeEvent(m_className, m_methodName, m_isOverload); + return aznew CreateClassMethodMimeEvent(m_className, m_methodName, m_isOverload, m_propertyStatus); } AZStd::string ClassMethodEventPaletteTreeItem::GetClassMethodName() const @@ -125,6 +137,11 @@ namespace ScriptCanvasEditor return m_isOverload; } + ScriptCanvas::PropertyStatus ClassMethodEventPaletteTreeItem::GetPropertyStatus() const + { + return m_propertyStatus; + } + //! Implementation of the CreateGlobalMethod Mime Event void CreateGlobalMethodMimeEvent::Reflect(AZ::ReflectContext* reflectContext) { diff --git a/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/GeneralNodePaletteTreeItemTypes.h b/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/GeneralNodePaletteTreeItemTypes.h index 38a2be88f5..66b29ef3b9 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/GeneralNodePaletteTreeItemTypes.h +++ b/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/GeneralNodePaletteTreeItemTypes.h @@ -30,7 +30,7 @@ namespace ScriptCanvasEditor static void Reflect(AZ::ReflectContext* reflectContext); CreateClassMethodMimeEvent() = default; - CreateClassMethodMimeEvent(const QString& className, const QString& methodName, bool isOverload); + CreateClassMethodMimeEvent(const QString& className, const QString& methodName, bool isOverload, ScriptCanvas::PropertyStatus); ~CreateClassMethodMimeEvent() = default; protected: @@ -40,6 +40,7 @@ namespace ScriptCanvasEditor bool m_isOverload = false; AZStd::string m_className; AZStd::string m_methodName; + ScriptCanvas::PropertyStatus m_propertyStatus = ScriptCanvas::PropertyStatus::None; }; class ClassMethodEventPaletteTreeItem @@ -49,7 +50,7 @@ namespace ScriptCanvasEditor AZ_CLASS_ALLOCATOR(ClassMethodEventPaletteTreeItem, AZ::SystemAllocator, 0); AZ_RTTI(ClassMethodEventPaletteTreeItem, "{96F93970-F38A-4F08-8DC5-D52FCCE34E25}", GraphCanvas::DraggableNodePaletteTreeItem); - ClassMethodEventPaletteTreeItem(AZStd::string_view className, AZStd::string_view methodName, bool isOverload); + ClassMethodEventPaletteTreeItem(AZStd::string_view className, AZStd::string_view methodName, bool isOverload, ScriptCanvas::PropertyStatus propertyStatus); ~ClassMethodEventPaletteTreeItem() = default; GraphCanvas::GraphCanvasMimeEvent* CreateMimeEvent() const override; @@ -57,11 +58,13 @@ namespace ScriptCanvasEditor AZStd::string GetClassMethodName() const; AZStd::string GetMethodName() const; bool IsOverload() const; + ScriptCanvas::PropertyStatus GetPropertyStatus() const; private: bool m_isOverload = false; QString m_className; QString m_methodName; + ScriptCanvas::PropertyStatus m_propertyStatus = ScriptCanvas::PropertyStatus::None; }; // diff --git a/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.cpp b/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.cpp index 73b6551e85..8b00b5b71b 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.cpp +++ b/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.cpp @@ -129,6 +129,7 @@ namespace , const AZ::BehaviorClass* behaviorClass , const AZStd::string& name , const AZ::BehaviorMethod& method + , ScriptCanvas::PropertyStatus propertyStatus , bool isOverloaded) { if (IsDeprecated(method.m_attributes)) @@ -170,7 +171,7 @@ namespace serializeContext->RegisterType(resultParameter->m_typeId, AZStd::move(classData), EventPlaceholderAnyCreator); } - nodePaletteModel.RegisterClassNode(categoryPath, behaviorClass ? behaviorClass->m_name : "", name, &method, &behaviorContext, isOverloaded); + nodePaletteModel.RegisterClassNode(categoryPath, behaviorClass ? behaviorClass->m_name : "", name, &method, &behaviorContext, propertyStatus, isOverloaded); } void RegisterGlobalMethod(ScriptCanvasEditor::NodePaletteModel& nodePaletteModel, const AZ::BehaviorContext& behaviorContext, @@ -556,6 +557,19 @@ namespace categoryPath.append(displayName.c_str()); } + for (auto property : behaviorClass->m_properties) + { + if (property.second->m_getter) + { + RegisterMethod(nodePaletteModel, behaviorContext, categoryPath, behaviorClass, property.first, *property.second->m_getter, ScriptCanvas::PropertyStatus::Getter, behaviorClass->IsMethodOverloaded(property.first)); + } + + if (property.second->m_setter) + { + RegisterMethod(nodePaletteModel, behaviorContext, categoryPath, behaviorClass, property.first, *property.second->m_setter, ScriptCanvas::PropertyStatus::Setter, behaviorClass->IsMethodOverloaded(property.first)); + } + } + for (auto methodIter : behaviorClass->m_methods) { if (!IsExplicitOverload(*methodIter.second)) @@ -567,7 +581,7 @@ namespace continue; } - RegisterMethod(nodePaletteModel, behaviorContext, categoryPath, behaviorClass, methodIter.first, *methodIter.second, behaviorClass->IsMethodOverloaded(methodIter.first)); + RegisterMethod(nodePaletteModel, behaviorContext, categoryPath, behaviorClass, methodIter.first, *methodIter.second, ScriptCanvas::PropertyStatus::None, behaviorClass->IsMethodOverloaded(methodIter.first)); } } } @@ -579,7 +593,7 @@ namespace { for (const AZ::ExplicitOverloadInfo& explicitOverload : behaviorContext.m_explicitOverloads) { - RegisterMethod(nodePaletteModel, behaviorContext, explicitOverload.m_categoryPath, nullptr, explicitOverload.m_name, *explicitOverload.m_overloads.begin()->first, true); + RegisterMethod(nodePaletteModel, behaviorContext, explicitOverload.m_categoryPath, nullptr, explicitOverload.m_name, *explicitOverload.m_overloads.begin()->first, ScriptCanvas::PropertyStatus::None, true); } } @@ -717,7 +731,7 @@ namespace } const bool isOverload{ false }; // overloaded events are not trivially supported - nodePaletteModel.RegisterEBusSenderNodeModelInformation(categoryPath, behaviorEbus.m_name, event.first, ScriptCanvas::EBusBusId(behaviorEbus.m_name.c_str()), ScriptCanvas::EBusEventId(event.first.c_str()), event.second, isOverload); + nodePaletteModel.RegisterEBusSenderNodeModelInformation(categoryPath, behaviorEbus.m_name, event.first, ScriptCanvas::EBusBusId(behaviorEbus.m_name.c_str()), ScriptCanvas::EBusEventId(event.first.c_str()), event.second, ScriptCanvas::PropertyStatus::None, isOverload); } } } @@ -1024,11 +1038,16 @@ namespace ScriptCanvasEditor } } - void NodePaletteModel::RegisterClassNode(const AZStd::string& categoryPath, const AZStd::string& methodClass, - const AZStd::string& methodName, const AZ::BehaviorMethod* behaviorMethod, const AZ::BehaviorContext* behaviorContext, - bool isOverload) + void NodePaletteModel::RegisterClassNode + ( const AZStd::string& categoryPath + , const AZStd::string& methodClass + , const AZStd::string& methodName + , const AZ::BehaviorMethod* behaviorMethod + , const AZ::BehaviorContext* behaviorContext + , ScriptCanvas::PropertyStatus propertyStatus + , bool isOverload) { - ScriptCanvas::NodeTypeIdentifier nodeIdentifier = isOverload ? ScriptCanvas::NodeUtils::ConstructMethodOverloadedNodeIdentifier(methodName) : ScriptCanvas::NodeUtils::ConstructMethodNodeIdentifier(methodClass, methodName); + ScriptCanvas::NodeTypeIdentifier nodeIdentifier = isOverload ? ScriptCanvas::NodeUtils::ConstructMethodOverloadedNodeIdentifier(methodName) : ScriptCanvas::NodeUtils::ConstructMethodNodeIdentifier(methodClass, methodName, propertyStatus); auto registerIter = m_registeredNodes.find(nodeIdentifier); @@ -1039,7 +1058,7 @@ namespace ScriptCanvasEditor methodModelInformation->m_nodeIdentifier = nodeIdentifier; methodModelInformation->m_classMethod = methodClass; methodModelInformation->m_methodName = methodName; - + methodModelInformation->m_propertyStatus = propertyStatus; methodModelInformation->m_titlePaletteOverride = "MethodNodeTitlePalette"; methodModelInformation->m_displayName = TranslationHelper::GetKeyTranslation(TranslationContextGroup::ClassMethod, methodClass.c_str(), methodName.c_str(), TranslationItemType::Node, TranslationKeyId::Name); @@ -1198,7 +1217,15 @@ namespace ScriptCanvasEditor } } - void NodePaletteModel::RegisterEBusSenderNodeModelInformation(AZStd::string_view categoryPath, AZStd::string_view busName, AZStd::string_view eventName, const ScriptCanvas::EBusBusId& busId, const ScriptCanvas::EBusEventId& eventId, const AZ::BehaviorEBusEventSender&, bool isOverload) + void NodePaletteModel::RegisterEBusSenderNodeModelInformation + ( AZStd::string_view categoryPath + , AZStd::string_view busName + , AZStd::string_view eventName + , const ScriptCanvas::EBusBusId& busId + , const ScriptCanvas::EBusEventId& eventId + , const AZ::BehaviorEBusEventSender& + , ScriptCanvas::PropertyStatus propertyStatus + , bool isOverload) { ScriptCanvas::NodeTypeIdentifier nodeIdentifier = isOverload ? ScriptCanvas::NodeUtils::ConstructEBusEventSenderOverloadedIdentifier(busId, eventId) : ScriptCanvas::NodeUtils::ConstructEBusEventSenderIdentifier(busId, eventId); @@ -1212,6 +1239,7 @@ namespace ScriptCanvasEditor senderInformation->m_titlePaletteOverride = "MethodNodeTitlePalette"; senderInformation->m_categoryPath = categoryPath; senderInformation->m_nodeIdentifier = nodeIdentifier; + senderInformation->m_propertyStatus = propertyStatus; senderInformation->m_busName = busName; senderInformation->m_eventName = eventName; diff --git a/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.h b/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.h index 5ed0ff670b..3bc81c07a0 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.h +++ b/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.h @@ -83,12 +83,12 @@ namespace ScriptCanvasEditor void RepopulateModel(); void RegisterCustomNode(AZStd::string_view categoryPath, const AZ::Uuid& uuid, AZStd::string_view name, const AZ::SerializeContext::ClassData* classData); - void RegisterClassNode(const AZStd::string& categoryPath, const AZStd::string& methodClass, const AZStd::string& methodName, const AZ::BehaviorMethod* behaviorMethod, const AZ::BehaviorContext* behaviorContext, bool isOverload); + void RegisterClassNode(const AZStd::string& categoryPath, const AZStd::string& methodClass, const AZStd::string& methodName, const AZ::BehaviorMethod* behaviorMethod, const AZ::BehaviorContext* behaviorContext, ScriptCanvas::PropertyStatus propertyStatus, bool isOverload); void RegisterMethodNode(const AZ::BehaviorContext& behaviorContext, const AZ::BehaviorMethod& behaviorMethod); void RegisterGlobalConstant(const AZ::BehaviorContext& behaviorContext, const AZ::BehaviorMethod& behaviorMethod); void RegisterEBusHandlerNodeModelInformation(AZStd::string_view categoryPath, AZStd::string_view busName, AZStd::string_view eventName, const ScriptCanvas::EBusBusId& busId, const AZ::BehaviorEBusHandler::BusForwarderEvent& forwardEvent); - void RegisterEBusSenderNodeModelInformation(AZStd::string_view categoryPath, AZStd::string_view busName, AZStd::string_view eventName, const ScriptCanvas::EBusBusId& busId, const ScriptCanvas::EBusEventId& eventId, const AZ::BehaviorEBusEventSender& eventSender, bool isOverload); + void RegisterEBusSenderNodeModelInformation(AZStd::string_view categoryPath, AZStd::string_view busName, AZStd::string_view eventName, const ScriptCanvas::EBusBusId& busId, const ScriptCanvas::EBusEventId& eventId, const AZ::BehaviorEBusEventSender& eventSender, ScriptCanvas::PropertyStatus propertyStatus, bool isOverload); // Asset Based Registrations AZStd::vector RegisterScriptEvent(ScriptEvents::ScriptEventsAsset* scriptEventAsset); @@ -164,6 +164,7 @@ namespace ScriptCanvasEditor bool m_isOverload{}; AZStd::string m_classMethod; AZStd::string m_methodName; + ScriptCanvas::PropertyStatus m_propertyStatus = ScriptCanvas::PropertyStatus::None; }; struct GlobalMethodNodeModelInformation @@ -202,6 +203,7 @@ namespace ScriptCanvasEditor ScriptCanvas::EBusBusId m_busId; ScriptCanvas::EBusEventId m_eventId; + ScriptCanvas::PropertyStatus m_propertyStatus = ScriptCanvas::PropertyStatus::None; }; struct ScriptEventHandlerNodeModelInformation diff --git a/Gems/ScriptCanvas/Code/Editor/View/Widgets/ScriptCanvasNodePaletteDockWidget.cpp b/Gems/ScriptCanvas/Code/Editor/View/Widgets/ScriptCanvasNodePaletteDockWidget.cpp index 5d377fe241..062db5e315 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Widgets/ScriptCanvasNodePaletteDockWidget.cpp +++ b/Gems/ScriptCanvas/Code/Editor/View/Widgets/ScriptCanvasNodePaletteDockWidget.cpp @@ -111,7 +111,7 @@ namespace ScriptCanvasEditor } else if (auto methodNodeModelInformation = azrtti_cast(modelInformation)) { - createdItem = parentItem->CreateChildNode(methodNodeModelInformation->m_classMethod, methodNodeModelInformation->m_methodName, methodNodeModelInformation->m_isOverload); + createdItem = parentItem->CreateChildNode(methodNodeModelInformation->m_classMethod, methodNodeModelInformation->m_methodName, methodNodeModelInformation->m_isOverload, methodNodeModelInformation->m_propertyStatus); } else if (auto globalMethodNodeModelInformation = azrtti_cast(modelInformation); globalMethodNodeModelInformation != nullptr) @@ -130,7 +130,7 @@ namespace ScriptCanvasEditor { if (!azrtti_istypeof(ebusSenderNodeModelInformation)) { - createdItem = parentItem->CreateChildNode(ebusSenderNodeModelInformation->m_busName, ebusSenderNodeModelInformation->m_eventName, ebusSenderNodeModelInformation->m_busId, ebusSenderNodeModelInformation->m_eventId, ebusSenderNodeModelInformation->m_isOverload); + createdItem = parentItem->CreateChildNode(ebusSenderNodeModelInformation->m_busName, ebusSenderNodeModelInformation->m_eventName, ebusSenderNodeModelInformation->m_busId, ebusSenderNodeModelInformation->m_eventId, ebusSenderNodeModelInformation->m_isOverload, ebusSenderNodeModelInformation->m_propertyStatus); } } diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Core.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Core.h index d6ac7e5482..5b1e5eee76 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Core.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Core.h @@ -88,6 +88,13 @@ namespace ScriptCanvas Current, }; + enum class PropertyStatus : AZ::u8 + { + Getter, + None, + Setter, + }; + struct VersionData { AZ_TYPE_INFO(VersionData, "{14C629F6-467B-46FE-8B63-48FDFCA42175}"); diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/MethodConfiguration.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/MethodConfiguration.h index be1f4b39b8..049404ee6e 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/MethodConfiguration.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/MethodConfiguration.h @@ -33,6 +33,8 @@ namespace ScriptCanvas Event, Free, Member, + Getter, + Setter, Count, }; diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Node.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Node.h index d3d36ae71b..a2d06686e9 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Node.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Node.h @@ -394,6 +394,7 @@ namespace ScriptCanvas AZ::Uuid m_type = AZ::Uuid::CreateNull(); AZStd::string m_className; AZStd::string m_methodName; + PropertyStatus m_propertyStatus = PropertyStatus::None; bool IsValid() { diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Nodeable.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Nodeable.cpp index b642ef3889..83680aa7de 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Nodeable.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Nodeable.cpp @@ -104,8 +104,7 @@ namespace ScriptCanvas const FunctorOut& Nodeable::GetExecutionOutChecked(size_t index) const { - - if (index >= m_outs.size() && m_outs[index]) + if (index >= m_outs.size() || !m_outs[index]) { return m_noOpFunctor; } diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/ParsingUtilities.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/ParsingUtilities.cpp index a5a98b5444..e16a3bc815 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/ParsingUtilities.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/ParsingUtilities.cpp @@ -537,6 +537,20 @@ namespace ScriptCanvas return azrtti_istypeof(execution->GetId().m_node); } + bool IsClassPropertyRead(ExecutionTreeConstPtr execution) + { + return execution->GetSymbol() == Symbol::FunctionCall + && azrtti_istypeof(execution->GetId().m_node) + && azrtti_cast(execution->GetId().m_node)->GetPropertyStatus() == PropertyStatus::Getter; + } + + bool IsClassPropertyWrite(ExecutionTreeConstPtr execution) + { + return execution->GetSymbol() == Symbol::FunctionCall + && azrtti_istypeof(execution->GetId().m_node) + && azrtti_cast(execution->GetId().m_node)->GetPropertyStatus() == PropertyStatus::Setter; + } + bool IsCodeConstructable(Grammar::VariableConstPtr value) { return Data::IsValueType(value->m_datum.GetType()) @@ -1280,7 +1294,6 @@ namespace ScriptCanvas return identifier; } - ExecutionTraversalResult TraverseExecutionConnectionsRecurse(const EndpointsResolved& nextEndpoints, AZStd::unordered_set& previousIns, GraphExecutionPathTraversalListener& listener); ExecutionTraversalResult TraverseExecutionConnectionsRecurse(const EndpointResolved& in, AZStd::unordered_set& previousIns, GraphExecutionPathTraversalListener& listener); diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/ParsingUtilities.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/ParsingUtilities.h index 1e2d102d2d..b5fd606fba 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/ParsingUtilities.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/ParsingUtilities.h @@ -79,6 +79,10 @@ namespace ScriptCanvas bool IsBreak(const ExecutionTreeConstPtr& execution); + bool IsClassPropertyRead(ExecutionTreeConstPtr execution); + + bool IsClassPropertyWrite(ExecutionTreeConstPtr execution); + bool IsCodeConstructable(VariableConstPtr value); bool IsCycle(const Node& node); diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/Method.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/Method.cpp index 836c3facab..b6f7bc2972 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/Method.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/Method.cpp @@ -193,6 +193,22 @@ namespace ScriptCanvas return DynamicDataType::Any; } + PropertyStatus Method::GetPropertyStatus() const + { + switch (m_methodType) + { + case MethodType::Getter: + return PropertyStatus::Getter; + + case MethodType::Setter: + return PropertyStatus::Setter; + + default: + return PropertyStatus::None; + } + } + + void Method::InitializeMethod(const MethodConfiguration& config) { m_namespaces = config.m_namespaces ? *config.m_namespaces : m_namespaces; @@ -239,7 +255,7 @@ namespace ScriptCanvas OnInitializeOutputPost(outputConfig); } - void Method::InitializeBehaviorMethod(const NamespacePath& namespaces, AZStd::string_view className, AZStd::string_view methodName) + void Method::InitializeBehaviorMethod(const NamespacePath& namespaces, AZStd::string_view className, AZStd::string_view methodName, PropertyStatus propertyStatus) { AZ::BehaviorContext* behaviorContext = nullptr; AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext); @@ -255,13 +271,13 @@ namespace ScriptCanvas { InitializeFree(namespaces, methodName); } - else if (auto ebusIterator = behaviorContext->m_ebuses.find(className); ebusIterator == behaviorContext->m_ebuses.end()) + else if (auto ebusIterator = behaviorContext->m_ebuses.find(className); ebusIterator != behaviorContext->m_ebuses.end()) { - InitializeClass(namespaces, className, methodName); + InitializeEvent(namespaces, className, methodName); } else { - InitializeEvent(namespaces, className, methodName); + InitializeClass(namespaces, className, methodName, propertyStatus); } } @@ -291,7 +307,7 @@ namespace ScriptCanvas } } - void Method::InitializeClass(const NamespacePath&, AZStd::string_view className, AZStd::string_view methodName) + void Method::InitializeClass(const NamespacePath&, AZStd::string_view className, AZStd::string_view methodName, PropertyStatus propertyStatus) { AZStd::lock_guard lock(m_mutex); @@ -299,9 +315,11 @@ namespace ScriptCanvas const AZ::BehaviorClass* bcClass{}; AZStd::string prettyClassName; - if (BehaviorContextUtils::FindClass(method, bcClass, className, methodName, &prettyClassName)) + if (BehaviorContextUtils::FindClass(method, bcClass, className, methodName, propertyStatus, &prettyClassName)) { - MethodConfiguration config(*method, MethodType::Member); + const auto methodType = propertyStatus == PropertyStatus::None ? MethodType::Member : propertyStatus == PropertyStatus::Getter ? MethodType::Getter : MethodType::Setter; + + MethodConfiguration config(*method, methodType); config.m_class = bcClass; config.m_namespaces = &m_namespaces; config.m_className = &className; @@ -647,8 +665,12 @@ namespace ScriptCanvas break; case MethodType::Member: + case MethodType::Getter: + case MethodType::Setter: { - if (BehaviorContextUtils::FindClass(method, bcClass, m_className, methodName, nullptr, m_warnOnMissingFunction)) + PropertyStatus status = m_methodType == MethodType::Getter ? PropertyStatus::Getter : m_methodType == MethodType::Setter ? PropertyStatus::Setter : PropertyStatus::None; + + if (BehaviorContextUtils::FindClass(method, bcClass, m_className, methodName, status, nullptr, m_warnOnMissingFunction)) { outClass = bcClass; outMethod = method; diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/Method.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/Method.h index b03e3aefd8..6a53054ddf 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/Method.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/Method.h @@ -87,14 +87,13 @@ namespace ScriptCanvas bool IsObjectClass(AZStd::string_view objectClass) const { return objectClass.compare(m_className) == 0; } //! Attempts to initialize node with a BehaviorContext BehaviorMethod - //! If the className is empty, then the methodName is searched on the BehaviorContext - //! If className is not empty the className is used to look for a registered BehaviorEBus in the BehaviorContext - //! and if found, the methodName is searched among the BehaviorEBus events - //! Otherwise the className is used to look for a registered BehaviorClass in the BehaviorContext - //! and if found, the methodName is searched among the BehaviorClass methods - void InitializeBehaviorMethod(const NamespacePath& namespaces, AZStd::string_view className, AZStd::string_view methodName); + //! 1) If the names match an overloaded method, including one using ExplicitOverloadInfo, then that method is used. Else: + //! 2) If the class name is empty, then search for a free method is searched for in the BehaviorContext and there is a warning if not found. + //! 3) If the class name matches an ebus, methodName is searched among the BehaviorEBus events, and there is a warning if not found. + //! 4) if the class name does NOT match an ebus, className and methodName are used to look for a registered BehaviorClass in the BehaviorContext, and there is a warning if not found. + void InitializeBehaviorMethod(const NamespacePath& namespaces, AZStd::string_view className, AZStd::string_view methodName, PropertyStatus propertyStatus); - void InitializeClass(const NamespacePath& namespaces, AZStd::string_view className, AZStd::string_view methodName); + void InitializeClass(const NamespacePath& namespaces, AZStd::string_view className, AZStd::string_view methodName, PropertyStatus propertyStatus); void InitializeEvent(const NamespacePath& namespaces, AZStd::string_view busName, AZStd::string_view eventName); @@ -126,6 +125,8 @@ namespace ScriptCanvas virtual DynamicDataType GetOverloadedOutputType(size_t resultIndex) const; + PropertyStatus GetPropertyStatus() const; + protected: void ConfigureMethod(const AZ::BehaviorMethod& method, const AZ::BehaviorClass* bcClass); diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToLua.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToLua.cpp index 128ef95b63..905f68604a 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToLua.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToLua.cpp @@ -637,6 +637,16 @@ namespace ScriptCanvas { WriteGlobalPropertyRead(execution); } + else if (Grammar::IsClassPropertyRead(execution)) + { + WriteClassPropertyRead(execution); + m_dotLua.WriteNewLine(); + } + else if (Grammar::IsClassPropertyWrite(execution)) + { + WriteClassPropertyWrite(execution); + m_dotLua.WriteNewLine(); + } else { const bool isNullCheckRequired = Grammar::IsFunctionCallNullCheckRequired(execution); @@ -1208,6 +1218,19 @@ namespace ScriptCanvas TranslateNodeableParse(); } + void GraphToLua::WriteClassPropertyRead(Grammar::ExecutionTreeConstPtr execution) + { + WriteFunctionCallInput(execution, 0, IsFormatStringInput::No); + m_dotLua.Write(".%s", Grammar::ToIdentifier(execution->GetName()).c_str()); + } + + void GraphToLua::WriteClassPropertyWrite(Grammar::ExecutionTreeConstPtr execution) + { + WriteClassPropertyRead(execution); + m_dotLua.Write(" = "); + WriteFunctionCallInput(execution, 1, IsFormatStringInput::No); + } + void GraphToLua::WriteConditionalCaseSwitch(Grammar::ExecutionTreeConstPtr execution, Grammar::Symbol symbol, const Grammar::ExecutionChild& child, size_t index) { if (symbol == Grammar::Symbol::RandomSwitch) diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToLua.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToLua.h index 3d4379d98d..82c865da8c 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToLua.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToLua.h @@ -116,6 +116,8 @@ namespace ScriptCanvas void TranslateNodeableParse(); void TranslateStaticInitialization(); void TranslateVariableInitialization(AZStd::string_view leftValue); + void WriteClassPropertyRead(Grammar::ExecutionTreeConstPtr); + void WriteClassPropertyWrite(Grammar::ExecutionTreeConstPtr); void WriteConditionalCaseSwitch(Grammar::ExecutionTreeConstPtr execution, Grammar::Symbol symbol, const Grammar::ExecutionChild& child, size_t index); enum class IsLeadingCommaRequired { No, Yes }; void WriteConstructionArgs(); diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Utils/BehaviorContextUtils.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Utils/BehaviorContextUtils.cpp index ff38b74d47..3689729338 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Utils/BehaviorContextUtils.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Utils/BehaviorContextUtils.cpp @@ -61,7 +61,7 @@ namespace ScriptCanvas return { typeID }; } - bool BehaviorContextUtils::FindClass(const AZ::BehaviorMethod*& outMethod, const AZ::BehaviorClass*& outClass, [[maybe_unused]] AZStd::string_view className, [[maybe_unused]] AZStd::string_view methodName, [[maybe_unused]] AZStd::string* outPrettyClassName, [[maybe_unused]] bool warnOnMissing) + bool BehaviorContextUtils::FindClass(const AZ::BehaviorMethod*& outMethod, const AZ::BehaviorClass*& outClass, [[maybe_unused]] AZStd::string_view className, [[maybe_unused]] AZStd::string_view methodName, PropertyStatus propertyStatus, [[maybe_unused]] AZStd::string* outPrettyClassName, [[maybe_unused]] bool warnOnMissing) { AZ::BehaviorContext* behaviorContext(nullptr); AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext); @@ -81,16 +81,36 @@ namespace ScriptCanvas const AZ::BehaviorClass* behaviorClass(classIter->second); AZ_Assert(behaviorClass, "BehaviorContext Class entry %s has no class pointer", className.data()); - const auto methodIter(behaviorClass->m_methods.find(methodName.data())); - if (methodIter == behaviorClass->m_methods.end()) + + AZ::BehaviorMethod* method{}; + + if (propertyStatus == PropertyStatus::None) { - AZ_Warning("Script Canvas", !warnOnMissing, "No method by name of %s found in BehaviorContext class %s", methodName.data(), className.data()); - return false; + const auto methodIter(behaviorClass->m_methods.find(methodName.data())); + if (methodIter != behaviorClass->m_methods.end()) + { + method = methodIter->second; + propertyStatus = PropertyStatus::None; + } + else + { + AZ_Warning("Script Canvas", !warnOnMissing, "No method by name of %s found in BehaviorContext class %s", methodName.data(), className.data()); + } + } + else + { + const auto propertyIter(behaviorClass->m_properties.find(methodName.data())); + if (propertyIter == behaviorClass->m_properties.end()) + { + AZ_Warning("Script Canvas", !warnOnMissing, "No property by name of %s found in BehaviorContext class %s", methodName.data(), className.data()); + return false; + } + + method = propertyStatus == PropertyStatus::Getter ? propertyIter->second->m_getter : propertyIter->second->m_setter; } // this argument is the first argument...so perhaps remove the distinction between class and member functions, since it probably won't follow polymorphism // if it will, keep the distinction, and add the first argument separately - AZ::BehaviorMethod* method(methodIter->second); if (!method) { AZ_Warning("Script Canvas", !warnOnMissing, "BehaviorContext Method entry %s has no method pointer", methodName.data()); diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Utils/BehaviorContextUtils.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Utils/BehaviorContextUtils.h index 057c99a774..8abe26dfd1 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Utils/BehaviorContextUtils.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Utils/BehaviorContextUtils.h @@ -21,7 +21,7 @@ namespace ScriptCanvas class BehaviorContextUtils { public: - static bool FindClass(const AZ::BehaviorMethod*& outMethod, const AZ::BehaviorClass*& outClass, AZStd::string_view className, AZStd::string_view methodName, AZStd::string* outPrettyClassName = nullptr, bool warnOnMissing = true); + static bool FindClass(const AZ::BehaviorMethod*& outMethod, const AZ::BehaviorClass*& outClass, AZStd::string_view className, AZStd::string_view methodName, PropertyStatus propertyStatus = PropertyStatus::None, AZStd::string* outPrettyClassName = nullptr, bool warnOnMissing = true); static bool FindEBus(const AZ::BehaviorEBus*& outEBus, AZStd::string_view ebusName, bool warnOnMissing = true); static bool FindExplicitOverload(const AZ::BehaviorMethod*& outMethod, const AZ::BehaviorClass*& outClass, AZStd::string_view className, AZStd::string_view methodName, AZStd::string* outPrettyClassName = nullptr); static AZStd::string FindExposedMethodName(const AZ::BehaviorMethod& method, const AZ::BehaviorClass* behaviorClass); diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Utils/NodeUtils.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Utils/NodeUtils.cpp index 06ad79c21d..c6849f0b12 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Utils/NodeUtils.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Utils/NodeUtils.cpp @@ -58,7 +58,7 @@ namespace ScriptCanvas } else { - return ConstructMethodNodeIdentifier(methodNode->GetRawMethodClassName(), methodNode->GetName()); + return ConstructMethodNodeIdentifier(methodNode->GetRawMethodClassName(), methodNode->GetName(), methodNode->GetPropertyStatus()); } } else if (auto ebusNode = azrtti_cast(scriptCanvasNode)) @@ -158,13 +158,14 @@ namespace ScriptCanvas return resultHash; } - NodeTypeIdentifier NodeUtils::ConstructMethodNodeIdentifier(AZStd::string_view methodClass, AZStd::string_view methodName) + NodeTypeIdentifier NodeUtils::ConstructMethodNodeIdentifier(AZStd::string_view methodClass, AZStd::string_view methodName, ScriptCanvas::PropertyStatus propertyStatus) { NodeTypeIdentifier resultHash = 0; AZStd::hash_combine(resultHash, AZStd::hash()(azrtti_typeid())); AZStd::hash_combine(resultHash, AZStd::hash()(methodClass)); AZStd::hash_combine(resultHash, AZStd::hash()(methodName)); + AZStd::hash_combine(resultHash, AZStd::hash()(static_cast(propertyStatus))); return resultHash; } @@ -253,7 +254,7 @@ namespace ScriptCanvas if (auto* method = azrtti_cast(node)) { ScriptCanvas::NamespacePath emptyNamespaces; - method->InitializeBehaviorMethod(emptyNamespaces, config.m_className, config.m_methodName); + method->InitializeBehaviorMethod(emptyNamespaces, config.m_className, config.m_methodName, config.m_propertyStatus); } } } diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Utils/NodeUtils.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Utils/NodeUtils.h index 0f5b0f495d..ad8356c42e 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Utils/NodeUtils.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Utils/NodeUtils.h @@ -38,7 +38,7 @@ namespace ScriptCanvas static NodeTypeIdentifier ConstructCustomNodeIdentifier(const AZ::Uuid& nodeId); - static NodeTypeIdentifier ConstructMethodNodeIdentifier(AZStd::string_view methodClass, AZStd::string_view methodName); + static NodeTypeIdentifier ConstructMethodNodeIdentifier(AZStd::string_view methodClass, AZStd::string_view methodName, ScriptCanvas::PropertyStatus propertyStatus); static NodeTypeIdentifier ConstructGlobalMethodNodeIdentifier(AZStd::string_view methodName); static NodeTypeIdentifier ConstructMethodOverloadedNodeIdentifier(AZStd::string_view methodName); diff --git a/Gems/ScriptCanvasTesting/Assets/ScriptCanvas/UnitTests/LY_SC_UnitTest_UseRawBehaviorProperties.scriptcanvas b/Gems/ScriptCanvasTesting/Assets/ScriptCanvas/UnitTests/LY_SC_UnitTest_UseRawBehaviorProperties.scriptcanvas new file mode 100644 index 0000000000..57e57116e0 --- /dev/null +++ b/Gems/ScriptCanvasTesting/Assets/ScriptCanvas/UnitTests/LY_SC_UnitTest_UseRawBehaviorProperties.scriptcanvas @@ -0,0 +1,2618 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gems/ScriptCanvasTesting/Code/Source/Framework/ScriptCanvasTestFixture.h b/Gems/ScriptCanvasTesting/Code/Source/Framework/ScriptCanvasTestFixture.h index 7f026edf58..766353c4c5 100644 --- a/Gems/ScriptCanvasTesting/Code/Source/Framework/ScriptCanvasTestFixture.h +++ b/Gems/ScriptCanvasTesting/Code/Source/Framework/ScriptCanvasTestFixture.h @@ -21,23 +21,24 @@ #include #include #include +#include #include #include #include +#include +#include +#include #include #include #include #include -#include #include "EntityRefTests.h" #include "ScriptCanvasTestApplication.h" #include "ScriptCanvasTestBus.h" #include "ScriptCanvasTestNodes.h" #include "ScriptCanvasTestUtilities.h" -#include -#include #define SC_EXPECT_DOUBLE_EQ(candidate, reference) EXPECT_NEAR(candidate, reference, 0.001) #define SC_EXPECT_FLOAT_EQ(candidate, reference) EXPECT_NEAR(candidate, reference, 0.001f) @@ -112,6 +113,9 @@ namespace ScriptCanvasTests ScriptCanvasTesting::Reflect(m_serializeContext); ScriptCanvasTesting::Reflect(m_behaviorContext); + ScriptCanvasTestingNodes::BehaviorContextObjectTest::Reflect(m_serializeContext); + ScriptCanvasTestingNodes::BehaviorContextObjectTest::Reflect(m_behaviorContext); + ::Nodes::InputMethodSharedDataSlotExampleNode::Reflect(m_serializeContext); ::Nodes::InputMethodSharedDataSlotExampleNode::Reflect(m_behaviorContext); ::Nodes::BranchMethodSharedDataSlotExampleNode::Reflect(m_serializeContext); diff --git a/Gems/ScriptCanvasTesting/Code/Source/Framework/ScriptCanvasTestUtilities.cpp b/Gems/ScriptCanvasTesting/Code/Source/Framework/ScriptCanvasTestUtilities.cpp index 9fc023601e..bd31edad09 100644 --- a/Gems/ScriptCanvasTesting/Code/Source/Framework/ScriptCanvasTestUtilities.cpp +++ b/Gems/ScriptCanvasTesting/Code/Source/Framework/ScriptCanvasTestUtilities.cpp @@ -496,7 +496,7 @@ namespace ScriptCanvasTests ScriptCanvas::Nodes::Core::Method* methodNode(nullptr); SystemRequestBus::BroadcastResult(methodNode, &SystemRequests::GetNode, methodNodeID); EXPECT_TRUE(methodNode != nullptr); - methodNode->InitializeBehaviorMethod(emptyNamespaces, className, methodName); + methodNode->InitializeBehaviorMethod(emptyNamespaces, className, methodName, ScriptCanvas::PropertyStatus::None); return methodNodeID; } diff --git a/Gems/ScriptCanvasTesting/Code/Source/Nodes/BehaviorContextObjectTestNode.h b/Gems/ScriptCanvasTesting/Code/Source/Nodes/BehaviorContextObjectTestNode.h index 2460d37c02..7fa3896ff1 100644 --- a/Gems/ScriptCanvasTesting/Code/Source/Nodes/BehaviorContextObjectTestNode.h +++ b/Gems/ScriptCanvasTesting/Code/Source/Nodes/BehaviorContextObjectTestNode.h @@ -30,10 +30,10 @@ namespace ScriptCanvasTestingNodes { serializeContext->Class() ->Version(0) - ->Field("StringName", &BehaviorContextObjectTest::m_string) + ->Field("String", &BehaviorContextObjectTest::m_string) + ->Field("Name", &BehaviorContextObjectTest::m_name) ; - - + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) { editContext->Class("Behavior Context Object Test", "An Object that lives within Behavior Context exclusively for testing") @@ -52,6 +52,7 @@ namespace ScriptCanvasTestingNodes ->Attribute(AZ::Script::Attributes::Category, "Tests/Behavior Context") ->Method("SetString", &BehaviorContextObjectTest::SetString) ->Method("GetString", &BehaviorContextObjectTest::GetString) + ->Property("Name", BehaviorValueProperty(&BehaviorContextObjectTest::m_name)) ; } } @@ -73,7 +74,7 @@ namespace ScriptCanvasTestingNodes } private: - + AZStd::string m_name; AZStd::string m_string; }; diff --git a/Gems/ScriptCanvasTesting/Code/Tests/ScriptCanvas_RuntimeInterpreted.cpp b/Gems/ScriptCanvasTesting/Code/Tests/ScriptCanvas_RuntimeInterpreted.cpp index a46bb3323b..1633b0fc51 100644 --- a/Gems/ScriptCanvasTesting/Code/Tests/ScriptCanvas_RuntimeInterpreted.cpp +++ b/Gems/ScriptCanvasTesting/Code/Tests/ScriptCanvas_RuntimeInterpreted.cpp @@ -90,6 +90,11 @@ public: } }; +TEST_F(ScriptCanvasTestFixture, UseRawBehaviorProperties) +{ + RunUnitTestGraph("LY_SC_UnitTest_UseRawBehaviorProperties"); +} + TEST_F(ScriptCanvasTestFixture, StringSanitization) { RunUnitTestGraph("LY_SC_UnitTest_StringSanitization"); diff --git a/scripts/build/Jenkins/Jenkinsfile b/scripts/build/Jenkins/Jenkinsfile index bc770e42f7..f85b0b5ef4 100644 --- a/scripts/build/Jenkins/Jenkinsfile +++ b/scripts/build/Jenkins/Jenkinsfile @@ -549,15 +549,17 @@ finally { message:"${currentBuild.currentResult}:${BUILD_URL}:${env.RECREATE_VOLUME}:${env.CLEAN_OUTPUT_DIRECTORY}:${env.CLEAN_ASSETS}" ) } - step([ - $class: 'Mailer', - notifyEveryUnstableBuild: true, - sendToIndividuals: true, - recipients: emailextrecipients([ - [$class: 'CulpritsRecipientProvider'], - [$class: 'RequesterRecipientProvider'] + node('controller') { + emailRecipients = [[$class: 'RequesterRecipientProvider']] + if (env.WATCHED_BRANCHES.tokenize(',').contains(branchName)) { + emailRecipients.add([$class: 'CulpritsRecipientProvider']) + } + step([ + $class: 'Mailer', + notifyEveryUnstableBuild: true, + recipients: emailextrecipients(emailRecipients) ]) - ]) + } } catch(Exception e) { } }