Merge branch 'main' into script_canvas

This commit is contained in:
darapan
2021-05-09 22:37:59 -07:00
114 changed files with 4296 additions and 822 deletions
@@ -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
@@ -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",
@@ -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;
};
@@ -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;
@@ -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
@@ -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<size_t>::max();
@@ -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)
{
@@ -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
@@ -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<void**>(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<void**>(valueAddress) = nullptr;
}
JSR::ResultCode valueResult = ContinueLoading(valueAddress, valueElement->m_typeId, value, context, valueLoadFlags);
@@ -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<void*>(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
@@ -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
@@ -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())
{
@@ -104,6 +104,11 @@ namespace JsonSerializationTests
AZ::AllocatorInstance<AZ::PoolAllocator>::Destroy();
}
void Reflect(AZStd::unique_ptr<AZ::SerializeContext>& context) override
{
context->RegisterGenericType<Asset>();
}
AZStd::shared_ptr<AZ::BaseJsonSerializer> CreateSerializer() override
{
return AZStd::make_shared<AZ::Data::AssetJsonSerializer>();
@@ -119,7 +119,8 @@ namespace JsonSerializationTests
int value = 0;
int* ptrValue = &value;
ResultCode result = ContinueLoading(&ptrValue, azrtti_typeid<int>(), json, *m_jsonDeserializationContext, Flags::ResolvePointer);
ResultCode result =
ContinueLoading(&ptrValue, azrtti_typeid<int>(), 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<int>(), json, *m_jsonDeserializationContext, Flags::ResolvePointer);
ResultCode result =
ContinueLoading(&ptrValue, azrtti_typeid<int>(), 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<int>(), json, *m_jsonDeserializationContext, Flags::ResolvePointer);
ResultCode result =
ContinueLoading(&ptrValue, azrtti_typeid<int>(), 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<int*>(azmalloc(sizeof(int), alignof(int), AZ::SystemAllocator));
ResultCode result = ContinueLoading(&ptrValue, azrtti_typeid<int>(), json, *m_jsonDeserializationContext, Flags::ResolvePointer);
ResultCode result =
ContinueLoading(&ptrValue, azrtti_typeid<int>(), 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<int>(), *m_jsonSerializationContext,
Flags::ResolvePointer);
ResultCode result = ContinueStoring(
*m_jsonDocument, &ptrValue, nullptr, azrtti_typeid<int>(), *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<int>(), *m_jsonSerializationContext, Flags::ResolvePointer);
ResultCode result = ContinueStoring(
*m_jsonDocument, &ptrValue, &defaultPtrValue, azrtti_typeid<int>(), *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<int>(), *m_jsonSerializationContext, Flags::ResolvePointer);
*m_jsonDocument, &ptrValue, nullptr, azrtti_typeid<int>(), *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<int>(), *m_jsonSerializationContext, Flags::ResolvePointer);
ResultCode result = ContinueStoring(
*m_jsonDocument, &ptrValue, &defaultPtrValue, azrtti_typeid<int>(), *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<int>(), *m_jsonSerializationContext, Flags::ResolvePointer);
ResultCode result = ContinueStoring(
*m_jsonDocument, &ptrValue, &defaultPtrValue, azrtti_typeid<int>(), *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<int>(), *m_jsonSerializationContext,
Flags::ReplaceDefault);
ResultCode result = ContinueStoring(
*m_jsonDocument, &value, nullptr, azrtti_typeid<int>(), *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<int>(), *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());
}
@@ -90,9 +90,14 @@ namespace JsonSerializationTests
virtual ~JsonSerializerConformityTestDescriptor() = default;
virtual AZStd::shared_ptr<AZ::BaseJsonSerializer> CreateSerializer() = 0;
//! Create an instance of the target type with all values set to default.
virtual AZStd::shared_ptr<T> 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<T> 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
@@ -32,6 +32,11 @@ namespace JsonSerializationTests
return AZStd::make_shared<AZ::JsonSmartPointerSerializer>();
}
AZStd::shared_ptr<SmartPointer> CreateDefaultConstructedInstance() override
{
return AZStd::make_shared<SmartPointer>();
}
void Reflect(AZStd::unique_ptr<AZ::SerializeContext>& context) override
{
context->RegisterGenericType<SmartPointer>();
@@ -228,13 +233,19 @@ namespace JsonSerializationTests
public:
using SmartPointer = typename SmartPointerSimpleDerivedClassTestDescription<T>::SmartPointer;
AZStd::shared_ptr<SmartPointer> CreateDefaultInstance() override
// This test is specific for derived classes being used as a default value.
AZStd::shared_ptr<SmartPointer> CreateDefaultConstructedInstance() override
{
auto result = AZStd::make_shared<SmartPointer>();
*result = SmartPointer(aznew SimpleInheritence());
return result;
}
AZStd::shared_ptr<SmartPointer> CreateDefaultInstance() override
{
return CreateDefaultConstructedInstance();
}
AZStd::string_view GetJsonForPartialDefaultInstance() override
{
return R"(
@@ -386,13 +397,19 @@ namespace JsonSerializationTests
public:
using SmartPointer = typename SmartPointerComplexDerivedClassTestDescription<T>::SmartPointer;
AZStd::shared_ptr<SmartPointer> CreateDefaultInstance() override
// This test is specific for derived classes being used as a default value.
AZStd::shared_ptr<SmartPointer> CreateDefaultConstructedInstance() override
{
auto result = AZStd::make_shared<SmartPointer>();
*result = SmartPointer(aznew MultipleInheritence());
return result;
}
AZStd::shared_ptr<SmartPointer> CreateDefaultInstance() override
{
return CreateDefaultConstructedInstance();
}
AZStd::string_view GetJsonForPartialDefaultInstance() override
{
return R"(
@@ -48,6 +48,9 @@ namespace Physics
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext
->RegisterGenericType<AZStd::shared_ptr<SphereShapeConfiguration>>();
serializeContext->Class<SphereShapeConfiguration, ShapeConfiguration>()
->Version(1)
->Field("Radius", &SphereShapeConfiguration::m_radius)
@@ -76,6 +79,9 @@ namespace Physics
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext
->RegisterGenericType<AZStd::shared_ptr<BoxShapeConfiguration>>();
serializeContext->Class<BoxShapeConfiguration, ShapeConfiguration>()
->Version(1)
->Field("Configuration", &BoxShapeConfiguration::m_dimensions)
@@ -104,6 +110,9 @@ namespace Physics
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext
->RegisterGenericType<AZStd::shared_ptr<CapsuleShapeConfiguration>>();
serializeContext->Class<CapsuleShapeConfiguration, ShapeConfiguration>()
->Version(1)
->Field("Height", &CapsuleShapeConfiguration::m_height)
@@ -153,6 +162,9 @@ namespace Physics
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext
->RegisterGenericType<AZStd::shared_ptr<PhysicsAssetShapeConfiguration>>();
serializeContext->Class<PhysicsAssetShapeConfiguration, ShapeConfiguration>()
->Version(1)
->Field("PhysicsAsset", &PhysicsAssetShapeConfiguration::m_asset)
@@ -185,6 +197,9 @@ namespace Physics
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext
->RegisterGenericType<AZStd::shared_ptr<NativeShapeConfiguration>>();
serializeContext->Class<NativeShapeConfiguration, ShapeConfiguration>()
->Version(1)
->Field("Scale", &NativeShapeConfiguration::m_nativeShapeScale)
@@ -208,6 +223,9 @@ namespace Physics
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext
->RegisterGenericType<AZStd::shared_ptr<CookedMeshShapeConfiguration>>();
serializeContext->Class<CookedMeshShapeConfiguration, ShapeConfiguration>()
->Version(1)
->Field("CookedData", &CookedMeshShapeConfiguration::m_cookedData)
@@ -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<AZ::u64>(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);
}
}
@@ -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<AZ::EntityId, AZ::u64> m_childIndexCache;
@@ -375,6 +373,5 @@ namespace AzToolsFramework
AZ::EntityId m_postInstantiateBeforeEntity;
AZ::EntityId m_postInstantiateSliceParent;
bool m_gotInstantiateSliceDetails = false;
bool m_isPrefabEnabled = false;
};
}
@@ -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<AzFramework::ScreenPoint> PreviousViewportCursorScreenPosition() = 0;
/// Is mouse over viewport.
virtual bool IsMouseOver() const = 0;
protected:
~ViewportMouseCursorRequests() = default;
@@ -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();
+1 -1
View File
@@ -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;
@@ -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;
@@ -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
@@ -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 <AzCore/Math/Uuid.h>
#include <QString>
#include <QStringList>
#include <QVector>
#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<AZ::Uuid> m_dependingGemUuids;
QVector<AZ::Uuid> m_conflictingGemUuids;
};
} // namespace O3DE::ProjectManager
Q_DECLARE_OPERATORS_FOR_FLAGS(O3DE::ProjectManager::GemInfo::Platforms)
@@ -37,4 +37,6 @@ set(FILES
Source/EngineSettings.h
Source/EngineSettings.cpp
Source/EngineSettings.ui
Source/GemCatalog/GemInfo.h
Source/GemCatalog/GemInfo.cpp
)
@@ -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;
@@ -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",
@@ -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<AZStd::string(const AWSResourceMappingAttributes&)> 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;
@@ -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
@@ -84,7 +84,7 @@ namespace AWSCore
{
AZ_Warning("AWSDefaultCredentialHandler", false, "Failed to get profile name, use default profile name instead");
SetProfileCredentialsProvider(Aws::MakeShared<Aws::Auth::ProfileConfigFileAWSCredentialsProvider>(
AWSDEFAULTCREDENTIALHANDLER_ALLOC_TAG, AWSCoreConfiguration::AWSCORE_DEFAULT_PROFILE_NAME));
AWSDEFAULTCREDENTIALHANDLER_ALLOC_TAG, AWSCoreConfiguration::AWSCoreDefaultProfileName));
}
else
{
@@ -12,12 +12,14 @@
#include <AzCore/IO/Path/Path.h>
#include <AzCore/JSON/schema.h>
#include <AzCore/JSON/prettywriter.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzCore/Settings/SettingsRegistryImpl.h>
#include <AzFramework/FileFunc/FileFunc.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AWSCoreInternalBus.h>
#include <Configuration/AWSCoreConfiguration.h>
#include <ResourceMapping/AWSResourceMappingConstants.h>
#include <ResourceMapping/AWSResourceMappingManager.h>
#include <ResourceMapping/AWSResourceMappingUtils.h>
@@ -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<rapidjson::StringBuffer> writer(error);
validator.GetError().Accept(writer);
AZ_Warning(AWSResourceMappingManagerName, false, ResourceMappingFileInvalidContentErrorMessage, error.GetString());
return false;
}
return true;
@@ -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());
}
}
@@ -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);
}
@@ -98,7 +98,7 @@ public:
"AWSResourceMappingManager", AZ::Uuid::CreateRandom().ToString<AZStd::string>(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);
}
@@ -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;
}
@@ -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": {
@@ -15,6 +15,8 @@
#include <Atom/Features/SrgSemantics.azsli>
#include <viewsrg.srgi>
#include <Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli>
#include <Atom/Features/PBR/AlphaUtils.azsli>
#include <Atom/Features/PBR/LightingOptions.azsli>
#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);
}
@@ -10,7 +10,6 @@
*
*/
#include <Atom/Features/PBR/AlphaUtils.azsli>
#include "./EnhancedPBR_Common.azsli"
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
#include <Atom/Features/ParallaxMapping.azsli>
@@ -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;
}
@@ -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 -------
@@ -12,7 +12,6 @@
#include <scenesrg.srgi>
#include "EnhancedPBR_Common.azsli"
#include <Atom/Features/PBR/AlphaUtils.azsli>
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
#include <Atom/Features/ParallaxMapping.azsli>
#include <Atom/Features/MatrixUtility.azsli>
@@ -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;
}
@@ -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);
}
@@ -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
//##############################################################################################
@@ -14,6 +14,7 @@
#include <Atom/Features/SrgSemantics.azsli>
#include <Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli>
#include <Atom/Features/PBR/LightingOptions.azsli>
#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);
}
@@ -11,12 +11,10 @@
*/
#include <viewsrg.srgi>
#include <Atom/Features/PBR/AlphaUtils.azsli>
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
#include <Atom/Features/ParallaxMapping.azsli>
#include <Atom/Features/MatrixUtility.azsli>
#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);
@@ -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;
@@ -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
@@ -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
@@ -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
@@ -12,12 +12,10 @@
#include <scenesrg.srgi>
#include <viewsrg.srgi>
#include <Atom/Features/PBR/AlphaUtils.azsli>
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
#include <Atom/Features/ParallaxMapping.azsli>
#include <Atom/Features/MatrixUtility.azsli>
#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;
}
@@ -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": [
@@ -15,6 +15,8 @@
#include <Atom/Features/SrgSemantics.azsli>
#include <viewsrg.srgi>
#include <Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli>
#include <Atom/Features/PBR/LightingOptions.azsli>
#include <Atom/Features/PBR/AlphaUtils.azsli>
#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);
}
@@ -10,7 +10,6 @@
*
*/
#include <Atom/Features/PBR/AlphaUtils.azsli>
#include "./StandardPBR_Common.azsli"
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
#include <Atom/Features/ParallaxMapping.azsli>
@@ -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;
}
@@ -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;
@@ -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)
@@ -12,7 +12,6 @@
#include <scenesrg.srgi>
#include "StandardPBR_Common.azsli"
#include <Atom/Features/PBR/AlphaUtils.azsli>
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
#include <Atom/Features/ParallaxMapping.azsli>
#include <Atom/Features/MatrixUtility.azsli>
@@ -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;
}
@@ -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
@@ -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;
@@ -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());
@@ -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;
@@ -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());
@@ -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;
@@ -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
@@ -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
@@ -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
}
}
}
@@ -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;
@@ -15,6 +15,7 @@
#if !defined(Q_MOC_RUN)
#include <AzCore/Memory/SystemAllocator.h>
#include <AzQtComponents/Components/ExtendedLabel.h>
#include <QMouseEvent>
#include <QPaintEvent>
#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:
@@ -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<InspectorRequests>;
@@ -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<Ui::InspectorWidget> m_ui;
AZStd::vector<InspectorGroupHeaderWidget*> m_headers;
AZStd::vector<QWidget*> m_groups;
};
} // namespace AtomToolsFramework
@@ -104,6 +104,7 @@ namespace AtomToolsFramework
void EndCursorCapture() override;
AzFramework::ScreenPoint ViewportCursorScreenPosition() override;
AZStd::optional<AzFramework::ScreenPoint> PreviousViewportCursorScreenPosition() override;
bool IsMouseOver() const override;
// AzFramework::WindowRequestBus::Handler ...
void SetWindowTitle(const AZStd::string& title) override;
@@ -14,10 +14,10 @@
#include <AzQtComponents/Components/StyleManager.h>
#include <AzQtComponents/Components/Widgets/Text.h>
#include <QStyle>
#include <QPainter>
#include <QApplication>
#include <QPainter>
#include <QPixmap>
#include <QStyle>
#include <QStyleOptionViewItem>
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
@@ -10,12 +10,13 @@
*
*/
#include <QMenu>
#include <QScrollArea>
#include <QScrollBar>
#include <QSizePolicy>
#include <AtomToolsFramework/Inspector/InspectorGroupWidget.h>
#include <AtomToolsFramework/Inspector/InspectorGroupHeaderWidget.h>
#include <AtomToolsFramework/Inspector/InspectorGroupWidget.h>
#include <AtomToolsFramework/Inspector/InspectorWidget.h>
#include <Source/Inspector/ui_InspectorWidget.h>
@@ -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 <AtomToolsFramework/Inspector/moc_InspectorWidget.cpp>
@@ -472,6 +472,11 @@ namespace AtomToolsFramework
: AZStd::optional<AzFramework::ScreenPoint>{};
}
bool RenderViewportWidget::IsMouseOver() const
{
return m_mouseOver;
}
void RenderViewportWidget::BeginCursorCapture()
{
if (m_capturingCursor)
@@ -17,6 +17,8 @@
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
#include <AzFramework/Components/CameraBus.h>
#include <AzFramework/Viewport/ScreenGeometry.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <AtomLyIntegration/CommonFeatures/Mesh/MeshComponentBus.h>
#include <Atom/RPI.Public/RPISystemInterface.h>
@@ -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;
}
@@ -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<AZ::RPI::DynamicDrawContext> dynamicDraw = m_atomFont->GetOrCreateDynamicDrawForScene(renderScene);
@@ -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);
}
}
@@ -14,6 +14,7 @@
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Component/NonUniformScaleBus.h>
#include <AtomLyIntegration/CommonFeatures/Decals/DecalBus.h>
#include <AtomLyIntegration/CommonFeatures/Decals/DecalComponentConfig.h>
#include <Atom/Feature/Decals/DecalFeatureProcessorInterface.h>
@@ -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
@@ -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)
@@ -164,6 +164,7 @@ namespace AZ
if (m_featureProcessor)
{
m_featureProcessor->RemoveProbe(m_handle);
m_handle = nullptr;
}
LmbrCentral::ShapeComponentNotificationsBus::Handler::BusDisconnect();
@@ -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;
}
@@ -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
@@ -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()
@@ -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();
@@ -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<const GlobalMethodEventPaletteTreeItem*>(treeItem); globalMethodTreeItem != nullptr)
@@ -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<ScriptCanvas::Nodes::Core::Method*>(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<int>(displayName.size()), displayName.data()));
@@ -208,7 +208,7 @@ namespace ScriptCanvasEditor::Nodes
auto* methodNode = azrtti_cast<ScriptCanvas::Nodes::Core::MethodOverloaded*>(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<int>(displayName.size()), displayName.data()));
@@ -34,7 +34,7 @@ namespace ScriptCanvasEditor::Nodes
AZStd::pair<ScriptCanvas::Node*, NodeIdPair> CreateAndGetNode(const AZ::Uuid& classData, const ScriptCanvas::ScriptCanvasId& scriptCanvasId, const StyleConfiguration& styleConfiguration, AZStd::function<void(ScriptCanvas::Node*)> = 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);
@@ -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<ClassMethodNodeDescriptorComponent>();
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;
}
@@ -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;
@@ -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;
};
// </EbusSender>
@@ -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)
{
@@ -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;
};
// </ClassMethod>
@@ -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;
@@ -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<ScriptCanvas::NodeTypeIdentifier> 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
@@ -111,7 +111,7 @@ namespace ScriptCanvasEditor
}
else if (auto methodNodeModelInformation = azrtti_cast<const MethodNodeModelInformation*>(modelInformation))
{
createdItem = parentItem->CreateChildNode<ScriptCanvasEditor::ClassMethodEventPaletteTreeItem>(methodNodeModelInformation->m_classMethod, methodNodeModelInformation->m_methodName, methodNodeModelInformation->m_isOverload);
createdItem = parentItem->CreateChildNode<ScriptCanvasEditor::ClassMethodEventPaletteTreeItem>(methodNodeModelInformation->m_classMethod, methodNodeModelInformation->m_methodName, methodNodeModelInformation->m_isOverload, methodNodeModelInformation->m_propertyStatus);
}
else if (auto globalMethodNodeModelInformation = azrtti_cast<const GlobalMethodNodeModelInformation*>(modelInformation);
globalMethodNodeModelInformation != nullptr)
@@ -130,7 +130,7 @@ namespace ScriptCanvasEditor
{
if (!azrtti_istypeof<const ScriptEventSenderNodeModelInformation*>(ebusSenderNodeModelInformation))
{
createdItem = parentItem->CreateChildNode<ScriptCanvasEditor::EBusSendEventPaletteTreeItem>(ebusSenderNodeModelInformation->m_busName, ebusSenderNodeModelInformation->m_eventName, ebusSenderNodeModelInformation->m_busId, ebusSenderNodeModelInformation->m_eventId, ebusSenderNodeModelInformation->m_isOverload);
createdItem = parentItem->CreateChildNode<ScriptCanvasEditor::EBusSendEventPaletteTreeItem>(ebusSenderNodeModelInformation->m_busName, ebusSenderNodeModelInformation->m_eventName, ebusSenderNodeModelInformation->m_busId, ebusSenderNodeModelInformation->m_eventId, ebusSenderNodeModelInformation->m_isOverload, ebusSenderNodeModelInformation->m_propertyStatus);
}
}
@@ -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}");
@@ -33,6 +33,8 @@ namespace ScriptCanvas
Event,
Free,
Member,
Getter,
Setter,
Count,
};
@@ -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()
{
@@ -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;
}
@@ -537,6 +537,20 @@ namespace ScriptCanvas
return azrtti_istypeof<const ScriptCanvas::Nodes::Logic::Break*>(execution->GetId().m_node);
}
bool IsClassPropertyRead(ExecutionTreeConstPtr execution)
{
return execution->GetSymbol() == Symbol::FunctionCall
&& azrtti_istypeof<const ScriptCanvas::Nodes::Core::Method*>(execution->GetId().m_node)
&& azrtti_cast<const ScriptCanvas::Nodes::Core::Method*>(execution->GetId().m_node)->GetPropertyStatus() == PropertyStatus::Getter;
}
bool IsClassPropertyWrite(ExecutionTreeConstPtr execution)
{
return execution->GetSymbol() == Symbol::FunctionCall
&& azrtti_istypeof<const ScriptCanvas::Nodes::Core::Method*>(execution->GetId().m_node)
&& azrtti_cast<const ScriptCanvas::Nodes::Core::Method*>(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<const Slot*>& previousIns, GraphExecutionPathTraversalListener& listener);
ExecutionTraversalResult TraverseExecutionConnectionsRecurse(const EndpointResolved& in, AZStd::unordered_set<const Slot*>& previousIns, GraphExecutionPathTraversalListener& listener);
@@ -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);

Some files were not shown because too many files have changed in this diff Show More