From 55eb85d5460523b60196df4eaa9b2d833a886735 Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Sat, 18 Sep 2021 23:57:52 -0700 Subject: [PATCH 01/53] Updated MaterialPropertyId class in preparation for nested material property sets. Here the class has been generalized for a list of group names and a final property name, rather than assuming a single group containing the property. This included removing the unused GetPropertyName and GetGroupName functions. All that's really need from this class is conversion to a full property ID string. Testing: New unit test. Reprocessed all core material types and StandardPBR test materials used in Atom Sample Viewer's material screenshot test. Atom Sample Viewer material screenshot test script. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../RPI.Edit/Material/MaterialPropertyId.h | 18 +-- .../RPI.Edit/Material/MaterialPropertyId.cpp | 79 +++++++---- .../RPI.Edit/Material/MaterialSourceData.cpp | 12 +- .../Material/MaterialTypeSourceData.cpp | 12 +- .../Material/MaterialPropertyIdTests.cpp | 131 ++++++++++++++++++ Gems/Atom/RPI/Code/atom_rpi_tests_files.cmake | 1 + .../Code/Source/Document/MaterialDocument.cpp | 8 +- .../MaterialInspector/MaterialInspector.cpp | 4 +- .../EditorMaterialComponentInspector.cpp | 2 +- .../Material/EditorMaterialComponentUtil.cpp | 4 +- .../EditorMaterialModelUvNameMapInspector.cpp | 4 +- 11 files changed, 214 insertions(+), 61 deletions(-) create mode 100644 Gems/Atom/RPI/Code/Tests/Material/MaterialPropertyIdTests.cpp diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialPropertyId.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialPropertyId.h index 77d49f3407..4b6d78c092 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialPropertyId.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialPropertyId.h @@ -9,6 +9,7 @@ #pragma once #include +#include namespace AZ { @@ -16,27 +17,28 @@ namespace AZ { class MaterialAsset; - //! Utility for building material property names consisting of a group name and a property sub-name. - //! Represented as "[groupName].[propertyName]". - //! The group name is optional, in which case the ID will just be "[propertyName]". + //! Utility for building material property IDs. + //! These IDs are represented like "groupA.groupB.[...].propertyName". + //! The groups are optional, in which case the full property ID will just be like "propertyName". class MaterialPropertyId { public: static bool IsValidName(AZStd::string_view name); static bool IsValidName(const AZ::Name& name); - //! Creates a MaterialPropertyId from a full name string like "[groupName].[propertyName]" or just "[propertyName]" + //! Creates a MaterialPropertyId from a full name string like "groupA.groupB.[...].propertyName" or just "propertyName". + //! Also checks the name for validity. static MaterialPropertyId Parse(AZStd::string_view fullPropertyId); MaterialPropertyId() = default; + explicit MaterialPropertyId(AZStd::string_view propertyName); MaterialPropertyId(AZStd::string_view groupName, AZStd::string_view propertyName); MaterialPropertyId(const Name& groupName, const Name& propertyName); + MaterialPropertyId(const AZStd::array_view names); AZ_DEFAULT_COPY_MOVE(MaterialPropertyId); - const Name& GetGroupName() const; - const Name& GetPropertyName() const; - const Name& GetFullName() const; + operator const Name&() const; //! Returns a pointer to the full name ("[groupName].[propertyName]"). //! This is included for convenience so it can be used for error messages in the same way an AZ::Name is used. @@ -52,8 +54,6 @@ namespace AZ private: Name m_fullName; - Name m_groupName; - Name m_propertyName; }; } // namespace RPI diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyId.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyId.cpp index e74ec3e6e0..1be5c4485c 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyId.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyId.cpp @@ -27,63 +27,84 @@ namespace AZ bool MaterialPropertyId::IsValid() const { - const bool groupNameIsValid = m_groupName.IsEmpty() || IsValidName(m_groupName); - const bool propertyNameIsValid = IsValidName(m_propertyName); - return groupNameIsValid && propertyNameIsValid; + return !m_fullName.IsEmpty(); } MaterialPropertyId MaterialPropertyId::Parse(AZStd::string_view fullPropertyId) { AZStd::vector tokens; - AzFramework::StringFunc::Tokenize(fullPropertyId.data(), tokens, '.', true, true); + AzFramework::StringFunc::Tokenize(fullPropertyId, tokens, '.', true, true); - if (tokens.size() == 1) + if (tokens.empty()) { - return MaterialPropertyId{"", tokens[0]}; + AZ_Error("MaterialPropertyId", false, "Property ID is empty.", fullPropertyId.data()); + return MaterialPropertyId{}; } - else if (tokens.size() == 2) + + for (const auto& token : tokens) + { + if (!IsValidName(token)) + { + AZ_Error("MaterialPropertyId", false, "Property ID '%.*s' is not a valid identifier.", AZ_STRING_ARG(fullPropertyId)); + return MaterialPropertyId{}; + } + } + + MaterialPropertyId id; + id.m_fullName = fullPropertyId; + return id; + } + + MaterialPropertyId::MaterialPropertyId(AZStd::string_view propertyName) + { + if (!IsValidName(propertyName)) { - return MaterialPropertyId{tokens[0], tokens[1]}; + AZ_Error("MaterialPropertyId", false, "Property name '%.*s' is not a valid identifier.", AZ_STRING_ARG(propertyName)); } else { - AZ_Error("MaterialPropertyId", false, "Property ID '%s' is not a valid identifier.", fullPropertyId.data()); - return MaterialPropertyId{}; + m_fullName = propertyName; } } MaterialPropertyId::MaterialPropertyId(AZStd::string_view groupName, AZStd::string_view propertyName) - : MaterialPropertyId(Name{groupName}, Name{propertyName}) - { - } - - MaterialPropertyId::MaterialPropertyId(const Name& groupName, const Name& propertyName) { - AZ_Error("MaterialPropertyId", groupName.IsEmpty() || IsValidName(groupName), "Group name '%s' is not a valid identifier.", groupName.GetCStr()); - AZ_Error("MaterialPropertyId", IsValidName(propertyName), "Property name '%s' is not a valid identifier.", propertyName.GetCStr()); - m_groupName = groupName; - m_propertyName = propertyName; - if (groupName.IsEmpty()) + if (!IsValidName(groupName)) { - m_fullName = m_propertyName.GetStringView(); + AZ_Error("MaterialPropertyId", false, "Group name '%.*s' is not a valid identifier.", AZ_STRING_ARG(groupName)); + } + else if (!IsValidName(propertyName)) + { + AZ_Error("MaterialPropertyId", false, "Property name '%.*s' is not a valid identifier.", AZ_STRING_ARG(propertyName)); } else { - m_fullName = AZStd::string::format("%s.%s", m_groupName.GetCStr(), m_propertyName.GetCStr()); + m_fullName = AZStd::string::format("%.*s.%.*s", AZ_STRING_ARG(groupName), AZ_STRING_ARG(propertyName)); } } - - const Name& MaterialPropertyId::GetGroupName() const + + MaterialPropertyId::MaterialPropertyId(const Name& groupName, const Name& propertyName) + : MaterialPropertyId(groupName.GetStringView(), propertyName.GetStringView()) { - return m_groupName; } - - const Name& MaterialPropertyId::GetPropertyName() const + + MaterialPropertyId::MaterialPropertyId(const AZStd::array_view names) { - return m_propertyName; + for (const auto& name : names) + { + if (!IsValidName(name)) + { + AZ_Error("MaterialPropertyId", false, "'%s' is not a valid identifier.", name.c_str()); + return; + } + } + + AZStd::string fullName; + AzFramework::StringFunc::Join(fullName, names.begin(), names.end(), "."); + m_fullName = fullName; } - const Name& MaterialPropertyId::GetFullName() const + MaterialPropertyId::operator const Name&() const { return m_fullName; } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp index f697f33a3f..b48d0938a7 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp @@ -130,7 +130,7 @@ namespace AZ } else { - MaterialPropertyIndex propertyIndex = materialAssetCreator.m_materialPropertiesLayout->FindPropertyIndex(propertyId.GetFullName()); + MaterialPropertyIndex propertyIndex = materialAssetCreator.m_materialPropertiesLayout->FindPropertyIndex(propertyId); if (propertyIndex.IsValid()) { const MaterialPropertyDescriptor* propertyDescriptor = materialAssetCreator.m_materialPropertiesLayout->GetPropertyDescriptor(propertyIndex); @@ -145,11 +145,11 @@ namespace AZ auto& imageAsset = imageAssetResult.GetValue(); // Load referenced images when load material imageAsset.SetAutoLoadBehavior(Data::AssetLoadBehavior::PreLoad); - materialAssetCreator.SetPropertyValue(propertyId.GetFullName(), imageAsset); + materialAssetCreator.SetPropertyValue(propertyId, imageAsset); } else { - materialAssetCreator.ReportError("Material property '%s': Could not find the image '%s'", propertyId.GetFullName().GetCStr(), property.second.m_value.GetValue().data()); + materialAssetCreator.ReportError("Material property '%s': Could not find the image '%s'", propertyId.GetCStr(), property.second.m_value.GetValue().data()); } } break; @@ -163,18 +163,18 @@ namespace AZ } else { - materialAssetCreator.SetPropertyValue(propertyId.GetFullName(), enumValue); + materialAssetCreator.SetPropertyValue(propertyId, enumValue); } } break; default: - materialAssetCreator.SetPropertyValue(propertyId.GetFullName(), property.second.m_value); + materialAssetCreator.SetPropertyValue(propertyId, property.second.m_value); break; } } else { - materialAssetCreator.ReportWarning("Can not find property id '%s' in MaterialPropertyLayout", propertyId.GetFullName().GetStringView().data()); + materialAssetCreator.ReportWarning("Can not find property id '%s' in MaterialPropertyLayout", propertyId.GetCStr()); } } } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp index a13a8df16e..7134830fe8 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp @@ -354,7 +354,7 @@ namespace AZ continue; } - materialTypeAssetCreator.BeginMaterialProperty(propertyId.GetFullName(), property.m_dataType); + materialTypeAssetCreator.BeginMaterialProperty(propertyId, property.m_dataType); if (property.m_dataType == MaterialPropertyDataType::Enum) { @@ -404,17 +404,17 @@ namespace AZ if (imageAssetResult.IsSuccess()) { - materialTypeAssetCreator.SetPropertyValue(propertyId.GetFullName(), imageAssetResult.GetValue()); + materialTypeAssetCreator.SetPropertyValue(propertyId, imageAssetResult.GetValue()); } else { - materialTypeAssetCreator.ReportError("Material property '%s': Could not find the image '%s'", propertyId.GetFullName().GetCStr(), property.m_value.GetValue().data()); + materialTypeAssetCreator.ReportError("Material property '%s': Could not find the image '%s'", propertyId.GetCStr(), property.m_value.GetValue().data()); } } break; case MaterialPropertyDataType::Enum: { - MaterialPropertyIndex propertyIndex = materialTypeAssetCreator.GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId.GetFullName()); + MaterialPropertyIndex propertyIndex = materialTypeAssetCreator.GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId); const MaterialPropertyDescriptor* propertyDescriptor = materialTypeAssetCreator.GetMaterialPropertiesLayout()->GetPropertyDescriptor(propertyIndex); AZ::Name enumName = AZ::Name(property.m_value.GetValue()); @@ -425,12 +425,12 @@ namespace AZ } else { - materialTypeAssetCreator.SetPropertyValue(propertyId.GetFullName(), enumValue); + materialTypeAssetCreator.SetPropertyValue(propertyId, enumValue); } } break; default: - materialTypeAssetCreator.SetPropertyValue(propertyId.GetFullName(), property.m_value); + materialTypeAssetCreator.SetPropertyValue(propertyId, property.m_value); break; } } diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertyIdTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertyIdTests.cpp new file mode 100644 index 0000000000..2b729ed8ef --- /dev/null +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertyIdTests.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include + +namespace UnitTest +{ + using namespace AZ; + using namespace RPI; + + class MaterialPropertyIdTests + : public RPITestFixture + { + }; + + TEST_F(MaterialPropertyIdTests, TestConstructWithPropertyName) + { + MaterialPropertyId id{"color"}; + EXPECT_TRUE(id.IsValid()); + EXPECT_STREQ(id.GetCStr(), "color"); + AZ::Name idCastedToName = id; + EXPECT_EQ(idCastedToName, AZ::Name{"color"}); + } + + TEST_F(MaterialPropertyIdTests, TestConstructWithPropertyName_BadName) + { + ErrorMessageFinder errorMessageFinder; + errorMessageFinder.AddExpectedErrorMessage("not a valid identifier"); + + MaterialPropertyId id{"color?"}; + EXPECT_FALSE(id.IsValid()); + + errorMessageFinder.CheckExpectedErrorsFound(); + } + + TEST_F(MaterialPropertyIdTests, TestConstructWithTwoNames) + { + MaterialPropertyId id{"baseColor", "factor"}; + EXPECT_TRUE(id.IsValid()); + EXPECT_STREQ(id.GetCStr(), "baseColor.factor"); + AZ::Name idCastedToName = id; + EXPECT_EQ(idCastedToName, AZ::Name{"baseColor.factor"}); + } + + TEST_F(MaterialPropertyIdTests, TestConstructWithTwoNames_BadGroupName) + { + ErrorMessageFinder errorMessageFinder; + errorMessageFinder.AddExpectedErrorMessage("not a valid identifier"); + + MaterialPropertyId id{"layer1.baseColor", "factor"}; + EXPECT_FALSE(id.IsValid()); + + errorMessageFinder.CheckExpectedErrorsFound(); + } + + TEST_F(MaterialPropertyIdTests, TestConstructWithTwoNames_BadPropertyName) + { + ErrorMessageFinder errorMessageFinder; + errorMessageFinder.AddExpectedErrorMessage("not a valid identifier"); + + MaterialPropertyId id{"baseColor", ".factor"}; + EXPECT_FALSE(id.IsValid()); + + errorMessageFinder.CheckExpectedErrorsFound(); + } + + TEST_F(MaterialPropertyIdTests, TestConstructWithMultipleNames) + { + AZStd::vector names{"layer1", "clearCoat", "normal", "factor"}; + MaterialPropertyId id{names}; + EXPECT_TRUE(id.IsValid()); + EXPECT_STREQ(id.GetCStr(), "layer1.clearCoat.normal.factor"); + AZ::Name idCastedToName = id; + EXPECT_EQ(idCastedToName, AZ::Name{"layer1.clearCoat.normal.factor"}); + } + + TEST_F(MaterialPropertyIdTests, TestConstructWithMultipleNames_BadName) + { + ErrorMessageFinder errorMessageFinder; + errorMessageFinder.AddExpectedErrorMessage("not a valid identifier"); + + AZStd::vector names{"layer1", "clear-coat", "normal", "factor"}; + MaterialPropertyId id{names}; + EXPECT_FALSE(id.IsValid()); + + errorMessageFinder.CheckExpectedErrorsFound(); + } + + TEST_F(MaterialPropertyIdTests, TestParse) + { + MaterialPropertyId id = MaterialPropertyId::Parse("layer1.clearCoat.normal.factor"); + EXPECT_TRUE(id.IsValid()); + EXPECT_STREQ(id.GetCStr(), "layer1.clearCoat.normal.factor"); + AZ::Name idCastedToName = id; + EXPECT_EQ(idCastedToName, AZ::Name{"layer1.clearCoat.normal.factor"}); + } + + TEST_F(MaterialPropertyIdTests, TestParse_BadName) + { + ErrorMessageFinder errorMessageFinder; + errorMessageFinder.AddExpectedErrorMessage("not a valid identifier"); + + MaterialPropertyId id = MaterialPropertyId::Parse("layer1.clearCoat.normal,factor"); + EXPECT_FALSE(id.IsValid()); + + errorMessageFinder.CheckExpectedErrorsFound(); + } + + TEST_F(MaterialPropertyIdTests, TestNameValidity) + { + EXPECT_TRUE(MaterialPropertyId::IsValidName("a")); + EXPECT_TRUE(MaterialPropertyId::IsValidName("z")); + EXPECT_TRUE(MaterialPropertyId::IsValidName("A")); + EXPECT_TRUE(MaterialPropertyId::IsValidName("Z")); + EXPECT_TRUE(MaterialPropertyId::IsValidName("_")); + EXPECT_TRUE(MaterialPropertyId::IsValidName("m_layer10bazBAZ")); + EXPECT_FALSE(MaterialPropertyId::IsValidName("")); + EXPECT_FALSE(MaterialPropertyId::IsValidName("1layer")); + EXPECT_FALSE(MaterialPropertyId::IsValidName("base-color")); + EXPECT_FALSE(MaterialPropertyId::IsValidName("base.color")); + EXPECT_FALSE(MaterialPropertyId::IsValidName("base/color")); + } +} diff --git a/Gems/Atom/RPI/Code/atom_rpi_tests_files.cmake b/Gems/Atom/RPI/Code/atom_rpi_tests_files.cmake index e99e4e456b..923fdc9338 100644 --- a/Gems/Atom/RPI/Code/atom_rpi_tests_files.cmake +++ b/Gems/Atom/RPI/Code/atom_rpi_tests_files.cmake @@ -39,6 +39,7 @@ set(FILES Tests/Material/MaterialSourceDataTests.cpp Tests/Material/MaterialFunctorTests.cpp Tests/Material/MaterialFunctorSourceDataSerializerTests.cpp + Tests/Material/MaterialPropertyIdTests.cpp Tests/Material/MaterialPropertyValueSourceDataTests.cpp Tests/Material/MaterialTests.cpp Tests/Model/ModelTests.cpp diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp index 17e292ac16..3f4aa71a9c 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp @@ -596,7 +596,7 @@ namespace MaterialEditor const MaterialPropertyId propertyId(groupName, propertyName); - const auto it = m_properties.find(propertyId.GetFullName()); + const auto it = m_properties.find(propertyId); if (it != m_properties.end() && propertyFilter(it->second)) { MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(it->second.GetValue()); @@ -604,7 +604,7 @@ namespace MaterialEditor { if (!m_materialTypeSourceData.ConvertPropertyValueToSourceDataFormat(propertyDefinition, propertyValue)) { - AZ_Error("MaterialDocument", false, "Material document property could not be converted: '%s' in '%s'.", propertyId.GetFullName().GetCStr(), m_absolutePath.c_str()); + AZ_Error("MaterialDocument", false, "Material document property could not be converted: '%s' in '%s'.", propertyId.GetCStr(), m_absolutePath.c_str()); result = false; return false; } @@ -774,7 +774,7 @@ namespace MaterialEditor AtomToolsFramework::DynamicPropertyConfig propertyConfig; // Assign id before conversion so it can be used in dynamic description - propertyConfig.m_id = MaterialPropertyId(groupName, propertyName).GetCStr(); + propertyConfig.m_id = MaterialPropertyId(groupName, propertyName); const auto& propertyIndex = m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id); const bool propertyIndexInBounds = propertyIndex.IsValid() && propertyIndex.GetIndex() < m_materialAsset->GetPropertyValues().size(); @@ -845,7 +845,7 @@ namespace MaterialEditor propertyConfig = {}; propertyConfig.m_dataType = AtomToolsFramework::DynamicPropertyType::String; - propertyConfig.m_id = MaterialPropertyId(UvGroupName, shaderInput).GetCStr(); + propertyConfig.m_id = MaterialPropertyId(UvGroupName, shaderInput); propertyConfig.m_name = shaderInput; propertyConfig.m_displayName = shaderInput; propertyConfig.m_groupName = "UV Sets"; diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp index df0b179dc1..f6b42bf13a 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp @@ -145,7 +145,7 @@ namespace MaterialEditor AtomToolsFramework::DynamicProperty property; AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult( property, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetProperty, - AZ::RPI::MaterialPropertyId(groupName, uvNamePair.m_shaderInput.ToString()).GetFullName()); + AZ::RPI::MaterialPropertyId(groupName, uvNamePair.m_shaderInput.ToString())); group.m_properties.push_back(property); property.SetValue(property.GetConfig().m_parentValue); @@ -182,7 +182,7 @@ namespace MaterialEditor AtomToolsFramework::DynamicProperty property; AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult( property, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetProperty, - AZ::RPI::MaterialPropertyId(groupName, propertyDefinition.m_name).GetFullName()); + AZ::RPI::MaterialPropertyId(groupName, propertyDefinition.m_name)); group.m_properties.push_back(property); } } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp index f10a125456..c05b84db39 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp @@ -300,7 +300,7 @@ namespace AZ AtomToolsFramework::DynamicPropertyConfig propertyConfig; // Assign id before conversion so it can be used in dynamic description - propertyConfig.m_id = AZ::RPI::MaterialPropertyId(groupName, propertyDefinition.m_name).GetFullName(); + propertyConfig.m_id = AZ::RPI::MaterialPropertyId(groupName, propertyDefinition.m_name); AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, propertyDefinition); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp index 070c42fd7c..e851bc5fce 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp @@ -140,7 +140,7 @@ namespace AZ editData.m_materialTypeSourceData.EnumerateProperties([&](const AZStd::string& groupName, const AZStd::string& propertyName, const auto& propertyDefinition) { const AZ::RPI::MaterialPropertyId propertyId(groupName, propertyName); const AZ::RPI::MaterialPropertyIndex propertyIndex = - editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId.GetFullName()); + editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId); AZ::RPI::MaterialPropertyValue propertyValue = editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]; @@ -151,7 +151,7 @@ namespace AZ } // Check for and apply any property overrides before saving property values - auto propertyOverrideItr = editData.m_materialPropertyOverrideMap.find(propertyId.GetFullName()); + auto propertyOverrideItr = editData.m_materialPropertyOverrideMap.find(propertyId); if(propertyOverrideItr != editData.m_materialPropertyOverrideMap.end()) { propertyValue = AZ::RPI::MaterialPropertyValue::FromAny(propertyOverrideItr->second); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialModelUvNameMapInspector.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialModelUvNameMapInspector.cpp index 64bc54bae8..1fa6a4e58d 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialModelUvNameMapInspector.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialModelUvNameMapInspector.cpp @@ -96,7 +96,7 @@ namespace AZ const AZStd::string materialUvName = m_materialUvNames[i].m_uvName.GetStringView(); propertyConfig.m_dataType = AtomToolsFramework::DynamicPropertyType::Enum; - propertyConfig.m_id = AZ::RPI::MaterialPropertyId(groupName, shaderInput).GetFullName(); + propertyConfig.m_id = AZ::RPI::MaterialPropertyId(groupName, shaderInput); propertyConfig.m_name = shaderInput; propertyConfig.m_displayName = materialUvName; propertyConfig.m_description = shaderInput; @@ -248,7 +248,7 @@ namespace AZ const AZStd::string materialUvName = m_materialUvNames[i].m_uvName.GetStringView(); propertyConfig.m_dataType = AtomToolsFramework::DynamicPropertyType::Enum; - propertyConfig.m_id = AZ::RPI::MaterialPropertyId(groupName, shaderInput).GetFullName(); + propertyConfig.m_id = AZ::RPI::MaterialPropertyId(groupName, shaderInput); propertyConfig.m_name = shaderInput; propertyConfig.m_displayName = materialUvName; propertyConfig.m_description = shaderInput; From f0e8af72aed61a6163aa88d741c98480f27d4e5c Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Thu, 30 Sep 2021 01:08:54 -0700 Subject: [PATCH 02/53] Updated StringFunc::Tokenize to support returning a list of string_view instead of string, which should be more efficient. string is still supported as well, but users should prefer the string_view version. Testing: Updated unit tests. Reprocessed Atom material assets. Ran AtomSampleViewer material screenshot test. Opened, edited, saved materail in the Material Editor. Opened a level, edited material property overrides, saved and reloaded. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../AzCore/AzCore/StringFunc/StringFunc.cpp | 24 ++++++++++---- .../AzCore/AzCore/StringFunc/StringFunc.h | 12 ++++--- Code/Framework/AzCore/Tests/StringFunc.cpp | 31 +++++++++++++++---- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp b/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp index ff30291a70..eec23b6bcb 100644 --- a/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp +++ b/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp @@ -759,13 +759,18 @@ namespace AZ } return value; } - - void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings) + + template + void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings) { return Tokenize(in, tokens, { &delimiter, 1 }, keepEmptyStrings, keepSpaceStrings); } - void Tokenize(AZStd::string_view in, AZStd::vector& tokens, AZStd::string_view delimiters, bool keepEmptyStrings, bool keepSpaceStrings) + template void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings); + template void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings); + + template + void Tokenize(AZStd::string_view in, AZStd::vector& tokens, AZStd::string_view delimiters, bool keepEmptyStrings, bool keepSpaceStrings) { auto insertVisitor = [&tokens](AZStd::string_view token) { @@ -773,6 +778,9 @@ namespace AZ }; return TokenizeVisitor(in, insertVisitor, delimiters, keepEmptyStrings, keepSpaceStrings); } + + template void Tokenize(AZStd::string_view in, AZStd::vector& tokens, AZStd::string_view delimiters, bool keepEmptyStrings, bool keepSpaceStrings); + template void Tokenize(AZStd::string_view in, AZStd::vector& tokens, AZStd::string_view delimiters, bool keepEmptyStrings, bool keepSpaceStrings); void TokenizeVisitor(AZStd::string_view in, const TokenVisitor& tokenVisitor, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings) { @@ -920,8 +928,9 @@ namespace AZ return found; } - - void Tokenize(AZStd::string_view input, AZStd::vector& tokens, const AZStd::vector& delimiters, bool keepEmptyStrings /*= false*/, bool keepSpaceStrings /*= false*/) + + template + void Tokenize(AZStd::string_view input, AZStd::vector& tokens, const AZStd::vector& delimiters, bool keepEmptyStrings /*= false*/, bool keepSpaceStrings /*= false*/) { if (input.empty()) { @@ -941,7 +950,7 @@ namespace AZ } // Take the substring, not including the separator, and increment our offset - AZStd::string nextSubstring = input.substr(offset, nextOffset - offset); + AZStd::string_view nextSubstring = input.substr(offset, nextOffset - offset); if (keepEmptyStrings || keepSpaceStrings || !nextSubstring.empty()) { tokens.push_back(nextSubstring); @@ -950,6 +959,9 @@ namespace AZ offset = nextOffset + delimiters[nextMatch].size(); } } + + template void Tokenize(AZStd::string_view input, AZStd::vector& tokens, const AZStd::vector& delimiters, bool keepEmptyStrings /*= false*/, bool keepSpaceStrings /*= false*/); + template void Tokenize(AZStd::string_view input, AZStd::vector& tokens, const AZStd::vector& delimiters, bool keepEmptyStrings /*= false*/, bool keepSpaceStrings /*= false*/); int ToInt(const char* in) { diff --git a/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.h b/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.h index 1e651afc93..51357312bc 100644 --- a/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.h +++ b/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.h @@ -258,17 +258,21 @@ namespace AZ bool Strip(AZStd::string& inout, const char* stripCharacters = " ", bool bCaseSensitive = false, bool bStripBeginning = false, bool bStripEnding = false); //! Tokenize - /*! Tokenize a c-string, into a vector of AZStd::string(s) optionally keeping empty string + /*! Tokenize a c-string, into a vector of strings optionally keeping empty string *! and optionally keeping space only strings + *! (The string type may be AZStd::string or AZStd::string_view. New code should use AZStd::string_view for better performance. AZStd::string version is preserved for compatibility.) Example: Tokenize the words of a sentence. StringFunc::Tokenize("Hello World", d, ' '); s[0] == "Hello", s[1] == "World" Example: Tokenize a comma and end line delimited string StringFunc::Tokenize("Hello,World\nHello,World", d, ' '); s[0] == "Hello", s[1] == "World" s[2] == "Hello", s[3] == "World" */ - void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const char delimiter, bool keepEmptyStrings = false, bool keepSpaceStrings = false); - void Tokenize(AZStd::string_view in, AZStd::vector& tokens, AZStd::string_view delimiters = "\\//, \t\n", bool keepEmptyStrings = false, bool keepSpaceStrings = false); - void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const AZStd::vector& delimiters, bool keepEmptyStrings = false, bool keepSpaceStrings = false); + template + void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const char delimiter, bool keepEmptyStrings = false, bool keepSpaceStrings = false); + template + void Tokenize(AZStd::string_view in, AZStd::vector& tokens, AZStd::string_view delimiters = "\\//, \t\n", bool keepEmptyStrings = false, bool keepSpaceStrings = false); + template + void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const AZStd::vector& delimiters, bool keepEmptyStrings = false, bool keepSpaceStrings = false); //! TokenizeVisitor /*! Tokenize a string_view and invoke a handler for each token found. diff --git a/Code/Framework/AzCore/Tests/StringFunc.cpp b/Code/Framework/AzCore/Tests/StringFunc.cpp index 68821ba3f9..79dabe3462 100644 --- a/Code/Framework/AzCore/Tests/StringFunc.cpp +++ b/Code/Framework/AzCore/Tests/StringFunc.cpp @@ -199,7 +199,7 @@ namespace AZ TEST_F(StringFuncTest, Tokenize_SingleDelimeter_Empty) { AZStd::string input = ""; - AZStd::vector tokens; + AZStd::vector tokens; AZ::StringFunc::Tokenize(input.c_str(), tokens, ' '); ASSERT_EQ(tokens.size(), 0); } @@ -207,7 +207,7 @@ namespace AZ TEST_F(StringFuncTest, Tokenize_SingleDelimeter) { AZStd::string input = "a b,c"; - AZStd::vector tokens; + AZStd::vector tokens; AZ::StringFunc::Tokenize(input.c_str(), tokens, ' '); ASSERT_EQ(tokens.size(), 2); @@ -218,7 +218,7 @@ namespace AZ TEST_F(StringFuncTest, Tokenize_MultiDelimeter_Empty) { AZStd::string input = ""; - AZStd::vector tokens; + AZStd::vector tokens; AZ::StringFunc::Tokenize(input.c_str(), tokens, " ,"); ASSERT_EQ(tokens.size(), 0); } @@ -226,7 +226,7 @@ namespace AZ TEST_F(StringFuncTest, Tokenize_MultiDelimeters) { AZStd::string input = " -a +b +c -d-e"; - AZStd::vector tokens; + AZStd::vector tokens; AZ::StringFunc::Tokenize(input.c_str(), tokens, "-+"); ASSERT_EQ(tokens.size(), 5); @@ -240,7 +240,7 @@ namespace AZ TEST_F(StringFuncTest, Tokenize_SubstringDelimeters_Empty) { AZStd::string input = ""; - AZStd::vector tokens; + AZStd::vector tokens; AZStd::vector delimeters = {" -", " +"}; AZ::StringFunc::Tokenize(input.c_str(), tokens, delimeters); ASSERT_EQ(tokens.size(), 0); @@ -249,7 +249,7 @@ namespace AZ TEST_F(StringFuncTest, Tokenize_SubstringDelimeters) { AZStd::string input = " -a +b +c -d-e"; - AZStd::vector tokens; + AZStd::vector tokens; AZStd::vector delimeters = { " -", " +" }; AZ::StringFunc::Tokenize(input.c_str(), tokens, delimeters); @@ -259,6 +259,25 @@ namespace AZ ASSERT_TRUE(tokens[2] == "c"); ASSERT_TRUE(tokens[3] == "d-e"); // Test for something like a guid, which contain typical separator characters } + + TEST_F(StringFuncTest, Tokenize_MultiDelimeters_String) + { + // Test with AZStd::string for backward compatibility. The functions + // use to only work with AZStd::string, and now they are templatized + // to support both AZStd::string and AZStd::string_view (the latter + // being perferred for performance). + + AZStd::string input = " -a +b +c -d-e"; + AZStd::vector tokens; + AZ::StringFunc::Tokenize(input.c_str(), tokens, "-+"); + + ASSERT_EQ(tokens.size(), 5); + ASSERT_TRUE(tokens[0] == "a "); + ASSERT_TRUE(tokens[1] == "b "); + ASSERT_TRUE(tokens[2] == "c "); + ASSERT_TRUE(tokens[3] == "d"); + ASSERT_TRUE(tokens[4] == "e"); + } TEST_F(StringFuncTest, TokenizeVisitor_EmptyString_DoesNotInvokeVisitor) { From 1a99103999e132b21fc67ed4948401f788dc1c59 Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Thu, 30 Sep 2021 01:30:28 -0700 Subject: [PATCH 03/53] Overhauled the .materialtype file format to group all related properties data together. This prepares the way for a number of possible improvements, especially unlocking the ability to factor out material type configuration to be shared by multiple material types. Here we formalize the concept of a Property Set, which replaces property "groups", containing the group name and description, properties, and functors all in one place. The Property Set structure will allow arbitrarily deep nesting, whereas before you only had one level of grouping. This nesting is not fully supported yet throughout the system, particularly in the Material Editor. It was easier to go ahead and put in some of the nesting mechanims, parituclar in the implementation of MaterialTypeSourceData. This change is backward compatible, which is proved with unit tests, and by the fact that only MinimalPBR.materialtype has been updated to the new format. StandardPBR, EnhancedPBR, and others are still using the old format. (In a subsequent commit I'll update these as well, to prove that the new format works correctly). Other changes and improvements... - A new constructor for MaterialPropertyId - Improved API for MaterialTypeSourceData that hides a good deal more of it's data as private, with clear and convenient APIs. Especially AddProperty, AddPropertySet, FindProperty, FindPropertySet, EnumerateProperties, EnumeratePropertySets. - Added lots of new unit tests - Updated MinimalPBR.materialtype to the new format. Testing: - Updated unit tests. - Reprocessed Atom material assets. - Ran AtomSampleViewer material screenshot test. - Opened, edited, saved material in the Material Editor. - Opened a level, edited material property overrides, saved and reloaded. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../RPI.Edit/Material/MaterialPropertyId.h | 4 + .../Material/MaterialTypeSourceData.h | 142 +- .../RPI.Edit/Material/MaterialPropertyId.cpp | 35 + .../MaterialPropertyValueSerializer.cpp | 4 +- .../Material/MaterialSourceDataSerializer.cpp | 1 + .../Material/MaterialTypeSourceData.cpp | 731 ++++++++--- .../RPI.Edit/Material/MaterialUtils.cpp | 1 + .../Code/Tests/Common/ErrorMessageFinder.cpp | 2 +- .../Material/MaterialPropertyIdTests.cpp | 34 + .../Material/MaterialSourceDataTests.cpp | 117 +- .../Material/MaterialTypeSourceDataTests.cpp | 1142 +++++++++++++---- .../Materials/Types/MinimalPBR.materialtype | 80 +- .../Code/Source/Document/MaterialDocument.cpp | 109 +- .../MaterialInspector/MaterialInspector.cpp | 31 +- .../EditorMaterialComponentInspector.cpp | 42 +- .../Material/EditorMaterialComponentUtil.cpp | 76 +- 16 files changed, 1886 insertions(+), 665 deletions(-) diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialPropertyId.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialPropertyId.h index 4b6d78c092..496485d0d6 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialPropertyId.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialPropertyId.h @@ -35,6 +35,7 @@ namespace AZ MaterialPropertyId(AZStd::string_view groupName, AZStd::string_view propertyName); MaterialPropertyId(const Name& groupName, const Name& propertyName); MaterialPropertyId(const AZStd::array_view names); + MaterialPropertyId(const AZStd::array_view groupNames, AZStd::string_view propertyName); AZ_DEFAULT_COPY_MOVE(MaterialPropertyId); @@ -44,6 +45,9 @@ namespace AZ //! This is included for convenience so it can be used for error messages in the same way an AZ::Name is used. const char* GetCStr() const; + //! Wraps Name::GetStringView() for convenience. + AZStd::string_view GetStringView() const; + //! Returns a hash of the full name. This is needed for compatibility with NameIdReflectionMap. Name::Hash GetHash() const; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h index 04b6222404..56f7c4612c 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h @@ -68,8 +68,9 @@ namespace AZ struct PropertyDefinition { + AZ_CLASS_ALLOCATOR(PropertyDefinition, SystemAllocator, 0); AZ_TYPE_INFO(AZ::RPI::MaterialTypeSourceData::PropertyDefinition, "{E0DB3C0D-75DB-4ADB-9E79-30DA63FA18B7}"); - + static const float DefaultMin; static const float DefaultMax; static const float DefaultStep; @@ -117,68 +118,159 @@ namespace AZ AZStd::unordered_map m_shaderOptionValues; }; - using PropertyList = AZStd::vector; + using PropertyList = AZStd::vector>; + + struct PropertySet + { + friend class MaterialTypeSourceData; + + AZ_CLASS_ALLOCATOR(PropertySet, SystemAllocator, 0); + AZ_TYPE_INFO(AZ::RPI::MaterialTypeSourceData::PropertySet, "{BA3AA0E4-C74D-4FD0-ADB2-00B060F06314}"); + + public: + + PropertySet() = default; + AZ_DISABLE_COPY(PropertySet) + + const AZStd::string& GetName() const { return m_name; } + const AZStd::string& GetDisplayName() const { return m_displayName; } + const AZStd::string& GetDescription() const { return m_description; } + const PropertyList& GetProperties() const { return m_properties; } + const AZStd::vector>& GetPropertySets() const { return m_propertySets; } + const AZStd::vector>& GetFunctors() const { return m_materialFunctorSourceData; } + + void SetDisplayName(AZStd::string_view displayName) { m_displayName = displayName; } + void SetDescription(AZStd::string_view description) { m_description = description; } + + PropertyDefinition* AddProperty(AZStd::string_view name); + PropertySet* AddPropertySet(AZStd::string_view name); + + private: + + static PropertySet* AddPropertySet(AZStd::string_view name, AZStd::vector>& toPropertySetList); + + AZStd::string m_name; + AZStd::string m_displayName; + AZStd::string m_description; + PropertyList m_properties; + AZStd::vector> m_propertySets; + AZStd::vector> m_materialFunctorSourceData; + }; + struct PropertyLayout { AZ_TYPE_INFO(AZ::RPI::MaterialTypeSourceData::PropertyLayout, "{AE53CF3F-5C3B-44F5-B2FB-306F0EB06393}"); + PropertyLayout() = default; + AZ_DISABLE_COPY(PropertyLayout) + //! Indicates the version of the set of available properties. Can be used to detect materials that might need to be updated. uint32_t m_version = 0; + //! [Deprecated] Use m_propertySets instead //! List of groups that will contain the available properties AZStd::vector m_groups; + //! [Deprecated] Use m_propertySets instead //! Collection of all available user-facing properties - AZStd::map m_properties; + AZStd::map> m_properties; + + AZStd::vector> m_propertySets; }; + + PropertySet* AddPropertySet(AZStd::string_view propertySetId); + //PropertySet* AddPropertySet(AZStd::string_view parentPropertySetId, AZStd::string_view name); + PropertyDefinition* AddProperty(AZStd::string_view propertyId); + //PropertyDefinition* AddProperty(AZStd::string_view parentPropertySetId, AZStd::string_view name); - AZStd::string m_description; + const PropertyLayout& GetPropertyLayout() const { return m_propertyLayout; } - PropertyLayout m_propertyLayout; + AZStd::string m_description; //< TODO: Make this private //! A list of shader variants that are always used at runtime; they cannot be turned off - AZStd::vector m_shaderCollection; + AZStd::vector m_shaderCollection; //< TODO: Make this private //! Material functors provide custom logic and calculations to configure shaders, render states, and more. See MaterialFunctor.h for details. - AZStd::vector> m_materialFunctorSourceData; + AZStd::vector> m_materialFunctorSourceData; //< TODO: Make this private //! Override names for UV input in the shaders of this material type. //! Using ordered map to sort names on loading. using UvNameMap = AZStd::map; - UvNameMap m_uvNameMap; + UvNameMap m_uvNameMap; //< TODO: Make this private //! Copy over UV custom names to the properties enum values. void ResolveUvEnums(); + + const PropertySet* FindPropertySet(AZStd::string_view propertySetId) const; - const GroupDefinition* FindGroup(AZStd::string_view groupName) const; + const PropertyDefinition* FindProperty(AZStd::string_view propertyId) const; - const PropertyDefinition* FindProperty(AZStd::string_view groupName, AZStd::string_view propertyName) const; + //! Tokenizes an ID string like "itemA.itemB.itemC" into a vector like ["itemA", "itemB", "itemC"] + static AZStd::vector TokenizeId(AZStd::string_view id); + + //! Splits an ID string like "itemA.itemB.itemC" into a vector like ["itemA.itemB", "itemC"] + static AZStd::vector SplitId(AZStd::string_view id); - //! Construct a complete list of group definitions, including implicit groups, arranged in the same order as the source data - //! Groups with the same name will be consolidated into a single entry - AZStd::vector GetGroupDefinitionsInDisplayOrder() const; + //! Call back function type used with the enumeration functions + using EnumeratePropertySetsCallback = AZStd::function; + //! Recursively traverses all of the property sets contained in the material type, executing a callback function for each. + //! @return false if the enumeration was terminated early by the callback returning false. + bool EnumeratePropertySets(const EnumeratePropertySetsCallback& callback) const; //! Call back function type used with the numeration functions using EnumeratePropertiesCallback = AZStd::function; - - //! Traverse all of the properties contained in the source data executing a callback function - //! Traversal will occur in group alphabetical order and stop once all properties have been enumerated or the callback function returns false - void EnumerateProperties(const EnumeratePropertiesCallback& callback) const; - - //! Traverse all of the properties in the source data in display/storage order executing a callback function - //! Traversal will stop once all properties have been enumerated or the callback function returns false - void EnumeratePropertiesInDisplayOrder(const EnumeratePropertiesCallback& callback) const; + + //! Recursively traverses all of the properties contained in the material type, executing a callback function for each. + //! @return false if the enumeration was terminated early by the callback returning false. + bool EnumerateProperties(const EnumeratePropertiesCallback& callback) const; //! Convert the property value into the format that will be stored in the source data //! This is primarily needed to support conversions of special types like enums and images bool ConvertPropertyValueToSourceDataFormat(const PropertyDefinition& propertyDefinition, MaterialPropertyValue& propertyValue) const; Outcome> CreateMaterialTypeAsset(Data::AssetId assetId, AZStd::string_view materialTypeSourceFilePath = "", bool elevateWarnings = true) const; + + bool ConvertToNewDataFormat(); + + private: + + //PropertySet* FindPropertySet(AZStd::array_view parsedPropertySetId, AZStd::array_view> inPropertySetList); + const PropertySet* FindPropertySet(AZStd::array_view parsedPropertySetId, AZStd::array_view> inPropertySetList) const; + + //PropertyDefinition* FindProperty(AZStd::array_view parsedPropertyId, AZStd::array_view> inPropertySetList); + const PropertyDefinition* FindProperty(AZStd::array_view parsedPropertyId, AZStd::array_view> inPropertySetList) const; + + //PropertyDefinition* FindProperty(AZStd::array_view parsedPropertyId, PropertySet& inPropertySet); + //const PropertyDefinition* FindProperty(AZStd::array_view parsedPropertyId, const PropertySet& inPropertySet) const; + + // Function overloads for recursion, returns false to indicate that recursion should end. + bool EnumeratePropertySets(const EnumeratePropertySetsCallback& callback, AZStd::string propertyIdContext, const AZStd::vector>& inPropertySetList) const; + bool EnumerateProperties(const EnumeratePropertiesCallback& callback, AZStd::string propertyIdContext, const AZStd::vector>& inPropertySetList) const; + + //! Recursively populates a material asset with properties from the tree of material property sets. + //! @param materialTypeSourceFilePath path to the material type file that is being processed, used to look up relative paths + //! @param propertyNameContext the accumulated prefix that should be applied to any property names encountered in the current @propertySet + //! @param propertySet the current PropertySet that is being processed + //! @return false if errors are detected and processing should abort + bool BuildPropertyList( + const AZStd::string& materialTypeSourceFilePath, + MaterialTypeAssetCreator& materialTypeAssetCreator, + AZStd::vector& propertyNameContext, + const MaterialTypeSourceData::PropertySet* propertySet) const; + + //! Construct a complete list of group definitions, including implicit groups, arranged in the same order as the source data. + //! Groups with the same name will be consolidated into a single entry. + //! Operates on the old format PropertyLayout::m_groups, used for conversion to the new format. + AZStd::vector GetOldFormatGroupDefinitionsInDisplayOrder() const; + + PropertyLayout m_propertyLayout; }; //! The wrapper class for derived material functors. @@ -207,7 +299,7 @@ namespace AZ return m_actualSourceData ? m_actualSourceData->CreateFunctor(editorContext) : Failure(); } - const Ptr GetActualSourceData() const { return m_actualSourceData; } + Ptr GetActualSourceData() const { return m_actualSourceData; } private: Ptr m_actualSourceData = nullptr; // The derived material functor instance. }; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyId.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyId.cpp index 1be5c4485c..47e41fa6fa 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyId.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyId.cpp @@ -103,6 +103,36 @@ namespace AZ AzFramework::StringFunc::Join(fullName, names.begin(), names.end(), "."); m_fullName = fullName; } + + MaterialPropertyId::MaterialPropertyId(const AZStd::array_view groupNames, AZStd::string_view propertyName) + { + for (const auto& name : groupNames) + { + if (!IsValidName(name)) + { + AZ_Error("MaterialPropertyId", false, "'%s' is not a valid identifier.", name.c_str()); + return; + } + } + + if (!IsValidName(propertyName)) + { + AZ_Error("MaterialPropertyId", false, "'%.*s' is not a valid identifier.", AZ_STRING_ARG(propertyName)); + return; + } + + if (groupNames.empty()) + { + m_fullName = propertyName; + } + else + { + AZStd::string fullName; + AzFramework::StringFunc::Join(fullName, groupNames.begin(), groupNames.end(), "."); + fullName = AZStd::string::format("%s.%.*s", fullName.c_str(), AZ_STRING_ARG(propertyName)); + m_fullName = fullName; + } + } MaterialPropertyId::operator const Name&() const { @@ -113,6 +143,11 @@ namespace AZ { return m_fullName.GetCStr(); } + + AZStd::string_view MaterialPropertyId::GetStringView() const + { + return m_fullName.GetStringView(); + } Name::Hash MaterialPropertyId::GetHash() const { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyValueSerializer.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyValueSerializer.cpp index 3b2d36451a..5bf78d1639 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyValueSerializer.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyValueSerializer.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -63,6 +64,7 @@ namespace AZ } // Construct the full property name (groupName.propertyName) by parsing it from the JSON path string. + // Note we don't yet support full nested property sets, but eventually this should not be limited to just one group with one list of properties... size_t startPropertyName = context.GetPath().Get().rfind('/'); size_t startGroupName = context.GetPath().Get().rfind('/', startPropertyName-1); AZStd::string_view groupName = context.GetPath().Get().substr(startGroupName + 1, startPropertyName - startGroupName - 1); @@ -70,7 +72,7 @@ namespace AZ JSR::ResultCode result(JSR::Tasks::ReadField); - auto propertyDefinition = materialType->FindProperty(groupName, propertyName); + auto propertyDefinition = materialType->FindProperty(MaterialPropertyId{groupName, propertyName}.GetStringView()); if (!propertyDefinition) { AZStd::string message = AZStd::string::format("Property '%.*s.%.*s' not found in material type.", AZ_STRING_ARG(groupName), AZ_STRING_ARG(propertyName)); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceDataSerializer.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceDataSerializer.cpp index 5e4af07aae..e24655b7a6 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceDataSerializer.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceDataSerializer.cpp @@ -103,6 +103,7 @@ namespace AZ settings.m_clearContainers = context.ShouldClearContainers(); JsonSerializationResult::ResultCode materialTypeLoadResult = JsonSerialization::Load(materialTypeData, materialTypeJson.GetValue(), settings); + materialTypeData.ConvertToNewDataFormat(); materialTypeData.ResolveUvEnums(); // Restore prior configuration diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp index f69b082292..de55cbecb5 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp @@ -56,7 +56,11 @@ namespace AZ serializeContext->Class()->Version(3); serializeContext->Class()->Version(4); serializeContext->Class()->Version(1); - + + serializeContext->RegisterGenericType>(); + serializeContext->RegisterGenericType>(); + serializeContext->RegisterGenericType>>(); + serializeContext->RegisterGenericType>>(); serializeContext->RegisterGenericType(); serializeContext->Class() @@ -66,11 +70,22 @@ namespace AZ ->Field("options", &ShaderVariantReferenceData::m_shaderOptionValues) ; + serializeContext->Class() + ->Version(1) + ->Field("name", &PropertySet::m_name) + ->Field("displayName", &PropertySet::m_displayName) + ->Field("description", &PropertySet::m_description) + ->Field("properties", &PropertySet::m_properties) + ->Field("propertySets", &PropertySet::m_propertySets) + ->Field("functors", &PropertySet::m_materialFunctorSourceData) + ; + serializeContext->Class() ->Version(1) ->Field("version", &PropertyLayout::m_version) - ->Field("groups", &PropertyLayout::m_groups) - ->Field("properties", &PropertyLayout::m_properties) + ->Field("groups", &PropertyLayout::m_groups) //< Old, preserved for backward compatibility, replaced by propertySets + ->Field("properties", &PropertyLayout::m_properties) //< Old, preserved for backward compatibility, replaced by propertySets + ->Field("propertySets", &PropertyLayout::m_propertySets) ; serializeContext->RegisterGenericType(); @@ -92,146 +107,453 @@ namespace AZ , m_shaderIndex(shaderIndex) { } - + const float MaterialTypeSourceData::PropertyDefinition::DefaultMin = std::numeric_limits::lowest(); const float MaterialTypeSourceData::PropertyDefinition::DefaultMax = std::numeric_limits::max(); const float MaterialTypeSourceData::PropertyDefinition::DefaultStep = 0.1f; - - const MaterialTypeSourceData::GroupDefinition* MaterialTypeSourceData::FindGroup(AZStd::string_view groupName) const + + /*static*/ MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::PropertySet::AddPropertySet(AZStd::string_view name, AZStd::vector>& toPropertySetList) { - for (const GroupDefinition& group : m_propertyLayout.m_groups) - { - if (group.m_name == groupName) + auto iter = AZStd::find_if(toPropertySetList.begin(), toPropertySetList.end(), [name](const AZStd::unique_ptr& existingPropertySet) { - return &group; - } + return existingPropertySet->m_name == name; + }); + + if (iter != toPropertySetList.end()) + { + AZ_Error("Material source data", false, "PropertySet named '%.*s' already exists", AZ_STRING_ARG(name)); + return nullptr; + } + + if (!MaterialPropertyId::IsValidName(name)) + { + AZ_Error("Material source data", false, "'%.*s' is not a valid identifier", AZ_STRING_ARG(name)); + return nullptr; } - return nullptr; + toPropertySetList.push_back(AZStd::make_unique()); + toPropertySetList.back()->m_name = name; + return toPropertySetList.back().get(); } - const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::string_view groupName, AZStd::string_view propertyName) const + MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::PropertySet::AddProperty(AZStd::string_view name) { - auto groupIter = m_propertyLayout.m_properties.find(groupName); - if (groupIter == m_propertyLayout.m_properties.end()) + auto propertyIter = AZStd::find_if(m_properties.begin(), m_properties.end(), [name](const AZStd::unique_ptr& existingProperty) + { + return existingProperty->m_name == name; + }); + + if (propertyIter != m_properties.end()) + { + AZ_Error("Material source data", false, "PropertySet '%s' already contains a property named '%.*s'", m_name.c_str(), AZ_STRING_ARG(name)); + return nullptr; + } + + auto propertySetIter = AZStd::find_if(m_propertySets.begin(), m_propertySets.end(), [name](const AZStd::unique_ptr& existingPropertySet) + { + return existingPropertySet->m_name == name; + }); + + if (propertySetIter != m_propertySets.end()) { + AZ_Error("Material source data", false, "Property name '%.*s' collides with a PropertySet of the same name", AZ_STRING_ARG(name)); return nullptr; } - for (const PropertyDefinition& property : groupIter->second) + if (!MaterialPropertyId::IsValidName(name)) { - if (property.m_name == propertyName) + AZ_Error("Material source data", false, "'%.*s' is not a valid identifier", AZ_STRING_ARG(name)); + return nullptr; + } + + m_properties.emplace_back(AZStd::make_unique()); + m_properties.back()->m_name = name; + return m_properties.back().get(); + } + + MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::PropertySet::AddPropertySet(AZStd::string_view name) + { + auto iter = AZStd::find_if(m_properties.begin(), m_properties.end(), [name](const AZStd::unique_ptr& existingProperty) { - return &property; - } + return existingProperty->m_name == name; + }); + + if (iter != m_properties.end()) + { + AZ_Error("Material source data", false, "PropertySet name '%.*s' collides with a Property of the same name", AZ_STRING_ARG(name)); + return nullptr; } - return nullptr; + return AddPropertySet(name, m_propertySets); } + + MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::AddPropertySet(AZStd::string_view propertySetId) + { + AZStd::vector splitPropertySetId = SplitId(propertySetId); - void MaterialTypeSourceData::ResolveUvEnums() + if (splitPropertySetId.size() == 1) + { + return PropertySet::AddPropertySet(propertySetId, m_propertyLayout.m_propertySets); + } + + // TODO: Delete + //return AddPropertySet(splitPropertySetId[0], splitPropertySetId[1]); + + PropertySet* parentPropertySet = const_cast(const_cast(this)->FindPropertySet(splitPropertySetId[0])); + + if (!parentPropertySet) + { + AZ_Error("Material source data", false, "PropertySet '%.*s' does not exists", AZ_STRING_ARG(splitPropertySetId[0])); + return nullptr; + } + + return parentPropertySet->AddPropertySet(splitPropertySetId[1]); + } + + // TODO: Delete + //MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::AddPropertySet(AZStd::string_view parentPropertySetId, AZStd::string_view name) + //{ + // PropertySet* parentPropertySet = const_cast(const_cast(this)->FindPropertySet(parentPropertySetId)); + // + // if (!parentPropertySet) + // { + // AZ_Error("Material source data", false, "PropertySet '%.*s' does not exists", AZ_STRING_ARG(parentPropertySetId)); + // return nullptr; + // } + + // return parentPropertySet->AddPropertySet(name); + //} + + MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::AddProperty(AZStd::string_view propertyId) { - AZStd::vector enumValues; - enumValues.reserve(m_uvNameMap.size()); - for (const auto& uvNamePair : m_uvNameMap) + AZStd::vector splitPropertyId = SplitId(propertyId); + //if (splitPropertyId.empty()) + //{ + // return nullptr; + //} + + if (splitPropertyId.size() == 1) { - enumValues.push_back(uvNamePair.second); + AZ_Error("Material source data", false, "Property id '%.*s' is invalid. Properties must be added to a PropertySet (i.e. \"general.%.*s\").", AZ_STRING_ARG(propertyId), AZ_STRING_ARG(propertyId)); + return nullptr; + } + + // TODO: Delete + //return AddProperty(splitPropertyId[0], splitPropertyId[1]); + + PropertySet* parentPropertySet = const_cast(const_cast(this)->FindPropertySet(splitPropertyId[0])); + + if (!parentPropertySet) + { + AZ_Error("Material source data", false, "PropertySet '%.*s' does not exists", AZ_STRING_ARG(splitPropertyId[0])); + return nullptr; } - for (auto& group : m_propertyLayout.m_properties) + return parentPropertySet->AddProperty(splitPropertyId[1]); + } + + // TODO: Delete + //MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::AddProperty(AZStd::string_view parentPropertySetId, AZStd::string_view name) + //{ + // PropertySet* parentPropertySet = const_cast(const_cast(this)->FindPropertySet(parentPropertySetId)); + // + // if (!parentPropertySet) + // { + // AZ_Error("Material source data", false, "PropertySet '%.*s' does not exists", AZ_STRING_ARG(parentPropertySetId)); + // return nullptr; + // } + + // return parentPropertySet->AddProperty(name); + //} + + const MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::FindPropertySet(AZStd::array_view parsedPropertySetId, AZStd::array_view> inPropertySetList) const + { + for (const auto& propertySet : inPropertySetList) { - for (PropertyDefinition& property : group.second) + if (propertySet->m_name != parsedPropertySetId[0]) + { + continue; + } + else if (parsedPropertySetId.size() == 1) + { + return propertySet.get(); + } + else { - if (property.m_dataType == AZ::RPI::MaterialPropertyDataType::Enum && property.m_enumIsUv) + AZStd::array_view subPath{parsedPropertySetId.begin() + 1, parsedPropertySetId.end()}; + + if (!subPath.empty()) { - property.m_enumValues = enumValues; + const MaterialTypeSourceData::PropertySet* propertySubset = FindPropertySet(subPath, propertySet->m_propertySets); + if (propertySubset) + { + return propertySubset; + } } } } + + return nullptr; } - AZStd::vector MaterialTypeSourceData::GetGroupDefinitionsInDisplayOrder() const - { - AZStd::vector groupDefinitions; - groupDefinitions.reserve(m_propertyLayout.m_properties.size()); + //MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::FindPropertySet(AZStd::array_view parsedPropertySetId, AZStd::array_view> inPropertySetList) + //{ + // return const_cast(const_cast(this)->FindPropertySet(parsedPropertySetId, inPropertySetList)); + //} - // Some groups are defined explicitly in the .materialtype file's "groups" section. This is the primary way groups are sorted in the UI. - AZStd::unordered_set foundGroups; - for (const auto& groupDefinition : m_propertyLayout.m_groups) + const MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::FindPropertySet(AZStd::string_view propertySetId) const + { + AZStd::vector tokens = TokenizeId(propertySetId); + return FindPropertySet(tokens, m_propertyLayout.m_propertySets); + } + + //MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::array_view parsedPropertyId, PropertySet& inPropertySet) + //{ + // if (parsedPropertyId.size() == 1) + // { + // for (AZStd::unique_ptr& property : inPropertySet.m_properties) + // { + // if (property->m_name == parsedPropertyId[0]) + // { + // return property.get(); + // } + // } + // } + + // return FindProperty(parsedPropertyId, inPropertySet.m_propertySets); + //} + + //const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::array_view parsedPropertyId, const PropertySet& inPropertySet) const + //{ + // MaterialTypeSourceData* nonConstThis = const_cast(this); + // PropertySet& nonConstPropertySet = *const_cast(&inPropertySet); + // return const_cast(nonConstThis->FindProperty(parsedPropertyId, nonConstPropertySet)); + //} + + const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty( + AZStd::array_view parsedPropertyId, + AZStd::array_view> inPropertySetList) const + { + for (const auto& propertySet : inPropertySetList) { - if (foundGroups.insert(groupDefinition.m_name).second) - { - groupDefinitions.push_back(groupDefinition); - } - else + if (propertySet->m_name == parsedPropertyId[0]) { - AZ_Warning("Material source data", false, "Duplicate group '%s' found.", groupDefinition.m_name.c_str()); + AZStd::array_view subPath {parsedPropertyId.begin() + 1, parsedPropertyId.end()}; + + if (subPath.size() == 1) + { + for (AZStd::unique_ptr& property : propertySet->m_properties) + { + if (property->m_name == subPath[0]) + { + return property.get(); + } + } + } + else if(subPath.size() > 1) + { + const MaterialTypeSourceData::PropertyDefinition* property = FindProperty(subPath, propertySet->m_propertySets); + if (property) + { + return property; + } + } } } - // Some groups are defined implicitly, in the "properties" section where a group name is used but not explicitly defined in the "groups" section. - for (const auto& propertyListPair : m_propertyLayout.m_properties) + return nullptr; + } + + //MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::array_view parsedPropertyId, AZStd::array_view> inPropertySetList) + //{ + // return const_cast(const_cast(this)->FindProperty(parsedPropertyId, inPropertySetList)); + //} + + const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::string_view propertyId) const + { + AZStd::vector tokens = TokenizeId(propertyId); + return FindProperty(tokens, m_propertyLayout.m_propertySets); + } + + AZStd::vector MaterialTypeSourceData::TokenizeId(AZStd::string_view id) + { + AZStd::vector tokens; + AzFramework::StringFunc::Tokenize(id, tokens, "./", true, true); + return tokens; + } + + AZStd::vector MaterialTypeSourceData::SplitId(AZStd::string_view id) + { + AZStd::vector parts; + parts.reserve(2); + size_t lastDelim = id.rfind('.', id.size()-1); + if (lastDelim == AZStd::string::npos) { - const AZStd::string& groupName = propertyListPair.first; - if (foundGroups.insert(groupName).second) + parts.push_back(id); + } + else + { + parts.push_back(AZStd::string_view{id.begin(), id.begin()+lastDelim}); + parts.push_back(AZStd::string_view{id.begin()+lastDelim+1, id.end()}); + } + + return parts; + } + + bool MaterialTypeSourceData::EnumeratePropertySets(const EnumeratePropertySetsCallback& callback, AZStd::string propertyNameContext, const AZStd::vector>& inPropertySetList) const + { + for (auto& propertySet : inPropertySetList) + { + if (!callback(propertyNameContext, propertySet.get())) { - MaterialTypeSourceData::GroupDefinition groupDefinition; - groupDefinition.m_name = groupName; - groupDefinitions.push_back(groupDefinition); + return false; // Stop processing + } + + const AZStd::string propertyNameContext2 = propertyNameContext + propertySet->m_name + "."; + + if (!EnumeratePropertySets(callback, propertyNameContext2, propertySet->m_propertySets)) + { + return false; // Stop processing } } - return groupDefinitions; + return true; } - void MaterialTypeSourceData::EnumerateProperties(const EnumeratePropertiesCallback& callback) const + bool MaterialTypeSourceData::EnumeratePropertySets(const EnumeratePropertySetsCallback& callback) const { if (!callback) { - return; + return false; } - for (const auto& propertyListPair : m_propertyLayout.m_properties) + return EnumeratePropertySets(callback, {}, m_propertyLayout.m_propertySets); + } + + bool MaterialTypeSourceData::EnumerateProperties(const EnumeratePropertiesCallback& callback, AZStd::string propertyNameContext, const AZStd::vector>& inPropertySetList) const + { + + for (auto& propertySet : inPropertySetList) { - const AZStd::string& groupName = propertyListPair.first; - const auto& propertyList = propertyListPair.second; - for (const auto& propertyDefinition : propertyList) + const AZStd::string propertyNameContext2 = propertyNameContext + propertySet->m_name + "."; + + for (auto& property : propertySet->m_properties) { - const AZStd::string& propertyName = propertyDefinition.m_name; - if (!callback(groupName, propertyName, propertyDefinition)) + if (!callback(propertyNameContext2, property.get())) { - return; + return false; // Stop processing } } + + if (!EnumerateProperties(callback, propertyNameContext2, propertySet->m_propertySets)) + { + return false; // Stop processing + } } + + return true; } - void MaterialTypeSourceData::EnumeratePropertiesInDisplayOrder(const EnumeratePropertiesCallback& callback) const + bool MaterialTypeSourceData::EnumerateProperties(const EnumeratePropertiesCallback& callback) const { if (!callback) { - return; + return false; } - for (const auto& groupDefinition : GetGroupDefinitionsInDisplayOrder()) + return EnumerateProperties(callback, {}, m_propertyLayout.m_propertySets); + } + + bool MaterialTypeSourceData::ConvertToNewDataFormat() + { + for (const auto& group : GetOldFormatGroupDefinitionsInDisplayOrder()) { - const AZStd::string& groupName = groupDefinition.m_name; - const auto propertyListItr = m_propertyLayout.m_properties.find(groupName); + auto propertyListItr = m_propertyLayout.m_properties.find(group.m_name); if (propertyListItr != m_propertyLayout.m_properties.end()) { const auto& propertyList = propertyListItr->second; - for (const auto& propertyDefinition : propertyList) + for (auto& propertyDefinition : propertyList) { - const AZStd::string& propertyName = propertyDefinition.m_name; - if (!callback(groupName, propertyName, propertyDefinition)) + PropertySet* propertySet = const_cast(const_cast(this)->FindPropertySet(group.m_name)); + + if (!propertySet) { - return; + m_propertyLayout.m_propertySets.emplace_back(AZStd::make_unique()); + m_propertyLayout.m_propertySets.back()->m_name = group.m_name; + m_propertyLayout.m_propertySets.back()->m_displayName = group.m_displayName; + m_propertyLayout.m_propertySets.back()->m_description = group.m_description; + propertySet = m_propertyLayout.m_propertySets.back().get(); } + + PropertyDefinition* newProperty = propertySet->AddProperty(propertyDefinition.m_name); + + *newProperty = propertyDefinition; } } } + + m_propertyLayout.m_groups.clear(); + m_propertyLayout.m_properties.clear(); + + return true; + } + + void MaterialTypeSourceData::ResolveUvEnums() + { + AZStd::vector enumValues; + enumValues.reserve(m_uvNameMap.size()); + for (const auto& uvNamePair : m_uvNameMap) + { + enumValues.push_back(uvNamePair.second); + } + + EnumerateProperties([&enumValues](const AZStd::string&, const MaterialTypeSourceData::PropertyDefinition* property) + { + if (property->m_dataType == AZ::RPI::MaterialPropertyDataType::Enum && property->m_enumIsUv) + { + // const_cast is safe because this is internal to the MaterialTypeSourceData. It isn't worth complicating things + // by adding another version of EnumerateProperties. + const_cast(property)->m_enumValues = enumValues; + } + return true; + }); + } + + AZStd::vector MaterialTypeSourceData::GetOldFormatGroupDefinitionsInDisplayOrder() const + { + AZStd::vector groupDefinitions; + groupDefinitions.reserve(m_propertyLayout.m_properties.size()); + + // Some groups are defined explicitly in the .materialtype file's "groups" section. This is the primary way groups are sorted in the UI. + AZStd::unordered_set foundGroups; + for (const auto& groupDefinition : m_propertyLayout.m_groups) + { + if (foundGroups.insert(groupDefinition.m_name).second) + { + groupDefinitions.push_back(groupDefinition); + } + else + { + AZ_Warning("Material source data", false, "Duplicate group '%s' found.", groupDefinition.m_name.c_str()); + } + } + + // Some groups are defined implicitly, in the "properties" section where a group name is used but not explicitly defined in the "groups" section. + for (const auto& propertyListPair : m_propertyLayout.m_properties) + { + const AZStd::string& groupName = propertyListPair.first; + if (foundGroups.insert(groupName).second) + { + MaterialTypeSourceData::GroupDefinition groupDefinition; + groupDefinition.m_name = groupName; + groupDefinitions.push_back(groupDefinition); + } + } + + return groupDefinitions; } + // TODO: It looks like this function doesn't operate on MaterialTypeSourceData data, it belongs in MaterialUtils bool MaterialTypeSourceData::ConvertPropertyValueToSourceDataFormat(const PropertyDefinition& propertyDefinition, MaterialPropertyValue& propertyValue) const { if (propertyDefinition.m_dataType == AZ::RPI::MaterialPropertyDataType::Enum && propertyValue.Is()) @@ -274,6 +596,178 @@ namespace AZ return true; } + bool MaterialTypeSourceData::BuildPropertyList( + const AZStd::string& materialTypeSourceFilePath, + MaterialTypeAssetCreator& materialTypeAssetCreator, + AZStd::vector& propertyNameContext, + const MaterialTypeSourceData::PropertySet* propertySet) const + { + for (const AZStd::unique_ptr& property : propertySet->m_properties) + { + // Register the property... + + MaterialPropertyId propertyId{propertyNameContext, property->m_name}; + + if (!propertyId.IsValid()) + { + // MaterialPropertyId reports an error message + return false; + } + + auto propertySetIter = AZStd::find_if(propertySet->GetPropertySets().begin(), propertySet->GetPropertySets().end(), + [&property](const AZStd::unique_ptr& existingPropertySet) + { + return existingPropertySet->GetName() == property->m_name; + }); + + if (propertySetIter != propertySet->GetPropertySets().end()) + { + AZ_Error("Material source data", false, "Material property '%s' collides with a PropertySet with the same ID.", propertyId.GetCStr()); + return false; + } + + materialTypeAssetCreator.BeginMaterialProperty(propertyId, property->m_dataType); + + if (property->m_dataType == MaterialPropertyDataType::Enum) + { + materialTypeAssetCreator.SetMaterialPropertyEnumNames(property->m_enumValues); + } + + for (auto& output : property->m_outputConnections) + { + switch (output.m_type) + { + case MaterialPropertyOutputType::ShaderInput: + { + materialTypeAssetCreator.ConnectMaterialPropertyToShaderInput(Name{output.m_fieldName}); + break; + } + case MaterialPropertyOutputType::ShaderOption: + { + if (output.m_shaderIndex >= 0) + { + materialTypeAssetCreator.ConnectMaterialPropertyToShaderOption(Name{output.m_fieldName}, output.m_shaderIndex); + } + else + { + materialTypeAssetCreator.ConnectMaterialPropertyToShaderOptions(Name{output.m_fieldName}); + } + break; + } + case MaterialPropertyOutputType::Invalid: + // Don't add any output mappings, this is the case when material functors are expected to process the property + break; + default: + AZ_Assert(false, "Unsupported MaterialPropertyOutputType"); + return false; + } + } + + materialTypeAssetCreator.EndMaterialProperty(); + + // Parse and set the property's value... + if (!property->m_value.IsValid()) + { + AZ_Warning("Material source data", false, "Source data for material property value is invalid."); + } + else + { + switch (property->m_dataType) + { + case MaterialPropertyDataType::Image: + { + Outcome> imageAssetResult = MaterialUtils::GetImageAssetReference(materialTypeSourceFilePath, property->m_value.GetValue()); + + if (imageAssetResult.IsSuccess()) + { + materialTypeAssetCreator.SetPropertyValue(propertyId, imageAssetResult.GetValue()); + } + else + { + materialTypeAssetCreator.ReportError("Material property '%s': Could not find the image '%s'", propertyId.GetCStr(), property->m_value.GetValue().data()); + } + } + break; + case MaterialPropertyDataType::Enum: + { + MaterialPropertyIndex propertyIndex = materialTypeAssetCreator.GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId); + const MaterialPropertyDescriptor* propertyDescriptor = materialTypeAssetCreator.GetMaterialPropertiesLayout()->GetPropertyDescriptor(propertyIndex); + + AZ::Name enumName = AZ::Name(property->m_value.GetValue()); + uint32_t enumValue = propertyDescriptor->GetEnumValue(enumName); + if (enumValue == MaterialPropertyDescriptor::InvalidEnumValue) + { + materialTypeAssetCreator.ReportError("Enum value '%s' couldn't be found in the 'enumValues' list", enumName.GetCStr()); + } + else + { + materialTypeAssetCreator.SetPropertyValue(propertyId, enumValue); + } + } + break; + default: + materialTypeAssetCreator.SetPropertyValue(propertyId, property->m_value); + break; + } + } + } + + for (const AZStd::unique_ptr& propertySubset : propertySet->m_propertySets) + { + propertyNameContext.push_back(propertySubset->m_name); + + bool success = BuildPropertyList( + materialTypeSourceFilePath, + materialTypeAssetCreator, + propertyNameContext, + propertySubset.get()); + + propertyNameContext.pop_back(); + + if (!success) + { + return false; + } + } + + // We cannot create the MaterialFunctor until after all the properties are added because + // CreateFunctor() may need to look up properties in the MaterialPropertiesLayout + for (auto& functorData : propertySet->m_materialFunctorSourceData) + { + MaterialFunctorSourceData::FunctorResult result = functorData->CreateFunctor( + MaterialFunctorSourceData::RuntimeContext( + materialTypeSourceFilePath, + materialTypeAssetCreator.GetMaterialPropertiesLayout(), + materialTypeAssetCreator.GetMaterialShaderResourceGroupLayout(), + materialTypeAssetCreator.GetShaderCollection() + ) + ); + + if (result.IsSuccess()) + { + Ptr& functor = result.GetValue(); + if (functor != nullptr) + { + materialTypeAssetCreator.AddMaterialFunctor(functor); + + for (const AZ::Name& optionName : functorData->GetActualSourceData()->GetShaderOptionDependencies()) + { + materialTypeAssetCreator.ClaimShaderOptionOwnership(Name{optionName.GetCStr()}); + } + } + } + else + { + materialTypeAssetCreator.ReportError("Failed to create MaterialFunctor"); + return false; + } + } + + + return true; + } + + Outcome> MaterialTypeSourceData::CreateMaterialTypeAsset(Data::AssetId assetId, AZStd::string_view materialTypeSourceFilePath, bool elevateWarnings) const { MaterialTypeAssetCreator materialTypeAssetCreator; @@ -327,103 +821,16 @@ namespace AZ return Failure(); } } - - for (auto& groupIter : m_propertyLayout.m_properties) + + for (const AZStd::unique_ptr& propertySet : m_propertyLayout.m_propertySets) { - const AZStd::string& groupName = groupIter.first; + AZStd::vector propertyNameContext; + propertyNameContext.push_back(propertySet->m_name); + bool success = BuildPropertyList(materialTypeSourceFilePath, materialTypeAssetCreator, propertyNameContext, propertySet.get()); - for (const PropertyDefinition& property : groupIter.second) + if (!success) { - // Register the property... - - MaterialPropertyId propertyId{ groupName, property.m_name }; - - if (!propertyId.IsValid()) - { - materialTypeAssetCreator.ReportWarning("Cannot create material property with invalid ID '%s'.", propertyId.GetCStr()); - continue; - } - - materialTypeAssetCreator.BeginMaterialProperty(propertyId, property.m_dataType); - - if (property.m_dataType == MaterialPropertyDataType::Enum) - { - materialTypeAssetCreator.SetMaterialPropertyEnumNames(property.m_enumValues); - } - - for (auto& output : property.m_outputConnections) - { - switch (output.m_type) - { - case MaterialPropertyOutputType::ShaderInput: - materialTypeAssetCreator.ConnectMaterialPropertyToShaderInput(Name{ output.m_fieldName.data() }); - break; - case MaterialPropertyOutputType::ShaderOption: - if (output.m_shaderIndex >= 0) - { - materialTypeAssetCreator.ConnectMaterialPropertyToShaderOption(Name{ output.m_fieldName.data() }, output.m_shaderIndex); - } - else - { - materialTypeAssetCreator.ConnectMaterialPropertyToShaderOptions(Name{ output.m_fieldName.data() }); - } - break; - case MaterialPropertyOutputType::Invalid: - // Don't add any output mappings, this is the case when material functors are expected to process the property - break; - default: - AZ_Assert(false, "Unsupported MaterialPropertyOutputType"); - return Failure(); - } - } - - materialTypeAssetCreator.EndMaterialProperty(); - - // Parse and set the property's value... - if (!property.m_value.IsValid()) - { - AZ_Warning("Material source data", false, "Source data for material property value is invalid."); - } - else - { - switch (property.m_dataType) - { - case MaterialPropertyDataType::Image: - { - Outcome> imageAssetResult = MaterialUtils::GetImageAssetReference(materialTypeSourceFilePath, property.m_value.GetValue()); - - if (imageAssetResult.IsSuccess()) - { - materialTypeAssetCreator.SetPropertyValue(propertyId, imageAssetResult.GetValue()); - } - else - { - materialTypeAssetCreator.ReportError("Material property '%s': Could not find the image '%s'", propertyId.GetCStr(), property.m_value.GetValue().data()); - } - } - break; - case MaterialPropertyDataType::Enum: - { - MaterialPropertyIndex propertyIndex = materialTypeAssetCreator.GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId); - const MaterialPropertyDescriptor* propertyDescriptor = materialTypeAssetCreator.GetMaterialPropertiesLayout()->GetPropertyDescriptor(propertyIndex); - - AZ::Name enumName = AZ::Name(property.m_value.GetValue()); - uint32_t enumValue = propertyDescriptor ? propertyDescriptor->GetEnumValue(enumName) : MaterialPropertyDescriptor::InvalidEnumValue; - if (enumValue == MaterialPropertyDescriptor::InvalidEnumValue) - { - materialTypeAssetCreator.ReportError("Enum value '%s' couldn't be found in the 'enumValues' list", enumName.GetCStr()); - } - else - { - materialTypeAssetCreator.SetPropertyValue(propertyId, enumValue); - } - } - break; - default: - materialTypeAssetCreator.SetPropertyValue(propertyId, property.m_value); - break; - } - } + return Failure(); } } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialUtils.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialUtils.cpp index 90ce9e66ce..174a2e682a 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialUtils.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialUtils.cpp @@ -90,6 +90,7 @@ namespace AZ settings.m_metadata.Add(fileLoadContext); JsonSerialization::Load(materialType, *document, settings); + materialType.ConvertToNewDataFormat(); materialType.ResolveUvEnums(); if (reportingHelper.ErrorsReported()) diff --git a/Gems/Atom/RPI/Code/Tests/Common/ErrorMessageFinder.cpp b/Gems/Atom/RPI/Code/Tests/Common/ErrorMessageFinder.cpp index fa88f145a6..06cdb073e9 100644 --- a/Gems/Atom/RPI/Code/Tests/Common/ErrorMessageFinder.cpp +++ b/Gems/Atom/RPI/Code/Tests/Common/ErrorMessageFinder.cpp @@ -84,7 +84,7 @@ namespace UnitTest } } - m_checked = true; + m_checked = true; } void ErrorMessageFinder::ReportFailure(const AZStd::string& failureMessage) diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertyIdTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertyIdTests.cpp index 2b729ed8ef..80da59b430 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertyIdTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertyIdTests.cpp @@ -94,6 +94,40 @@ namespace UnitTest errorMessageFinder.CheckExpectedErrorsFound(); } + TEST_F(MaterialPropertyIdTests, TestConstructWithMultipleParentNamesSeparateFromPropertyName) + { + AZStd::vector names{"layer1", "clearCoat", "normal"}; + MaterialPropertyId id{names, "factor"}; + EXPECT_TRUE(id.IsValid()); + EXPECT_STREQ(id.GetCStr(), "layer1.clearCoat.normal.factor"); + AZ::Name idCastedToName = id; + EXPECT_EQ(idCastedToName, AZ::Name{"layer1.clearCoat.normal.factor"}); + } + + TEST_F(MaterialPropertyIdTests, TestConstructWithMultipleParentNamesSeparateFromPropertyName_BadParentName) + { + ErrorMessageFinder errorMessageFinder; + errorMessageFinder.AddExpectedErrorMessage("not a valid identifier"); + + AZStd::vector names{"layer1", "clear-coat", "normal"}; + MaterialPropertyId id{names, "factor"}; + EXPECT_FALSE(id.IsValid()); + + errorMessageFinder.CheckExpectedErrorsFound(); + } + + TEST_F(MaterialPropertyIdTests, TestConstructWithMultipleParentNamesSeparateFromPropertyName_BadPropertyName) + { + ErrorMessageFinder errorMessageFinder; + errorMessageFinder.AddExpectedErrorMessage("not a valid identifier"); + + AZStd::vector names{"layer1", "clearCoat", "normal"}; + MaterialPropertyId id{names, "#factor"}; + EXPECT_FALSE(id.IsValid()); + + errorMessageFinder.CheckExpectedErrorsFound(); + } + TEST_F(MaterialPropertyIdTests, TestParse) { MaterialPropertyId id = MaterialPropertyId::Parse("layer1.clearCoat.normal.factor"); diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp index dd3b3b2711..293289b00f 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp @@ -201,33 +201,38 @@ namespace UnitTest TEST_F(MaterialSourceDataTests, TestJsonRoundTrip) { const char* materialTypeJson = - "{ \n" - " \"propertyLayout\": { \n" - " \"version\": 1, \n" - " \"groups\": [ \n" - " { \"name\": \"groupA\" }, \n" - " { \"name\": \"groupB\" }, \n" - " { \"name\": \"groupC\" } \n" - " ], \n" - " \"properties\": { \n" - " \"groupA\": [ \n" - " {\"name\": \"MyBool\", \"type\": \"bool\"}, \n" - " {\"name\": \"MyInt\", \"type\": \"int\"}, \n" - " {\"name\": \"MyUInt\", \"type\": \"uint\"} \n" - " ], \n" - " \"groupB\": [ \n" - " {\"name\": \"MyFloat\", \"type\": \"float\"}, \n" - " {\"name\": \"MyFloat2\", \"type\": \"vector2\"}, \n" - " {\"name\": \"MyFloat3\", \"type\": \"vector3\"} \n" - " ], \n" - " \"groupC\": [ \n" - " {\"name\": \"MyFloat4\", \"type\": \"vector4\"}, \n" - " {\"name\": \"MyColor\", \"type\": \"color\"}, \n" - " {\"name\": \"MyImage\", \"type\": \"image\"} \n" - " ] \n" - " } \n" - " } \n" - "} \n"; + R"( + { + "propertyLayout": { + "propertySets": [ + { + "name": "groupA", + "properties": [ + {"name": "MyBool", "type": "bool"}, + {"name": "MyInt", "type": "int"}, + {"name": "MyUInt", "type": "uint"} + ] + }, + { + "name": "groupB", + "properties": [ + {"name": "MyFloat", "type": "float"}, + {"name": "MyFloat2", "type": "vector2"}, + {"name": "MyFloat3", "type": "vector3"} + ] + }, + { + "name": "groupC", + "properties": [ + {"name": "MyFloat4", "type": "vector4"}, + {"name": "MyColor", "type": "color"}, + {"name": "MyImage", "type": "image"} + ] + } + ] + } + } + )"; const char* materialTypeFilePath = "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/Temp/roundTripTest.materialtype"; @@ -259,25 +264,29 @@ namespace UnitTest MaterialSourceData sourceDataCopy; JsonTestResult loadResult = LoadTestDataFromJson(sourceDataCopy, sourceDataSerialized); - + CheckEqual(sourceDataOriginal, sourceDataCopy); } TEST_F(MaterialSourceDataTests, Load_MaterialTypeAfterPropertyList) { const AZStd::string simpleMaterialTypeJson = R"( - { - "propertyLayout": { - "properties": { - "general": [ + { + "propertyLayout": { + "propertySets": + [ { - "name": "testColor", - "type": "color" + "name": "general", + "properties": [ + { + "name": "testColor", + "type": "color" + } + ] } ] } } - } )"; const char* materialTypeFilePath = "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/Temp/simpleMaterialType.materialtype"; @@ -376,18 +385,22 @@ namespace UnitTest TEST_F(MaterialSourceDataTests, Load_MaterialTypeMessagesAreReported) { const AZStd::string simpleMaterialTypeJson = R"( - { - "propertyLayout": { - "properties": { - "general": [ + { + "propertyLayout": { + "propertySets": + [ { - "name": "testColor", - "type": "color" + "name": "general", + "properties": [ + { + "name": "testColor", + "type": "color" + } + ] } ] } } - } )"; const char* materialTypeFilePath = "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/Temp/simpleMaterialType.materialtype"; @@ -416,24 +429,28 @@ namespace UnitTest EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing()); // propertyLayout is a field in the material type, not the material - EXPECT_TRUE(loadResult.ContainsMessage("[simpleMaterialType.materialtype]/propertyLayout/properties", "Successfully read")); + EXPECT_TRUE(loadResult.ContainsMessage("[simpleMaterialType.materialtype]/propertyLayout/propertySets", "Successfully read")); } TEST_F(MaterialSourceDataTests, Load_Error_PropertyNotFound) { const AZStd::string simpleMaterialTypeJson = R"( - { - "propertyLayout": { - "properties": { - "general": [ + { + "propertyLayout": { + "propertySets": + [ { - "name": "testColor", - "type": "color" + "name": "general", + "properties": [ + { + "name": "testColor", + "type": "color" + } + ] } ] } } - } )"; const char* materialTypeFilePath = "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/Temp/simpleMaterialType.materialtype"; diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp index ba4a58b9ff..00523abbd8 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -225,6 +226,7 @@ namespace UnitTest { serializeContext->Class() ->Version(1) + ->Field("enableProperty", &SetShaderOptionFunctorSourceData::m_enablePropertyName) ; } } @@ -243,6 +245,8 @@ namespace UnitTest Ptr functor = aznew SetShaderOptionFunctor; return Success(Ptr(functor)); } + + AZStd::string m_enablePropertyName; }; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -350,9 +354,316 @@ namespace UnitTest EXPECT_EQ(propertyDescriptor->GetOutputConnections()[i].m_containerIndex.GetIndex(), expectedValues.m_outputConnections[i].m_shaderIndex); } } - }; + TEST_F(MaterialTypeSourceDataTests, PopulateAndSearchPropertyLayout) + { + MaterialTypeSourceData sourceData; + + // Here we are building up multiple layers of property sets and properties, using a variety of different Add functions, + // going through the MaterialTypeSourceData or going to the PropertySet directly. + + MaterialTypeSourceData::PropertySet* layer1 = sourceData.AddPropertySet("layer1"); + MaterialTypeSourceData::PropertySet* layer2 = sourceData.AddPropertySet("layer2"); + MaterialTypeSourceData::PropertySet* blend = sourceData.AddPropertySet("blend"); + + MaterialTypeSourceData::PropertySet* layer1_baseColor = layer1->AddPropertySet("baseColor"); + MaterialTypeSourceData::PropertySet* layer2_baseColor = layer2->AddPropertySet("baseColor"); + + MaterialTypeSourceData::PropertySet* layer1_roughness = sourceData.AddPropertySet("layer1.roughness"); + MaterialTypeSourceData::PropertySet* layer2_roughness = sourceData.AddPropertySet("layer2.roughness"); + + MaterialTypeSourceData::PropertyDefinition* layer1_baseColor_texture = layer1_baseColor->AddProperty("texture"); + MaterialTypeSourceData::PropertyDefinition* layer2_baseColor_texture = layer2_baseColor->AddProperty("texture"); + + MaterialTypeSourceData::PropertyDefinition* layer1_roughness_texture = sourceData.AddProperty("layer1.roughness.texture"); + MaterialTypeSourceData::PropertyDefinition* layer2_roughness_texture = sourceData.AddProperty("layer2.roughness.texture"); + + // We're doing clear coat only on layer2, for brevity + MaterialTypeSourceData::PropertySet* layer2_clearCoat = layer2->AddPropertySet("clearCoat"); + MaterialTypeSourceData::PropertySet* layer2_clearCoat_roughness = layer2_clearCoat->AddPropertySet("roughness"); + MaterialTypeSourceData::PropertySet* layer2_clearCoat_normal = layer2_clearCoat->AddPropertySet("normal"); + MaterialTypeSourceData::PropertyDefinition* layer2_clearCoat_enabled = layer2_clearCoat->AddProperty("enabled"); + MaterialTypeSourceData::PropertyDefinition* layer2_clearCoat_roughness_texture = layer2_clearCoat_roughness->AddProperty("texture"); + MaterialTypeSourceData::PropertyDefinition* layer2_clearCoat_normal_texture = layer2_clearCoat_normal->AddProperty("texture"); + MaterialTypeSourceData::PropertyDefinition* layer2_clearCoat_normal_factor = layer2_clearCoat_normal->AddProperty("factor"); + + MaterialTypeSourceData::PropertyDefinition* blend_factor = blend->AddProperty("factor"); + + // Check the available Find functions + + EXPECT_EQ(nullptr, sourceData.FindProperty("DoesNotExist")); + EXPECT_EQ(nullptr, sourceData.FindProperty("layer1.DoesNotExist")); + EXPECT_EQ(nullptr, sourceData.FindProperty("layer1.baseColor.DoesNotExist")); + EXPECT_EQ(nullptr, sourceData.FindProperty("baseColor.texture")); + EXPECT_EQ(nullptr, sourceData.FindProperty("baseColor")); // This is a property set, not a property + EXPECT_EQ(nullptr, sourceData.FindPropertySet("baseColor.texture")); // This is a property, not a property set + + EXPECT_EQ(layer1, sourceData.FindPropertySet("layer1")); + EXPECT_EQ(layer2, sourceData.FindPropertySet("layer2")); + EXPECT_EQ(blend, sourceData.FindPropertySet("blend")); + + EXPECT_EQ(layer1_baseColor, sourceData.FindPropertySet("layer1.baseColor")); + EXPECT_EQ(layer2_baseColor, sourceData.FindPropertySet("layer2.baseColor")); + + EXPECT_EQ(layer1_roughness, sourceData.FindPropertySet("layer1.roughness")); + EXPECT_EQ(layer2_roughness, sourceData.FindPropertySet("layer2.roughness")); + + EXPECT_EQ(layer1_baseColor_texture, sourceData.FindProperty("layer1.baseColor.texture")); + EXPECT_EQ(layer2_baseColor_texture, sourceData.FindProperty("layer2.baseColor.texture")); + EXPECT_EQ(layer1_roughness_texture, sourceData.FindProperty("layer1.roughness.texture")); + EXPECT_EQ(layer2_roughness_texture, sourceData.FindProperty("layer2.roughness.texture")); + + EXPECT_EQ(layer2_clearCoat, sourceData.FindPropertySet("layer2.clearCoat")); + EXPECT_EQ(layer2_clearCoat_roughness, sourceData.FindPropertySet("layer2.clearCoat.roughness")); + EXPECT_EQ(layer2_clearCoat_normal, sourceData.FindPropertySet("layer2.clearCoat.normal")); + + EXPECT_EQ(layer2_clearCoat_enabled, sourceData.FindProperty("layer2.clearCoat.enabled")); + EXPECT_EQ(layer2_clearCoat_roughness_texture, sourceData.FindProperty("layer2.clearCoat.roughness.texture")); + EXPECT_EQ(layer2_clearCoat_normal_texture, sourceData.FindProperty("layer2.clearCoat.normal.texture")); + EXPECT_EQ(layer2_clearCoat_normal_factor, sourceData.FindProperty("layer2.clearCoat.normal.factor")); + + EXPECT_EQ(blend_factor, sourceData.FindProperty("blend.factor")); + + // Check EnumeratePropertySets + + struct EnumeratePropertySetsResult + { + AZStd::string m_propertyIdContext; + const MaterialTypeSourceData::PropertySet* m_propertySet; + + void Check(AZStd::string expectedIdContext, const MaterialTypeSourceData::PropertySet* expectedPropertySet) + { + EXPECT_EQ(expectedIdContext, m_propertyIdContext); + EXPECT_EQ(expectedPropertySet, m_propertySet); + } + }; + AZStd::vector enumeratePropertySetsResults; + + sourceData.EnumeratePropertySets([&enumeratePropertySetsResults](const AZStd::string& propertyIdContext, const MaterialTypeSourceData::PropertySet* propertySet) + { + enumeratePropertySetsResults.push_back(EnumeratePropertySetsResult{propertyIdContext, propertySet}); + return true; + }); + + int resultIndex = 0; + enumeratePropertySetsResults[resultIndex++].Check("", layer1); + enumeratePropertySetsResults[resultIndex++].Check("layer1.", layer1_baseColor); + enumeratePropertySetsResults[resultIndex++].Check("layer1.", layer1_roughness); + enumeratePropertySetsResults[resultIndex++].Check("", layer2); + enumeratePropertySetsResults[resultIndex++].Check("layer2.", layer2_baseColor); + enumeratePropertySetsResults[resultIndex++].Check("layer2.", layer2_roughness); + enumeratePropertySetsResults[resultIndex++].Check("layer2.", layer2_clearCoat); + enumeratePropertySetsResults[resultIndex++].Check("layer2.clearCoat.", layer2_clearCoat_roughness); + enumeratePropertySetsResults[resultIndex++].Check("layer2.clearCoat.", layer2_clearCoat_normal); + enumeratePropertySetsResults[resultIndex++].Check("", blend); + EXPECT_EQ(resultIndex, enumeratePropertySetsResults.size()); + + // Check EnumerateProperties + + struct EnumeratePropertiesResult + { + AZStd::string m_propertyIdContext; + const MaterialTypeSourceData::PropertyDefinition* m_propertyDefinition; + + void Check(AZStd::string expectedIdContext, const MaterialTypeSourceData::PropertyDefinition* expectedPropertyDefinition) + { + EXPECT_EQ(expectedIdContext, m_propertyIdContext); + EXPECT_EQ(expectedPropertyDefinition, m_propertyDefinition); + } + }; + AZStd::vector enumeratePropertiesResults; + + sourceData.EnumerateProperties([&enumeratePropertiesResults](const AZStd::string& propertyIdContext, const MaterialTypeSourceData::PropertyDefinition* propertyDefinition) + { + enumeratePropertiesResults.push_back(EnumeratePropertiesResult{propertyIdContext, propertyDefinition}); + return true; + }); + + resultIndex = 0; + enumeratePropertiesResults[resultIndex++].Check("layer1.baseColor.", layer1_baseColor_texture); + enumeratePropertiesResults[resultIndex++].Check("layer1.roughness.", layer1_roughness_texture); + enumeratePropertiesResults[resultIndex++].Check("layer2.baseColor.", layer2_baseColor_texture); + enumeratePropertiesResults[resultIndex++].Check("layer2.roughness.", layer2_roughness_texture); + enumeratePropertiesResults[resultIndex++].Check("layer2.clearCoat.", layer2_clearCoat_enabled); + enumeratePropertiesResults[resultIndex++].Check("layer2.clearCoat.roughness.", layer2_clearCoat_roughness_texture); + enumeratePropertiesResults[resultIndex++].Check("layer2.clearCoat.normal.", layer2_clearCoat_normal_texture); + enumeratePropertiesResults[resultIndex++].Check("layer2.clearCoat.normal.", layer2_clearCoat_normal_factor); + enumeratePropertiesResults[resultIndex++].Check("blend.", blend_factor); + EXPECT_EQ(resultIndex, enumeratePropertiesResults.size()); + } + + TEST_F(MaterialTypeSourceDataTests, AddProperty_Error_AddPropertyWithInvalidName) + { + MaterialTypeSourceData sourceData; + + MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("main"); + + ErrorMessageFinder errorMessageFinder; + errorMessageFinder.AddExpectedErrorMessage("'' is not a valid identifier"); + errorMessageFinder.AddExpectedErrorMessage("'main.' is not a valid identifier"); + errorMessageFinder.AddExpectedErrorMessage("'base-color' is not a valid identifier"); + + EXPECT_FALSE(propertySet->AddProperty("")); + EXPECT_FALSE(propertySet->AddProperty("main.")); + EXPECT_FALSE(sourceData.AddProperty("main.base-color")); + + EXPECT_TRUE(propertySet->GetProperties().empty()); + + errorMessageFinder.CheckExpectedErrorsFound(); + } + + TEST_F(MaterialTypeSourceDataTests, AddPropertySet_Error_InvalidName) + { + MaterialTypeSourceData sourceData; + + MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("general"); + + ErrorMessageFinder errorMessageFinder; + errorMessageFinder.AddExpectedErrorMessage("'' is not a valid identifier", 2); + errorMessageFinder.AddExpectedErrorMessage("'base-color' is not a valid identifier"); + errorMessageFinder.AddExpectedErrorMessage("'look@it' is not a valid identifier"); + + EXPECT_FALSE(propertySet->AddPropertySet("")); + EXPECT_FALSE(sourceData.AddPropertySet("")); + EXPECT_FALSE(sourceData.AddPropertySet("base-color")); + EXPECT_FALSE(sourceData.AddPropertySet("general.look@it")); + + EXPECT_TRUE(propertySet->GetProperties().empty()); + + errorMessageFinder.CheckExpectedErrorsFound(); + } + + TEST_F(MaterialTypeSourceDataTests, AddProperty_Error_AddDuplicateProperty) + { + MaterialTypeSourceData sourceData; + + MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("main"); + + ErrorMessageFinder errorMessageFinder; + errorMessageFinder.AddExpectedErrorMessage("PropertySet 'main' already contains a property named 'foo'", 2); + + EXPECT_TRUE(propertySet->AddProperty("foo")); + EXPECT_FALSE(propertySet->AddProperty("foo")); + EXPECT_FALSE(sourceData.AddProperty("main.foo")); + + EXPECT_EQ(propertySet->GetProperties().size(), 1); + + errorMessageFinder.CheckExpectedErrorsFound(); + } + + TEST_F(MaterialTypeSourceDataTests, AddProperty_Error_AddLooseProperty) + { + MaterialTypeSourceData sourceData; + ErrorMessageFinder errorMessageFinder("Property id 'foo' is invalid. Properties must be added to a PropertySet"); + EXPECT_FALSE(sourceData.AddProperty("foo")); + errorMessageFinder.CheckExpectedErrorsFound(); + } + + TEST_F(MaterialTypeSourceDataTests, AddProperty_Error_PropertySetDoesNotExist ) + { + MaterialTypeSourceData sourceData; + ErrorMessageFinder errorMessageFinder("PropertySet 'DNE' does not exists"); + EXPECT_FALSE(sourceData.AddProperty("DNE.foo")); + errorMessageFinder.CheckExpectedErrorsFound(); + } + + TEST_F(MaterialTypeSourceDataTests, AddPropertySet_Error_PropertySetDoesNotExist ) + { + MaterialTypeSourceData sourceData; + ErrorMessageFinder errorMessageFinder("PropertySet 'DNE' does not exists"); + EXPECT_FALSE(sourceData.AddPropertySet("DNE.foo")); + errorMessageFinder.CheckExpectedErrorsFound(); + } + + TEST_F(MaterialTypeSourceDataTests, AddPropertySet_Error_AddDuplicatePropertySet) + { + MaterialTypeSourceData sourceData; + + MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("main"); + sourceData.AddPropertySet("main.level2"); + + ErrorMessageFinder errorMessageFinder; + errorMessageFinder.AddExpectedErrorMessage("PropertySet named 'main' already exists", 1); + errorMessageFinder.AddExpectedErrorMessage("PropertySet named 'level2' already exists", 2); + + EXPECT_FALSE(sourceData.AddPropertySet("main")); + EXPECT_FALSE(sourceData.AddPropertySet("main.level2")); + EXPECT_FALSE(propertySet->AddPropertySet("level2")); + + errorMessageFinder.CheckExpectedErrorsFound(); + + EXPECT_EQ(sourceData.GetPropertyLayout().m_propertySets.size(), 1); + EXPECT_EQ(propertySet->GetPropertySets().size(), 1); + } + + TEST_F(MaterialTypeSourceDataTests, AddPropertySet_Error_NameCollidesWithProperty ) + { + MaterialTypeSourceData sourceData; + sourceData.AddPropertySet("main"); + sourceData.AddProperty("main.foo"); + + ErrorMessageFinder errorMessageFinder("PropertySet name 'foo' collides with a Property of the same name"); + EXPECT_FALSE(sourceData.AddPropertySet("main.foo")); + errorMessageFinder.CheckExpectedErrorsFound(); + } + + TEST_F(MaterialTypeSourceDataTests, AddProperty_Error_NameCollidesWithPropertySet ) + { + MaterialTypeSourceData sourceData; + sourceData.AddPropertySet("main"); + sourceData.AddPropertySet("main.foo"); + + ErrorMessageFinder errorMessageFinder("Property name 'foo' collides with a PropertySet of the same name"); + EXPECT_FALSE(sourceData.AddProperty("main.foo")); + errorMessageFinder.CheckExpectedErrorsFound(); + } + + TEST_F(MaterialTypeSourceDataTests, ResolveUvStreamAsEnum) + { + MaterialTypeSourceData sourceData; + + sourceData.m_uvNameMap["UV0"] = "Tiled"; + sourceData.m_uvNameMap["UV1"] = "Unwrapped"; + sourceData.m_uvNameMap["UV2"] = "Other"; + + sourceData.AddPropertySet("a"); + sourceData.AddPropertySet("a.b"); + sourceData.AddPropertySet("c"); + sourceData.AddPropertySet("c.d"); + sourceData.AddPropertySet("c.d.e"); + + MaterialTypeSourceData::PropertyDefinition* enum1 = sourceData.AddProperty("a.enum1"); + MaterialTypeSourceData::PropertyDefinition* enum2 = sourceData.AddProperty("a.b.enum2"); + MaterialTypeSourceData::PropertyDefinition* enum3 = sourceData.AddProperty("c.d.e.enum3"); + MaterialTypeSourceData::PropertyDefinition* notEnum = sourceData.AddProperty("c.d.myFloat"); + + enum1->m_dataType = MaterialPropertyDataType::Enum; + enum2->m_dataType = MaterialPropertyDataType::Enum; + enum3->m_dataType = MaterialPropertyDataType::Enum; + notEnum->m_dataType = MaterialPropertyDataType::Float; + + enum1->m_enumIsUv = true; + enum2->m_enumIsUv = false; + enum3->m_enumIsUv = true; + + sourceData.ResolveUvEnums(); + + EXPECT_STREQ(enum1->m_enumValues[0].c_str(), "Tiled"); + EXPECT_STREQ(enum1->m_enumValues[1].c_str(), "Unwrapped"); + EXPECT_STREQ(enum1->m_enumValues[2].c_str(), "Other"); + + EXPECT_STREQ(enum3->m_enumValues[0].c_str(), "Tiled"); + EXPECT_STREQ(enum3->m_enumValues[1].c_str(), "Unwrapped"); + EXPECT_STREQ(enum3->m_enumValues[2].c_str(), "Other"); + + // enum2 is not a UV stream enum + EXPECT_EQ(enum2->m_enumValues.size(), 0); + + // myFloat is not even an enum + EXPECT_EQ(notEnum->m_enumValues.size(), 0); + } + TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_GetMaterialSrgAsset) { MaterialTypeSourceData sourceData; @@ -509,15 +820,14 @@ namespace UnitTest sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{ TestShaderFilename }); - MaterialTypeSourceData::PropertyDefinition propertySource; - propertySource.m_name = "MyBool"; - propertySource.m_displayName = "My Bool"; - propertySource.m_description = "This is a bool"; - propertySource.m_dataType = MaterialPropertyDataType::Bool; - propertySource.m_value = true; - propertySource.m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{ MaterialPropertyOutputType::ShaderInput, AZStd::string("m_bool") }); - sourceData.m_propertyLayout.m_properties["general"].push_back(propertySource); - + MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("general"); + MaterialTypeSourceData::PropertyDefinition* property = propertySet->AddProperty("MyBool"); + property->m_displayName = "My Bool"; + property->m_description = "This is a bool"; + property->m_dataType = MaterialPropertyDataType::Bool; + property->m_value = true; + property->m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{ MaterialPropertyOutputType::ShaderInput, AZStd::string("m_bool") }); + auto materialTypeOutcome = sourceData.CreateMaterialTypeAsset(Uuid::CreateRandom()); EXPECT_TRUE(materialTypeOutcome.IsSuccess()); Data::Asset materialTypeAsset = materialTypeOutcome.GetValue(); @@ -525,7 +835,7 @@ namespace UnitTest const MaterialPropertyIndex propertyIndex = materialTypeAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(Name{ "general.MyBool" }); const MaterialPropertyDescriptor* propertyDescriptor = materialTypeAsset->GetMaterialPropertiesLayout()->GetPropertyDescriptor(propertyIndex); - ValidateCommonDescriptorFields(propertySource, propertyDescriptor); + ValidateCommonDescriptorFields(*property, propertyDescriptor); EXPECT_EQ(propertyDescriptor->GetOutputConnections()[0].m_itemIndex.GetIndex(), 7); } @@ -534,20 +844,19 @@ namespace UnitTest MaterialTypeSourceData sourceData; sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{ TestShaderFilename }); - - MaterialTypeSourceData::PropertyDefinition propertySource; - propertySource.m_name = "MyFloat"; - propertySource.m_displayName = "My Float"; - propertySource.m_description = "This is a float"; - propertySource.m_min = 0.0f; - propertySource.m_max = 1.0f; - propertySource.m_softMin = 0.2f; - propertySource.m_softMax = 1.0f; - propertySource.m_step = 0.01f; - propertySource.m_dataType = MaterialPropertyDataType::Float; - propertySource.m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{ MaterialPropertyOutputType::ShaderInput, AZStd::string("m_float") }); - sourceData.m_propertyLayout.m_properties["general"].push_back(propertySource); - + + MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("general"); + MaterialTypeSourceData::PropertyDefinition* property = propertySet->AddProperty("MyFloat"); + property->m_displayName = "My Float"; + property->m_description = "This is a float"; + property->m_min = 0.0f; + property->m_max = 1.0f; + property->m_softMin = 0.2f; + property->m_softMax = 1.0f; + property->m_step = 0.01f; + property->m_dataType = MaterialPropertyDataType::Float; + property->m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{ MaterialPropertyOutputType::ShaderInput, AZStd::string("m_float") }); + auto materialTypeOutcome = sourceData.CreateMaterialTypeAsset(Uuid::CreateRandom()); EXPECT_TRUE(materialTypeOutcome.IsSuccess()); Data::Asset materialTypeAsset = materialTypeOutcome.GetValue(); @@ -555,7 +864,7 @@ namespace UnitTest const MaterialPropertyIndex propertyIndex = materialTypeAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(Name{"general.MyFloat" }); const MaterialPropertyDescriptor* propertyDescriptor = materialTypeAsset->GetMaterialPropertiesLayout()->GetPropertyDescriptor(propertyIndex); - ValidateCommonDescriptorFields(propertySource, propertyDescriptor); + ValidateCommonDescriptorFields(*property, propertyDescriptor); EXPECT_EQ(propertyDescriptor->GetOutputConnections()[0].m_itemIndex.GetIndex(), 1); } @@ -564,15 +873,14 @@ namespace UnitTest MaterialTypeSourceData sourceData; sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{ TestShaderFilename }); - - MaterialTypeSourceData::PropertyDefinition propertySource; - propertySource.m_name = "MyImage"; - propertySource.m_displayName = "My Image"; - propertySource.m_description = "This is an image"; - propertySource.m_dataType = MaterialPropertyDataType::Image; - propertySource.m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{ MaterialPropertyOutputType::ShaderInput, AZStd::string("m_image") }); - sourceData.m_propertyLayout.m_properties["general"].push_back(propertySource); - + + MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("general"); + MaterialTypeSourceData::PropertyDefinition* property = propertySet->AddProperty("MyImage"); + property->m_displayName = "My Image"; + property->m_description = "This is an image"; + property->m_dataType = MaterialPropertyDataType::Image; + property->m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{ MaterialPropertyOutputType::ShaderInput, AZStd::string("m_image") }); + auto materialTypeOutcome = sourceData.CreateMaterialTypeAsset(Uuid::CreateRandom()); EXPECT_TRUE(materialTypeOutcome.IsSuccess()); Data::Asset materialTypeAsset = materialTypeOutcome.GetValue(); @@ -580,7 +888,7 @@ namespace UnitTest const MaterialPropertyIndex propertyIndex = materialTypeAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(Name{"general.MyImage" }); const MaterialPropertyDescriptor* propertyDescriptor = materialTypeAsset->GetMaterialPropertiesLayout()->GetPropertyDescriptor(propertyIndex); - ValidateCommonDescriptorFields(propertySource, propertyDescriptor); + ValidateCommonDescriptorFields(*property, propertyDescriptor); EXPECT_EQ(propertyDescriptor->GetOutputConnections()[0].m_itemIndex.GetIndex(), 0); } @@ -589,21 +897,20 @@ namespace UnitTest MaterialTypeSourceData sourceData; sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{TestShaderFilename}); - - MaterialTypeSourceData::PropertyDefinition propertySource; - propertySource.m_name = "MyInt"; - propertySource.m_displayName = "My Integer"; - propertySource.m_dataType = MaterialPropertyDataType::Int; - propertySource.m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{MaterialPropertyOutputType::ShaderOption, AZStd::string("o_foo"), 0}); - sourceData.m_propertyLayout.m_properties["general"].push_back(propertySource); - + + MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("general"); + MaterialTypeSourceData::PropertyDefinition* property = propertySet->AddProperty("MyInt"); + property->m_displayName = "My Integer"; + property->m_dataType = MaterialPropertyDataType::Int; + property->m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{MaterialPropertyOutputType::ShaderOption, AZStd::string("o_foo"), 0}); + auto materialTypeOutcome = sourceData.CreateMaterialTypeAsset(Uuid::CreateRandom()); EXPECT_TRUE(materialTypeOutcome.IsSuccess()); Data::Asset materialTypeAsset = materialTypeOutcome.GetValue(); const MaterialPropertyDescriptor* propertyDescriptor = materialTypeAsset->GetMaterialPropertiesLayout()->GetPropertyDescriptor(MaterialPropertyIndex{0}); - ValidateCommonDescriptorFields(propertySource, propertyDescriptor); + ValidateCommonDescriptorFields(*property, propertyDescriptor); EXPECT_EQ(propertyDescriptor->GetOutputConnections()[0].m_itemIndex.GetIndex(), 1); } @@ -612,13 +919,12 @@ namespace UnitTest MaterialTypeSourceData sourceData; sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{TestShaderFilename}); - - MaterialTypeSourceData::PropertyDefinition propertySource; - propertySource.m_name = "MyInt"; - propertySource.m_dataType = MaterialPropertyDataType::Int; - propertySource.m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{MaterialPropertyOutputType::ShaderOption, AZStd::string("DoesNotExist"), 0}); - sourceData.m_propertyLayout.m_properties["general"].push_back(propertySource); - + + MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("general"); + MaterialTypeSourceData::PropertyDefinition* property = propertySet->AddProperty("MyInt"); + property->m_dataType = MaterialPropertyDataType::Int; + property->m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{MaterialPropertyOutputType::ShaderOption, AZStd::string("DoesNotExist"), 0}); + AZ_TEST_START_TRACE_SUPPRESSION; auto materialTypeOutcome = sourceData.CreateMaterialTypeAsset(Uuid::CreateRandom()); AZ_TEST_STOP_TRACE_SUPPRESSION(2); // There happens to be an extra assert for "Cannot continue building MaterialAsset because 1 error(s) reported" @@ -626,66 +932,140 @@ namespace UnitTest EXPECT_FALSE(materialTypeOutcome.IsSuccess()); } - TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_Error_InvalidGroupNameId) + TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_Error_InvalidGroupName) { - MaterialTypeSourceData sourceData; - - MaterialTypeSourceData::PropertyDefinition propertySource; - propertySource.m_dataType = MaterialPropertyDataType::Int; - - propertySource.m_name = "a"; - sourceData.m_propertyLayout.m_properties["not a valid name because it has spaces"].push_back(propertySource); + const AZStd::string inputJson = R"( + { + "propertyLayout": { + "propertySets": [ + { + "name": "not a valid name because it has spaces", + "properties": [ + { + "name": "foo", + "type": "Bool" + } + ] + } + ] + } + } + )"; - // Expected errors: - // Group name 'not a valid name because it has spaces' is not a valid identifier. - // Warning: Cannot create material property with invalid ID 'not a valid name because it has spaces'. - // Failed to build MaterialAsset because 1 warning(s) reported - AZ_TEST_START_TRACE_SUPPRESSION; + MaterialTypeSourceData sourceData; + JsonTestResult loadResult = LoadTestDataFromJson(sourceData, inputJson); + EXPECT_EQ(loadResult.m_jsonResultCode.GetProcessing(), JsonSerializationResult::Processing::Completed); + + ErrorMessageFinder errorMessageFinder{"'not a valid name because it has spaces' is not a valid identifier"}; auto materialTypeOutcome = sourceData.CreateMaterialTypeAsset(Uuid::CreateRandom()); - AZ_TEST_STOP_TRACE_SUPPRESSION(2); - EXPECT_FALSE(materialTypeOutcome.IsSuccess()); + errorMessageFinder.CheckExpectedErrorsFound(); } - TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_Error_InvalidPropertyNameId) + TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_Error_InvalidPropertyName) { - MaterialTypeSourceData sourceData; - - MaterialTypeSourceData::PropertyDefinition propertySource; - propertySource.m_dataType = MaterialPropertyDataType::Int; - - propertySource.m_name = "not a valid name because it has spaces"; - sourceData.m_propertyLayout.m_properties["general"].push_back(propertySource); + const AZStd::string inputJson = R"( + { + "propertyLayout": { + "propertySets": [ + { + "name": "general", + "properties": [ + { + "name": "not a valid name because it has spaces", + "type": "Bool" + } + ] + } + ] + } + } + )"; - // Expected errors: - // Property name 'not a valid name because it has spaces' is not a valid identifier. - // Warning: Cannot create material property with invalid ID 'not a valid name because it has spaces'. - // Failed to build MaterialAsset because 1 warning(s) reported - AZ_TEST_START_TRACE_SUPPRESSION; + MaterialTypeSourceData sourceData; + JsonTestResult loadResult = LoadTestDataFromJson(sourceData, inputJson); + EXPECT_EQ(loadResult.m_jsonResultCode.GetProcessing(), JsonSerializationResult::Processing::Completed); + + ErrorMessageFinder errorMessageFinder{"'not a valid name because it has spaces' is not a valid identifier"}; auto materialTypeOutcome = sourceData.CreateMaterialTypeAsset(Uuid::CreateRandom()); - AZ_TEST_STOP_TRACE_SUPPRESSION(2); - EXPECT_FALSE(materialTypeOutcome.IsSuccess()); + errorMessageFinder.CheckExpectedErrorsFound(); } TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_Error_DuplicatePropertyId) { - MaterialTypeSourceData sourceData; - - MaterialTypeSourceData::PropertyDefinition propertySource; - propertySource.m_dataType = MaterialPropertyDataType::Int; - propertySource.m_name = "a"; - sourceData.m_propertyLayout.m_properties["general"].push_back(propertySource); - sourceData.m_propertyLayout.m_properties["general"].push_back(propertySource); + const AZStd::string inputJson = R"( + { + "propertyLayout": { + "propertySets": [ + { + "name": "general", + "properties": [ + { + "name": "foo", + "type": "Bool" + }, + { + "name": "foo", + "type": "Bool" + } + ] + } + ] + } + } + )"; - // Expected errors: - // Material property 'general.a': A property with this ID already exists. - // Cannot continue building MaterialAsset because 1 error(s) reported - AZ_TEST_START_TRACE_SUPPRESSION; + MaterialTypeSourceData sourceData; + JsonTestResult loadResult = LoadTestDataFromJson(sourceData, inputJson); + EXPECT_EQ(loadResult.m_jsonResultCode.GetProcessing(), JsonSerializationResult::Processing::Completed); + + ErrorMessageFinder errorMessageFinder("Material property 'general.foo': A property with this ID already exists"); + errorMessageFinder.AddExpectedErrorMessage("Cannot continue building MaterialTypeAsset"); auto materialTypeOutcome = sourceData.CreateMaterialTypeAsset(Uuid::CreateRandom()); - AZ_TEST_STOP_TRACE_SUPPRESSION(2); + EXPECT_FALSE(materialTypeOutcome.IsSuccess()); + errorMessageFinder.CheckExpectedErrorsFound(); + } + + TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_Error_PropertyAndPropertySetNameCollision) + { + const AZStd::string inputJson = R"( + { + "propertyLayout": { + "propertySets": [ + { + "name": "general", + "properties": [ + { + "name": "foo", + "type": "Bool" + } + ], + "propertySets": [ + { + "name": "foo", + "properties": [ + { + "name": "bar", + "type": "Bool" + } + ] + } + ] + } + ] + } + } + )"; + MaterialTypeSourceData sourceData; + JsonTestResult loadResult = LoadTestDataFromJson(sourceData, inputJson); + EXPECT_EQ(loadResult.m_jsonResultCode.GetProcessing(), JsonSerializationResult::Processing::Completed); + + ErrorMessageFinder errorMessageFinder("Material property 'general.foo' collides with a PropertySet with the same ID"); + auto materialTypeOutcome = sourceData.CreateMaterialTypeAsset(Uuid::CreateRandom()); EXPECT_FALSE(materialTypeOutcome.IsSuccess()); + errorMessageFinder.CheckExpectedErrorsFound(); } TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_PropertyConnectedToMultipleOutputs) @@ -736,25 +1116,25 @@ namespace UnitTest sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{ "shaderA.shader" }); sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{ "shaderB.shader" }); sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{ "shaderC.shader" }); + + MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("general"); + MaterialTypeSourceData::PropertyDefinition* property = propertySet->AddProperty("MyInt"); - MaterialTypeSourceData::PropertyDefinition propertySource; - propertySource.m_name = "MyInt"; - propertySource.m_displayName = "Integer"; - propertySource.m_description = "Integer property that is connected to multiple shader settings"; - propertySource.m_dataType = MaterialPropertyDataType::Int; + property->m_displayName = "Integer"; + property->m_description = "Integer property that is connected to multiple shader settings"; + property->m_dataType = MaterialPropertyDataType::Int; // The value maps to m_int in the SRG - propertySource.m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{ MaterialPropertyOutputType::ShaderInput, AZStd::string("m_int") }); + property->m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{ MaterialPropertyOutputType::ShaderInput, AZStd::string("m_int") }); // The value also maps to m_uint in the SRG - propertySource.m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{ MaterialPropertyOutputType::ShaderInput, AZStd::string("m_uint") }); + property->m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{ MaterialPropertyOutputType::ShaderInput, AZStd::string("m_uint") }); // The value also maps to the first shader's "o_speed" option - propertySource.m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{ MaterialPropertyOutputType::ShaderOption, AZStd::string("o_speed"), 0 }); + property->m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{ MaterialPropertyOutputType::ShaderOption, AZStd::string("o_speed"), 0 }); // The value also maps to the second shader's "o_speed" option - propertySource.m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{ MaterialPropertyOutputType::ShaderOption, AZStd::string("o_speed"), 1 }); + property->m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{ MaterialPropertyOutputType::ShaderOption, AZStd::string("o_speed"), 1 }); // This case doesn't specify an index, so it will apply to all shaders that have a "o_efficiency", which means it will create two outputs in the property descriptor. - propertySource.m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{ MaterialPropertyOutputType::ShaderOption, AZStd::string("o_efficiency") }); - - sourceData.m_propertyLayout.m_properties["general"].push_back(propertySource); + property->m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{ MaterialPropertyOutputType::ShaderOption, AZStd::string("o_efficiency") }); + // Do the actual test... @@ -795,15 +1175,15 @@ namespace UnitTest TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_PropertyWithShaderInputFunctor) { MaterialTypeSourceData sourceData; + + MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("general"); + MaterialTypeSourceData::PropertyDefinition* property = propertySet->AddProperty("floatForFunctor"); - MaterialTypeSourceData::PropertyDefinition propertySource; - propertySource.m_name = "NonAliasFloat"; - propertySource.m_displayName = "Non-Alias Float"; - propertySource.m_description = "This float is processed by a functor, not with a direct alias"; - propertySource.m_dataType = MaterialPropertyDataType::Float; - // Note that we don't fill propertySource.m_aliasOutputId because this is not an aliased property - sourceData.m_propertyLayout.m_properties["general"].push_back(propertySource); - + property->m_displayName = "Float for Functor"; + property->m_description = "This float is processed by a functor, not with a direct connection"; + property->m_dataType = MaterialPropertyDataType::Float; + // Note that we don't fill property->m_outputConnections because this is not an aliased property + sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{TestShaderFilename}); sourceData.m_materialFunctorSourceData.push_back( @@ -811,7 +1191,7 @@ namespace UnitTest ( aznew MaterialFunctorSourceDataHolder ( - aznew Splat3FunctorSourceData{ "general.NonAliasFloat", "m_float3" } + aznew Splat3FunctorSourceData{ "general.floatForFunctor", "m_float3" } ) ) ); @@ -820,10 +1200,10 @@ namespace UnitTest EXPECT_TRUE(materialTypeOutcome.IsSuccess()); Data::Asset materialTypeAsset = materialTypeOutcome.GetValue(); - const MaterialPropertyIndex propertyIndex = materialTypeAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(Name{"general.NonAliasFloat" }); + const MaterialPropertyIndex propertyIndex = materialTypeAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(Name{"general.floatForFunctor" }); const MaterialPropertyDescriptor* propertyDescriptor = materialTypeAsset->GetMaterialPropertiesLayout()->GetPropertyDescriptor(propertyIndex); - ValidateCommonDescriptorFields(propertySource, propertyDescriptor); + ValidateCommonDescriptorFields(*property, propertyDescriptor); EXPECT_EQ(1, materialTypeAsset->GetMaterialFunctors().size()); auto shaderInputFunctor = azrtti_cast(materialTypeAsset->GetMaterialFunctors()[0].get()); @@ -841,15 +1221,13 @@ namespace UnitTest sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{TestShaderFilename}); sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{TestShaderFilename}); - MaterialTypeSourceData::PropertyDefinition propertySource; - propertySource.m_name = "EnableSpecialPassA"; - propertySource.m_displayName = "Enable Special Pass"; - propertySource.m_description = "This is a bool to enable an extra shader/pass"; - propertySource.m_dataType = MaterialPropertyDataType::Bool; - // Note that we don't fill propertySource.m_outputConnections because this is not a direct-connected property - sourceData.m_propertyLayout.m_properties["general"].push_back(propertySource); - propertySource.m_name = "EnableSpecialPassB"; - sourceData.m_propertyLayout.m_properties["general"].push_back(propertySource); + MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("general"); + MaterialTypeSourceData::PropertyDefinition* property1 = propertySet->AddProperty("EnableSpecialPassA"); + MaterialTypeSourceData::PropertyDefinition* property2 = propertySet->AddProperty("EnableSpecialPassB"); + + property1->m_displayName = property2->m_displayName = "Enable Special Pass"; + property1->m_description = property2->m_description = "This is a bool to enable an extra shader/pass"; + property1->m_dataType = property2->m_dataType = MaterialPropertyDataType::Bool; sourceData.m_materialFunctorSourceData.push_back( Ptr @@ -880,8 +1258,8 @@ namespace UnitTest const MaterialPropertyIndex propertyBIndex = materialTypeAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(Name{"general.EnableSpecialPassB"}); const MaterialPropertyDescriptor* propertyBDescriptor = materialTypeAsset->GetMaterialPropertiesLayout()->GetPropertyDescriptor(propertyBIndex); - ValidateCommonDescriptorFields(propertySource, propertyADescriptor); - ValidateCommonDescriptorFields(propertySource, propertyBDescriptor); + ValidateCommonDescriptorFields(*sourceData.FindProperty("general.EnableSpecialPassA"), propertyADescriptor); + ValidateCommonDescriptorFields(*sourceData.FindProperty("general.EnableSpecialPassB"), propertyBDescriptor); EXPECT_EQ(2, materialTypeAsset->GetMaterialFunctors().size()); auto functorA = azrtti_cast(materialTypeAsset->GetMaterialFunctors()[0].get()); @@ -900,13 +1278,13 @@ namespace UnitTest sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{TestShaderFilename}); sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{TestShaderFilename}); + + MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("general"); + MaterialTypeSourceData::PropertyDefinition* property = propertySet->AddProperty("MyProperty"); - MaterialTypeSourceData::PropertyDefinition propertySource; - propertySource.m_name = "MyProperty"; - propertySource.m_dataType = MaterialPropertyDataType::Bool; - // Note that we don't fill propertySource.m_outputConnections because this is not a direct-connected property - sourceData.m_propertyLayout.m_properties["general"].push_back(propertySource); - + property->m_dataType = MaterialPropertyDataType::Bool; + // Note that we don't fill property->m_outputConnections because this is not a direct-connected property + sourceData.m_materialFunctorSourceData.push_back( Ptr ( @@ -928,6 +1306,45 @@ namespace UnitTest EXPECT_TRUE(materialTypeAsset->GetShaderCollection()[0].MaterialOwnsShaderOption(Name{"o_foo"})); EXPECT_TRUE(materialTypeAsset->GetShaderCollection()[0].MaterialOwnsShaderOption(Name{"o_bar"})); } + + TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_FunctorIsInsidePropertySet) + { + MaterialTypeSourceData sourceData; + + MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("general"); + MaterialTypeSourceData::PropertyDefinition* property = propertySet->AddProperty("floatForFunctor"); + + property->m_dataType = MaterialPropertyDataType::Float; + + sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{TestShaderFilename}); + + sourceData.m_materialFunctorSourceData.push_back( + Ptr + ( + aznew MaterialFunctorSourceDataHolder + ( + aznew Splat3FunctorSourceData{ "general.floatForFunctor", "m_float3" } + ) + ) + ); + + auto materialTypeOutcome = sourceData.CreateMaterialTypeAsset(Uuid::CreateRandom()); + EXPECT_TRUE(materialTypeOutcome.IsSuccess()); + Data::Asset materialTypeAsset = materialTypeOutcome.GetValue(); + + const MaterialPropertyIndex propertyIndex = materialTypeAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(Name{"general.floatForFunctor" }); + const MaterialPropertyDescriptor* propertyDescriptor = materialTypeAsset->GetMaterialPropertiesLayout()->GetPropertyDescriptor(propertyIndex); + + ValidateCommonDescriptorFields(*property, propertyDescriptor); + + EXPECT_EQ(1, materialTypeAsset->GetMaterialFunctors().size()); + auto shaderInputFunctor = azrtti_cast(materialTypeAsset->GetMaterialFunctors()[0].get()); + EXPECT_TRUE(nullptr != shaderInputFunctor); + EXPECT_EQ(propertyIndex, shaderInputFunctor->m_floatIndex); + + const RHI::ShaderInputConstantIndex expectedVector3Index = materialTypeAsset->GetMaterialSrgLayout()->FindShaderInputConstantIndex(Name{ "m_float3" }); + EXPECT_EQ(expectedVector3Index, shaderInputFunctor->m_vector3Index); + } TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_PropertyValues_AllTypes) { @@ -937,23 +1354,23 @@ namespace UnitTest auto addProperty = [&sourceData](MaterialPropertyDataType dateType, const char* propertyName, const char* srgConstantName, const AZ::RPI::MaterialPropertyValue& value) { - MaterialTypeSourceData::PropertyDefinition propertySource; - propertySource.m_name = propertyName; - propertySource.m_dataType = dateType; - propertySource.m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{ MaterialPropertyOutputType::ShaderInput, AZStd::string(srgConstantName) }); - propertySource.m_value = value; - sourceData.m_propertyLayout.m_properties["general"].push_back(propertySource); + MaterialTypeSourceData::PropertyDefinition* property = sourceData.AddProperty(propertyName); + property->m_dataType = dateType; + property->m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{ MaterialPropertyOutputType::ShaderInput, AZStd::string(srgConstantName) }); + property->m_value = value; }; - - addProperty(MaterialPropertyDataType::Bool, "MyBool", "m_bool", true); - addProperty(MaterialPropertyDataType::Float, "MyFloat", "m_float", 1.2f); - addProperty(MaterialPropertyDataType::Int, "MyInt", "m_int", -12); - addProperty(MaterialPropertyDataType::UInt, "MyUInt", "m_uint", 12u); - addProperty(MaterialPropertyDataType::Vector2, "MyFloat2", "m_float2", AZ::Vector2{1.1f, 2.2f}); - addProperty(MaterialPropertyDataType::Vector3, "MyFloat3", "m_float3", AZ::Vector3{3.3f, 4.4f, 5.5f}); - addProperty(MaterialPropertyDataType::Vector4, "MyFloat4", "m_float4", AZ::Vector4{6.6f, 7.7f, 8.8f, 9.9f}); - addProperty(MaterialPropertyDataType::Color, "MyColor", "m_color", AZ::Color{0.1f, 0.2f, 0.3f, 0.4f}); - addProperty(MaterialPropertyDataType::Image, "MyImage", "m_image", AZStd::string{TestImageFilename}); + + sourceData.AddPropertySet("general"); + + addProperty(MaterialPropertyDataType::Bool, "general.MyBool", "m_bool", true); + addProperty(MaterialPropertyDataType::Float, "general.MyFloat", "m_float", 1.2f); + addProperty(MaterialPropertyDataType::Int, "general.MyInt", "m_int", -12); + addProperty(MaterialPropertyDataType::UInt, "general.MyUInt", "m_uint", 12u); + addProperty(MaterialPropertyDataType::Vector2, "general.MyFloat2", "m_float2", AZ::Vector2{1.1f, 2.2f}); + addProperty(MaterialPropertyDataType::Vector3, "general.MyFloat3", "m_float3", AZ::Vector3{3.3f, 4.4f, 5.5f}); + addProperty(MaterialPropertyDataType::Vector4, "general.MyFloat4", "m_float4", AZ::Vector4{6.6f, 7.7f, 8.8f, 9.9f}); + addProperty(MaterialPropertyDataType::Color, "general.MyColor", "m_color", AZ::Color{0.1f, 0.2f, 0.3f, 0.4f}); + addProperty(MaterialPropertyDataType::Image, "general.MyImage", "m_image", AZStd::string{TestImageFilename}); auto materialTypeOutcome = sourceData.CreateMaterialTypeAsset(Uuid::CreateRandom()); EXPECT_TRUE(materialTypeOutcome.IsSuccess()); @@ -970,6 +1387,88 @@ namespace UnitTest CheckPropertyValue>(materialTypeAsset, Name{"general.MyImage"}, m_testImageAsset); } + TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_NestedPropertySets) + { + RHI::Ptr layeredMaterialSrgLayout = RHI::ShaderResourceGroupLayout::Create(); + layeredMaterialSrgLayout->SetName(Name{"MaterialSrg"}); + layeredMaterialSrgLayout->SetBindingSlot(SrgBindingSlot::Material); + layeredMaterialSrgLayout->AddShaderInput(RHI::ShaderInputImageDescriptor{ Name{ "m_layer1_baseColor_texture" }, RHI::ShaderInputImageAccess::Read, RHI::ShaderInputImageType::Image2D, 1, 1 }); + layeredMaterialSrgLayout->AddShaderInput(RHI::ShaderInputImageDescriptor{ Name{ "m_layer1_roughness_texture" }, RHI::ShaderInputImageAccess::Read, RHI::ShaderInputImageType::Image2D, 1, 1 }); + layeredMaterialSrgLayout->AddShaderInput(RHI::ShaderInputImageDescriptor{ Name{ "m_layer2_baseColor_texture" }, RHI::ShaderInputImageAccess::Read, RHI::ShaderInputImageType::Image2D, 1, 1 }); + layeredMaterialSrgLayout->AddShaderInput(RHI::ShaderInputImageDescriptor{ Name{ "m_layer2_roughness_texture" }, RHI::ShaderInputImageAccess::Read, RHI::ShaderInputImageType::Image2D, 1, 1 }); + layeredMaterialSrgLayout->AddShaderInput(RHI::ShaderInputImageDescriptor{ Name{ "m_layer2_clearCoat_roughness_texture" }, RHI::ShaderInputImageAccess::Read, RHI::ShaderInputImageType::Image2D, 1, 1 }); + layeredMaterialSrgLayout->AddShaderInput(RHI::ShaderInputImageDescriptor{ Name{ "m_layer2_clearCoat_normal_texture" }, RHI::ShaderInputImageAccess::Read, RHI::ShaderInputImageType::Image2D, 1, 1 }); + layeredMaterialSrgLayout->AddShaderInput(RHI::ShaderInputConstantDescriptor{ Name{ "m_layer2_clearCoat_normal_factor" }, 0, 4, 0 }); + layeredMaterialSrgLayout->AddShaderInput(RHI::ShaderInputConstantDescriptor{ Name{ "m_blendFactor" }, 4, 4, 0 }); + layeredMaterialSrgLayout->Finalize(); + + AZStd::vector boolOptionValues; + boolOptionValues.push_back({Name("False"), RPI::ShaderOptionValue(0)}); + boolOptionValues.push_back({Name("True"), RPI::ShaderOptionValue(1)}); + Ptr shaderOptionsLayout = ShaderOptionGroupLayout::Create(); + uint32_t order = 0; + shaderOptionsLayout->AddShaderOption(ShaderOptionDescriptor{Name{"o_layer2_clearCoat_enable"}, ShaderOptionType::Boolean, 0, order++, boolOptionValues, Name{"False"}}); + shaderOptionsLayout->Finalize(); + + Data::Asset layeredMaterialShaderAsset = CreateTestShaderAsset(Uuid::CreateRandom(), layeredMaterialSrgLayout, shaderOptionsLayout); + + Data::AssetInfo testShaderAssetInfo; + testShaderAssetInfo.m_assetId = layeredMaterialShaderAsset.GetId(); + m_assetSystemStub.RegisterSourceInfo("layeredMaterial.shader", testShaderAssetInfo, ""); + + MaterialTypeSourceData sourceData; + + sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{ "layeredMaterial.shader" }); + + auto addSrgProperty = [&sourceData](MaterialPropertyDataType dateType, MaterialPropertyOutputType connectionType, const char* propertyName, const char* srgConstantName, const AZ::RPI::MaterialPropertyValue& value) + { + MaterialTypeSourceData::PropertyDefinition* property = sourceData.AddProperty(propertyName); + property->m_dataType = dateType; + property->m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{ connectionType, AZStd::string(srgConstantName) }); + property->m_value = value; + }; + + sourceData.AddPropertySet("layer1"); + sourceData.AddPropertySet("layer2"); + sourceData.AddPropertySet("blend"); + sourceData.AddPropertySet("layer1.baseColor"); + sourceData.AddPropertySet("layer2.baseColor"); + sourceData.AddPropertySet("layer1.roughness"); + sourceData.AddPropertySet("layer2.roughness"); + sourceData.AddPropertySet("layer2.clearCoat"); + sourceData.AddPropertySet("layer2.clearCoat.roughness"); + sourceData.AddPropertySet("layer2.clearCoat.normal"); + + addSrgProperty(MaterialPropertyDataType::Image, MaterialPropertyOutputType::ShaderInput, "layer1.baseColor.texture", "m_layer1_baseColor_texture", AZStd::string{TestImageFilename}); + addSrgProperty(MaterialPropertyDataType::Image, MaterialPropertyOutputType::ShaderInput, "layer1.roughness.texture", "m_layer1_roughness_texture", AZStd::string{TestImageFilename}); + addSrgProperty(MaterialPropertyDataType::Image, MaterialPropertyOutputType::ShaderInput, "layer2.baseColor.texture", "m_layer2_baseColor_texture", AZStd::string{TestImageFilename}); + addSrgProperty(MaterialPropertyDataType::Image, MaterialPropertyOutputType::ShaderInput, "layer2.roughness.texture", "m_layer2_roughness_texture", AZStd::string{TestImageFilename}); + addSrgProperty(MaterialPropertyDataType::Bool, MaterialPropertyOutputType::ShaderOption, "layer2.clearCoat.enabled", "o_layer2_clearCoat_enable", true); + addSrgProperty(MaterialPropertyDataType::Image, MaterialPropertyOutputType::ShaderInput, "layer2.clearCoat.roughness.texture", "m_layer2_clearCoat_roughness_texture", AZStd::string{TestImageFilename}); + addSrgProperty(MaterialPropertyDataType::Image, MaterialPropertyOutputType::ShaderInput, "layer2.clearCoat.normal.texture", "m_layer2_clearCoat_normal_texture", AZStd::string{TestImageFilename}); + addSrgProperty(MaterialPropertyDataType::Float, MaterialPropertyOutputType::ShaderInput, "layer2.clearCoat.normal.factor", "m_layer2_clearCoat_normal_factor", 0.4f); + addSrgProperty(MaterialPropertyDataType::Float, MaterialPropertyOutputType::ShaderInput, "blend.factor", "m_blendFactor", 0.5f); + + auto materialTypeOutcome = sourceData.CreateMaterialTypeAsset(Uuid::CreateRandom()); + EXPECT_TRUE(materialTypeOutcome.IsSuccess()); + Data::Asset materialTypeAsset = materialTypeOutcome.GetValue(); + + CheckPropertyValue>(materialTypeAsset, Name{"layer1.baseColor.texture"}, m_testImageAsset); + CheckPropertyValue>(materialTypeAsset, Name{"layer1.roughness.texture"}, m_testImageAsset); + CheckPropertyValue>(materialTypeAsset, Name{"layer2.baseColor.texture"}, m_testImageAsset); + CheckPropertyValue>(materialTypeAsset, Name{"layer2.roughness.texture"}, m_testImageAsset); + CheckPropertyValue(materialTypeAsset, Name{"layer2.clearCoat.enabled"}, true); + CheckPropertyValue>(materialTypeAsset, Name{"layer2.clearCoat.roughness.texture"}, m_testImageAsset); + CheckPropertyValue>(materialTypeAsset, Name{"layer2.clearCoat.normal.texture"}, m_testImageAsset); + CheckPropertyValue(materialTypeAsset, Name{"layer2.clearCoat.normal.factor"}, 0.4f); + CheckPropertyValue(materialTypeAsset, Name{"blend.factor"}, 0.5f); + + // Note it might be nice to check that the right property connections are prescribed in the final MaterialTypeAsset, + // but it's not really necessary because CreateMaterialTypeAsset reports errors when a connection target is not found + // in the shader options layout or SRG layout. If one of the output names like "m_layer2_roughness_texture" is wrong + // these errors will cause this test to fail. + } + TEST_F(MaterialTypeSourceDataTests, LoadAndStoreJson_AllFields) { // Note that serialization of individual fields within material properties is thoroughly tested in @@ -980,46 +1479,92 @@ namespace UnitTest "description": "This is a general description about the material", "propertyLayout": { "version": 2, - "groups": [ + "propertySets": [ { "name": "groupA", "displayName": "Property Group A", - "description": "Description of property group A" + "description": "Description of property group A", + "properties": [ + { + "name": "foo", + "type": "Bool", + "defaultValue": true + }, + { + "name": "bar", + "type": "Image", + "defaultValue": "Default.png", + "visibility": "Hidden" + } + ], + "functors": [ + { + "type": "EnableShader", + "args": { + "enablePassProperty": "foo", + "shaderIndex": 1 + } + } + ] }, { "name": "groupB", "displayName": "Property Group B", - "description": "Description of property group B" + "description": "Description of property group B", + "properties": [ + { + "name": "foo", + "type": "Float", + "defaultValue": 0.5 + }, + { + "name": "bar", + "type": "Color", + "defaultValue": [0.5, 0.5, 0.5], + "visibility": "Disabled" + } + ], + "functors": [ + { + "type": "Splat3", + "args": { + "floatPropertyInput": "foo", + "float3ShaderSettingOutput": "m_someFloat3" + } + } + ] + }, + { + "name": "groupC", + "displayName": "Property Group C", + "description": "Property group C has a nested property set", + "propertySets": [ + { + "name": "groupD", + "displayName": "Property Group D", + "description": "Description of property group D", + "properties": [ + { + "name": "foo", + "type": "Int", + "defaultValue": -1 + } + ] + }, + { + "name": "groupE", + "displayName": "Property Group E", + "description": "Description of property group E", + "properties": [ + { + "name": "bar", + "type": "UInt" + } + ] + } + ] } - ], - "properties": { - "groupA": [ - { - "name": "foo", - "type": "Bool", - "defaultValue": true - }, - { - "name": "bar", - "type": "Image", - "defaultValue": "Default.png", - "visibility": "Hidden" - } - ], - "groupB": [ - { - "name": "foo", - "type": "Float", - "defaultValue": 0.5 - }, - { - "name": "bar", - "type": "Color", - "defaultValue": [0.5, 0.5, 0.5], - "visibility": "Disabled" - } - ] - } + ] }, "shaders": [ { @@ -1040,17 +1585,9 @@ namespace UnitTest ], "functors": [ { - "type": "EnableShader", - "args": { - "enablePassProperty": "groupA.foo", - "shaderIndex": 1 - } - }, - { - "type": "Splat3", + "type": "SetShaderOption", "args": { - "floatPropertyInput": "groupB.foo", - "float3ShaderSettingOutput": "m_someFloat3" + "enableProperty": "groupA.foo" } } ] @@ -1062,35 +1599,72 @@ namespace UnitTest EXPECT_EQ(material.m_description, "This is a general description about the material"); - EXPECT_EQ(material.m_propertyLayout.m_version, 2); - - EXPECT_EQ(material.m_propertyLayout.m_groups.size(), 2); - EXPECT_TRUE(material.FindGroup("groupA") != nullptr); - EXPECT_TRUE(material.FindGroup("groupB") != nullptr); - EXPECT_EQ(material.FindGroup("groupA")->m_displayName, "Property Group A"); - EXPECT_EQ(material.FindGroup("groupB")->m_displayName, "Property Group B"); - EXPECT_EQ(material.FindGroup("groupA")->m_description, "Description of property group A"); - EXPECT_EQ(material.FindGroup("groupB")->m_description, "Description of property group B"); - - EXPECT_EQ(material.m_propertyLayout.m_properties.size(), 2); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupA"].size(), 2); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupB"].size(), 2); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupA"][0].m_name, "foo"); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupA"][1].m_name, "bar"); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupB"][0].m_name, "foo"); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupB"][1].m_name, "bar"); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupA"][0].m_dataType, MaterialPropertyDataType::Bool); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupA"][1].m_dataType, MaterialPropertyDataType::Image); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupB"][0].m_dataType, MaterialPropertyDataType::Float); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupB"][1].m_dataType, MaterialPropertyDataType::Color); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupA"][0].m_visibility, MaterialPropertyVisibility::Enabled); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupA"][1].m_visibility, MaterialPropertyVisibility::Hidden); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupB"][0].m_visibility, MaterialPropertyVisibility::Enabled); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupB"][1].m_visibility, MaterialPropertyVisibility::Disabled); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupA"][0].m_value, true); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupA"][1].m_value, AZStd::string{"Default.png"}); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupB"][0].m_value, 0.5f); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupB"][1].m_value, AZ::Color(0.5f, 0.5f, 0.5f, 1.0f)); + EXPECT_EQ(material.GetPropertyLayout().m_version, 2); + + EXPECT_EQ(material.GetPropertyLayout().m_propertySets.size(), 3); + EXPECT_TRUE(material.FindPropertySet("groupA") != nullptr); + EXPECT_TRUE(material.FindPropertySet("groupB") != nullptr); + EXPECT_TRUE(material.FindPropertySet("groupC") != nullptr); + EXPECT_TRUE(material.FindPropertySet("groupC.groupD") != nullptr); + EXPECT_TRUE(material.FindPropertySet("groupC.groupE") != nullptr); + EXPECT_EQ(material.FindPropertySet("groupA")->GetDisplayName(), "Property Group A"); + EXPECT_EQ(material.FindPropertySet("groupB")->GetDisplayName(), "Property Group B"); + EXPECT_EQ(material.FindPropertySet("groupC")->GetDisplayName(), "Property Group C"); + EXPECT_EQ(material.FindPropertySet("groupC.groupD")->GetDisplayName(), "Property Group D"); + EXPECT_EQ(material.FindPropertySet("groupC.groupE")->GetDisplayName(), "Property Group E"); + EXPECT_EQ(material.FindPropertySet("groupA")->GetDescription(), "Description of property group A"); + EXPECT_EQ(material.FindPropertySet("groupB")->GetDescription(), "Description of property group B"); + EXPECT_EQ(material.FindPropertySet("groupC")->GetDescription(), "Property group C has a nested property set"); + EXPECT_EQ(material.FindPropertySet("groupC.groupD")->GetDescription(), "Description of property group D"); + EXPECT_EQ(material.FindPropertySet("groupC.groupE")->GetDescription(), "Description of property group E"); + EXPECT_EQ(material.FindPropertySet("groupA")->GetProperties().size(), 2); + EXPECT_EQ(material.FindPropertySet("groupB")->GetProperties().size(), 2); + EXPECT_EQ(material.FindPropertySet("groupC")->GetProperties().size(), 0); + EXPECT_EQ(material.FindPropertySet("groupC.groupD")->GetProperties().size(), 1); + EXPECT_EQ(material.FindPropertySet("groupC.groupE")->GetProperties().size(), 1); + + EXPECT_NE(material.FindProperty("groupA.foo"), nullptr); + EXPECT_NE(material.FindProperty("groupA.bar"), nullptr); + EXPECT_NE(material.FindProperty("groupB.foo"), nullptr); + EXPECT_NE(material.FindProperty("groupB.bar"), nullptr); + EXPECT_NE(material.FindProperty("groupC.groupD.foo"), nullptr); + EXPECT_NE(material.FindProperty("groupC.groupE.bar"), nullptr); + + EXPECT_EQ(material.FindProperty("groupA.foo")->m_name, "foo"); + EXPECT_EQ(material.FindProperty("groupA.bar")->m_name, "bar"); + EXPECT_EQ(material.FindProperty("groupB.foo")->m_name, "foo"); + EXPECT_EQ(material.FindProperty("groupB.bar")->m_name, "bar"); + EXPECT_EQ(material.FindProperty("groupC.groupD.foo")->m_name, "foo"); + EXPECT_EQ(material.FindProperty("groupC.groupE.bar")->m_name, "bar"); + EXPECT_EQ(material.FindProperty("groupA.foo")->m_dataType, MaterialPropertyDataType::Bool); + EXPECT_EQ(material.FindProperty("groupA.bar")->m_dataType, MaterialPropertyDataType::Image); + EXPECT_EQ(material.FindProperty("groupB.foo")->m_dataType, MaterialPropertyDataType::Float); + EXPECT_EQ(material.FindProperty("groupB.bar")->m_dataType, MaterialPropertyDataType::Color); + EXPECT_EQ(material.FindProperty("groupC.groupD.foo")->m_dataType, MaterialPropertyDataType::Int); + EXPECT_EQ(material.FindProperty("groupC.groupE.bar")->m_dataType, MaterialPropertyDataType::UInt); + EXPECT_EQ(material.FindProperty("groupA.foo")->m_visibility, MaterialPropertyVisibility::Enabled); + EXPECT_EQ(material.FindProperty("groupA.bar")->m_visibility, MaterialPropertyVisibility::Hidden); + EXPECT_EQ(material.FindProperty("groupB.foo")->m_visibility, MaterialPropertyVisibility::Enabled); + EXPECT_EQ(material.FindProperty("groupB.bar")->m_visibility, MaterialPropertyVisibility::Disabled); + EXPECT_EQ(material.FindProperty("groupC.groupD.foo")->m_visibility, MaterialPropertyVisibility::Enabled); + EXPECT_EQ(material.FindProperty("groupC.groupE.bar")->m_visibility, MaterialPropertyVisibility::Enabled); + EXPECT_EQ(material.FindProperty("groupA.foo")->m_value, true); + EXPECT_EQ(material.FindProperty("groupA.bar")->m_value, AZStd::string{"Default.png"}); + EXPECT_EQ(material.FindProperty("groupB.foo")->m_value, 0.5f); + EXPECT_EQ(material.FindProperty("groupB.bar")->m_value, AZ::Color(0.5f, 0.5f, 0.5f, 1.0f)); + EXPECT_EQ(material.FindProperty("groupC.groupD.foo")->m_value, -1); + EXPECT_EQ(material.FindProperty("groupC.groupE.bar")->m_value, 0u); + + EXPECT_EQ(material.FindPropertySet("groupA")->GetFunctors().size(), 1); + EXPECT_EQ(material.FindPropertySet("groupB")->GetFunctors().size(), 1); + Ptr functorA = material.FindPropertySet("groupA")->GetFunctors()[0]->GetActualSourceData(); + Ptr functorB = material.FindPropertySet("groupB")->GetFunctors()[0]->GetActualSourceData(); + EXPECT_TRUE(azrtti_cast(functorA.get())); + EXPECT_EQ(azrtti_cast(functorA.get())->m_enablePassPropertyId, "foo"); + EXPECT_EQ(azrtti_cast(functorA.get())->m_shaderIndex, 1); + EXPECT_TRUE(azrtti_cast(functorB.get())); + EXPECT_EQ(azrtti_cast(functorB.get())->m_floatPropertyInputId, "foo"); + EXPECT_EQ(azrtti_cast(functorB.get())->m_float3ShaderSettingOutputId, "m_someFloat3"); EXPECT_EQ(material.m_shaderCollection.size(), 2); EXPECT_EQ(material.m_shaderCollection[0].m_shaderFilePath, "ForwardPass.shader"); @@ -1103,13 +1677,10 @@ namespace UnitTest EXPECT_EQ(material.m_shaderCollection[1].m_shaderOptionValues[Name{"o_optionD"}], Name{"2"}); EXPECT_EQ(material.m_shaderCollection[0].m_shaderTag, Name{"ForwardPass"}); - EXPECT_EQ(material.m_materialFunctorSourceData.size(), 2); - EXPECT_TRUE(azrtti_cast(material.m_materialFunctorSourceData[0]->GetActualSourceData().get())); - EXPECT_EQ(azrtti_cast(material.m_materialFunctorSourceData[0]->GetActualSourceData().get())->m_enablePassPropertyId, "groupA.foo"); - EXPECT_EQ(azrtti_cast(material.m_materialFunctorSourceData[0]->GetActualSourceData().get())->m_shaderIndex, 1); - EXPECT_TRUE(azrtti_cast(material.m_materialFunctorSourceData[1]->GetActualSourceData().get())); - EXPECT_EQ(azrtti_cast(material.m_materialFunctorSourceData[1]->GetActualSourceData().get())->m_floatPropertyInputId, "groupB.foo"); - EXPECT_EQ(azrtti_cast(material.m_materialFunctorSourceData[1]->GetActualSourceData().get())->m_float3ShaderSettingOutputId, "m_someFloat3"); + EXPECT_EQ(material.m_materialFunctorSourceData.size(), 1); + Ptr functorC = material.m_materialFunctorSourceData[0]->GetActualSourceData(); + EXPECT_TRUE(azrtti_cast(functorC.get())); + EXPECT_EQ(azrtti_cast(functorC.get())->m_enablePropertyName, "groupA.foo"); AZStd::string outputJson; JsonTestResult storeResult = StoreTestDataToJson(material, outputJson); @@ -1120,6 +1691,9 @@ namespace UnitTest { // The content of this test was copied from LoadAndStoreJson_AllFields to prove backward compatibility. // (The "store" part of the test was not included because the saved data will be the new format). + // Notable differences include: + // 1) the key "id" is used instead of "name" + // 2) the group metadata, property definitions, and functors are all defined in different sections rather than in a property set const AZStd::string inputJson = R"( { @@ -1206,37 +1780,57 @@ namespace UnitTest MaterialTypeSourceData material; JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson); + // Before conversion to the new format, the data is in the old place + EXPECT_EQ(material.GetPropertyLayout().m_groups.size(), 2); + EXPECT_EQ(material.GetPropertyLayout().m_properties.size(), 2); + EXPECT_EQ(material.GetPropertyLayout().m_propertySets.size(), 0); + + material.ConvertToNewDataFormat(); + + // After conversion to the new format, the data is in the new place + EXPECT_EQ(material.GetPropertyLayout().m_groups.size(), 0); + EXPECT_EQ(material.GetPropertyLayout().m_properties.size(), 0); + EXPECT_EQ(material.GetPropertyLayout().m_propertySets.size(), 2); + EXPECT_EQ(material.m_description, "This is a general description about the material"); - EXPECT_EQ(material.m_propertyLayout.m_version, 2); - - EXPECT_EQ(material.m_propertyLayout.m_groups.size(), 2); - EXPECT_TRUE(material.FindGroup("groupA") != nullptr); - EXPECT_TRUE(material.FindGroup("groupB") != nullptr); - EXPECT_EQ(material.FindGroup("groupA")->m_displayName, "Property Group A"); - EXPECT_EQ(material.FindGroup("groupB")->m_displayName, "Property Group B"); - EXPECT_EQ(material.FindGroup("groupA")->m_description, "Description of property group A"); - EXPECT_EQ(material.FindGroup("groupB")->m_description, "Description of property group B"); - - EXPECT_EQ(material.m_propertyLayout.m_properties.size(), 2); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupA"].size(), 2); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupB"].size(), 2); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupA"][0].m_name, "foo"); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupA"][1].m_name, "bar"); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupB"][0].m_name, "foo"); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupB"][1].m_name, "bar"); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupA"][0].m_dataType, MaterialPropertyDataType::Bool); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupA"][1].m_dataType, MaterialPropertyDataType::Image); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupB"][0].m_dataType, MaterialPropertyDataType::Float); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupB"][1].m_dataType, MaterialPropertyDataType::Color); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupA"][0].m_visibility, MaterialPropertyVisibility::Enabled); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupA"][1].m_visibility, MaterialPropertyVisibility::Hidden); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupB"][0].m_visibility, MaterialPropertyVisibility::Enabled); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupB"][1].m_visibility, MaterialPropertyVisibility::Disabled); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupA"][0].m_value, true); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupA"][1].m_value, AZStd::string{"Default.png"}); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupB"][0].m_value, 0.5f); - EXPECT_EQ(material.m_propertyLayout.m_properties["groupB"][1].m_value, AZ::Color(0.5f, 0.5f, 0.5f, 1.0f)); + EXPECT_EQ(material.GetPropertyLayout().m_version, 2); + + EXPECT_TRUE(material.FindPropertySet("groupA") != nullptr); + EXPECT_TRUE(material.FindPropertySet("groupB") != nullptr); + EXPECT_EQ(material.FindPropertySet("groupA")->GetDisplayName(), "Property Group A"); + EXPECT_EQ(material.FindPropertySet("groupB")->GetDisplayName(), "Property Group B"); + EXPECT_EQ(material.FindPropertySet("groupA")->GetDescription(), "Description of property group A"); + EXPECT_EQ(material.FindPropertySet("groupB")->GetDescription(), "Description of property group B"); + EXPECT_EQ(material.FindPropertySet("groupA")->GetProperties().size(), 2); + EXPECT_EQ(material.FindPropertySet("groupB")->GetProperties().size(), 2); + + EXPECT_TRUE(material.FindProperty("groupA.foo") != nullptr); + EXPECT_TRUE(material.FindProperty("groupA.bar") != nullptr); + EXPECT_TRUE(material.FindProperty("groupB.foo") != nullptr); + EXPECT_TRUE(material.FindProperty("groupB.bar") != nullptr); + + EXPECT_EQ(material.FindProperty("groupA.foo")->m_name, "foo"); + EXPECT_EQ(material.FindProperty("groupA.bar")->m_name, "bar"); + EXPECT_EQ(material.FindProperty("groupB.foo")->m_name, "foo"); + EXPECT_EQ(material.FindProperty("groupB.bar")->m_name, "bar"); + EXPECT_EQ(material.FindProperty("groupA.foo")->m_dataType, MaterialPropertyDataType::Bool); + EXPECT_EQ(material.FindProperty("groupA.bar")->m_dataType, MaterialPropertyDataType::Image); + EXPECT_EQ(material.FindProperty("groupB.foo")->m_dataType, MaterialPropertyDataType::Float); + EXPECT_EQ(material.FindProperty("groupB.bar")->m_dataType, MaterialPropertyDataType::Color); + EXPECT_EQ(material.FindProperty("groupA.foo")->m_visibility, MaterialPropertyVisibility::Enabled); + EXPECT_EQ(material.FindProperty("groupA.bar")->m_visibility, MaterialPropertyVisibility::Hidden); + EXPECT_EQ(material.FindProperty("groupB.foo")->m_visibility, MaterialPropertyVisibility::Enabled); + EXPECT_EQ(material.FindProperty("groupB.bar")->m_visibility, MaterialPropertyVisibility::Disabled); + EXPECT_EQ(material.FindProperty("groupA.foo")->m_value, true); + EXPECT_EQ(material.FindProperty("groupA.bar")->m_value, AZStd::string{"Default.png"}); + EXPECT_EQ(material.FindProperty("groupB.foo")->m_value, 0.5f); + EXPECT_EQ(material.FindProperty("groupB.bar")->m_value, AZ::Color(0.5f, 0.5f, 0.5f, 1.0f)); + + // The functors can appear either at the top level or within each property set. The format conversion + // function doesn't know how to move the functors, and they will be left at the top level. + EXPECT_EQ(material.FindPropertySet("groupA")->GetFunctors().size(), 0); + EXPECT_EQ(material.FindPropertySet("groupB")->GetFunctors().size(), 0); EXPECT_EQ(material.m_shaderCollection.size(), 2); EXPECT_EQ(material.m_shaderCollection[0].m_shaderFilePath, "ForwardPass.shader"); @@ -1257,7 +1851,7 @@ namespace UnitTest EXPECT_EQ(azrtti_cast(material.m_materialFunctorSourceData[1]->GetActualSourceData().get())->m_floatPropertyInputId, "groupB.foo"); EXPECT_EQ(azrtti_cast(material.m_materialFunctorSourceData[1]->GetActualSourceData().get())->m_float3ShaderSettingOutputId, "m_someFloat3"); } - + TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_PropertyImagePath) { char inputJson[2048]; @@ -1267,27 +1861,25 @@ namespace UnitTest "description": "", "propertyLayout": { "version": 2, - "groups": [ + "propertySets": [ { "name": "general", "displayName": "General", - "description": "" + "description": "", + "properties": [ + { + "name": "absolute", + "type": "Image", + "defaultValue": "%s" + }, + { + "name": "relative", + "type": "Image", + "defaultValue": "%s" + } + ] } - ], - "properties": { - "general": [ - { - "name": "absolute", - "type": "Image", - "defaultValue": "%s" - }, - { - "name": "relative", - "type": "Image", - "defaultValue": "%s" - } - ] - } + ] } } )", diff --git a/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR.materialtype b/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR.materialtype index 81ebd63c28..ce59ec8b8e 100644 --- a/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR.materialtype +++ b/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR.materialtype @@ -2,50 +2,48 @@ "description": "Base Material with properties used to define Standard PBR, a metallic-roughness Physically-Based Rendering (PBR) material shading model.", "propertyLayout": { "version": 3, - "groups": [ + "propertySets": [ { "name": "settings", - "displayName": "Settings" - } - ], - "properties": { - "settings": [ - { - "name": "color", - "displayName": "Color", - "type": "Color", - "defaultValue": [ 1.0, 1.0, 1.0 ], - "connection": { - "type": "ShaderInput", - "name": "m_baseColor" - } - }, - { - "name": "metallic", - "displayName": "Metallic", - "type": "Float", - "defaultValue": 0.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_metallic" - } - }, - { - "name": "roughness", - "displayName": "Roughness", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_roughness" + "displayName": "Settings", + "properties": [ + { + "name": "color", + "displayName": "Color", + "type": "Color", + "defaultValue": [ 1.0, 1.0, 1.0 ], + "connection": { + "type": "ShaderInput", + "name": "m_baseColor" + } + }, + { + "name": "metallic", + "displayName": "Metallic", + "type": "Float", + "defaultValue": 0.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_metallic" + } + }, + { + "name": "roughness", + "displayName": "Roughness", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_roughness" + } } - } - ] - } + ] + } + ] }, "shaders": [ { diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp index 3f4aa71a9c..cf3205a037 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp @@ -230,7 +230,7 @@ namespace MaterialEditor // create source data from properties MaterialSourceData sourceData; - sourceData.m_propertyLayoutVersion = m_materialTypeSourceData.m_propertyLayout.m_version; + sourceData.m_propertyLayoutVersion = m_materialTypeSourceData.GetPropertyLayout().m_version; sourceData.m_materialType = m_materialSourceData.m_materialType; sourceData.m_parentMaterial = m_materialSourceData.m_parentMaterial; @@ -302,7 +302,7 @@ namespace MaterialEditor // create source data from properties MaterialSourceData sourceData; - sourceData.m_propertyLayoutVersion = m_materialTypeSourceData.m_propertyLayout.m_version; + sourceData.m_propertyLayoutVersion = m_materialTypeSourceData.GetPropertyLayout().m_version; sourceData.m_materialType = m_materialSourceData.m_materialType; sourceData.m_parentMaterial = m_materialSourceData.m_parentMaterial; @@ -373,7 +373,7 @@ namespace MaterialEditor // create source data from properties MaterialSourceData sourceData; - sourceData.m_propertyLayoutVersion = m_materialTypeSourceData.m_propertyLayout.m_version; + sourceData.m_propertyLayoutVersion = m_materialTypeSourceData.GetPropertyLayout().m_version; sourceData.m_materialType = m_materialSourceData.m_materialType; // Only assign a parent path if the source was a .material @@ -592,24 +592,26 @@ namespace MaterialEditor bool result = true; // populate sourceData with properties that meet the filter - m_materialTypeSourceData.EnumerateProperties([this, &sourceData, &propertyFilter, &result](const AZStd::string& groupName, const AZStd::string& propertyName, const auto& propertyDefinition) { + m_materialTypeSourceData.EnumerateProperties([this, &sourceData, &propertyFilter, &result](const AZStd::string& propertyIdContext, const auto& propertyDefinition) { - const MaterialPropertyId propertyId(groupName, propertyName); + const AZStd::string propertyId = propertyIdContext + propertyDefinition->m_name; - const auto it = m_properties.find(propertyId); + const auto it = m_properties.find(Name{propertyId}); if (it != m_properties.end() && propertyFilter(it->second)) { MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(it->second.GetValue()); if (propertyValue.IsValid()) { - if (!m_materialTypeSourceData.ConvertPropertyValueToSourceDataFormat(propertyDefinition, propertyValue)) + if (!m_materialTypeSourceData.ConvertPropertyValueToSourceDataFormat(*propertyDefinition, propertyValue)) { - AZ_Error("MaterialDocument", false, "Material document property could not be converted: '%s' in '%s'.", propertyId.GetCStr(), m_absolutePath.c_str()); + AZ_Error("MaterialDocument", false, "Material document property could not be converted: '%s' in '%s'.", propertyId.c_str(), m_absolutePath.c_str()); result = false; return false; } - - sourceData.m_properties[groupName][propertyName].m_value = propertyValue; + + // TODO: Support populating the Material Editor with nested property sets, not just the top level. + const AZStd::string groupName = propertyId.substr(0, propertyId.size() - propertyDefinition->m_name.size() - 1); + sourceData.m_properties[groupName][propertyDefinition->m_name].m_value = propertyValue; } } return true; @@ -678,7 +680,7 @@ namespace MaterialEditor AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", materialTypeSourceFilePath.c_str()); return false; } - m_materialTypeSourceData = materialTypeOutcome.GetValue(); + m_materialTypeSourceData = materialTypeOutcome.TakeValue(); } else if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), MaterialTypeSourceData::Extension)) { @@ -691,7 +693,7 @@ namespace MaterialEditor AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_absolutePath.c_str()); return false; } - m_materialTypeSourceData = materialTypeOutcome.GetValue(); + m_materialTypeSourceData = materialTypeOutcome.TakeValue(); // The document represents a material, not a material type. // If the input data is a material type file we have to generate the material source data by referencing it. @@ -770,33 +772,41 @@ namespace MaterialEditor // Populate the property map from a combination of source data and assets // Assets must still be used for now because they contain the final accumulated value after all other materials // in the hierarchy are applied - m_materialTypeSourceData.EnumerateProperties([this, &parentPropertyValues](const AZStd::string& groupName, const AZStd::string& propertyName, const auto& propertyDefinition) { - AtomToolsFramework::DynamicPropertyConfig propertyConfig; + m_materialTypeSourceData.EnumeratePropertySets([this, &parentPropertyValues](const AZStd::string& propertyIdContext, const MaterialTypeSourceData::PropertySet* propertySet) + { + AtomToolsFramework::DynamicPropertyConfig propertyConfig; - // Assign id before conversion so it can be used in dynamic description - propertyConfig.m_id = MaterialPropertyId(groupName, propertyName); + for (const auto& propertyDefinition : propertySet->GetProperties()) + { + // Assign id before conversion so it can be used in dynamic description + propertyConfig.m_id = propertyIdContext + propertySet->GetName() + "." + propertyDefinition->m_name; - const auto& propertyIndex = m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id); - const bool propertyIndexInBounds = propertyIndex.IsValid() && propertyIndex.GetIndex() < m_materialAsset->GetPropertyValues().size(); - AZ_Warning("MaterialDocument", propertyIndexInBounds, "Failed to add material property '%s' to document '%s'.", propertyConfig.m_id.GetCStr(), m_absolutePath.c_str()); + const auto& propertyIndex = m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id); + const bool propertyIndexInBounds = propertyIndex.IsValid() && propertyIndex.GetIndex() < m_materialAsset->GetPropertyValues().size(); + AZ_Warning("MaterialDocument", propertyIndexInBounds, "Failed to add material property '%s' to document '%s'.", propertyConfig.m_id.GetCStr(), m_absolutePath.c_str()); - if (propertyIndexInBounds) - { - AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, propertyDefinition); - propertyConfig.m_showThumbnail = true; - propertyConfig.m_originalValue = AtomToolsFramework::ConvertToEditableType(m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]); - propertyConfig.m_parentValue = AtomToolsFramework::ConvertToEditableType(parentPropertyValues[propertyIndex.GetIndex()]); - auto groupDefinition = m_materialTypeSourceData.FindGroup(groupName); - propertyConfig.m_groupName = groupDefinition ? groupDefinition->m_displayName : groupName; - m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig); - } - return true; - }); + if (propertyIndexInBounds) + { + AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, *propertyDefinition); + propertyConfig.m_showThumbnail = true; + propertyConfig.m_originalValue = AtomToolsFramework::ConvertToEditableType(m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]); + propertyConfig.m_parentValue = AtomToolsFramework::ConvertToEditableType(parentPropertyValues[propertyIndex.GetIndex()]); + + // TODO: Support populating the Material Editor with nested property sets, not just the top level. + // (Does DynamicPropertyConfig really even need m_groupName?) + propertyConfig.m_groupName = propertySet->GetDisplayName(); + m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig); + } + } + + return true; + }); // Populate the property group visibility map - for (MaterialTypeSourceData::GroupDefinition& group : m_materialTypeSourceData.GetGroupDefinitionsInDisplayOrder()) + // TODO: Support populating the Material Editor with nested property sets, not just the top level. + for (const AZStd::unique_ptr& propertySet : m_materialTypeSourceData.GetPropertyLayout().m_propertySets) { - m_propertyGroupVisibility[AZ::Name{group.m_name}] = true; + m_propertyGroupVisibility[AZ::Name{propertySet->GetName()}] = true; } // Adding properties for material type and parent as part of making dynamic @@ -877,6 +887,39 @@ namespace MaterialEditor return false; } } + + bool enumerateResult = m_materialTypeSourceData.EnumeratePropertySets( + [this, &materialTypeSourceFilePath](const AZStd::string&, const MaterialTypeSourceData::PropertySet* propertySet) + { + const MaterialFunctorSourceData::EditorContext editorContext = MaterialFunctorSourceData::EditorContext( + materialTypeSourceFilePath, m_materialAsset->GetMaterialPropertiesLayout()); + + for (Ptr functorData : propertySet->GetFunctors()) + { + MaterialFunctorSourceData::FunctorResult result = functorData->CreateFunctor(editorContext); + + if (result.IsSuccess()) + { + Ptr& functor = result.GetValue(); + if (functor != nullptr) + { + m_editorFunctors.push_back(functor); + } + } + else + { + AZ_Error("MaterialDocument", false, "Material functors were not created: '%s'.", m_absolutePath.c_str()); + return false; + } + } + + return true; + }); + + if (!enumerateResult) + { + return false; + } AZ::RPI::MaterialPropertyFlags dirtyFlags; dirtyFlags.set(); // Mark all properties as dirty since we just loaded the material and need to initialize property visibility diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp index f6b42bf13a..e279b7e5b0 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp @@ -163,28 +163,23 @@ namespace MaterialEditor const AZ::RPI::MaterialTypeSourceData* materialTypeSourceData = nullptr; MaterialDocumentRequestBus::EventResult( materialTypeSourceData, m_documentId, &MaterialDocumentRequestBus::Events::GetMaterialTypeSourceData); - - for (const auto& groupDefinition : materialTypeSourceData->GetGroupDefinitionsInDisplayOrder()) + + // TODO: Support populating the Material Editor with nested property sets, not just the top level. + for (const AZStd::unique_ptr& propertySet : materialTypeSourceData->GetPropertyLayout().m_propertySets) { - const AZStd::string& groupName = groupDefinition.m_name; - const AZStd::string& groupDisplayName = !groupDefinition.m_displayName.empty() ? groupDefinition.m_displayName : groupName; - const AZStd::string& groupDescription = - !groupDefinition.m_description.empty() ? groupDefinition.m_description : groupDisplayName; + const AZStd::string& groupName = propertySet->GetName(); + const AZStd::string& groupDisplayName = !propertySet->GetDisplayName().empty() ? propertySet->GetDisplayName() : groupName; + const AZStd::string& groupDescription = !propertySet->GetDescription().empty() ? propertySet->GetDescription() : groupDisplayName; auto& group = m_groups[groupName]; - const auto& propertyLayout = materialTypeSourceData->m_propertyLayout; - const auto& propertyListItr = propertyLayout.m_properties.find(groupName); - if (propertyListItr != propertyLayout.m_properties.end()) + group.m_properties.reserve(propertySet->GetProperties().size()); + for (const auto& propertyDefinition : propertySet->GetProperties()) { - group.m_properties.reserve(propertyListItr->second.size()); - for (const auto& propertyDefinition : propertyListItr->second) - { - AtomToolsFramework::DynamicProperty property; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult( - property, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetProperty, - AZ::RPI::MaterialPropertyId(groupName, propertyDefinition.m_name)); - group.m_properties.push_back(property); - } + AtomToolsFramework::DynamicProperty property; + AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult( + property, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetProperty, + AZ::RPI::MaterialPropertyId(groupName, propertyDefinition->m_name)); + group.m_properties.push_back(property); } // Passing in same group as main and comparison instance to enable custom value comparison for highlighting modified properties diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp index c05b84db39..86610ecc93 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp @@ -283,35 +283,31 @@ namespace AZ AddUvNamesGroup(); // Copy all of the properties from the material asset to the source data that will be exported - for (const auto& groupDefinition : m_editData.m_materialTypeSourceData.GetGroupDefinitionsInDisplayOrder()) + // TODO: Support populating the Material Editor with nested property sets, not just the top level. + for (const AZStd::unique_ptr& propertySet : m_editData.m_materialTypeSourceData.GetPropertyLayout().m_propertySets) { - const AZStd::string& groupName = groupDefinition.m_name; - const AZStd::string& groupDisplayName = !groupDefinition.m_displayName.empty() ? groupDefinition.m_displayName : groupName; - const AZStd::string& groupDescription = !groupDefinition.m_description.empty() ? groupDefinition.m_description : groupDisplayName; + const AZStd::string& groupName = propertySet->GetName(); + const AZStd::string& groupDisplayName = !propertySet->GetDisplayName().empty() ? propertySet->GetDisplayName() : groupName; + const AZStd::string& groupDescription = !propertySet->GetDescription().empty() ? propertySet->GetDescription() : groupDisplayName; auto& group = m_groups[groupName]; - - const auto& propertyLayout = m_editData.m_materialTypeSourceData.m_propertyLayout; - const auto& propertyListItr = propertyLayout.m_properties.find(groupName); - if (propertyListItr != propertyLayout.m_properties.end()) + + group.m_properties.reserve(propertySet->GetProperties().size()); + for (const auto& propertyDefinition : propertySet->GetProperties()) { - group.m_properties.reserve(propertyListItr->second.size()); - for (const auto& propertyDefinition : propertyListItr->second) - { - AtomToolsFramework::DynamicPropertyConfig propertyConfig; + AtomToolsFramework::DynamicPropertyConfig propertyConfig; - // Assign id before conversion so it can be used in dynamic description - propertyConfig.m_id = AZ::RPI::MaterialPropertyId(groupName, propertyDefinition.m_name); + // Assign id before conversion so it can be used in dynamic description + propertyConfig.m_id = AZ::RPI::MaterialPropertyId(groupName, propertyDefinition->m_name); - AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, propertyDefinition); + AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, *propertyDefinition.get()); - propertyConfig.m_groupName = groupDisplayName; - const auto& propertyIndex = m_editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id); - propertyConfig.m_showThumbnail = true; - propertyConfig.m_defaultValue = AtomToolsFramework::ConvertToEditableType(m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]); - propertyConfig.m_parentValue = AtomToolsFramework::ConvertToEditableType(m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]); - propertyConfig.m_originalValue = AtomToolsFramework::ConvertToEditableType(m_editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]); - group.m_properties.emplace_back(propertyConfig); - } + propertyConfig.m_groupName = groupDisplayName; + const auto& propertyIndex = m_editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id); + propertyConfig.m_showThumbnail = true; + propertyConfig.m_defaultValue = AtomToolsFramework::ConvertToEditableType(m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]); + propertyConfig.m_parentValue = AtomToolsFramework::ConvertToEditableType(m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]); + propertyConfig.m_originalValue = AtomToolsFramework::ConvertToEditableType(m_editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]); + group.m_properties.emplace_back(propertyConfig); } // Passing in same group as main and comparison instance to enable custom value comparison for highlighting modified properties diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp index e851bc5fce..2593a12f19 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp @@ -91,7 +91,7 @@ namespace AZ AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Failed to load material type source data: %s", editData.m_materialTypeSourcePath.c_str()); return false; } - editData.m_materialTypeSourceData = materialTypeOutcome.GetValue(); + editData.m_materialTypeSourceData = materialTypeOutcome.TakeValue(); return true; } @@ -99,7 +99,7 @@ namespace AZ { // Construct the material source data object that will be exported AZ::RPI::MaterialSourceData exportData; - exportData.m_propertyLayoutVersion = editData.m_materialTypeSourceData.m_propertyLayout.m_version; + exportData.m_propertyLayoutVersion = editData.m_materialTypeSourceData.GetPropertyLayout().m_version; // Converting absolute material paths to relative paths bool result = false; @@ -137,42 +137,46 @@ namespace AZ // Copy all of the properties from the material asset to the source data that will be exported result = true; - editData.m_materialTypeSourceData.EnumerateProperties([&](const AZStd::string& groupName, const AZStd::string& propertyName, const auto& propertyDefinition) { - const AZ::RPI::MaterialPropertyId propertyId(groupName, propertyName); - const AZ::RPI::MaterialPropertyIndex propertyIndex = - editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId); - - AZ::RPI::MaterialPropertyValue propertyValue = editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]; - - AZ::RPI::MaterialPropertyValue propertyValueDefault = propertyDefinition.m_value; - if (editData.m_materialParentAsset.IsReady()) - { - propertyValueDefault = editData.m_materialParentAsset->GetPropertyValues()[propertyIndex.GetIndex()]; - } - - // Check for and apply any property overrides before saving property values - auto propertyOverrideItr = editData.m_materialPropertyOverrideMap.find(propertyId); - if(propertyOverrideItr != editData.m_materialPropertyOverrideMap.end()) - { - propertyValue = AZ::RPI::MaterialPropertyValue::FromAny(propertyOverrideItr->second); - } - - if (!editData.m_materialTypeSourceData.ConvertPropertyValueToSourceDataFormat(propertyDefinition, propertyValue)) - { - AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Failed to export: %s", path.c_str()); - result = false; - return false; - } - - // Don't export values if they are the same as the material type or parent - if (propertyValueDefault == propertyValue) + editData.m_materialTypeSourceData.EnumerateProperties([&](const AZStd::string& propertyIdContext, const AZ::RPI::MaterialTypeSourceData::PropertyDefinition* propertyDefinition) { + AZ::Name propertyId(propertyIdContext + propertyDefinition->m_name); + + const AZ::RPI::MaterialPropertyIndex propertyIndex = + editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId); + + AZ::RPI::MaterialPropertyValue propertyValue = editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]; + + AZ::RPI::MaterialPropertyValue propertyValueDefault = propertyDefinition->m_value; + if (editData.m_materialParentAsset.IsReady()) + { + propertyValueDefault = editData.m_materialParentAsset->GetPropertyValues()[propertyIndex.GetIndex()]; + } + + // Check for and apply any property overrides before saving property values + auto propertyOverrideItr = editData.m_materialPropertyOverrideMap.find(propertyId); + if(propertyOverrideItr != editData.m_materialPropertyOverrideMap.end()) + { + propertyValue = AZ::RPI::MaterialPropertyValue::FromAny(propertyOverrideItr->second); + } + + if (!editData.m_materialTypeSourceData.ConvertPropertyValueToSourceDataFormat(*propertyDefinition, propertyValue)) + { + AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Failed to export: %s", path.c_str()); + result = false; + return false; + } + + // Don't export values if they are the same as the material type or parent + if (propertyValueDefault == propertyValue) + { + return true; + } + + // TODO: Support populating the Material Editor with nested property sets, not just the top level. + const AZStd::string groupName = propertyId.GetStringView().substr(0, propertyId.GetStringView().size() - propertyDefinition->m_name.size() - 1); + exportData.m_properties[groupName][propertyDefinition->m_name].m_value = propertyValue; return true; - } - - exportData.m_properties[groupName][propertyDefinition.m_name].m_value = propertyValue; - return true; - }); + }); return result && AZ::RPI::JsonUtils::SaveObjectToFile(path, exportData); } From 9d09656023899e3faa0dcd1e6a6633f8a4dfb1d7 Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Thu, 30 Sep 2021 01:33:05 -0700 Subject: [PATCH 04/53] Removed commented out code. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Material/MaterialTypeSourceData.h | 8 --- .../Material/MaterialTypeSourceData.cpp | 71 ------------------- 2 files changed, 79 deletions(-) diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h index 56f7c4612c..1d918702ff 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h @@ -180,9 +180,7 @@ namespace AZ }; PropertySet* AddPropertySet(AZStd::string_view propertySetId); - //PropertySet* AddPropertySet(AZStd::string_view parentPropertySetId, AZStd::string_view name); PropertyDefinition* AddProperty(AZStd::string_view propertyId); - //PropertyDefinition* AddProperty(AZStd::string_view parentPropertySetId, AZStd::string_view name); const PropertyLayout& GetPropertyLayout() const { return m_propertyLayout; } @@ -241,15 +239,9 @@ namespace AZ private: - //PropertySet* FindPropertySet(AZStd::array_view parsedPropertySetId, AZStd::array_view> inPropertySetList); const PropertySet* FindPropertySet(AZStd::array_view parsedPropertySetId, AZStd::array_view> inPropertySetList) const; - - //PropertyDefinition* FindProperty(AZStd::array_view parsedPropertyId, AZStd::array_view> inPropertySetList); const PropertyDefinition* FindProperty(AZStd::array_view parsedPropertyId, AZStd::array_view> inPropertySetList) const; - //PropertyDefinition* FindProperty(AZStd::array_view parsedPropertyId, PropertySet& inPropertySet); - //const PropertyDefinition* FindProperty(AZStd::array_view parsedPropertyId, const PropertySet& inPropertySet) const; - // Function overloads for recursion, returns false to indicate that recursion should end. bool EnumeratePropertySets(const EnumeratePropertySetsCallback& callback, AZStd::string propertyIdContext, const AZStd::vector>& inPropertySetList) const; bool EnumerateProperties(const EnumeratePropertiesCallback& callback, AZStd::string propertyIdContext, const AZStd::vector>& inPropertySetList) const; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp index de55cbecb5..816e9f4f8d 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp @@ -196,9 +196,6 @@ namespace AZ return PropertySet::AddPropertySet(propertySetId, m_propertyLayout.m_propertySets); } - // TODO: Delete - //return AddPropertySet(splitPropertySetId[0], splitPropertySetId[1]); - PropertySet* parentPropertySet = const_cast(const_cast(this)->FindPropertySet(splitPropertySetId[0])); if (!parentPropertySet) @@ -210,27 +207,9 @@ namespace AZ return parentPropertySet->AddPropertySet(splitPropertySetId[1]); } - // TODO: Delete - //MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::AddPropertySet(AZStd::string_view parentPropertySetId, AZStd::string_view name) - //{ - // PropertySet* parentPropertySet = const_cast(const_cast(this)->FindPropertySet(parentPropertySetId)); - // - // if (!parentPropertySet) - // { - // AZ_Error("Material source data", false, "PropertySet '%.*s' does not exists", AZ_STRING_ARG(parentPropertySetId)); - // return nullptr; - // } - - // return parentPropertySet->AddPropertySet(name); - //} - MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::AddProperty(AZStd::string_view propertyId) { AZStd::vector splitPropertyId = SplitId(propertyId); - //if (splitPropertyId.empty()) - //{ - // return nullptr; - //} if (splitPropertyId.size() == 1) { @@ -238,9 +217,6 @@ namespace AZ return nullptr; } - // TODO: Delete - //return AddProperty(splitPropertyId[0], splitPropertyId[1]); - PropertySet* parentPropertySet = const_cast(const_cast(this)->FindPropertySet(splitPropertyId[0])); if (!parentPropertySet) @@ -252,20 +228,6 @@ namespace AZ return parentPropertySet->AddProperty(splitPropertyId[1]); } - // TODO: Delete - //MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::AddProperty(AZStd::string_view parentPropertySetId, AZStd::string_view name) - //{ - // PropertySet* parentPropertySet = const_cast(const_cast(this)->FindPropertySet(parentPropertySetId)); - // - // if (!parentPropertySet) - // { - // AZ_Error("Material source data", false, "PropertySet '%.*s' does not exists", AZ_STRING_ARG(parentPropertySetId)); - // return nullptr; - // } - - // return parentPropertySet->AddProperty(name); - //} - const MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::FindPropertySet(AZStd::array_view parsedPropertySetId, AZStd::array_view> inPropertySetList) const { for (const auto& propertySet : inPropertySetList) @@ -296,40 +258,12 @@ namespace AZ return nullptr; } - //MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::FindPropertySet(AZStd::array_view parsedPropertySetId, AZStd::array_view> inPropertySetList) - //{ - // return const_cast(const_cast(this)->FindPropertySet(parsedPropertySetId, inPropertySetList)); - //} - const MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::FindPropertySet(AZStd::string_view propertySetId) const { AZStd::vector tokens = TokenizeId(propertySetId); return FindPropertySet(tokens, m_propertyLayout.m_propertySets); } - //MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::array_view parsedPropertyId, PropertySet& inPropertySet) - //{ - // if (parsedPropertyId.size() == 1) - // { - // for (AZStd::unique_ptr& property : inPropertySet.m_properties) - // { - // if (property->m_name == parsedPropertyId[0]) - // { - // return property.get(); - // } - // } - // } - - // return FindProperty(parsedPropertyId, inPropertySet.m_propertySets); - //} - - //const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::array_view parsedPropertyId, const PropertySet& inPropertySet) const - //{ - // MaterialTypeSourceData* nonConstThis = const_cast(this); - // PropertySet& nonConstPropertySet = *const_cast(&inPropertySet); - // return const_cast(nonConstThis->FindProperty(parsedPropertyId, nonConstPropertySet)); - //} - const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty( AZStd::array_view parsedPropertyId, AZStd::array_view> inPropertySetList) const @@ -364,11 +298,6 @@ namespace AZ return nullptr; } - //MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::array_view parsedPropertyId, AZStd::array_view> inPropertySetList) - //{ - // return const_cast(const_cast(this)->FindProperty(parsedPropertyId, inPropertySetList)); - //} - const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::string_view propertyId) const { AZStd::vector tokens = TokenizeId(propertyId); From 95cfe056a1bc72eb5c77f01a347d538e9e7de8e8 Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Fri, 1 Oct 2021 17:00:45 -0700 Subject: [PATCH 05/53] Updated .materialtype files to use the new format, "propertySets" instead of "groups". First I used some local changes to do an auto-conversion that loaded, upgraded, and then saved each .materialtype but some details were lost in translation. For example, comments were lost, and default values changed slightly. So I grabbed an original copy of each file, and copied/pasted each section from the old file to the new one, while preserving the new layout. I did not update StandardMultilayerPBR because that will be too much work, will wait until after common property sets can be factored out. And I will likely discard that auto-conversion soon. Testing: AtomSampleViewer MaterialScreenshotTest script passed with the same results as before. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Special/ShadowCatcher.materialtype | 58 +- .../Materials/Types/EnhancedPBR.materialtype | 2761 ++++++++--------- .../Assets/Materials/Types/Skin.materialtype | 1848 ++++++----- .../Materials/Types/StandardPBR.materialtype | 1909 ++++++------ .../Materials/Types/AutoBrick.materialtype | 327 +- 5 files changed, 3446 insertions(+), 3457 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.materialtype index 74246f85db..62d3db022b 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.materialtype @@ -2,38 +2,40 @@ "description": "Base material for the reflection probe visualization model.", "propertyLayout": { "version": 1, - "properties": { - "settings": [ - { - "name": "opacity", - "displayName": "Opacity", - "description": "Opacity of the shadow effect.", - "type": "Float", - "defaultValue": 0.5, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_opacity" + "propertySets": [ + { + "name": "settings", + "properties": [ + { + "name": "opacity", + "displayName": "Opacity", + "description": "Opacity of the shadow effect.", + "type": "Float", + "defaultValue": 0.5, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_opacity" + } + }, + { + "name": "shadeAll", + "displayName": "Shade All", + "description": "Shades the entire geometry with the shadow color, not just what's in shadow. For debugging.", + "type": "Bool", + "connection": { + "type": "ShaderOption", + "name": "o_shadeAll" + } } - }, - { - "name": "shadeAll", - "displayName": "Shade All", - "description": "Shades the entire geometry with the shadow color, not just what's in shadow. For debugging.", - "type": "Bool", - "connection": { - "type": "ShaderOption", - "name": "o_shadeAll" - } - } - ] - } + ] + } + ] }, "shaders": [ { "file": "ShadowCatcher.shader" } ] -} - +} \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype index bed3b69c4c..3d227a9aa0 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype @@ -2,1456 +2,1453 @@ "description": "Material Type with properties used to define Enhanced PBR, a metallic-roughness Physically-Based Rendering (PBR) material shading model, with advanced features like subsurface scattering, transmission, and anisotropy.", "propertyLayout": { "version": 3, - "groups": [ + "propertySets": [ { "name": "baseColor", "displayName": "Base Color", - "description": "Properties for configuring the surface reflected color for dielectrics or reflectance values for metals." + "description": "Properties for configuring the surface reflected color for dielectrics or reflectance values for metals.", + "properties": [ + { + "name": "color", + "displayName": "Color", + "description": "Color is displayed as sRGB but the values are stored as linear color.", + "type": "Color", + "defaultValue": [ 1.0, 1.0, 1.0 ], + "connection": { + "type": "ShaderInput", + "name": "m_baseColor" + } + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Strength factor for scaling the base color values. Zero (0.0) is black, white (1.0) is full color.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_baseColorFactor" + } + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "Base color texture map", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_baseColorMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Base color map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_baseColorMapUvIndex" + } + }, + { + "name": "textureBlendMode", + "displayName": "Texture Blend Mode", + "description": "Selects the equation to use when combining Color, Factor, and Texture.", + "type": "Enum", + "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], + "defaultValue": "Multiply", + "connection": { + "type": "ShaderOption", + "name": "o_baseColorTextureBlendMode" + } + } + ] }, { "name": "metallic", "displayName": "Metallic", - "description": "Properties for configuring whether the surface is metallic or not." + "description": "Properties for configuring whether the surface is metallic or not.", + "properties": [ + { + "name": "factor", + "displayName": "Factor", + "description": "This value is linear, black is non-metal and white means raw metal.", + "type": "Float", + "defaultValue": 0.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_metallicFactor" + } + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_metallicMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just default to the Factor value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Metallic map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_metallicMapUvIndex" + } + } + ] }, { "name": "roughness", "displayName": "Roughness", - "description": "Properties for configuring how rough the surface appears." + "description": "Properties for configuring how rough the surface appears.", + "properties": [ + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface roughness.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_roughnessMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just default to the Factor value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Roughness map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_roughnessMapUvIndex" + } + }, + { + // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. + "name": "lowerBound", + "displayName": "Lower Bound", + "description": "The roughness value that corresponds to black in the texture.", + "type": "Float", + "defaultValue": 0.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_roughnessLowerBound" + } + }, + { + // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. + "name": "upperBound", + "displayName": "Upper Bound", + "description": "The roughness value that corresponds to white in the texture.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_roughnessUpperBound" + } + }, + { + // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. + "name": "factor", + "displayName": "Factor", + "description": "Controls the roughness value", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_roughnessFactor" + } + } + ] }, { "name": "specularF0", "displayName": "Specular Reflectance f0", - "description": "The constant f0 represents the specular reflectance at normal incidence (Fresnel 0 Angle). Used to adjust reflectance of non-metal surfaces." + "description": "The constant f0 represents the specular reflectance at normal incidence (Fresnel 0 Angle). Used to adjust reflectance of non-metal surfaces.", + "properties": [ + { + "name": "factor", + "displayName": "Factor", + "description": "The default IOR is 1.5, which gives you 0.04 (4% of light reflected at 0 degree angle for dielectric materials). F0 values lie in the range 0-0.08, so that is why the default F0 slider is set on 0.5.", + "type": "Float", + "defaultValue": 0.5, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_specularF0Factor" + } + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface reflectance.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_specularF0Map" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just default to the Factor value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Specular reflection map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_specularF0MapUvIndex" + } + }, + // Consider moving this to the "general" group to be consistent with StandardMultilayerPBR + { + "name": "enableMultiScatterCompensation", + "displayName": "Multiscattering Compensation", + "description": "Whether to enable multiple scattering compensation.", + "type": "Bool", + "connection": { + "type": "ShaderOption", + "name": "o_specularF0_enableMultiScatterCompensation" + } + } + ] }, { "name": "normal", "displayName": "Normal", - "description": "Properties related to configuring surface normal." + "description": "Properties related to configuring surface normal.", + "properties": [ + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface normal direction.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_normalMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just rely on vertex normals.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Normal map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_normalMapUvIndex" + } + }, + { + "name": "flipX", + "displayName": "Flip X Channel", + "description": "Flip tangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "name": "m_flipNormalX" + } + }, + { + "name": "flipY", + "displayName": "Flip Y Channel", + "description": "Flip bitangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "name": "m_flipNormalY" + } + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Strength factor for scaling the values", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_normalFactor" + } + } + ] }, { "name": "detailLayerGroup", "displayName": "Detail Layer", - "description": "Properties for Fine Details Layer." + "description": "Properties for Fine Details Layer.", + "properties": [ + { + "name": "enableDetailLayer", + "displayName": "Enable Detail Layer", + "description": "Enable detail layer for fine details and scratches", + "type": "Bool", + "defaultValue": false + }, + { + "name": "blendDetailFactor", + "displayName": "Blend Factor", + "description": "Scales the overall impact of the detail layer.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_detail_blendFactor" + } + }, + { + "name": "blendDetailMask", + "displayName": "Blend Mask", + "description": "Detailed blend mask for application of the detail maps.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_detail_blendMask_texture" + } + }, + { + "name": "enableDetailMaskTexture", + "displayName": " Use Texture", + "description": "Enable detail blend mask", + "type": "Bool", + "defaultValue": true + }, + { + "name": "blendDetailMaskUv", + "displayName": " Blend Mask UV", + "description": "Which UV set to use for sampling the detail blend mask", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_detail_blendMask_uvIndex" + } + }, + { + "name": "textureMapUv", + "displayName": "Detail Map UVs", + "description": "Which UV set to use for detail map sampling", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_detail_allMapsUvIndex" + } + }, + { + "name": "enableBaseColor", + "displayName": "Enable Base Color", + "description": "Enable detail blending for base color", + "type": "Bool", + "defaultValue": false + }, + { + "name": "baseColorDetailMap", + "displayName": " Texture", + "description": "Detailed Base Color Texture", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_detail_baseColor_texture" + } + }, + { + "name": "baseColorDetailBlend", + "displayName": " Blend Factor", + "description": "How much to blend the detail layer into the base color.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_detail_baseColor_factor" + } + }, + { + "name": "enableNormals", + "displayName": "Enable Normal", + "description": "Enable detail normal map to be used for fine detail normal such as scratches and small dents", + "type": "Bool", + "defaultValue": false + }, + { + "name": "normalDetailStrength", + "displayName": " Factor", + "description": "Strength factor for scaling the Detail Normal", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_detail_normal_factor" + } + }, + { + "name": "normalDetailMap", + "displayName": " Texture", + "description": "Detailed Normal map", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_detail_normal_texture" + } + }, + { + "name": "normalDetailFlipX", + "displayName": " Flip X Channel", + "description": "Flip Detail tangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "name": "m_detail_normal_flipX" + } + }, + { + "name": "normalDetailFlipY", + "displayName": " Flip Y Channel", + "description": "Flip Detail bitangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "name": "m_detail_normal_flipY" + } + } + ] }, { "name": "detailUV", "displayName": "Detail Layer UV", - "description": "Properties for modifying detail layer UV." + "description": "Properties for modifying detail layer UV.", + "properties": [ + { + "name": "center", + "displayName": "Center", + "description": "Center point for scaling and rotation transformations.", + "type": "vector2", + "vectorLabels": [ "U", "V" ], + "defaultValue": [ 0.5, 0.5 ] + }, + { + "name": "tileU", + "displayName": "Tile U", + "description": "Scales texture coordinates in V.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + }, + { + "name": "tileV", + "displayName": "Tile V", + "description": "Scales texture coordinates in V.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + }, + { + "name": "offsetU", + "displayName": "Offset U", + "description": "Offsets texture coordinates in the U direction.", + "type": "float", + "defaultValue": 0.0, + "min": -1.0, + "max": 1.0 + }, + { + "name": "offsetV", + "displayName": "Offset V", + "description": "Offsets texture coordinates in the V direction.", + "type": "float", + "defaultValue": 0.0, + "min": -1.0, + "max": 1.0 + }, + { + "name": "rotateDegrees", + "displayName": "Rotate", + "description": "Rotates the texture coordinates (degrees).", + "type": "float", + "defaultValue": 0.0, + "min": -180.0, + "max": 180.0, + "step": 1.0 + }, + { + "name": "scale", + "displayName": "Scale", + "description": "Scales texture coordinates in both U and V.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + } + ] }, { "name": "anisotropy", "displayName": "Anisotropic Material Response", - "description": "How much is this material response anisotropic." + "description": "How much is this material response anisotropic.", + "properties": [ + { + "name": "enableAnisotropy", + "displayName": "Enable Anisotropy", + "description": "Enable anisotropic surface response for non uniform reflection along the axis", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_enableAnisotropy" + } + }, + { + "name": "factor", + "displayName": "Anisotropy Factor", + "description": "Strength factor for the anisotropy: negative = along v, positive = along u", + "type": "Float", + "defaultValue": 0.0, + "min": -0.95, + "max": 0.95, + "connection": { + "type": "ShaderInput", + "name": "m_anisotropicFactor" + } + }, + { + "name": "anisotropyAngle", + "displayName": "Anisotropy Angle", + "description": "Anisotropy direction of major reflection axis: 0 = 0 degrees, 1.0 = 180 degrees", + "type": "Float", + "defaultValue": 0.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_anisotropicAngle" + } + } + ] }, { "name": "occlusion", "displayName": "Occlusion", - "description": "Properties for baked textures that represent geometric occlusion of light." + "description": "Properties for baked textures that represent geometric occlusion of light.", + "properties": [ + { + "name": "diffuseTextureMap", + "displayName": "Diffuse AO", + "description": "Texture for defining occlusion area for diffuse ambient lighting.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_diffuseOcclusionMap" + } + }, + { + "name": "diffuseUseTexture", + "displayName": " Use Texture", + "description": "Whether to use the Diffuse AO map.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "diffuseTextureMapUv", + "displayName": " UV", + "description": "Diffuse AO map UV set.", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_diffuseOcclusionMapUvIndex" + } + }, + { + "name": "diffuseFactor", + "displayName": " Factor", + "description": "Strength factor for scaling the values of Diffuse AO", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_diffuseOcclusionFactor" + } + }, + { + "name": "specularTextureMap", + "displayName": "Specular Cavity", + "description": "Texture for defining occlusion area for specular lighting.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_specularOcclusionMap" + } + }, + { + "name": "specularUseTexture", + "displayName": " Use Texture", + "description": "Whether to use the Specular Cavity map.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "specularTextureMapUv", + "displayName": " UV", + "description": "Specular Cavity map UV set.", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_specularOcclusionMapUvIndex" + } + }, + { + "name": "specularFactor", + "displayName": " Factor", + "description": "Strength factor for scaling the values of Specular Cavity", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_specularOcclusionFactor" + } + } + ] }, { "name": "emissive", "displayName": "Emissive", - "description": "Properties to add light emission, independent of other lights in the scene." + "description": "Properties to add light emission, independent of other lights in the scene.", + "properties": [ + { + "name": "enable", + "displayName": "Enable", + "description": "Enable the emissive group", + "type": "Bool", + "defaultValue": false + }, + { + "name": "unit", + "displayName": "Units", + "description": "The photometric units of the Intensity property.", + "type": "Enum", + "enumValues": ["Ev100"], + "defaultValue": "Ev100" + }, + { + "name": "color", + "displayName": "Color", + "description": "Color is displayed as sRGB but the values are stored as linear color.", + "type": "Color", + "defaultValue": [ 1.0, 1.0, 1.0 ], + "connection": { + "type": "ShaderInput", + "name": "m_emissiveColor" + } + }, + { + "name": "intensity", + "displayName": "Intensity", + "description": "The amount of energy emitted.", + "type": "Float", + "defaultValue": 4, + "min": -10, + "max": 20, + "softMin": -6, + "softMax": 16 + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining emissive area.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_emissiveMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Emissive map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_emissiveMapUvIndex" + } + } + ] }, { "name": "subsurfaceScattering", "displayName": "Subsurface Scattering", - "description": "Properties for configuring subsurface scattering effects." + "description": "Properties for configuring subsurface scattering effects.", + "properties": [ + { + "name": "enableSubsurfaceScattering", + "displayName": "Subsurface Scattering", + "description": "Enable subsurface scattering feature, this will disable metallic and parallax mapping property due to incompatibility", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_enableSubsurfaceScattering" + } + }, + { + "name": "subsurfaceScatterFactor", + "displayName": " Factor", + "description": "Strength factor for scaling percentage of subsurface scattering effect applied", + "type": "float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_subsurfaceScatteringFactor" + } + }, + { + "name": "influenceMap", + "displayName": " Influence Map", + "description": "Texture for controlling the strength of subsurface scattering", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_subsurfaceScatteringInfluenceMap" + } + }, + { + "name": "useInfluenceMap", + "displayName": " Use Influence Map", + "description": "Whether to use the influence map.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "influenceMapUv", + "displayName": " UV", + "description": "Influence map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_subsurfaceScatteringInfluenceMapUvIndex" + } + }, + { + "name": "scatterColor", + "displayName": " Scatter color", + "description": "Color of volume light traveled through", + "type": "Color", + "defaultValue": [ 1.0, 0.27, 0.13 ] + }, + { + "name": "scatterDistance", + "displayName": " Scatter distance", + "description": "How far light traveled inside the volume", + "type": "float", + "defaultValue": 8, + "min": 0.0, + "softMax": 20.0 + }, + { + "name": "quality", + "displayName": " Quality", + "description": "How much percent of sample will be used for each pixel, more samples improve quality and reduce artifacts, especially when the scatter distance is relatively large, but slow down computation time, 1.0 = full set 200 samples per pixel", + "type": "float", + "defaultValue": 0.4, + "min": 0.2, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_subsurfaceScatteringQuality" + } + }, + { + "name": "transmissionMode", + "displayName": "Transmission", + "description": "Algorithm used for calculating transmission", + "type": "Enum", + "enumValues": [ "None", "ThickObject", "ThinObject" ], + "defaultValue": "None", + "connection": { + "type": "ShaderOption", + "name": "o_transmission_mode" + } + }, + { + "name": "thickness", + "displayName": " Thickness", + "description": "Normalized global thickness, the maxima between this value (multiplied by thickness map if enabled) and thickness from shadow map (if applicable) will be used as final thickness of pixel", + "type": "float", + "defaultValue": 0.5, + "min": 0.0, + "max": 1.0 + }, + { + "name": "thicknessMap", + "displayName": " Thickness Map", + "description": "Texture for controlling per pixel thickness", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_transmissionThicknessMap" + } + }, + { + "name": "useThicknessMap", + "displayName": " Use Thickness Map", + "description": "Whether to use the thickness map", + "type": "Bool", + "defaultValue": true + }, + { + "name": "thicknessMapUv", + "displayName": " UV", + "description": "Thickness map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_transmissionThicknessMapUvIndex" + } + }, + { + "name": "transmissionTint", + "displayName": " Transmission Tint", + "description": "Color of the volume light traveling through", + "type": "Color", + "defaultValue": [ 1.0, 0.8, 0.6 ] + }, + { + "name": "transmissionPower", + "displayName": " Power", + "description": "How much transmitted light scatter radially ", + "type": "float", + "defaultValue": 6.0, + "min": 0.0, + "softMax": 20.0 + }, + { + "name": "transmissionDistortion", + "displayName": " Distortion", + "description": "How much light direction distorted towards surface normal", + "type": "float", + "defaultValue": 0.1, + "min": 0.0, + "max": 1.0 + }, + { + "name": "transmissionAttenuation", + "displayName": " Attenuation", + "description": "How fast transmitted light fade with thickness", + "type": "float", + "defaultValue": 4.0, + "min": 0.0, + "softMax": 20.0 + }, + { + "name": "transmissionScale", + "displayName": " Scale", + "description": "Strength of transmission", + "type": "float", + "defaultValue": 3.0, + "min": 0.0, + "softMax": 20.0 + } + ] }, { "name": "clearCoat", "displayName": "Clear Coat", - "description": "Properties for configuring gloss clear coat" - }, + "description": "Properties for configuring gloss clear coat", + "properties": [ + { + "name": "enable", + "displayName": "Enable", + "description": "Enable clear coat", + "type": "Bool", + "defaultValue": false + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Strength factor for scaling the percentage of effect applied", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatFactor" + } + }, + { + "name": "influenceMap", + "displayName": " Influence Map", + "description": "Strength factor texture", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatInfluenceMap" + } + }, + { + "name": "useInfluenceMap", + "displayName": " Use Texture", + "description": "Whether to use the texture, or just default to the Factor value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "influenceMapUv", + "displayName": " UV", + "description": "Strength factor map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatInfluenceMapUvIndex" + } + }, + { + "name": "roughness", + "displayName": "Roughness", + "description": "Clear coat layer roughness", + "type": "Float", + "defaultValue": 0.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatRoughness" + } + }, + { + "name": "roughnessMap", + "displayName": " Roughness Map", + "description": "Texture for defining surface roughness", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatRoughnessMap" + } + }, + { + "name": "useRoughnessMap", + "displayName": " Use Texture", + "description": "Whether to use the texture, or just default to the roughness value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "roughnessMapUv", + "displayName": " UV", + "description": "Roughness map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatRoughnessMapUvIndex" + } + }, + { + "name": "normalStrength", + "displayName": "Normal Strength", + "description": "Scales the impact of the clear coat normal map", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatNormalStrength" + } + }, + { + "name": "normalMap", + "displayName": "Normal Map", + "description": "Normal map for clear coat layer, as top layer material clear coat doesn't affect by base layer normal map", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatNormalMap" + } + }, + { + "name": "useNormalMap", + "displayName": " Use Texture", + "description": "Whether to use the normal map", + "type": "Bool", + "defaultValue": true + }, + { + "name": "normalMapUv", + "displayName": " UV", + "description": "Normal map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatNormalMapUvIndex" + } + } + ] + }, { "name": "parallax", "displayName": "Displacement", - "description": "Properties for parallax effect produced by a height map." + "description": "Properties for parallax effect produced by a height map.", + "properties": [ + { + "name": "textureMap", + "displayName": "Height Map", + "description": "Displacement height map to create parallax effect.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_heightmap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the height map.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Height map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_parallaxUvIndex" + } + }, + { + "name": "factor", + "displayName": "Height Map Scale", + "description": "The total height of the height map in local model units.", + "type": "Float", + "defaultValue": 0.05, + "min": 0.0, + "softMax": 0.1, + "connection": { + "type": "ShaderInput", + "name": "m_heightmapScale" + } + }, + { + "name": "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", + "name": "m_heightmapOffset" + } + }, + { + "name": "algorithm", + "displayName": "Algorithm", + "description": "Select the algorithm to use for parallax mapping.", + "type": "Enum", + "enumValues": [ "Basic", "Steep", "POM", "Relief", "ContactRefinement" ], + "defaultValue": "POM", + "connection": { + "type": "ShaderOption", + "name": "o_parallax_algorithm" + } + }, + { + "name": "quality", + "displayName": "Quality", + "description": "Quality of parallax mapping.", + "type": "Enum", + "enumValues": [ "Low", "Medium", "High", "Ultra" ], + "defaultValue": "Low", + "connection": { + "type": "ShaderOption", + "name": "o_parallax_quality" + } + }, + { + "name": "pdo", + "displayName": "Pixel Depth Offset", + "description": "Enable PDO to offset the original pixel depths. This will affect any shaders using depth, for example, when receiving shadows.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_parallax_enablePixelDepthOffset" + } + }, + { + "name": "showClipping", + "displayName": "Show Clipping", + "description": "Highlight areas where the height map is clipped by the mesh surface.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_parallax_highlightClipping" + } + } + ] }, { "name": "opacity", "displayName": "Opacity", - "description": "Properties for configuring the materials transparency." + "description": "Properties for configuring the materials transparency.", + "properties": [ + { + "name": "mode", + "displayName": "Opacity Mode", + "description": "Indicates the general approach how transparency is to be applied.", + "type": "Enum", + "enumValues": [ "Opaque", "Cutout", "Blended", "TintedTransparent" ], + "defaultValue": "Opaque", + "connection": { + "type": "ShaderOption", + "name": "o_opacity_mode" + } + }, + { + "name": "alphaSource", + "displayName": "Alpha Source", + "description": "Indicates whether to get the opacity texture from the Base Color map (Packed) or from a separate greyscale texture (Split).", + "type": "Enum", + "enumValues": [ "Packed", "Split", "None" ], + "defaultValue": "Packed", + "connection": { + "type": "ShaderOption", + "name": "o_opacity_source" + } + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface opacity.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_opacityMap" + } + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Opacity map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_opacityMapUvIndex" + } + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Factor for cutout threshold and blending", + "type": "Float", + "min": 0.0, + "max": 1.0, + "defaultValue": 0.5, + "connection": { + "type": "ShaderInput", + "name": "m_opacityFactor" + } + }, + { + "name": "doubleSided", + "displayName": "Double-sided", + "description": "Whether to render back-faces or just front-faces.", + "type": "Bool" + }, + { + "name": "alphaAffectsSpecular", + "displayName": "Alpha affects specular", + "description": "How much the alpha value should also affect specular reflection. This should be 0.0 for materials where light can transmit through their physical surface (like glass), but 1.0 when alpha determines the very presence of a surface (like hair or grass)", + "type": "float", + "min": 0.0, + "max": 1.0, + "defaultValue": 0.0, + "connection": { + "type": "ShaderInput", + "name": "m_opacityAffectsSpecularFactor" + } + } + ] }, { "name": "uv", "displayName": "UVs", - "description": "Properties for configuring UV transforms." + "description": "Properties for configuring UV transforms.", + "properties": [ + { + "name": "center", + "displayName": "Center", + "description": "Center point for scaling and rotation transformations.", + "type": "vector2", + "vectorLabels": [ "U", "V" ], + "defaultValue": [ 0.5, 0.5 ] + }, + { + "name": "tileU", + "displayName": "Tile U", + "description": "Scales texture coordinates in U.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + }, + { + "name": "tileV", + "displayName": "Tile V", + "description": "Scales texture coordinates in V.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + }, + { + "name": "offsetU", + "displayName": "Offset U", + "description": "Offsets texture coordinates in the U direction.", + "type": "float", + "defaultValue": 0.0, + "min": -1.0, + "max": 1.0 + }, + { + "name": "offsetV", + "displayName": "Offset V", + "description": "Offsets texture coordinates in the V direction.", + "type": "float", + "defaultValue": 0.0, + "min": -1.0, + "max": 1.0 + }, + { + "name": "rotateDegrees", + "displayName": "Rotate", + "description": "Rotates the texture coordinates (degrees).", + "type": "float", + "defaultValue": 0.0, + "min": -180.0, + "max": 180.0, + "step": 1.0 + }, + { + "name": "scale", + "displayName": "Scale", + "description": "Scales texture coordinates in both U and V.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + } + ] }, { // Note: this property group is used in the DiffuseGlobalIllumination pass and not by the main forward shader "name": "irradiance", "displayName": "Irradiance", - "description": "Properties for configuring the irradiance used in global illumination." + "description": "Properties for configuring the irradiance used in global illumination.", + "properties": [ + { + "name": "color", + "displayName": "Color", + "description": "Color is displayed as sRGB but the values are stored as linear color.", + "type": "Color", + "defaultValue": [ 1.0, 1.0, 1.0 ] + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Strength factor for scaling the irradiance color values. Zero (0.0) is black, white (1.0) is full color.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0 + } + ] }, { "name": "general", "displayName": "General Settings", - "description": "General settings." + "description": "General settings.", + "properties": [ + { + "name": "applySpecularAA", + "displayName": "Apply Specular AA", + "description": "Whether to apply specular anti-aliasing in the shader.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_applySpecularAA" + } + }, + { + "name": "enableShadows", + "displayName": "Enable Shadows", + "description": "Whether to use the shadow maps.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableShadows" + } + }, + { + "name": "enableDirectionalLights", + "displayName": "Enable Directional Lights", + "description": "Whether to use directional lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableDirectionalLights" + } + }, + { + "name": "enablePunctualLights", + "displayName": "Enable Punctual Lights", + "description": "Whether to use punctual lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enablePunctualLights" + } + }, + { + "name": "enableAreaLights", + "displayName": "Enable Area Lights", + "description": "Whether to use area lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableAreaLights" + } + }, + { + "name": "enableIBL", + "displayName": "Enable IBL", + "description": "Whether to use Image Based Lighting (IBL).", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableIBL" + } + }, + { + "name": "forwardPassIBLSpecular", + "displayName": "Forward Pass IBL Specular", + "description": "Whether to apply IBL specular in the forward pass.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_materialUseForwardPassIBLSpecular" + } + } + ] } - ], - "properties": { - "general": [ - { - "name": "applySpecularAA", - "displayName": "Apply Specular AA", - "description": "Whether to apply specular anti-aliasing in the shader.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_applySpecularAA" - } - }, - { - "name": "enableShadows", - "displayName": "Enable Shadows", - "description": "Whether to use the shadow maps.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableShadows" - } - }, - { - "name": "enableDirectionalLights", - "displayName": "Enable Directional Lights", - "description": "Whether to use directional lights.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableDirectionalLights" - } - }, - { - "name": "enablePunctualLights", - "displayName": "Enable Punctual Lights", - "description": "Whether to use punctual lights.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enablePunctualLights" - } - }, - { - "name": "enableAreaLights", - "displayName": "Enable Area Lights", - "description": "Whether to use area lights.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableAreaLights" - } - }, - { - "name": "enableIBL", - "displayName": "Enable IBL", - "description": "Whether to use Image Based Lighting (IBL).", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableIBL" - } - }, - { - "name": "forwardPassIBLSpecular", - "displayName": "Forward Pass IBL Specular", - "description": "Whether to apply IBL specular in the forward pass.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_materialUseForwardPassIBLSpecular" - } - } - ], - "baseColor": [ - { - "name": "color", - "displayName": "Color", - "description": "Color is displayed as sRGB but the values are stored as linear color.", - "type": "Color", - "defaultValue": [ 1.0, 1.0, 1.0 ], - "connection": { - "type": "ShaderInput", - "name": "m_baseColor" - } - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the base color values. Zero (0.0) is black, white (1.0) is full color.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_baseColorFactor" - } - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "Base color texture map", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_baseColorMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Base color map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_baseColorMapUvIndex" - } - }, - { - "name": "textureBlendMode", - "displayName": "Texture Blend Mode", - "description": "Selects the equation to use when combining Color, Factor, and Texture.", - "type": "Enum", - "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], - "defaultValue": "Multiply", - "connection": { - "type": "ShaderOption", - "name": "o_baseColorTextureBlendMode" - } - } - ], - "metallic": [ - { - "name": "factor", - "displayName": "Factor", - "description": "This value is linear, black is non-metal and white means raw metal.", - "type": "Float", - "defaultValue": 0.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_metallicFactor" - } - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_metallicMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture, or just default to the Factor value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Metallic map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_metallicMapUvIndex" - } - } - ], - "roughness": [ - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface roughness.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_roughnessMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture, or just default to the Factor value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Roughness map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_roughnessMapUvIndex" - } - }, - { - // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. - "name": "lowerBound", - "displayName": "Lower Bound", - "description": "The roughness value that corresponds to black in the texture.", - "type": "Float", - "defaultValue": 0.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_roughnessLowerBound" - } - }, - { - // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. - "name": "upperBound", - "displayName": "Upper Bound", - "description": "The roughness value that corresponds to white in the texture.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_roughnessUpperBound" - } - }, - { - // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. - "name": "factor", - "displayName": "Factor", - "description": "Controls the roughness value", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_roughnessFactor" - } - } - ], - "anisotropy": [ - { - "name": "enableAnisotropy", - "displayName": "Enable Anisotropy", - "description": "Enable anisotropic surface response for non uniform reflection along the axis", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_enableAnisotropy" - } - }, - { - "name": "factor", - "displayName": "Anisotropy Factor", - "description": "Strength factor for the anisotropy: negative = along v, positive = along u", - "type": "Float", - "defaultValue": 0.0, - "min": -0.95, - "max": 0.95, - "connection": { - "type": "ShaderInput", - "name": "m_anisotropicFactor" - } - }, - { - "name": "anisotropyAngle", - "displayName": "Anisotropy Angle", - "description": "Anisotropy direction of major reflection axis: 0 = 0 degrees, 1.0 = 180 degrees", - "type": "Float", - "defaultValue": 0.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_anisotropicAngle" - } - } - ], - "specularF0": [ - { - "name": "factor", - "displayName": "Factor", - "description": "The default IOR is 1.5, which gives you 0.04 (4% of light reflected at 0 degree angle for dielectric materials). F0 values lie in the range 0-0.08, so that is why the default F0 slider is set on 0.5.", - "type": "Float", - "defaultValue": 0.5, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_specularF0Factor" - } - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface reflectance.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_specularF0Map" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture, or just default to the Factor value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Specular reflection map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_specularF0MapUvIndex" - } - }, - // Consider moving this to the "general" group to be consistent with StandardMultilayerPBR - { - "name": "enableMultiScatterCompensation", - "displayName": "Multiscattering Compensation", - "description": "Whether to enable multiple scattering compensation.", - "type": "Bool", - "connection": { - "type": "ShaderOption", - "name": "o_specularF0_enableMultiScatterCompensation" - } - } - ], - "clearCoat": [ - { - "name": "enable", - "displayName": "Enable", - "description": "Enable clear coat", - "type": "Bool", - "defaultValue": false - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the percentage of effect applied", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatFactor" - } - }, - { - "name": "influenceMap", - "displayName": " Influence Map", - "description": "Strength factor texture", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatInfluenceMap" - } - }, - { - "name": "useInfluenceMap", - "displayName": " Use Texture", - "description": "Whether to use the texture, or just default to the Factor value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "influenceMapUv", - "displayName": " UV", - "description": "Strength factor map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatInfluenceMapUvIndex" - } - }, - { - "name": "roughness", - "displayName": "Roughness", - "description": "Clear coat layer roughness", - "type": "Float", - "defaultValue": 0.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatRoughness" - } - }, - { - "name": "roughnessMap", - "displayName": " Roughness Map", - "description": "Texture for defining surface roughness", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatRoughnessMap" - } - }, - { - "name": "useRoughnessMap", - "displayName": " Use Texture", - "description": "Whether to use the texture, or just default to the roughness value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "roughnessMapUv", - "displayName": " UV", - "description": "Roughness map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatRoughnessMapUvIndex" - } - }, - { - "name": "normalStrength", - "displayName": "Normal Strength", - "description": "Scales the impact of the clear coat normal map", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatNormalStrength" - } - }, - { - "name": "normalMap", - "displayName": "Normal Map", - "description": "Normal map for clear coat layer, as top layer material clear coat doesn't affect by base layer normal map", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatNormalMap" - } - }, - { - "name": "useNormalMap", - "displayName": " Use Texture", - "description": "Whether to use the normal map", - "type": "Bool", - "defaultValue": true - }, - { - "name": "normalMapUv", - "displayName": " UV", - "description": "Normal map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatNormalMapUvIndex" - } - } - ], - "normal": [ - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface normal direction.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_normalMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture, or just rely on vertex normals.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Normal map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_normalMapUvIndex" - } - }, - { - "name": "flipX", - "displayName": "Flip X Channel", - "description": "Flip tangent direction for this normal map.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderInput", - "name": "m_flipNormalX" - } - }, - { - "name": "flipY", - "displayName": "Flip Y Channel", - "description": "Flip bitangent direction for this normal map.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderInput", - "name": "m_flipNormalY" - } - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the values", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_normalFactor" - } - } - ], - "opacity": [ - { - "name": "mode", - "displayName": "Opacity Mode", - "description": "Indicates the general approach how transparency is to be applied.", - "type": "Enum", - "enumValues": [ "Opaque", "Cutout", "Blended", "TintedTransparent" ], - "defaultValue": "Opaque", - "connection": { - "type": "ShaderOption", - "name": "o_opacity_mode" - } - }, - { - "name": "alphaSource", - "displayName": "Alpha Source", - "description": "Indicates whether to get the opacity texture from the Base Color map (Packed) or from a separate greyscale texture (Split).", - "type": "Enum", - "enumValues": [ "Packed", "Split", "None" ], - "defaultValue": "Packed", - "connection": { - "type": "ShaderOption", - "name": "o_opacity_source" - } - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface opacity.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_opacityMap" - } - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Opacity map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_opacityMapUvIndex" - } - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Factor for cutout threshold and blending", - "type": "Float", - "min": 0.0, - "max": 1.0, - "defaultValue": 0.5, - "connection": { - "type": "ShaderInput", - "name": "m_opacityFactor" - } - }, - { - "name": "doubleSided", - "displayName": "Double-sided", - "description": "Whether to render back-faces or just front-faces.", - "type": "Bool" - }, - { - "name": "alphaAffectsSpecular", - "displayName": "Alpha affects specular", - "description": "How much the alpha value should also affect specular reflection. This should be 0.0 for materials where light can transmit through their physical surface (like glass), but 1.0 when alpha determines the very presence of a surface (like hair or grass)", - "type": "float", - "min": 0.0, - "max": 1.0, - "defaultValue": 0.0, - "connection": { - "type": "ShaderInput", - "name": "m_opacityAffectsSpecularFactor" - } - } - ], - "uv": [ - { - "name": "center", - "displayName": "Center", - "description": "Center point for scaling and rotation transformations.", - "type": "vector2", - "vectorLabels": [ "U", "V" ], - "defaultValue": [ 0.5, 0.5 ] - }, - { - "name": "tileU", - "displayName": "Tile U", - "description": "Scales texture coordinates in U.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - }, - { - "name": "tileV", - "displayName": "Tile V", - "description": "Scales texture coordinates in V.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - }, - { - "name": "offsetU", - "displayName": "Offset U", - "description": "Offsets texture coordinates in the U direction.", - "type": "float", - "defaultValue": 0.0, - "min": -1.0, - "max": 1.0 - }, - { - "name": "offsetV", - "displayName": "Offset V", - "description": "Offsets texture coordinates in the V direction.", - "type": "float", - "defaultValue": 0.0, - "min": -1.0, - "max": 1.0 - }, - { - "name": "rotateDegrees", - "displayName": "Rotate", - "description": "Rotates the texture coordinates (degrees).", - "type": "float", - "defaultValue": 0.0, - "min": -180.0, - "max": 180.0, - "step": 1.0 - }, - { - "name": "scale", - "displayName": "Scale", - "description": "Scales texture coordinates in both U and V.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - } - ], - "occlusion": [ - { - "name": "diffuseTextureMap", - "displayName": "Diffuse AO", - "description": "Texture for defining occlusion area for diffuse ambient lighting.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_diffuseOcclusionMap" - } - }, - { - "name": "diffuseUseTexture", - "displayName": " Use Texture", - "description": "Whether to use the Diffuse AO map.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "diffuseTextureMapUv", - "displayName": " UV", - "description": "Diffuse AO map UV set.", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_diffuseOcclusionMapUvIndex" - } - }, - { - "name": "diffuseFactor", - "displayName": " Factor", - "description": "Strength factor for scaling the values of Diffuse AO", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_diffuseOcclusionFactor" - } - }, - { - "name": "specularTextureMap", - "displayName": "Specular Cavity", - "description": "Texture for defining occlusion area for specular lighting.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_specularOcclusionMap" - } - }, - { - "name": "specularUseTexture", - "displayName": " Use Texture", - "description": "Whether to use the Specular Cavity map.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "specularTextureMapUv", - "displayName": " UV", - "description": "Specular Cavity map UV set.", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_specularOcclusionMapUvIndex" - } - }, - { - "name": "specularFactor", - "displayName": " Factor", - "description": "Strength factor for scaling the values of Specular Cavity", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_specularOcclusionFactor" - } - } - ], - "emissive": [ - { - "name": "enable", - "displayName": "Enable", - "description": "Enable the emissive group", - "type": "Bool", - "defaultValue": false - }, - { - "name": "unit", - "displayName": "Units", - "description": "The photometric units of the Intensity property.", - "type": "Enum", - "enumValues": ["Ev100"], - "defaultValue": "Ev100" - }, - { - "name": "color", - "displayName": "Color", - "description": "Color is displayed as sRGB but the values are stored as linear color.", - "type": "Color", - "defaultValue": [ 1.0, 1.0, 1.0 ], - "connection": { - "type": "ShaderInput", - "name": "m_emissiveColor" - } - }, - { - "name": "intensity", - "displayName": "Intensity", - "description": "The amount of energy emitted.", - "type": "Float", - "defaultValue": 4, - "min": -10, - "max": 20, - "softMin": -6, - "softMax": 16 - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining emissive area.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_emissiveMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Emissive map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_emissiveMapUvIndex" - } - } - ], - "parallax": [ - { - "name": "textureMap", - "displayName": "Height Map", - "description": "Displacement height map to create parallax effect.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_heightmap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the height map.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Height map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_parallaxUvIndex" - } - }, - { - "name": "factor", - "displayName": "Height Map Scale", - "description": "The total height of the height map in local model units.", - "type": "Float", - "defaultValue": 0.05, - "min": 0.0, - "softMax": 0.1, - "connection": { - "type": "ShaderInput", - "name": "m_heightmapScale" - } - }, - { - "name": "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", - "name": "m_heightmapOffset" - } - }, - { - "name": "algorithm", - "displayName": "Algorithm", - "description": "Select the algorithm to use for parallax mapping.", - "type": "Enum", - "enumValues": [ "Basic", "Steep", "POM", "Relief", "ContactRefinement" ], - "defaultValue": "POM", - "connection": { - "type": "ShaderOption", - "name": "o_parallax_algorithm" - } - }, - { - "name": "quality", - "displayName": "Quality", - "description": "Quality of parallax mapping.", - "type": "Enum", - "enumValues": [ "Low", "Medium", "High", "Ultra" ], - "defaultValue": "Low", - "connection": { - "type": "ShaderOption", - "name": "o_parallax_quality" - } - }, - { - "name": "pdo", - "displayName": "Pixel Depth Offset", - "description": "Enable PDO to offset the original pixel depths. This will affect any shaders using depth, for example, when receiving shadows.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_parallax_enablePixelDepthOffset" - } - }, - { - "name": "showClipping", - "displayName": "Show Clipping", - "description": "Highlight areas where the height map is clipped by the mesh surface.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_parallax_highlightClipping" - } - } - ], - "subsurfaceScattering": [ - { - "name": "enableSubsurfaceScattering", - "displayName": "Subsurface Scattering", - "description": "Enable subsurface scattering feature, this will disable metallic and parallax mapping property due to incompatibility", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_enableSubsurfaceScattering" - } - }, - { - "name": "subsurfaceScatterFactor", - "displayName": " Factor", - "description": "Strength factor for scaling percentage of subsurface scattering effect applied", - "type": "float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_subsurfaceScatteringFactor" - } - }, - { - "name": "influenceMap", - "displayName": " Influence Map", - "description": "Texture for controlling the strength of subsurface scattering", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_subsurfaceScatteringInfluenceMap" - } - }, - { - "name": "useInfluenceMap", - "displayName": " Use Influence Map", - "description": "Whether to use the influence map.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "influenceMapUv", - "displayName": " UV", - "description": "Influence map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_subsurfaceScatteringInfluenceMapUvIndex" - } - }, - { - "name": "scatterColor", - "displayName": " Scatter color", - "description": "Color of volume light traveled through", - "type": "Color", - "defaultValue": [ 1.0, 0.27, 0.13 ] - }, - { - "name": "scatterDistance", - "displayName": " Scatter distance", - "description": "How far light traveled inside the volume", - "type": "float", - "defaultValue": 8, - "min": 0.0, - "softMax": 20.0 - }, - { - "name": "quality", - "displayName": " Quality", - "description": "How much percent of sample will be used for each pixel, more samples improve quality and reduce artifacts, especially when the scatter distance is relatively large, but slow down computation time, 1.0 = full set 200 samples per pixel", - "type": "float", - "defaultValue": 0.4, - "min": 0.2, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_subsurfaceScatteringQuality" - } - }, - { - "name": "transmissionMode", - "displayName": "Transmission", - "description": "Algorithm used for calculating transmission", - "type": "Enum", - "enumValues": [ "None", "ThickObject", "ThinObject" ], - "defaultValue": "None", - "connection": { - "type": "ShaderOption", - "name": "o_transmission_mode" - } - }, - { - "name": "thickness", - "displayName": " Thickness", - "description": "Normalized global thickness, the maxima between this value (multiplied by thickness map if enabled) and thickness from shadow map (if applicable) will be used as final thickness of pixel", - "type": "float", - "defaultValue": 0.5, - "min": 0.0, - "max": 1.0 - }, - { - "name": "thicknessMap", - "displayName": " Thickness Map", - "description": "Texture for controlling per pixel thickness", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_transmissionThicknessMap" - } - }, - { - "name": "useThicknessMap", - "displayName": " Use Thickness Map", - "description": "Whether to use the thickness map", - "type": "Bool", - "defaultValue": true - }, - { - "name": "thicknessMapUv", - "displayName": " UV", - "description": "Thickness map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_transmissionThicknessMapUvIndex" - } - }, - { - "name": "transmissionTint", - "displayName": " Transmission Tint", - "description": "Color of the volume light traveling through", - "type": "Color", - "defaultValue": [ 1.0, 0.8, 0.6 ] - }, - { - "name": "transmissionPower", - "displayName": " Power", - "description": "How much transmitted light scatter radially ", - "type": "float", - "defaultValue": 6.0, - "min": 0.0, - "softMax": 20.0 - }, - { - "name": "transmissionDistortion", - "displayName": " Distortion", - "description": "How much light direction distorted towards surface normal", - "type": "float", - "defaultValue": 0.1, - "min": 0.0, - "max": 1.0 - }, - { - "name": "transmissionAttenuation", - "displayName": " Attenuation", - "description": "How fast transmitted light fade with thickness", - "type": "float", - "defaultValue": 4.0, - "min": 0.0, - "softMax": 20.0 - }, - { - "name": "transmissionScale", - "displayName": " Scale", - "description": "Strength of transmission", - "type": "float", - "defaultValue": 3.0, - "min": 0.0, - "softMax": 20.0 - } - ], - "detailLayerGroup": [ - { - "name": "enableDetailLayer", - "displayName": "Enable Detail Layer", - "description": "Enable detail layer for fine details and scratches", - "type": "Bool", - "defaultValue": false - }, - { - "name": "blendDetailFactor", - "displayName": "Blend Factor", - "description": "Scales the overall impact of the detail layer.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_detail_blendFactor" - } - }, - { - "name": "blendDetailMask", - "displayName": "Blend Mask", - "description": "Detailed blend mask for application of the detail maps.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_detail_blendMask_texture" - } - }, - { - "name": "enableDetailMaskTexture", - "displayName": " Use Texture", - "description": "Enable detail blend mask", - "type": "Bool", - "defaultValue": true - }, - { - "name": "blendDetailMaskUv", - "displayName": " Blend Mask UV", - "description": "Which UV set to use for sampling the detail blend mask", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_detail_blendMask_uvIndex" - } - }, - { - "name": "textureMapUv", - "displayName": "Detail Map UVs", - "description": "Which UV set to use for detail map sampling", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_detail_allMapsUvIndex" - } - }, - { - "name": "enableBaseColor", - "displayName": "Enable Base Color", - "description": "Enable detail blending for base color", - "type": "Bool", - "defaultValue": false - }, - { - "name": "baseColorDetailMap", - "displayName": " Texture", - "description": "Detailed Base Color Texture", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_detail_baseColor_texture" - } - }, - { - "name": "baseColorDetailBlend", - "displayName": " Blend Factor", - "description": "How much to blend the detail layer into the base color.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_detail_baseColor_factor" - } - }, - { - "name": "enableNormals", - "displayName": "Enable Normal", - "description": "Enable detail normal map to be used for fine detail normal such as scratches and small dents", - "type": "Bool", - "defaultValue": false - }, - { - "name": "normalDetailStrength", - "displayName": " Factor", - "description": "Strength factor for scaling the Detail Normal", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_detail_normal_factor" - } - }, - { - "name": "normalDetailMap", - "displayName": " Texture", - "description": "Detailed Normal map", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_detail_normal_texture" - } - }, - { - "name": "normalDetailFlipX", - "displayName": " Flip X Channel", - "description": "Flip Detail tangent direction for this normal map.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderInput", - "name": "m_detail_normal_flipX" - } - }, - { - "name": "normalDetailFlipY", - "displayName": " Flip Y Channel", - "description": "Flip Detail bitangent direction for this normal map.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderInput", - "name": "m_detail_normal_flipY" - } - } - ], - "detailUV": [ - { - "name": "center", - "displayName": "Center", - "description": "Center point for scaling and rotation transformations.", - "type": "vector2", - "vectorLabels": [ "U", "V" ], - "defaultValue": [ 0.5, 0.5 ] - }, - { - "name": "tileU", - "displayName": "Tile U", - "description": "Scales texture coordinates in V.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - }, - { - "name": "tileV", - "displayName": "Tile V", - "description": "Scales texture coordinates in V.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - }, - { - "name": "offsetU", - "displayName": "Offset U", - "description": "Offsets texture coordinates in the U direction.", - "type": "float", - "defaultValue": 0.0, - "min": -1.0, - "max": 1.0 - }, - { - "name": "offsetV", - "displayName": "Offset V", - "description": "Offsets texture coordinates in the V direction.", - "type": "float", - "defaultValue": 0.0, - "min": -1.0, - "max": 1.0 - }, - { - "name": "rotateDegrees", - "displayName": "Rotate", - "description": "Rotates the texture coordinates (degrees).", - "type": "float", - "defaultValue": 0.0, - "min": -180.0, - "max": 180.0, - "step": 1.0 - }, - { - "name": "scale", - "displayName": "Scale", - "description": "Scales texture coordinates in both U and V.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - } - ], - "irradiance": [ - // Note: this property group is used in the DiffuseGlobalIllumination pass and not by the main forward shader - { - "name": "color", - "displayName": "Color", - "description": "Color is displayed as sRGB but the values are stored as linear color.", - "type": "Color", - "defaultValue": [ 1.0, 1.0, 1.0 ] - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the irradiance color values. Zero (0.0) is black, white (1.0) is full color.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0 - } - ] - } + ] }, "shaders": [ { diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype index 0969cc36e8..b462c936e9 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype @@ -2,970 +2,968 @@ "description": "Material Type tailored for rendering skin, with support for blended wrinkle maps that work with animated vertex blend shapes.", "propertyLayout": { "version": 3, - "groups": [ + "propertySets": [ { "name": "baseColor", "displayName": "Base Color", - "description": "Properties for configuring the surface reflected color for dielectrics or reflectance values for metals." + "description": "Properties for configuring the surface reflected color for dielectrics or reflectance values for metals.", + "properties": [ + { + "name": "color", + "displayName": "Color", + "description": "Color is displayed as sRGB but the values are stored as linear color.", + "type": "Color", + "defaultValue": [ 1.0, 1.0, 1.0 ], + "connection": { + "type": "ShaderInput", + "name": "m_baseColor" + } + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Strength factor for scaling the base color values. Zero (0.0) is black, white (1.0) is full color.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_baseColorFactor" + } + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "Base color texture map", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_baseColorMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Base color map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Unwrapped", + "connection": { + "type": "ShaderInput", + "name": "m_baseColorMapUvIndex" + } + }, + { + "name": "textureBlendMode", + "displayName": "Texture Blend Mode", + "description": "Selects the equation to use when combining Color, Factor, and Texture.", + "type": "Enum", + "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], + "defaultValue": "Multiply", + "connection": { + "type": "ShaderOption", + "name": "o_baseColorTextureBlendMode" + } + } + ] }, { "name": "roughness", "displayName": "Roughness", - "description": "Properties for configuring how rough the surface appears." + "description": "Properties for configuring how rough the surface appears.", + "properties": [ + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface roughness.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_roughnessMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just default to the Factor value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Roughness map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Unwrapped", + "connection": { + "type": "ShaderInput", + "name": "m_roughnessMapUvIndex" + } + }, + { + // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. + "name": "lowerBound", + "displayName": "Lower Bound", + "description": "The roughness value that corresponds to black in the texture.", + "type": "Float", + "defaultValue": 0.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_roughnessLowerBound" + } + }, + { + // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. + "name": "upperBound", + "displayName": "Upper Bound", + "description": "The roughness value that corresponds to white in the texture.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_roughnessUpperBound" + } + }, + { + // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. + "name": "factor", + "displayName": "Factor", + "description": "Controls the roughness value", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_roughnessFactor" + } + } + ] }, { "name": "specularF0", "displayName": "Specular Reflectance f0", - "description": "The constant f0 represents the specular reflectance at normal incidence (Fresnel 0 Angle). Used to adjust reflectance of non-metal surfaces." + "description": "The constant f0 represents the specular reflectance at normal incidence (Fresnel 0 Angle). Used to adjust reflectance of non-metal surfaces.", + "properties": [ + { + "name": "factor", + "displayName": "Factor", + "description": "The default IOR is 1.5, which gives you 0.04 (4% of light reflected at 0 degree angle for dielectric materials). F0 values lie in the range 0-0.08, so that is why the default F0 slider is set on 0.5.", + "type": "Float", + "defaultValue": 0.5, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_specularF0Factor" + } + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface reflectance.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_specularF0Map" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just default to the Factor value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Specular reflection map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Unwrapped", + "connection": { + "type": "ShaderInput", + "name": "m_specularF0MapUvIndex" + } + }, + // Consider moving this to the "general" group to be consistent with StandardMultilayerPBR + { + "name": "enableMultiScatterCompensation", + "displayName": "Multiscattering Compensation", + "description": "Whether to enable multiple scattering compensation.", + "type": "Bool", + "connection": { + "type": "ShaderOption", + "name": "o_specularF0_enableMultiScatterCompensation" + } + } + ] }, { "name": "normal", "displayName": "Normal", - "description": "Properties related to configuring surface normal." + "description": "Properties related to configuring surface normal.", + "properties": [ + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface normal direction.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_normalMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just rely on vertex normals.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Normal map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Unwrapped", + "connection": { + "type": "ShaderInput", + "name": "m_normalMapUvIndex" + } + }, + { + "name": "flipX", + "displayName": "Flip X Channel", + "description": "Flip tangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "name": "m_flipNormalX" + } + }, + { + "name": "flipY", + "displayName": "Flip Y Channel", + "description": "Flip bitangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "name": "m_flipNormalY" + } + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Strength factor for scaling the values", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_normalFactor" + } + } + ] }, { "name": "detailLayerGroup", "displayName": "Detail Layer", - "description": "Properties for Fine Details Layer." + "description": "Properties for Fine Details Layer.", + "properties": [ + { + "name": "enableDetailLayer", + "displayName": "Enable Detail Layer", + "description": "Enable detail layer for fine details and scratches", + "type": "Bool", + "defaultValue": false + }, + { + "name": "blendDetailFactor", + "displayName": "Blend Factor", + "description": "Scales the overall impact of the detail layer.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_detail_blendFactor" + } + }, + { + "name": "blendDetailMask", + "displayName": "Blend Mask", + "description": "Detailed blend mask for application of the detail maps.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_detail_blendMask_texture" + } + }, + { + "name": "enableDetailMaskTexture", + "displayName": " Use Texture", + "description": "Enable detail blend mask", + "type": "Bool", + "defaultValue": true + }, + { + "name": "blendDetailMaskUv", + "displayName": " Blend Mask UV", + "description": "Which UV set to use for sampling the detail blend mask", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Unwrapped", + "connection": { + "type": "ShaderInput", + "name": "m_detail_blendMask_uvIndex" + } + }, + { + "name": "textureMapUv", + "displayName": "Detail Map UVs", + "description": "Which UV set to use for detail map sampling", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Unwrapped", + "connection": { + "type": "ShaderInput", + "name": "m_detail_allMapsUvIndex" + } + }, + { + "name": "enableBaseColor", + "displayName": "Enable Base Color", + "description": "Enable detail blending for base color", + "type": "Bool", + "defaultValue": false + }, + { + "name": "baseColorDetailMap", + "displayName": " Texture", + "description": "Detailed Base Color Texture", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_detail_baseColor_texture" + } + }, + { + "name": "baseColorDetailBlend", + "displayName": " Blend Factor", + "description": "How much to blend the detail layer into the base color.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_detail_baseColor_factor" + } + }, + { + "name": "enableNormals", + "displayName": "Enable Normal", + "description": "Enable detail normal map to be used for fine detail normal such as scratches and small dents", + "type": "Bool", + "defaultValue": false + }, + { + "name": "normalDetailStrength", + "displayName": " Factor", + "description": "Strength factor for scaling the Detail Normal", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_detail_normal_factor" + } + }, + { + "name": "normalDetailMap", + "displayName": " Texture", + "description": "Detailed Normal map", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_detail_normal_texture" + } + }, + { + "name": "normalDetailFlipX", + "displayName": " Flip X Channel", + "description": "Flip Detail tangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "name": "m_detail_normal_flipX" + } + }, + { + "name": "normalDetailFlipY", + "displayName": " Flip Y Channel", + "description": "Flip Detail bitangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "name": "m_detail_normal_flipY" + } + } + ] }, { "name": "detailUV", "displayName": "Detail Layer UV", - "description": "Properties for modifying detail layer UV." + "description": "Properties for modifying detail layer UV.", + "properties": [ + { + "name": "center", + "displayName": "Center", + "description": "Center point for scaling and rotation transformations.", + "type": "vector2", + "vectorLabels": [ "U", "V" ], + "defaultValue": [ 0.5, 0.5 ] + }, + { + "name": "tileU", + "displayName": "Tile U", + "description": "Scales texture coordinates in V.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + }, + { + "name": "tileV", + "displayName": "Tile V", + "description": "Scales texture coordinates in V.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + }, + { + "name": "offsetU", + "displayName": "Offset U", + "description": "Offsets texture coordinates in the U direction.", + "type": "float", + "defaultValue": 0.0, + "min": -1.0, + "max": 1.0 + }, + { + "name": "offsetV", + "displayName": "Offset V", + "description": "Offsets texture coordinates in the V direction.", + "type": "float", + "defaultValue": 0.0, + "min": -1.0, + "max": 1.0 + }, + { + "name": "rotateDegrees", + "displayName": "Rotate", + "description": "Rotates the texture coordinates (degrees).", + "type": "float", + "defaultValue": 0.0, + "min": -180.0, + "max": 180.0, + "step": 1.0 + }, + { + "name": "scale", + "displayName": "Scale", + "description": "Scales texture coordinates in both U and V.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + } + ] }, { "name": "occlusion", "displayName": "Occlusion", - "description": "Properties for baked textures that represent geometric occlusion of light." + "description": "Properties for baked textures that represent geometric occlusion of light.", + "properties": [ + { + "name": "diffuseTextureMap", + "displayName": "Diffuse AO", + "description": "Texture for defining occlusion area for diffuse ambient lighting.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_diffuseOcclusionMap" + } + }, + { + "name": "diffuseUseTexture", + "displayName": " Use Texture", + "description": "Whether to use the Diffuse AO map.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "diffuseTextureMapUv", + "displayName": " UV", + "description": "Diffuse AO map UV set.", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_diffuseOcclusionMapUvIndex" + } + }, + { + "name": "diffuseFactor", + "displayName": " Factor", + "description": "Strength factor for scaling the values of Diffuse AO", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_diffuseOcclusionFactor" + } + }, + { + "name": "specularTextureMap", + "displayName": "Specular Cavity", + "description": "Texture for defining occlusion area for specular lighting.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_specularOcclusionMap" + } + }, + { + "name": "specularUseTexture", + "displayName": " Use Texture", + "description": "Whether to use the Specular Cavity map.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "specularTextureMapUv", + "displayName": " UV", + "description": "Specular Cavity map UV set.", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_specularOcclusionMapUvIndex" + } + }, + { + "name": "specularFactor", + "displayName": " Factor", + "description": "Strength factor for scaling the values of Specular Cavity", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_specularOcclusionFactor" + } + } + ] }, { "name": "subsurfaceScattering", "displayName": "Subsurface Scattering", - "description": "Properties for configuring subsurface scattering effects." + "description": "Properties for configuring subsurface scattering effects.", + "properties": [ + { + "name": "enableSubsurfaceScattering", + "displayName": "Subsurface Scattering", + "description": "Enable subsurface scattering feature, this will disable metallic and parallax mapping property due to incompatibility", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_enableSubsurfaceScattering" + } + }, + { + "name": "subsurfaceScatterFactor", + "displayName": " Factor", + "description": "Strength factor for scaling percentage of subsurface scattering effect applied", + "type": "float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_subsurfaceScatteringFactor" + } + }, + { + "name": "influenceMap", + "displayName": " Influence Map", + "description": "Texture for controlling the strength of subsurface scattering", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_subsurfaceScatteringInfluenceMap" + } + }, + { + "name": "useInfluenceMap", + "displayName": " Use Influence Map", + "description": "Whether to use the influence map.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "influenceMapUv", + "displayName": " UV", + "description": "Influence map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Unwrapped", + "connection": { + "type": "ShaderInput", + "name": "m_subsurfaceScatteringInfluenceMapUvIndex" + } + }, + { + "name": "scatterColor", + "displayName": " Scatter color", + "description": "Color of volume light traveled through", + "type": "Color", + "defaultValue": [ 1.0, 0.27, 0.13 ] + }, + { + "name": "scatterDistance", + "displayName": " Scatter distance", + "description": "How far light traveled inside the volume", + "type": "float", + "defaultValue": 8, + "min": 0.0, + "softMax": 20.0 + }, + { + "name": "quality", + "displayName": " Quality", + "description": "How much percent of sample will be used for each pixel, more samples improve quality and reduce artifacts, especially when the scatter distance is relatively large, but slow down computation time, 1.0 = full set 200 samples per pixel", + "type": "float", + "defaultValue": 0.4, + "min": 0.2, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_subsurfaceScatteringQuality" + } + }, + { + "name": "transmissionMode", + "displayName": "Transmission", + "description": "Algorithm used for calculating transmission", + "type": "Enum", + "enumValues": [ "None", "ThickObject", "ThinObject" ], + "defaultValue": "None", + "connection": { + "type": "ShaderOption", + "name": "o_transmission_mode" + } + }, + { + "name": "thickness", + "displayName": " Thickness", + "description": "Normalized global thickness, the maxima between this value (multiplied by thickness map if enabled) and thickness from shadow map (if applicable) will be used as final thickness of pixel", + "type": "float", + "defaultValue": 0.5, + "min": 0.0, + "max": 1.0 + }, + { + "name": "thicknessMap", + "displayName": " Thickness Map", + "description": "Texture for controlling per pixel thickness", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_transmissionThicknessMap" + } + }, + { + "name": "useThicknessMap", + "displayName": " Use Thickness Map", + "description": "Whether to use the thickness map", + "type": "Bool", + "defaultValue": true + }, + { + "name": "thicknessMapUv", + "displayName": " UV", + "description": "Thickness map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Unwrapped", + "connection": { + "type": "ShaderInput", + "name": "m_transmissionThicknessMapUvIndex" + } + }, + { + "name": "transmissionTint", + "displayName": " Transmission Tint", + "description": "Color of the volume light traveling through", + "type": "Color", + "defaultValue": [ 1.0, 0.8, 0.6 ] + }, + { + "name": "transmissionPower", + "displayName": " Power", + "description": "How much transmitted light scatter radially ", + "type": "float", + "defaultValue": 6.0, + "min": 0.0, + "softMax": 20.0 + }, + { + "name": "transmissionDistortion", + "displayName": " Distortion", + "description": "How much light direction distorted towards surface normal", + "type": "float", + "defaultValue": 0.1, + "min": 0.0, + "max": 1.0 + }, + { + "name": "transmissionAttenuation", + "displayName": " Attenuation", + "description": "How fast transmitted light fade with thickness", + "type": "float", + "defaultValue": 4.0, + "min": 0.0, + "softMax": 20.0 + }, + { + "name": "transmissionScale", + "displayName": " Scale", + "description": "Strength of transmission", + "type": "float", + "defaultValue": 3.0, + "min": 0.0, + "softMax": 20.0 + } + ] }, { "name": "wrinkleLayers", "displayName": "Wrinkle Layers", - "description": "Properties for wrinkle maps to support morph animation, using vertex color blend weights." + "description": "Properties for wrinkle maps to support morph animation, using vertex color blend weights.", + "properties": [ + { + "name": "enable", + "displayName": "Enable Wrinkle Layers", + "description": "Enable wrinkle layers for morph animations, using vertex color blend weights.", + "type": "Bool", + "defaultValue": false + }, + { + "name": "count", + "displayName": "Number Of Layers", + "description": "The number of wrinkle map layers to use. The blend values come from the 'COLOR0' vertex stream, where R/G/B/A correspond to wrinkle layers 1/2/3/4 respectively.", + "type": "UInt", + "defaultValue": 4, + "min": 1, + "max": 4 + }, + { + "name": "showBlendValues", + "displayName": "Show Blend Values", + "description": "Enable a debug mode that draws the blend values as red, green, blue, and white overlays.", + "type": "Bool", + "defaultValue": false + }, + { + "name": "enableBaseColor", + "displayName": "Enable Base Color Maps", + "description": "Enable support for blending the base color according to morph animations.", + "type": "Bool", + "defaultValue": false + }, + { + "name": "baseColorMap1", + "displayName": " Base Color 1", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_wrinkle_baseColor_texture1" + } + }, + { + "name": "baseColorMap2", + "displayName": " Base Color 2", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_wrinkle_baseColor_texture2" + } + }, + { + "name": "baseColorMap3", + "displayName": " Base Color 3", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_wrinkle_baseColor_texture3" + } + }, + { + "name": "baseColorMap4", + "displayName": " Base Color 4", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_wrinkle_baseColor_texture4" + } + }, + { + "name": "enableNormal", + "displayName": "Enable Normal Maps", + "description": "Enable support for blending the normal maps according to morph animations.", + "type": "Bool", + "defaultValue": false + }, + { + "name": "normalMap1", + "displayName": " Normals 1", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_wrinkle_normal_texture1" + } + }, + { + "name": "normalMap2", + "displayName": " Normals 2", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_wrinkle_normal_texture2" + } + }, + { + "name": "normalMap3", + "displayName": " Normals 3", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_wrinkle_normal_texture3" + } + }, + { + "name": "normalMap4", + "displayName": " Normals 4", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_wrinkle_normal_texture4" + } + } + ] }, { "name": "general", "displayName": "General Settings", - "description": "General settings." - } - ], - "properties": { - "general": [ - { - "name": "applySpecularAA", - "displayName": "Apply Specular AA", - "description": "Whether to apply specular anti-aliasing in the shader.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_applySpecularAA" - } - }, - { - "name": "enableShadows", - "displayName": "Enable Shadows", - "description": "Whether to use the shadow maps.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableShadows" - } - }, - { - "name": "enableDirectionalLights", - "displayName": "Enable Directional Lights", - "description": "Whether to use directional lights.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableDirectionalLights" - } - }, - { - "name": "enablePunctualLights", - "displayName": "Enable Punctual Lights", - "description": "Whether to use punctual lights.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enablePunctualLights" - } - }, - { - "name": "enableAreaLights", - "displayName": "Enable Area Lights", - "description": "Whether to use area lights.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableAreaLights" - } - }, - { - "name": "enableIBL", - "displayName": "Enable IBL", - "description": "Whether to use Image Based Lighting (IBL).", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableIBL" - } - } - ], - "baseColor": [ - { - "name": "color", - "displayName": "Color", - "description": "Color is displayed as sRGB but the values are stored as linear color.", - "type": "Color", - "defaultValue": [ 1.0, 1.0, 1.0 ], - "connection": { - "type": "ShaderInput", - "name": "m_baseColor" - } - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the base color values. Zero (0.0) is black, white (1.0) is full color.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_baseColorFactor" - } - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "Base color texture map", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_baseColorMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Base color map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Unwrapped", - "connection": { - "type": "ShaderInput", - "name": "m_baseColorMapUvIndex" - } - }, - { - "name": "textureBlendMode", - "displayName": "Texture Blend Mode", - "description": "Selects the equation to use when combining Color, Factor, and Texture.", - "type": "Enum", - "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], - "defaultValue": "Multiply", - "connection": { - "type": "ShaderOption", - "name": "o_baseColorTextureBlendMode" - } - } - ], - "roughness": [ - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface roughness.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_roughnessMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture, or just default to the Factor value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Roughness map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Unwrapped", - "connection": { - "type": "ShaderInput", - "name": "m_roughnessMapUvIndex" - } - }, - { - // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. - "name": "lowerBound", - "displayName": "Lower Bound", - "description": "The roughness value that corresponds to black in the texture.", - "type": "Float", - "defaultValue": 0.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_roughnessLowerBound" - } - }, - { - // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. - "name": "upperBound", - "displayName": "Upper Bound", - "description": "The roughness value that corresponds to white in the texture.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_roughnessUpperBound" - } - }, - { - // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. - "name": "factor", - "displayName": "Factor", - "description": "Controls the roughness value", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_roughnessFactor" - } - } - ], - "specularF0": [ - { - "name": "factor", - "displayName": "Factor", - "description": "The default IOR is 1.5, which gives you 0.04 (4% of light reflected at 0 degree angle for dielectric materials). F0 values lie in the range 0-0.08, so that is why the default F0 slider is set on 0.5.", - "type": "Float", - "defaultValue": 0.5, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_specularF0Factor" - } - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface reflectance.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_specularF0Map" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture, or just default to the Factor value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Specular reflection map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Unwrapped", - "connection": { - "type": "ShaderInput", - "name": "m_specularF0MapUvIndex" - } - }, - // Consider moving this to the "general" group to be consistent with StandardMultilayerPBR - { - "name": "enableMultiScatterCompensation", - "displayName": "Multiscattering Compensation", - "description": "Whether to enable multiple scattering compensation.", - "type": "Bool", - "connection": { - "type": "ShaderOption", - "name": "o_specularF0_enableMultiScatterCompensation" - } - } - ], - "normal": [ - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface normal direction.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_normalMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture, or just rely on vertex normals.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Normal map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Unwrapped", - "connection": { - "type": "ShaderInput", - "name": "m_normalMapUvIndex" - } - }, - { - "name": "flipX", - "displayName": "Flip X Channel", - "description": "Flip tangent direction for this normal map.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderInput", - "name": "m_flipNormalX" - } - }, - { - "name": "flipY", - "displayName": "Flip Y Channel", - "description": "Flip bitangent direction for this normal map.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderInput", - "name": "m_flipNormalY" - } - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the values", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_normalFactor" + "description": "General settings.", + "properties": [ + { + "name": "applySpecularAA", + "displayName": "Apply Specular AA", + "description": "Whether to apply specular anti-aliasing in the shader.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_applySpecularAA" + } + }, + { + "name": "enableShadows", + "displayName": "Enable Shadows", + "description": "Whether to use the shadow maps.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableShadows" + } + }, + { + "name": "enableDirectionalLights", + "displayName": "Enable Directional Lights", + "description": "Whether to use directional lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableDirectionalLights" + } + }, + { + "name": "enablePunctualLights", + "displayName": "Enable Punctual Lights", + "description": "Whether to use punctual lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enablePunctualLights" + } + }, + { + "name": "enableAreaLights", + "displayName": "Enable Area Lights", + "description": "Whether to use area lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableAreaLights" + } + }, + { + "name": "enableIBL", + "displayName": "Enable IBL", + "description": "Whether to use Image Based Lighting (IBL).", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableIBL" + } } - } - ], - "occlusion": [ - { - "name": "diffuseTextureMap", - "displayName": "Diffuse AO", - "description": "Texture for defining occlusion area for diffuse ambient lighting.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_diffuseOcclusionMap" - } - }, - { - "name": "diffuseUseTexture", - "displayName": " Use Texture", - "description": "Whether to use the Diffuse AO map.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "diffuseTextureMapUv", - "displayName": " UV", - "description": "Diffuse AO map UV set.", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_diffuseOcclusionMapUvIndex" - } - }, - { - "name": "diffuseFactor", - "displayName": " Factor", - "description": "Strength factor for scaling the values of Diffuse AO", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_diffuseOcclusionFactor" - } - }, - { - "name": "specularTextureMap", - "displayName": "Specular Cavity", - "description": "Texture for defining occlusion area for specular lighting.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_specularOcclusionMap" - } - }, - { - "name": "specularUseTexture", - "displayName": " Use Texture", - "description": "Whether to use the Specular Cavity map.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "specularTextureMapUv", - "displayName": " UV", - "description": "Specular Cavity map UV set.", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_specularOcclusionMapUvIndex" - } - }, - { - "name": "specularFactor", - "displayName": " Factor", - "description": "Strength factor for scaling the values of Specular Cavity", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_specularOcclusionFactor" - } - } - ], - "subsurfaceScattering": [ - { - "name": "enableSubsurfaceScattering", - "displayName": "Subsurface Scattering", - "description": "Enable subsurface scattering feature, this will disable metallic and parallax mapping property due to incompatibility", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_enableSubsurfaceScattering" - } - }, - { - "name": "subsurfaceScatterFactor", - "displayName": " Factor", - "description": "Strength factor for scaling percentage of subsurface scattering effect applied", - "type": "float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_subsurfaceScatteringFactor" - } - }, - { - "name": "influenceMap", - "displayName": " Influence Map", - "description": "Texture for controlling the strength of subsurface scattering", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_subsurfaceScatteringInfluenceMap" - } - }, - { - "name": "useInfluenceMap", - "displayName": " Use Influence Map", - "description": "Whether to use the influence map.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "influenceMapUv", - "displayName": " UV", - "description": "Influence map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Unwrapped", - "connection": { - "type": "ShaderInput", - "name": "m_subsurfaceScatteringInfluenceMapUvIndex" - } - }, - { - "name": "scatterColor", - "displayName": " Scatter color", - "description": "Color of volume light traveled through", - "type": "Color", - "defaultValue": [ 1.0, 0.27, 0.13 ] - }, - { - "name": "scatterDistance", - "displayName": " Scatter distance", - "description": "How far light traveled inside the volume", - "type": "float", - "defaultValue": 8, - "min": 0.0, - "softMax": 20.0 - }, - { - "name": "quality", - "displayName": " Quality", - "description": "How much percent of sample will be used for each pixel, more samples improve quality and reduce artifacts, especially when the scatter distance is relatively large, but slow down computation time, 1.0 = full set 200 samples per pixel", - "type": "float", - "defaultValue": 0.4, - "min": 0.2, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_subsurfaceScatteringQuality" - } - }, - { - "name": "transmissionMode", - "displayName": "Transmission", - "description": "Algorithm used for calculating transmission", - "type": "Enum", - "enumValues": [ "None", "ThickObject", "ThinObject" ], - "defaultValue": "None", - "connection": { - "type": "ShaderOption", - "name": "o_transmission_mode" - } - }, - { - "name": "thickness", - "displayName": " Thickness", - "description": "Normalized global thickness, the maxima between this value (multiplied by thickness map if enabled) and thickness from shadow map (if applicable) will be used as final thickness of pixel", - "type": "float", - "defaultValue": 0.5, - "min": 0.0, - "max": 1.0 - }, - { - "name": "thicknessMap", - "displayName": " Thickness Map", - "description": "Texture for controlling per pixel thickness", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_transmissionThicknessMap" - } - }, - { - "name": "useThicknessMap", - "displayName": " Use Thickness Map", - "description": "Whether to use the thickness map", - "type": "Bool", - "defaultValue": true - }, - { - "name": "thicknessMapUv", - "displayName": " UV", - "description": "Thickness map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Unwrapped", - "connection": { - "type": "ShaderInput", - "name": "m_transmissionThicknessMapUvIndex" - } - }, - { - "name": "transmissionTint", - "displayName": " Transmission Tint", - "description": "Color of the volume light traveling through", - "type": "Color", - "defaultValue": [ 1.0, 0.8, 0.6 ] - }, - { - "name": "transmissionPower", - "displayName": " Power", - "description": "How much transmitted light scatter radially ", - "type": "float", - "defaultValue": 6.0, - "min": 0.0, - "softMax": 20.0 - }, - { - "name": "transmissionDistortion", - "displayName": " Distortion", - "description": "How much light direction distorted towards surface normal", - "type": "float", - "defaultValue": 0.1, - "min": 0.0, - "max": 1.0 - }, - { - "name": "transmissionAttenuation", - "displayName": " Attenuation", - "description": "How fast transmitted light fade with thickness", - "type": "float", - "defaultValue": 4.0, - "min": 0.0, - "softMax": 20.0 - }, - { - "name": "transmissionScale", - "displayName": " Scale", - "description": "Strength of transmission", - "type": "float", - "defaultValue": 3.0, - "min": 0.0, - "softMax": 20.0 - } - ], - "wrinkleLayers": [ - { - "name": "enable", - "displayName": "Enable Wrinkle Layers", - "description": "Enable wrinkle layers for morph animations, using vertex color blend weights.", - "type": "Bool", - "defaultValue": false - }, - { - "name": "count", - "displayName": "Number Of Layers", - "description": "The number of wrinkle map layers to use. The blend values come from the 'COLOR0' vertex stream, where R/G/B/A correspond to wrinkle layers 1/2/3/4 respectively.", - "type": "UInt", - "defaultValue": 4, - "min": 1, - "max": 4 - }, - { - "name": "showBlendValues", - "displayName": "Show Blend Values", - "description": "Enable a debug mode that draws the blend values as red, green, blue, and white overlays.", - "type": "Bool", - "defaultValue": false - }, - { - "name": "enableBaseColor", - "displayName": "Enable Base Color Maps", - "description": "Enable support for blending the base color according to morph animations.", - "type": "Bool", - "defaultValue": false - }, - { - "name": "baseColorMap1", - "displayName": " Base Color 1", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_wrinkle_baseColor_texture1" - } - }, - { - "name": "baseColorMap2", - "displayName": " Base Color 2", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_wrinkle_baseColor_texture2" - } - }, - { - "name": "baseColorMap3", - "displayName": " Base Color 3", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_wrinkle_baseColor_texture3" - } - }, - { - "name": "baseColorMap4", - "displayName": " Base Color 4", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_wrinkle_baseColor_texture4" - } - }, - { - "name": "enableNormal", - "displayName": "Enable Normal Maps", - "description": "Enable support for blending the normal maps according to morph animations.", - "type": "Bool", - "defaultValue": false - }, - { - "name": "normalMap1", - "displayName": " Normals 1", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_wrinkle_normal_texture1" - } - }, - { - "name": "normalMap2", - "displayName": " Normals 2", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_wrinkle_normal_texture2" - } - }, - { - "name": "normalMap3", - "displayName": " Normals 3", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_wrinkle_normal_texture3" - } - }, - { - "name": "normalMap4", - "displayName": " Normals 4", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_wrinkle_normal_texture4" - } - } - ], - "detailLayerGroup": [ - { - "name": "enableDetailLayer", - "displayName": "Enable Detail Layer", - "description": "Enable detail layer for fine details and scratches", - "type": "Bool", - "defaultValue": false - }, - { - "name": "blendDetailFactor", - "displayName": "Blend Factor", - "description": "Scales the overall impact of the detail layer.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_detail_blendFactor" - } - }, - { - "name": "blendDetailMask", - "displayName": "Blend Mask", - "description": "Detailed blend mask for application of the detail maps.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_detail_blendMask_texture" - } - }, - { - "name": "enableDetailMaskTexture", - "displayName": " Use Texture", - "description": "Enable detail blend mask", - "type": "Bool", - "defaultValue": true - }, - { - "name": "blendDetailMaskUv", - "displayName": " Blend Mask UV", - "description": "Which UV set to use for sampling the detail blend mask", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Unwrapped", - "connection": { - "type": "ShaderInput", - "name": "m_detail_blendMask_uvIndex" - } - }, - { - "name": "textureMapUv", - "displayName": "Detail Map UVs", - "description": "Which UV set to use for detail map sampling", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Unwrapped", - "connection": { - "type": "ShaderInput", - "name": "m_detail_allMapsUvIndex" - } - }, - { - "name": "enableBaseColor", - "displayName": "Enable Base Color", - "description": "Enable detail blending for base color", - "type": "Bool", - "defaultValue": false - }, - { - "name": "baseColorDetailMap", - "displayName": " Texture", - "description": "Detailed Base Color Texture", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_detail_baseColor_texture" - } - }, - { - "name": "baseColorDetailBlend", - "displayName": " Blend Factor", - "description": "How much to blend the detail layer into the base color.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_detail_baseColor_factor" - } - }, - { - "name": "enableNormals", - "displayName": "Enable Normal", - "description": "Enable detail normal map to be used for fine detail normal such as scratches and small dents", - "type": "Bool", - "defaultValue": false - }, - { - "name": "normalDetailStrength", - "displayName": " Factor", - "description": "Strength factor for scaling the Detail Normal", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_detail_normal_factor" - } - }, - { - "name": "normalDetailMap", - "displayName": " Texture", - "description": "Detailed Normal map", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_detail_normal_texture" - } - }, - { - "name": "normalDetailFlipX", - "displayName": " Flip X Channel", - "description": "Flip Detail tangent direction for this normal map.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderInput", - "name": "m_detail_normal_flipX" - } - }, - { - "name": "normalDetailFlipY", - "displayName": " Flip Y Channel", - "description": "Flip Detail bitangent direction for this normal map.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderInput", - "name": "m_detail_normal_flipY" - } - } - ], - "detailUV": [ - { - "name": "center", - "displayName": "Center", - "description": "Center point for scaling and rotation transformations.", - "type": "vector2", - "vectorLabels": [ "U", "V" ], - "defaultValue": [ 0.5, 0.5 ] - }, - { - "name": "tileU", - "displayName": "Tile U", - "description": "Scales texture coordinates in V.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - }, - { - "name": "tileV", - "displayName": "Tile V", - "description": "Scales texture coordinates in V.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - }, - { - "name": "offsetU", - "displayName": "Offset U", - "description": "Offsets texture coordinates in the U direction.", - "type": "float", - "defaultValue": 0.0, - "min": -1.0, - "max": 1.0 - }, - { - "name": "offsetV", - "displayName": "Offset V", - "description": "Offsets texture coordinates in the V direction.", - "type": "float", - "defaultValue": 0.0, - "min": -1.0, - "max": 1.0 - }, - { - "name": "rotateDegrees", - "displayName": "Rotate", - "description": "Rotates the texture coordinates (degrees).", - "type": "float", - "defaultValue": 0.0, - "min": -180.0, - "max": 180.0, - "step": 1.0 - }, - { - "name": "scale", - "displayName": "Scale", - "description": "Scales texture coordinates in both U and V.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - } - ] - } + ] + } + ] }, "shaders": [ { @@ -1095,4 +1093,4 @@ "UV0": "Tiled", "UV1": "Unwrapped" } -} +} \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype index 6eb82b85ae..d6a5dad8cf 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype @@ -2,1013 +2,1010 @@ "description": "Material Type with properties used to define Standard PBR, a metallic-roughness Physically-Based Rendering (PBR) material shading model.", "propertyLayout": { "version": 3, - "groups": [ + "propertySets": [ { "name": "baseColor", "displayName": "Base Color", - "description": "Properties for configuring the surface reflected color for dielectrics or reflectance values for metals." + "description": "Properties for configuring the surface reflected color for dielectrics or reflectance values for metals.", + "properties": [ + { + "name": "color", + "displayName": "Color", + "description": "Color is displayed as sRGB but the values are stored as linear color.", + "type": "Color", + "defaultValue": [ 1.0, 1.0, 1.0 ], + "connection": { + "type": "ShaderInput", + "name": "m_baseColor" + } + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Strength factor for scaling the base color values. Zero (0.0) is black, white (1.0) is full color.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_baseColorFactor" + } + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "Base color texture map", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_baseColorMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Base color map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_baseColorMapUvIndex" + } + }, + { + "name": "textureBlendMode", + "displayName": "Texture Blend Mode", + "description": "Selects the equation to use when combining Color, Factor, and Texture.", + "type": "Enum", + "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], + "defaultValue": "Multiply", + "connection": { + "type": "ShaderOption", + "name": "o_baseColorTextureBlendMode" + } + } + ] }, { "name": "metallic", "displayName": "Metallic", - "description": "Properties for configuring whether the surface is metallic or not." + "description": "Properties for configuring whether the surface is metallic or not.", + "properties": [ + { + "name": "factor", + "displayName": "Factor", + "description": "This value is linear, black is non-metal and white means raw metal.", + "type": "Float", + "defaultValue": 0.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_metallicFactor" + } + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_metallicMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just default to the Factor value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Metallic map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_metallicMapUvIndex" + } + } + ] }, { "name": "roughness", "displayName": "Roughness", - "description": "Properties for configuring how rough the surface appears." + "description": "Properties for configuring how rough the surface appears.", + "properties": [ + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface roughness.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_roughnessMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just default to the Factor value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Roughness map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_roughnessMapUvIndex" + } + }, + { + // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. + "name": "lowerBound", + "displayName": "Lower Bound", + "description": "The roughness value that corresponds to black in the texture.", + "type": "Float", + "defaultValue": 0.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_roughnessLowerBound" + } + }, + { + // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. + "name": "upperBound", + "displayName": "Upper Bound", + "description": "The roughness value that corresponds to white in the texture.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_roughnessUpperBound" + } + }, + { + // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. + "name": "factor", + "displayName": "Factor", + "description": "Controls the roughness value", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_roughnessFactor" + } + } + ] }, { "name": "specularF0", "displayName": "Specular Reflectance f0", - "description": "The constant f0 represents the specular reflectance at normal incidence (Fresnel 0 Angle). Used to adjust reflectance of non-metal surfaces." + "description": "The constant f0 represents the specular reflectance at normal incidence (Fresnel 0 Angle). Used to adjust reflectance of non-metal surfaces.", + "properties": [ + { + "name": "factor", + "displayName": "Factor", + "description": "The default IOR is 1.5, which gives you 0.04 (4% of light reflected at 0 degree angle for dielectric materials). F0 values lie in the range 0-0.08, so that is why the default F0 slider is set on 0.5.", + "type": "Float", + "defaultValue": 0.5, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_specularF0Factor" + } + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface reflectance.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_specularF0Map" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just default to the Factor value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Specular reflection map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_specularF0MapUvIndex" + } + }, + // Consider moving this to the "general" group to be consistent with StandardMultilayerPBR + { + "name": "enableMultiScatterCompensation", + "displayName": "Multiscattering Compensation", + "description": "Whether to enable multiple scattering compensation.", + "type": "Bool", + "connection": { + "type": "ShaderOption", + "name": "o_specularF0_enableMultiScatterCompensation" + } + } + ] }, { "name": "normal", "displayName": "Normal", - "description": "Properties related to configuring surface normal." + "description": "Properties related to configuring surface normal.", + "properties": [ + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface normal direction.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_normalMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just rely on vertex normals.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Normal map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_normalMapUvIndex" + } + }, + { + "name": "flipX", + "displayName": "Flip X Channel", + "description": "Flip tangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "name": "m_flipNormalX" + } + }, + { + "name": "flipY", + "displayName": "Flip Y Channel", + "description": "Flip bitangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "name": "m_flipNormalY" + } + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Strength factor for scaling the values", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_normalFactor" + } + } + ] }, { "name": "occlusion", "displayName": "Occlusion", - "description": "Properties for baked textures that represent geometric occlusion of light." + "description": "Properties for baked textures that represent geometric occlusion of light.", + "properties": [ + { + "name": "diffuseTextureMap", + "displayName": "Diffuse AO", + "description": "Texture for defining occlusion area for diffuse ambient lighting.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_diffuseOcclusionMap" + } + }, + { + "name": "diffuseUseTexture", + "displayName": " Use Texture", + "description": "Whether to use the Diffuse AO map.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "diffuseTextureMapUv", + "displayName": " UV", + "description": "Diffuse AO map UV set.", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_diffuseOcclusionMapUvIndex" + } + }, + { + "name": "diffuseFactor", + "displayName": " Factor", + "description": "Strength factor for scaling the values of Diffuse AO", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_diffuseOcclusionFactor" + } + }, + { + "name": "specularTextureMap", + "displayName": "Specular Cavity", + "description": "Texture for defining occlusion area for specular lighting.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_specularOcclusionMap" + } + }, + { + "name": "specularUseTexture", + "displayName": " Use Texture", + "description": "Whether to use the Specular Cavity map.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "specularTextureMapUv", + "displayName": " UV", + "description": "Specular Cavity map UV set.", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_specularOcclusionMapUvIndex" + } + }, + { + "name": "specularFactor", + "displayName": " Factor", + "description": "Strength factor for scaling the values of Specular Cavity", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_specularOcclusionFactor" + } + } + ] }, { "name": "emissive", "displayName": "Emissive", - "description": "Properties to add light emission, independent of other lights in the scene." + "description": "Properties to add light emission, independent of other lights in the scene.", + "properties": [ + { + "name": "enable", + "displayName": "Enable", + "description": "Enable the emissive group", + "type": "Bool", + "defaultValue": false + }, + { + "name": "unit", + "displayName": "Units", + "description": "The photometric units of the Intensity property.", + "type": "Enum", + "enumValues": ["Ev100"], + "defaultValue": "Ev100" + }, + { + "name": "color", + "displayName": "Color", + "description": "Color is displayed as sRGB but the values are stored as linear color.", + "type": "Color", + "defaultValue": [ 1.0, 1.0, 1.0 ], + "connection": { + "type": "ShaderInput", + "name": "m_emissiveColor" + } + }, + { + "name": "intensity", + "displayName": "Intensity", + "description": "The amount of energy emitted.", + "type": "Float", + "defaultValue": 4, + "min": -10, + "max": 20, + "softMin": -6, + "softMax": 16 + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining emissive area.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_emissiveMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Emissive map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_emissiveMapUvIndex" + } + } + ] }, { "name": "clearCoat", "displayName": "Clear Coat", - "description": "Properties for configuring gloss clear coat" - }, + "description": "Properties for configuring gloss clear coat", + "properties": [ + { + "name": "enable", + "displayName": "Enable", + "description": "Enable clear coat", + "type": "Bool", + "defaultValue": false + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Strength factor for scaling the percentage of effect applied", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatFactor" + } + }, + { + "name": "influenceMap", + "displayName": " Influence Map", + "description": "Strength factor texture", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatInfluenceMap" + } + }, + { + "name": "useInfluenceMap", + "displayName": " Use Texture", + "description": "Whether to use the texture, or just default to the Factor value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "influenceMapUv", + "displayName": " UV", + "description": "Strength factor map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatInfluenceMapUvIndex" + } + }, + { + "name": "roughness", + "displayName": "Roughness", + "description": "Clear coat layer roughness", + "type": "Float", + "defaultValue": 0.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatRoughness" + } + }, + { + "name": "roughnessMap", + "displayName": " Roughness Map", + "description": "Texture for defining surface roughness", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatRoughnessMap" + } + }, + { + "name": "useRoughnessMap", + "displayName": " Use Texture", + "description": "Whether to use the texture, or just default to the roughness value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "roughnessMapUv", + "displayName": " UV", + "description": "Roughness map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatRoughnessMapUvIndex" + } + }, + { + "name": "normalStrength", + "displayName": "Normal Strength", + "description": "Scales the impact of the clear coat normal map", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatNormalStrength" + } + }, + { + "name": "normalMap", + "displayName": "Normal Map", + "description": "Normal map for clear coat layer, as top layer material clear coat doesn't affect by base layer normal map", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatNormalMap" + } + }, + { + "name": "useNormalMap", + "displayName": " Use Texture", + "description": "Whether to use the normal map", + "type": "Bool", + "defaultValue": true + }, + { + "name": "normalMapUv", + "displayName": " UV", + "description": "Normal map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatNormalMapUvIndex" + } + } + ] + }, { "name": "parallax", "displayName": "Displacement", - "description": "Properties for parallax effect produced by a height map." + "description": "Properties for parallax effect produced by a height map.", + "properties": [ + { + "name": "textureMap", + "displayName": "Height Map", + "description": "Displacement height map to create parallax effect.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_heightmap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the height map.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Height map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_parallaxUvIndex" + } + }, + { + "name": "factor", + "displayName": "Height Map Scale", + "description": "The total height of the height map in local model units.", + "type": "Float", + "defaultValue": 0.05, + "min": 0.0, + "softMax": 0.1, + "connection": { + "type": "ShaderInput", + "name": "m_heightmapScale" + } + }, + { + "name": "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", + "name": "m_heightmapOffset" + } + }, + { + "name": "algorithm", + "displayName": "Algorithm", + "description": "Select the algorithm to use for parallax mapping.", + "type": "Enum", + "enumValues": [ "Basic", "Steep", "POM", "Relief", "ContactRefinement" ], + "defaultValue": "POM", + "connection": { + "type": "ShaderOption", + "name": "o_parallax_algorithm" + } + }, + { + "name": "quality", + "displayName": "Quality", + "description": "Quality of parallax mapping.", + "type": "Enum", + "enumValues": [ "Low", "Medium", "High", "Ultra" ], + "defaultValue": "Low", + "connection": { + "type": "ShaderOption", + "name": "o_parallax_quality" + } + }, + { + "name": "pdo", + "displayName": "Pixel Depth Offset", + "description": "Enable PDO to offset the original pixel depths. This will affect any shaders using depth, for example, when receiving shadows.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_parallax_enablePixelDepthOffset" + } + }, + { + "name": "showClipping", + "displayName": "Show Clipping", + "description": "Highlight areas where the height map is clipped by the mesh surface.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_parallax_highlightClipping" + } + } + ] }, { "name": "opacity", "displayName": "Opacity", - "description": "Properties for configuring the materials transparency." + "description": "Properties for configuring the materials transparency.", + "properties": [ + { + "name": "mode", + "displayName": "Opacity Mode", + "description": "Indicates the general approach how transparency is to be applied.", + "type": "Enum", + "enumValues": [ "Opaque", "Cutout", "Blended", "TintedTransparent" ], + "defaultValue": "Opaque", + "connection": { + "type": "ShaderOption", + "name": "o_opacity_mode" + } + }, + { + "name": "alphaSource", + "displayName": "Alpha Source", + "description": "Indicates whether to get the opacity texture from the Base Color map (Packed) or from a separate greyscale texture (Split).", + "type": "Enum", + "enumValues": [ "Packed", "Split", "None" ], + "defaultValue": "Packed", + "connection": { + "type": "ShaderOption", + "name": "o_opacity_source" + } + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface opacity.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_opacityMap" + } + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Opacity map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_opacityMapUvIndex" + } + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Factor for cutout threshold and blending", + "type": "Float", + "min": 0.0, + "max": 1.0, + "defaultValue": 0.5, + "connection": { + "type": "ShaderInput", + "name": "m_opacityFactor" + } + }, + { + "name": "doubleSided", + "displayName": "Double-sided", + "description": "Whether to render back-faces or just front-faces.", + "type": "Bool" + }, + { + "name": "alphaAffectsSpecular", + "displayName": "Alpha affects specular", + "description": "How much the alpha value should also affect specular reflection. This should be 0.0 for materials where light can transmit through their physical surface (like glass), but 1.0 when alpha determines the very presence of a surface (like hair or grass)", + "type": "float", + "min": 0.0, + "max": 1.0, + "defaultValue": 0.0, + "connection": { + "type": "ShaderInput", + "name": "m_opacityAffectsSpecularFactor" + } + } + ] }, { "name": "uv", "displayName": "UVs", - "description": "Properties for configuring UV transforms." + "description": "Properties for configuring UV transforms.", + "properties": [ + { + "name": "center", + "displayName": "Center", + "description": "Center point for scaling and rotation transformations.", + "type": "vector2", + "vectorLabels": [ "U", "V" ], + "defaultValue": [ 0.5, 0.5 ] + }, + { + "name": "tileU", + "displayName": "Tile U", + "description": "Scales texture coordinates in U.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + }, + { + "name": "tileV", + "displayName": "Tile V", + "description": "Scales texture coordinates in V.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + }, + { + "name": "offsetU", + "displayName": "Offset U", + "description": "Offsets texture coordinates in the U direction.", + "type": "float", + "defaultValue": 0.0, + "min": -1.0, + "max": 1.0 + }, + { + "name": "offsetV", + "displayName": "Offset V", + "description": "Offsets texture coordinates in the V direction.", + "type": "float", + "defaultValue": 0.0, + "min": -1.0, + "max": 1.0 + }, + { + "name": "rotateDegrees", + "displayName": "Rotate", + "description": "Rotates the texture coordinates (degrees).", + "type": "float", + "defaultValue": 0.0, + "min": -180.0, + "max": 180.0, + "step": 1.0 + }, + { + "name": "scale", + "displayName": "Scale", + "description": "Scales texture coordinates in both U and V.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + } + ] }, { // Note: this property group is used in the DiffuseGlobalIllumination pass, it is not read by the StandardPBR shader "name": "irradiance", "displayName": "Irradiance", - "description": "Properties for configuring the irradiance used in global illumination." + "description": "Properties for configuring the irradiance used in global illumination.", + "properties": [ + { + "name": "color", + "displayName": "Color", + "description": "Color is displayed as sRGB but the values are stored as linear color.", + "type": "Color", + "defaultValue": [ 1.0, 1.0, 1.0 ] + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Strength factor for scaling the irradiance color values. Zero (0.0) is black, white (1.0) is full color.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0 + } + ] }, { "name": "general", "displayName": "General Settings", - "description": "General settings." - } - ], - "properties": { - "general": [ - { - "name": "applySpecularAA", - "displayName": "Apply Specular AA", - "description": "Whether to apply specular anti-aliasing in the shader.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_applySpecularAA" - } - }, - { - "name": "enableShadows", - "displayName": "Enable Shadows", - "description": "Whether to use the shadow maps.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableShadows" - } - }, - { - "name": "enableDirectionalLights", - "displayName": "Enable Directional Lights", - "description": "Whether to use directional lights.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableDirectionalLights" - } - }, - { - "name": "enablePunctualLights", - "displayName": "Enable Punctual Lights", - "description": "Whether to use punctual lights.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enablePunctualLights" - } - }, - { - "name": "enableAreaLights", - "displayName": "Enable Area Lights", - "description": "Whether to use area lights.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableAreaLights" - } - }, - { - "name": "enableIBL", - "displayName": "Enable IBL", - "description": "Whether to use Image Based Lighting (IBL).", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableIBL" - } - }, - { - "name": "forwardPassIBLSpecular", - "displayName": "Forward Pass IBL Specular", - "description": "Whether to apply IBL specular in the forward pass.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_materialUseForwardPassIBLSpecular" - } - } - ], - "baseColor": [ - { - "name": "color", - "displayName": "Color", - "description": "Color is displayed as sRGB but the values are stored as linear color.", - "type": "Color", - "defaultValue": [ 1.0, 1.0, 1.0 ], - "connection": { - "type": "ShaderInput", - "name": "m_baseColor" - } - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the base color values. Zero (0.0) is black, white (1.0) is full color.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_baseColorFactor" - } - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "Base color texture map", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_baseColorMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Base color map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_baseColorMapUvIndex" - } - }, - { - "name": "textureBlendMode", - "displayName": "Texture Blend Mode", - "description": "Selects the equation to use when combining Color, Factor, and Texture.", - "type": "Enum", - "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], - "defaultValue": "Multiply", - "connection": { - "type": "ShaderOption", - "name": "o_baseColorTextureBlendMode" - } - } - ], - "metallic": [ - { - "name": "factor", - "displayName": "Factor", - "description": "This value is linear, black is non-metal and white means raw metal.", - "type": "Float", - "defaultValue": 0.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_metallicFactor" - } - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_metallicMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture, or just default to the Factor value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Metallic map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_metallicMapUvIndex" - } - } - ], - "roughness": [ - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface roughness.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_roughnessMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture, or just default to the Factor value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Roughness map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_roughnessMapUvIndex" - } - }, - { - // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. - "name": "lowerBound", - "displayName": "Lower Bound", - "description": "The roughness value that corresponds to black in the texture.", - "type": "Float", - "defaultValue": 0.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_roughnessLowerBound" - } - }, - { - // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. - "name": "upperBound", - "displayName": "Upper Bound", - "description": "The roughness value that corresponds to white in the texture.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_roughnessUpperBound" - } - }, - { - // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. - "name": "factor", - "displayName": "Factor", - "description": "Controls the roughness value", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_roughnessFactor" - } - } - ], - "specularF0": [ - { - "name": "factor", - "displayName": "Factor", - "description": "The default IOR is 1.5, which gives you 0.04 (4% of light reflected at 0 degree angle for dielectric materials). F0 values lie in the range 0-0.08, so that is why the default F0 slider is set on 0.5.", - "type": "Float", - "defaultValue": 0.5, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_specularF0Factor" - } - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface reflectance.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_specularF0Map" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture, or just default to the Factor value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Specular reflection map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_specularF0MapUvIndex" - } - }, - // Consider moving this to the "general" group to be consistent with StandardMultilayerPBR - { - "name": "enableMultiScatterCompensation", - "displayName": "Multiscattering Compensation", - "description": "Whether to enable multiple scattering compensation.", - "type": "Bool", - "connection": { - "type": "ShaderOption", - "name": "o_specularF0_enableMultiScatterCompensation" - } - } - ], - "clearCoat": [ - { - "name": "enable", - "displayName": "Enable", - "description": "Enable clear coat", - "type": "Bool", - "defaultValue": false - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the percentage of effect applied", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatFactor" + "description": "General settings.", + "properties": [ + { + "name": "applySpecularAA", + "displayName": "Apply Specular AA", + "description": "Whether to apply specular anti-aliasing in the shader.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_applySpecularAA" + } + }, + { + "name": "enableShadows", + "displayName": "Enable Shadows", + "description": "Whether to use the shadow maps.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableShadows" + } + }, + { + "name": "enableDirectionalLights", + "displayName": "Enable Directional Lights", + "description": "Whether to use directional lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableDirectionalLights" + } + }, + { + "name": "enablePunctualLights", + "displayName": "Enable Punctual Lights", + "description": "Whether to use punctual lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enablePunctualLights" + } + }, + { + "name": "enableAreaLights", + "displayName": "Enable Area Lights", + "description": "Whether to use area lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableAreaLights" + } + }, + { + "name": "enableIBL", + "displayName": "Enable IBL", + "description": "Whether to use Image Based Lighting (IBL).", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableIBL" + } + }, + { + "name": "forwardPassIBLSpecular", + "displayName": "Forward Pass IBL Specular", + "description": "Whether to apply IBL specular in the forward pass.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_materialUseForwardPassIBLSpecular" + } } - }, - { - "name": "influenceMap", - "displayName": " Influence Map", - "description": "Strength factor texture", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatInfluenceMap" - } - }, - { - "name": "useInfluenceMap", - "displayName": " Use Texture", - "description": "Whether to use the texture, or just default to the Factor value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "influenceMapUv", - "displayName": " UV", - "description": "Strength factor map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatInfluenceMapUvIndex" - } - }, - { - "name": "roughness", - "displayName": "Roughness", - "description": "Clear coat layer roughness", - "type": "Float", - "defaultValue": 0.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatRoughness" - } - }, - { - "name": "roughnessMap", - "displayName": " Roughness Map", - "description": "Texture for defining surface roughness", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatRoughnessMap" - } - }, - { - "name": "useRoughnessMap", - "displayName": " Use Texture", - "description": "Whether to use the texture, or just default to the roughness value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "roughnessMapUv", - "displayName": " UV", - "description": "Roughness map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatRoughnessMapUvIndex" - } - }, - { - "name": "normalStrength", - "displayName": "Normal Strength", - "description": "Scales the impact of the clear coat normal map", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatNormalStrength" - } - }, - { - "name": "normalMap", - "displayName": "Normal Map", - "description": "Normal map for clear coat layer, as top layer material clear coat doesn't affect by base layer normal map", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatNormalMap" - } - }, - { - "name": "useNormalMap", - "displayName": " Use Texture", - "description": "Whether to use the normal map", - "type": "Bool", - "defaultValue": true - }, - { - "name": "normalMapUv", - "displayName": " UV", - "description": "Normal map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatNormalMapUvIndex" - } - } - ], - "normal": [ - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface normal direction.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_normalMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture, or just rely on vertex normals.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Normal map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_normalMapUvIndex" - } - }, - { - "name": "flipX", - "displayName": "Flip X Channel", - "description": "Flip tangent direction for this normal map.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderInput", - "name": "m_flipNormalX" - } - }, - { - "name": "flipY", - "displayName": "Flip Y Channel", - "description": "Flip bitangent direction for this normal map.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderInput", - "name": "m_flipNormalY" - } - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the values", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_normalFactor" - } - } - ], - "opacity": [ - { - "name": "mode", - "displayName": "Opacity Mode", - "description": "Indicates the general approach how transparency is to be applied.", - "type": "Enum", - "enumValues": [ "Opaque", "Cutout", "Blended", "TintedTransparent" ], - "defaultValue": "Opaque", - "connection": { - "type": "ShaderOption", - "name": "o_opacity_mode" - } - }, - { - "name": "alphaSource", - "displayName": "Alpha Source", - "description": "Indicates whether to get the opacity texture from the Base Color map (Packed) or from a separate greyscale texture (Split).", - "type": "Enum", - "enumValues": [ "Packed", "Split", "None" ], - "defaultValue": "Packed", - "connection": { - "type": "ShaderOption", - "name": "o_opacity_source" - } - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface opacity.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_opacityMap" - } - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Opacity map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_opacityMapUvIndex" - } - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Factor for cutout threshold and blending", - "type": "Float", - "min": 0.0, - "max": 1.0, - "defaultValue": 0.5, - "connection": { - "type": "ShaderInput", - "name": "m_opacityFactor" - } - }, - { - "name": "doubleSided", - "displayName": "Double-sided", - "description": "Whether to render back-faces or just front-faces.", - "type": "Bool" - }, - { - "name": "alphaAffectsSpecular", - "displayName": "Alpha affects specular", - "description": "How much the alpha value should also affect specular reflection. This should be 0.0 for materials where light can transmit through their physical surface (like glass), but 1.0 when alpha determines the very presence of a surface (like hair or grass)", - "type": "float", - "min": 0.0, - "max": 1.0, - "defaultValue": 0.0, - "connection": { - "type": "ShaderInput", - "name": "m_opacityAffectsSpecularFactor" - } - } - ], - "uv": [ - { - "name": "center", - "displayName": "Center", - "description": "Center point for scaling and rotation transformations.", - "type": "vector2", - "vectorLabels": [ "U", "V" ], - "defaultValue": [ 0.5, 0.5 ] - }, - { - "name": "tileU", - "displayName": "Tile U", - "description": "Scales texture coordinates in U.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - }, - { - "name": "tileV", - "displayName": "Tile V", - "description": "Scales texture coordinates in V.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - }, - { - "name": "offsetU", - "displayName": "Offset U", - "description": "Offsets texture coordinates in the U direction.", - "type": "float", - "defaultValue": 0.0, - "min": -1.0, - "max": 1.0 - }, - { - "name": "offsetV", - "displayName": "Offset V", - "description": "Offsets texture coordinates in the V direction.", - "type": "float", - "defaultValue": 0.0, - "min": -1.0, - "max": 1.0 - }, - { - "name": "rotateDegrees", - "displayName": "Rotate", - "description": "Rotates the texture coordinates (degrees).", - "type": "float", - "defaultValue": 0.0, - "min": -180.0, - "max": 180.0, - "step": 1.0 - }, - { - "name": "scale", - "displayName": "Scale", - "description": "Scales texture coordinates in both U and V.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - } - ], - "occlusion": [ - { - "name": "diffuseTextureMap", - "displayName": "Diffuse AO", - "description": "Texture for defining occlusion area for diffuse ambient lighting.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_diffuseOcclusionMap" - } - }, - { - "name": "diffuseUseTexture", - "displayName": " Use Texture", - "description": "Whether to use the Diffuse AO map.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "diffuseTextureMapUv", - "displayName": " UV", - "description": "Diffuse AO map UV set.", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_diffuseOcclusionMapUvIndex" - } - }, - { - "name": "diffuseFactor", - "displayName": " Factor", - "description": "Strength factor for scaling the values of Diffuse AO", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_diffuseOcclusionFactor" - } - }, - { - "name": "specularTextureMap", - "displayName": "Specular Cavity", - "description": "Texture for defining occlusion area for specular lighting.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_specularOcclusionMap" - } - }, - { - "name": "specularUseTexture", - "displayName": " Use Texture", - "description": "Whether to use the Specular Cavity map.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "specularTextureMapUv", - "displayName": " UV", - "description": "Specular Cavity map UV set.", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_specularOcclusionMapUvIndex" - } - }, - { - "name": "specularFactor", - "displayName": " Factor", - "description": "Strength factor for scaling the values of Specular Cavity", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_specularOcclusionFactor" - } - } - ], - "emissive": [ - { - "name": "enable", - "displayName": "Enable", - "description": "Enable the emissive group", - "type": "Bool", - "defaultValue": false - }, - { - "name": "unit", - "displayName": "Units", - "description": "The photometric units of the Intensity property.", - "type": "Enum", - "enumValues": ["Ev100"], - "defaultValue": "Ev100" - }, - { - "name": "color", - "displayName": "Color", - "description": "Color is displayed as sRGB but the values are stored as linear color.", - "type": "Color", - "defaultValue": [ 1.0, 1.0, 1.0 ], - "connection": { - "type": "ShaderInput", - "name": "m_emissiveColor" - } - }, - { - "name": "intensity", - "displayName": "Intensity", - "description": "The amount of energy emitted.", - "type": "Float", - "defaultValue": 4, - "min": -10, - "max": 20, - "softMin": -6, - "softMax": 16 - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining emissive area.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_emissiveMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Emissive map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_emissiveMapUvIndex" - } - } - ], - "parallax": [ - { - "name": "textureMap", - "displayName": "Height Map", - "description": "Displacement height map to create parallax effect.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_heightmap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the height map.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Height map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_parallaxUvIndex" - } - }, - { - "name": "factor", - "displayName": "Height Map Scale", - "description": "The total height of the height map in local model units.", - "type": "Float", - "defaultValue": 0.05, - "min": 0.0, - "softMax": 0.1, - "connection": { - "type": "ShaderInput", - "name": "m_heightmapScale" - } - }, - { - "name": "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", - "name": "m_heightmapOffset" - } - }, - { - "name": "algorithm", - "displayName": "Algorithm", - "description": "Select the algorithm to use for parallax mapping.", - "type": "Enum", - "enumValues": [ "Basic", "Steep", "POM", "Relief", "ContactRefinement" ], - "defaultValue": "POM", - "connection": { - "type": "ShaderOption", - "name": "o_parallax_algorithm" - } - }, - { - "name": "quality", - "displayName": "Quality", - "description": "Quality of parallax mapping.", - "type": "Enum", - "enumValues": [ "Low", "Medium", "High", "Ultra" ], - "defaultValue": "Low", - "connection": { - "type": "ShaderOption", - "name": "o_parallax_quality" - } - }, - { - "name": "pdo", - "displayName": "Pixel Depth Offset", - "description": "Enable PDO to offset the original pixel depths. This will affect any shaders using depth, for example, when receiving shadows.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_parallax_enablePixelDepthOffset" - } - }, - { - "name": "showClipping", - "displayName": "Show Clipping", - "description": "Highlight areas where the height map is clipped by the mesh surface.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_parallax_highlightClipping" - } - } - ], - "irradiance": [ - // Note: this property group is used in the DiffuseGlobalIllumination pass and not by the main forward shader - { - "name": "color", - "displayName": "Color", - "description": "Color is displayed as sRGB but the values are stored as linear color.", - "type": "Color", - "defaultValue": [ 1.0, 1.0, 1.0 ] - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the irradiance color values. Zero (0.0) is black, white (1.0) is full color.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0 - } - ] - } + ] + } + ] }, "shaders": [ { @@ -1194,4 +1191,4 @@ "UV0": "Tiled", "UV1": "Unwrapped" } -} +} \ No newline at end of file diff --git a/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick.materialtype b/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick.materialtype index 00f11663f7..0d4d24d782 100644 --- a/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick.materialtype +++ b/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick.materialtype @@ -2,176 +2,174 @@ "description": "This is an example of a custom material type using Atom's PBR shading model: procedurally generated brick or tile.", "propertyLayout": { "version": 3, - "groups": [ + "propertySets": [ { "name": "shape", "displayName": "Shape", - "description": "Properties for configuring size, shape, and position of the bricks." + "description": "Properties for configuring size, shape, and position of the bricks.", + "properties": [ + { + "name": "brickWidth", + "displayName": "Brick Width", + "description": "The width of each brick.", + "type": "Float", + "defaultValue": 0.1, + "min": 0.0, + "softMax": 0.2, + "step": 0.001, + "connection": { + "type": "ShaderInput", + "name": "m_brickWidth" + } + }, + { + "name": "brickHeight", + "displayName": "Brick Height", + "description": "The height of each brick.", + "type": "Float", + "defaultValue": 0.05, + "min": 0.0, + "softMax": 0.2, + "step": 0.001, + "connection": { + "type": "ShaderInput", + "name": "m_brickHeight" + } + }, + { + "name": "brickOffset", + "displayName": "Offset", + "description": "The offset of each stack of bricks as a percentage of brick width.", + "type": "Float", + "defaultValue": 0.5, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_brickOffset" + } + }, + { + "name": "lineWidth", + "displayName": "Line Width", + "description": "The width of the grout lines.", + "type": "Float", + "defaultValue": 0.01, + "min": 0.0, + "softMax": 0.02, + "step": 0.0001, + "connection": { + "type": "ShaderInput", + "name": "m_lineWidth" + } + }, + { + "name": "lineDepth", + "displayName": "Line Depth", + "description": "The depth of the grout lines.", + "type": "Float", + "defaultValue": 0.01, + "min": 0.0, + "softMax": 0.02, + "connection": { + "type": "ShaderInput", + "name": "m_lineDepth" + } + } + ] }, { "name": "appearance", "displayName": "Appearance", - "description": "Properties for configuring the appearance of the bricks and grout lines." - } - ], - "properties": { - "shape": [ - { - "name": "brickWidth", - "displayName": "Brick Width", - "description": "The width of each brick.", - "type": "Float", - "defaultValue": 0.1, - "min": 0.0, - "softMax": 0.2, - "step": 0.001, - "connection": { - "type": "ShaderInput", - "name": "m_brickWidth" - } - }, - { - "name": "brickHeight", - "displayName": "Brick Height", - "description": "The height of each brick.", - "type": "Float", - "defaultValue": 0.05, - "min": 0.0, - "softMax": 0.2, - "step": 0.001, - "connection": { - "type": "ShaderInput", - "name": "m_brickHeight" - } - }, - { - "name": "brickOffset", - "displayName": "Offset", - "description": "The offset of each stack of bricks as a percentage of brick width.", - "type": "Float", - "defaultValue": 0.5, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_brickOffset" - } - }, - { - "name": "lineWidth", - "displayName": "Line Width", - "description": "The width of the grout lines.", - "type": "Float", - "defaultValue": 0.01, - "min": 0.0, - "softMax": 0.02, - "step": 0.0001, - "connection": { - "type": "ShaderInput", - "name": "m_lineWidth" - } - }, - { - "name": "lineDepth", - "displayName": "Line Depth", - "description": "The depth of the grout lines.", - "type": "Float", - "defaultValue": 0.01, - "min": 0.0, - "softMax": 0.02, - "connection": { - "type": "ShaderInput", - "name": "m_lineDepth" - } - } - ], - "appearance": [ - { - "name": "noiseTexture", - "type": "Image", - "defaultValue": "TestData/Textures/noise512.png", - "visibility": "Hidden", - "connection": { - "type": "ShaderInput", - "name": "m_noise" - } - }, - { - "name": "brickColor", - "displayName": "Brick Color", - "description": "The color of the bricks.", - "type": "Color", - "defaultValue": [1.0,1.0,1.0], - "connection": { - "type": "ShaderInput", - "name": "m_brickColor" + "description": "Properties for configuring the appearance of the bricks and grout lines.", + "properties": [ + { + "name": "noiseTexture", + "type": "Image", + "defaultValue": "TestData/Textures/noise512.png", + "visibility": "Hidden", + "connection": { + "type": "ShaderInput", + "name": "m_noise" + } + }, + { + "name": "brickColor", + "displayName": "Brick Color", + "description": "The color of the bricks.", + "type": "Color", + "defaultValue": [1.0,1.0,1.0], + "connection": { + "type": "ShaderInput", + "name": "m_brickColor" + } + }, + { + "name": "brickColorNoise", + "displayName": "Brick Color Noise", + "description": "Scale the variation of brick color.", + "type": "Float", + "defaultValue": 0.25, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_brickNoiseFactor" + } + }, + { + "name": "lineColor", + "displayName": "Line Color", + "description": "The color of the grout lines.", + "type": "Color", + "defaultValue": [0.5,0.5,0.5], + "connection": { + "type": "ShaderInput", + "name": "m_lineColor" + } + }, + { + "name": "lineColorNoise", + "displayName": "Line Color Noise", + "description": "Scale the variation of grout line color.", + "type": "Float", + "defaultValue": 0.25, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_lineNoiseFactor" + } + }, + { + "name": "brickColorBleed", + "displayName": "Brick Color Bleed", + "description": "Distance into the grout line that the brick color will continue.", + "type": "Float", + "defaultValue": 0.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_brickColorBleed" + } + }, + { + "name": "ao", + "displayName": "Ambient Occlusion", + "description": "The strength of baked ambient occlusion in the grout lines.", + "type": "Float", + "defaultValue": 0.5, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_aoFactor" + } } - }, - { - "name": "brickColorNoise", - "displayName": "Brick Color Noise", - "description": "Scale the variation of brick color.", - "type": "Float", - "defaultValue": 0.25, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_brickNoiseFactor" - } - }, - { - "name": "lineColor", - "displayName": "Line Color", - "description": "The color of the grout lines.", - "type": "Color", - "defaultValue": [0.5,0.5,0.5], - "connection": { - "type": "ShaderInput", - "name": "m_lineColor" - } - }, - { - "name": "lineColorNoise", - "displayName": "Line Color Noise", - "description": "Scale the variation of grout line color.", - "type": "Float", - "defaultValue": 0.25, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_lineNoiseFactor" - } - }, - { - "name": "brickColorBleed", - "displayName": "Brick Color Bleed", - "description": "Distance into the grout line that the brick color will continue.", - "type": "Float", - "defaultValue": 0.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_brickColorBleed" - } - }, - { - "name": "ao", - "displayName": "Ambient Occlusion", - "description": "The strength of baked ambient occlusion in the grout lines.", - "type": "Float", - "defaultValue": 0.5, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_aoFactor" - } - } - ] - } + ] + } + ] }, "shaders": [ { @@ -187,8 +185,5 @@ { "file": "Shaders/Depth/DepthPass.shader" } - ], - "functors": [ ] -} - +} \ No newline at end of file From b1c746968752a02a621220edf81c902cb3161ebd Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Fri, 1 Oct 2021 17:11:54 -0700 Subject: [PATCH 06/53] Renamed m_groups and m_properties to have "Old" in the name for clarity. Also fixed a potential uninitialized data bug in Conve. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../ConvertEmissiveUnitFunctorSourceData.h | 4 ++-- .../RPI.Edit/Material/MaterialTypeSourceData.h | 4 ++-- .../Material/MaterialTypeSourceData.cpp | 18 +++++++++--------- .../Material/MaterialTypeSourceDataTests.cpp | 8 ++++---- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/ConvertEmissiveUnitFunctorSourceData.h b/Gems/Atom/Feature/Common/Code/Source/Material/ConvertEmissiveUnitFunctorSourceData.h index 23219ce940..091351087f 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/ConvertEmissiveUnitFunctorSourceData.h +++ b/Gems/Atom/Feature/Common/Code/Source/Material/ConvertEmissiveUnitFunctorSourceData.h @@ -44,8 +44,8 @@ namespace AZ AZStd::string m_shaderInputName; // The indices of photometric units in the dropdown list - uint32_t m_ev100Index; - uint32_t m_nitIndex; + uint32_t m_ev100Index = 0; + uint32_t m_nitIndex = 1; // Minimum and Maximum value for different photometric units AZ::Vector2 m_ev100MinMax; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h index 1d918702ff..ec12384286 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h @@ -170,11 +170,11 @@ namespace AZ //! [Deprecated] Use m_propertySets instead //! List of groups that will contain the available properties - AZStd::vector m_groups; + AZStd::vector m_groupsOld; //! [Deprecated] Use m_propertySets instead //! Collection of all available user-facing properties - AZStd::map> m_properties; + AZStd::map> m_propertiesOld; AZStd::vector> m_propertySets; }; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp index 816e9f4f8d..bddbf726e2 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp @@ -83,8 +83,8 @@ namespace AZ serializeContext->Class() ->Version(1) ->Field("version", &PropertyLayout::m_version) - ->Field("groups", &PropertyLayout::m_groups) //< Old, preserved for backward compatibility, replaced by propertySets - ->Field("properties", &PropertyLayout::m_properties) //< Old, preserved for backward compatibility, replaced by propertySets + ->Field("groups", &PropertyLayout::m_groupsOld) //< Deprecated, preserved for backward compatibility, replaced by propertySets + ->Field("properties", &PropertyLayout::m_propertiesOld) //< Deprecated, preserved for backward compatibility, replaced by propertySets ->Field("propertySets", &PropertyLayout::m_propertySets) ; @@ -397,8 +397,8 @@ namespace AZ { for (const auto& group : GetOldFormatGroupDefinitionsInDisplayOrder()) { - auto propertyListItr = m_propertyLayout.m_properties.find(group.m_name); - if (propertyListItr != m_propertyLayout.m_properties.end()) + auto propertyListItr = m_propertyLayout.m_propertiesOld.find(group.m_name); + if (propertyListItr != m_propertyLayout.m_propertiesOld.end()) { const auto& propertyList = propertyListItr->second; for (auto& propertyDefinition : propertyList) @@ -421,8 +421,8 @@ namespace AZ } } - m_propertyLayout.m_groups.clear(); - m_propertyLayout.m_properties.clear(); + m_propertyLayout.m_groupsOld.clear(); + m_propertyLayout.m_propertiesOld.clear(); return true; } @@ -451,11 +451,11 @@ namespace AZ AZStd::vector MaterialTypeSourceData::GetOldFormatGroupDefinitionsInDisplayOrder() const { AZStd::vector groupDefinitions; - groupDefinitions.reserve(m_propertyLayout.m_properties.size()); + groupDefinitions.reserve(m_propertyLayout.m_propertiesOld.size()); // Some groups are defined explicitly in the .materialtype file's "groups" section. This is the primary way groups are sorted in the UI. AZStd::unordered_set foundGroups; - for (const auto& groupDefinition : m_propertyLayout.m_groups) + for (const auto& groupDefinition : m_propertyLayout.m_groupsOld) { if (foundGroups.insert(groupDefinition.m_name).second) { @@ -468,7 +468,7 @@ namespace AZ } // Some groups are defined implicitly, in the "properties" section where a group name is used but not explicitly defined in the "groups" section. - for (const auto& propertyListPair : m_propertyLayout.m_properties) + for (const auto& propertyListPair : m_propertyLayout.m_propertiesOld) { const AZStd::string& groupName = propertyListPair.first; if (foundGroups.insert(groupName).second) diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp index 00523abbd8..e5394106fd 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp @@ -1781,15 +1781,15 @@ namespace UnitTest JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson); // Before conversion to the new format, the data is in the old place - EXPECT_EQ(material.GetPropertyLayout().m_groups.size(), 2); - EXPECT_EQ(material.GetPropertyLayout().m_properties.size(), 2); + EXPECT_EQ(material.GetPropertyLayout().m_groupsOld.size(), 2); + EXPECT_EQ(material.GetPropertyLayout().m_propertiesOld.size(), 2); EXPECT_EQ(material.GetPropertyLayout().m_propertySets.size(), 0); material.ConvertToNewDataFormat(); // After conversion to the new format, the data is in the new place - EXPECT_EQ(material.GetPropertyLayout().m_groups.size(), 0); - EXPECT_EQ(material.GetPropertyLayout().m_properties.size(), 0); + EXPECT_EQ(material.GetPropertyLayout().m_groupsOld.size(), 0); + EXPECT_EQ(material.GetPropertyLayout().m_propertiesOld.size(), 0); EXPECT_EQ(material.GetPropertyLayout().m_propertySets.size(), 2); EXPECT_EQ(material.m_description, "This is a general description about the material"); From 4312c636afb458cb630a23100186150a6324240a Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Wed, 26 Jan 2022 12:19:49 -0800 Subject: [PATCH 07/53] Got the RPI unit tests building and working again after merge. There were some incorrectly resolved conflicts that I had to re-resolve, especially in MaterialTypeSourceData::CreateMaterialTypeAsset. I removed support for property rename version updates in MaterialTypeSourceData (i.e. ApplyPropertyRenames) because MaterialSourceData serialization no longer loads material property definitions from the .materialtype file, per a recent change on the development branch. The unit tests were broken and it wasn't worth updating them since we don't need this functionality anymore. Material property renames and other version updates are now exclusively applied by the MaterialAsset class. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Material/MaterialTypeSourceData.cpp | 327 +----------------- .../Material/MaterialSourceDataTests.cpp | 45 +-- .../Material/MaterialTypeSourceDataTests.cpp | 152 +------- 3 files changed, 38 insertions(+), 486 deletions(-) diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp index 9e72e13b34..2504981273 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp @@ -132,32 +132,6 @@ namespace AZ const float MaterialTypeSourceData::PropertyDefinition::DefaultMax = std::numeric_limits::max(); const float MaterialTypeSourceData::PropertyDefinition::DefaultStep = 0.1f; - bool MaterialTypeSourceData::ApplyPropertyRenames(MaterialPropertyId& propertyId) const - { - bool renamed = false; - - for (const VersionUpdateDefinition& versionUpdate : m_versionUpdates) - { - for (const VersionUpdatesRenameOperationDefinition& action : versionUpdate.m_actions) - { - if (action.m_operation == "rename") - { - if (action.m_renameFrom == propertyId.GetStringView()) - { - propertyId = MaterialPropertyId::Parse(action.m_renameTo); - renamed = true; - } - } - else - { - AZ_Warning("Material source data", false, "Unsupported material version update operation '%s'", action.m_operation.c_str()); - } - } - } - - return renamed; - } - /*static*/ MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::PropertySet::AddPropertySet(AZStd::string_view name, AZStd::vector>& toPropertySetList) { auto iter = AZStd::find_if(toPropertySetList.begin(), toPropertySetList.end(), [name](const AZStd::unique_ptr& existingPropertySet) @@ -528,49 +502,6 @@ namespace AZ return groupDefinitions; } - // TODO: It looks like this function doesn't operate on MaterialTypeSourceData data, it belongs in MaterialUtils - bool MaterialTypeSourceData::ConvertPropertyValueToSourceDataFormat(const PropertyDefinition& propertyDefinition, MaterialPropertyValue& propertyValue) const - { - if (propertyDefinition.m_dataType == AZ::RPI::MaterialPropertyDataType::Enum && propertyValue.Is()) - { - const uint32_t index = propertyValue.GetValue(); - if (index >= propertyDefinition.m_enumValues.size()) - { - AZ_Error("Material source data", false, "Invalid value for material enum property: '%s'.", propertyDefinition.m_name.c_str()); - return false; - } - - propertyValue = propertyDefinition.m_enumValues[index]; - return true; - } - - // Image asset references must be converted from asset IDs to a relative source file path - if (propertyDefinition.m_dataType == AZ::RPI::MaterialPropertyDataType::Image && propertyValue.Is>()) - { - const Data::Asset& imageAsset = propertyValue.GetValue>(); - - Data::AssetInfo imageAssetInfo; - if (imageAsset.GetId().IsValid()) - { - bool result = false; - AZStd::string rootFilePath; - const AZStd::string platformName = ""; // Empty for default - AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetAssetInfoById, - imageAsset.GetId(), imageAsset.GetType(), platformName, imageAssetInfo, rootFilePath); - if (!result) - { - AZ_Error("Material source data", false, "Image asset could not be found for property: '%s'.", propertyDefinition.m_name.c_str()); - return false; - } - } - - propertyValue = imageAssetInfo.m_relativePath; - return true; - } - - return true; - } - bool MaterialTypeSourceData::BuildPropertyList( const AZStd::string& materialTypeSourceFilePath, MaterialTypeAssetCreator& materialTypeAssetCreator, @@ -651,15 +582,20 @@ namespace AZ { case MaterialPropertyDataType::Image: { - Outcome> imageAssetResult = MaterialUtils::GetImageAssetReference(materialTypeSourceFilePath, property->m_value.GetValue()); + Data::Asset imageAsset; - if (imageAssetResult.IsSuccess()) + MaterialUtils::GetImageAssetResult result = MaterialUtils::GetImageAssetReference( + imageAsset, materialTypeSourceFilePath, property->m_value.GetValue()); + + if (result == MaterialUtils::GetImageAssetResult::Missing) { - materialTypeAssetCreator.SetPropertyValue(propertyId, imageAssetResult.GetValue()); + materialTypeAssetCreator.ReportError( + "Material property '%s': Could not find the image '%s'", propertyId.GetCStr(), + property->m_value.GetValue().data()); } else { - materialTypeAssetCreator.ReportError("Material property '%s': Could not find the image '%s'", propertyId.GetCStr(), property->m_value.GetValue().data()); + materialTypeAssetCreator.SetPropertyValue(propertyId, imageAsset); } } break; @@ -743,145 +679,6 @@ namespace AZ } - Outcome> MaterialTypeSourceData::CreateMaterialTypeAsset(Data::AssetId assetId, AZStd::string_view materialTypeSourceFilePath, bool elevateWarnings) const - { - MaterialTypeAssetCreator materialTypeAssetCreator; - materialTypeAssetCreator.SetElevateWarnings(elevateWarnings); - materialTypeAssetCreator.Begin(assetId); - - // Used to gather all the UV streams used in this material type from its shaders in alphabetical order. - auto semanticComp = [](const RHI::ShaderSemantic& lhs, const RHI::ShaderSemantic& rhs) -> bool - { - return lhs.ToString() < rhs.ToString(); - }; - AZStd::set uvsInThisMaterialType(semanticComp); - - for (const ShaderVariantReferenceData& shaderRef : m_shaderCollection) - { - const auto& shaderFile = shaderRef.m_shaderFilePath; - auto shaderAssetResult = AssetUtils::LoadAsset(materialTypeSourceFilePath, shaderFile, 0); - - if (shaderAssetResult) - { - auto shaderAsset = shaderAssetResult.GetValue(); - auto optionsLayout = shaderAsset->GetShaderOptionGroupLayout(); - ShaderOptionGroup options{ optionsLayout }; - for (auto& iter : shaderRef.m_shaderOptionValues) - { - if (!options.SetValue(iter.first, iter.second)) - { - return Failure(); - } - } - - materialTypeAssetCreator.AddShader( - shaderAsset, options.GetShaderVariantId(), - shaderRef.m_shaderTag.IsEmpty() ? Uuid::CreateRandom().ToString() : shaderRef.m_shaderTag); - - // Gather UV names - const ShaderInputContract& shaderInputContract = shaderAsset->GetInputContract(); - for (const ShaderInputContract::StreamChannelInfo& channel : shaderInputContract.m_streamChannels) - { - const RHI::ShaderSemantic& semantic = channel.m_semantic; - - if (semantic.m_name.GetStringView().starts_with(RHI::ShaderSemantic::UvStreamSemantic)) - { - uvsInThisMaterialType.insert(semantic); - } - } - } - else - { - materialTypeAssetCreator.ReportError("Shader '%s' not found", shaderFile.data()); - return Failure(); - } - } - - for (const AZStd::unique_ptr& propertySet : m_propertyLayout.m_propertySets) - { - AZStd::vector propertyNameContext; - propertyNameContext.push_back(propertySet->m_name); - materialTypeAssetCreator.BeginMaterialProperty(propertyId.GetFullName(), property.m_dataType); - - if (!success) - { - return Failure(); - Data::Asset imageAsset; - - MaterialUtils::GetImageAssetResult result = MaterialUtils::GetImageAssetReference( - Outcome> imageAssetResult = MaterialUtils::GetImageAssetReference(materialTypeSourceFilePath, property.m_value.GetValue()); - if (imageAssetResult.IsSuccess()) - materialTypeAssetCreator.SetPropertyValue(propertyId.GetFullName(), imageAssetResult.GetValue()); - "Material property '%s': Could not find the image '%s'", propertyId.GetCStr(), - property.m_value.GetValue().data()); - materialTypeAssetCreator.ReportError("Material property '%s': Could not find the image '%s'", propertyId.GetFullName().GetCStr(), property.m_value.GetValue().data()); - MaterialPropertyIndex propertyIndex = materialTypeAssetCreator.GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId.GetFullName()); - materialTypeAssetCreator.SetPropertyValue(propertyId.GetFullName(), enumValue); - materialTypeAssetCreator.SetPropertyValue(propertyId.GetFullName(), property.m_value); - } - } - - // We cannot create the MaterialFunctor until after all the properties are added because - // CreateFunctor() may need to look up properties in the MaterialPropertiesLayout - for (auto& functorData : m_materialFunctorSourceData) - { - MaterialFunctorSourceData::FunctorResult result = functorData->CreateFunctor( - MaterialFunctorSourceData::RuntimeContext( - materialTypeSourceFilePath, - materialTypeAssetCreator.GetMaterialPropertiesLayout(), - materialTypeAssetCreator.GetMaterialShaderResourceGroupLayout(), - materialTypeAssetCreator.GetShaderCollection() - ) - ); - - if (result.IsSuccess()) - { - Ptr& functor = result.GetValue(); - if (functor != nullptr) - { - materialTypeAssetCreator.AddMaterialFunctor(functor); - - for (const AZ::Name& optionName : functorData->GetActualSourceData()->GetShaderOptionDependencies()) - { - materialTypeAssetCreator.ClaimShaderOptionOwnership(optionName); - } - } - } - else - { - materialTypeAssetCreator.ReportError("Failed to create MaterialFunctor"); - return Failure(); - } - } - - // Only add the UV mapping related to this material type. - for (const auto& uvInput : uvsInThisMaterialType) - { - // We may have cases where the uv map is empty or inconsistent (exported from other projects), - // So we use semantic if mapping is not found. - auto iter = m_uvNameMap.find(uvInput.ToString()); - if (iter != m_uvNameMap.end()) - { - materialTypeAssetCreator.AddUvName(uvInput, Name(iter->second)); - } - else - { - materialTypeAssetCreator.AddUvName(uvInput, Name(uvInput.ToString())); - } - } - - Data::Asset materialTypeAsset; - if (materialTypeAssetCreator.End(materialTypeAsset)) - { - return Success(AZStd::move(materialTypeAsset)); - } - else - { - return Failure(); - } - } - - Outcome> MaterialTypeSourceData::CreateMaterialTypeAsset(Data::AssetId assetId, AZStd::string_view materialTypeSourceFilePath, bool elevateWarnings) const { MaterialTypeAssetCreator materialTypeAssetCreator; @@ -970,108 +767,16 @@ namespace AZ return Failure(); } } - - for (auto& groupIter : m_propertyLayout.m_properties) + + for (const AZStd::unique_ptr& propertySet : m_propertyLayout.m_propertySets) { - const AZStd::string& groupName = groupIter.first; + AZStd::vector propertyNameContext; + propertyNameContext.push_back(propertySet->m_name); + bool success = BuildPropertyList(materialTypeSourceFilePath, materialTypeAssetCreator, propertyNameContext, propertySet.get()); - for (const PropertyDefinition& property : groupIter.second) + if (!success) { - // Register the property... - - MaterialPropertyId propertyId{ groupName, property.m_name }; - - if (!propertyId.IsValid()) - { - materialTypeAssetCreator.ReportWarning("Cannot create material property with invalid ID '%s'.", propertyId.GetCStr()); - continue; - } - - materialTypeAssetCreator.BeginMaterialProperty(propertyId, property.m_dataType); - - if (property.m_dataType == MaterialPropertyDataType::Enum) - { - materialTypeAssetCreator.SetMaterialPropertyEnumNames(property.m_enumValues); - } - - for (auto& output : property.m_outputConnections) - { - switch (output.m_type) - { - case MaterialPropertyOutputType::ShaderInput: - materialTypeAssetCreator.ConnectMaterialPropertyToShaderInput(Name{ output.m_fieldName.data() }); - break; - case MaterialPropertyOutputType::ShaderOption: - if (output.m_shaderIndex >= 0) - { - materialTypeAssetCreator.ConnectMaterialPropertyToShaderOption(Name{ output.m_fieldName.data() }, output.m_shaderIndex); - } - else - { - materialTypeAssetCreator.ConnectMaterialPropertyToShaderOptions(Name{ output.m_fieldName.data() }); - } - break; - case MaterialPropertyOutputType::Invalid: - // Don't add any output mappings, this is the case when material functors are expected to process the property - break; - default: - AZ_Assert(false, "Unsupported MaterialPropertyOutputType"); - return Failure(); - } - } - - materialTypeAssetCreator.EndMaterialProperty(); - - // Parse and set the property's value... - if (!property.m_value.IsValid()) - { - AZ_Warning("Material source data", false, "Source data for material property value is invalid."); - } - else - { - switch (property.m_dataType) - { - case MaterialPropertyDataType::Image: - { - Data::Asset imageAsset; - - MaterialUtils::GetImageAssetResult result = MaterialUtils::GetImageAssetReference( - imageAsset, materialTypeSourceFilePath, property.m_value.GetValue()); - - if (result == MaterialUtils::GetImageAssetResult::Missing) - { - materialTypeAssetCreator.ReportError( - "Material property '%s': Could not find the image '%s'", propertyId.GetCStr(), - property.m_value.GetValue().data()); - } - else - { - materialTypeAssetCreator.SetPropertyValue(propertyId, imageAsset); - } - } - break; - case MaterialPropertyDataType::Enum: - { - MaterialPropertyIndex propertyIndex = materialTypeAssetCreator.GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId); - const MaterialPropertyDescriptor* propertyDescriptor = materialTypeAssetCreator.GetMaterialPropertiesLayout()->GetPropertyDescriptor(propertyIndex); - - AZ::Name enumName = AZ::Name(property.m_value.GetValue()); - uint32_t enumValue = propertyDescriptor ? propertyDescriptor->GetEnumValue(enumName) : MaterialPropertyDescriptor::InvalidEnumValue; - if (enumValue == MaterialPropertyDescriptor::InvalidEnumValue) - { - materialTypeAssetCreator.ReportError("Enum value '%s' couldn't be found in the 'enumValues' list", enumName.GetCStr()); - } - else - { - materialTypeAssetCreator.SetPropertyValue(propertyId, enumValue); - } - } - break; - default: - materialTypeAssetCreator.SetPropertyValue(propertyId, property.m_value); - break; - } - } + return Failure(); } } diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp index 808e312b71..1a3b46ac86 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp @@ -93,20 +93,23 @@ namespace UnitTest { "version": 10, "propertyLayout": { - "properties": { - "general": [ - {"name": "MyBool", "type": "bool"}, - {"name": "MyInt", "type": "Int"}, - {"name": "MyUInt", "type": "UInt"}, - {"name": "MyFloat", "type": "Float"}, - {"name": "MyFloat2", "type": "Vector2"}, - {"name": "MyFloat3", "type": "Vector3"}, - {"name": "MyFloat4", "type": "Vector4"}, - {"name": "MyColor", "type": "Color"}, - {"name": "MyImage", "type": "Image"}, - {"name": "MyEnum", "type": "Enum", "enumValues": ["Enum0", "Enum1", "Enum2"], "defaultValue": "Enum0"} - ] - } + "propertySets": [ + { + "name": "general", + "properties": [ + {"name": "MyBool", "type": "bool"}, + {"name": "MyInt", "type": "Int"}, + {"name": "MyUInt", "type": "UInt"}, + {"name": "MyFloat", "type": "Float"}, + {"name": "MyFloat2", "type": "Vector2"}, + {"name": "MyFloat3", "type": "Vector3"}, + {"name": "MyFloat4", "type": "Vector4"}, + {"name": "MyColor", "type": "Color"}, + {"name": "MyImage", "type": "Image"}, + {"name": "MyEnum", "type": "Enum", "enumValues": ["Enum0", "Enum1", "Enum2"], "defaultValue": "Enum0"} + ] + } + ] }, "shaders": [ { @@ -580,18 +583,12 @@ namespace UnitTest errorMessageFinder.Reset(); errorMessageFinder.AddExpectedErrorMessage("Could not find asset [DoesNotExist.materialtype]"); - "properties": { - [ - { - "general": [ - "properties": [ - ] result = material.CreateMaterialAsset(AZ::Uuid::CreateRandom(), "test.material", AZ::RPI::MaterialAssetProcessingMode::PreBake, elevateWarnings); EXPECT_FALSE(result.IsSuccess()); errorMessageFinder.CheckExpectedErrorsFound(); errorMessageFinder.Reset(); - EXPECT_TRUE(loadResult.ContainsMessage("[simpleMaterialType.materialtype]/propertyLayout/properties", "Successfully read")); + errorMessageFinder.AddExpectedErrorMessage("Could not find asset [DoesNotExist.materialtype]"); errorMessageFinder.AddIgnoredErrorMessage("Failed to create material type asset ID", true); result = material.CreateMaterialAssetFromSourceData(AZ::Uuid::CreateRandom(), "test.material", elevateWarnings); EXPECT_FALSE(result.IsSuccess()); @@ -600,12 +597,6 @@ namespace UnitTest TEST_F(MaterialSourceDataTests, CreateMaterialAsset_MaterialPropertyNotFound) { - "properties": { - [ - { - "general": [ - "properties": [ - ] MaterialSourceData material; material.m_materialType = "@exefolder@/Temp/test.materialtype"; AddPropertyGroup(material, "general"); diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp index 8e65b41e6c..f07e1da6c2 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp @@ -1911,157 +1911,13 @@ namespace UnitTest } - TEST_F(MaterialTypeSourceDataTests, FindPropertyUsingOldName) - { - const AZStd::string inputJson = R"( - { - "version": 10, - "versionUpdates": [ - { - "toVersion": 2, - "actions": [ - { "op": "rename", "from": "general.fooA", "to": "general.fooB" } - ] - }, - { - "toVersion": 4, - "actions": [ - { "op": "rename", "from": "general.barA", "to": "general.barB" } - ] - }, - { - "toVersion": 6, - "actions": [ - { "op": "rename", "from": "general.fooB", "to": "general.fooC" }, - { "op": "rename", "from": "general.barB", "to": "general.barC" } - ] - }, - { - "toVersion": 7, - "actions": [ - { "op": "rename", "from": "general.bazA", "to": "otherGroup.bazB" }, - { "op": "rename", "from": "onlyOneProperty.bopA", "to": "otherGroup.bopB" } // This tests a group 'onlyOneProperty' that no longer exists in the material type - ] - } - ], - "propertyLayout": { - "properties": { - "general": [ - { - "name": "fooC", - "type": "Bool" - }, - { - "name": "barC", - "type": "Float" - } - ], - "otherGroup": [ - { - "name": "dontMindMe", - "type": "Bool" - }, - { - "name": "bazB", - "type": "Float" - }, - { - "name": "bopB", - "type": "Float" - } - ] - } - } - } - )"; - - MaterialTypeSourceData materialType; - JsonTestResult loadResult = LoadTestDataFromJson(materialType, inputJson); - - EXPECT_EQ(materialType.m_version, 10); - - // First find the properties using their correct current names - const MaterialTypeSourceData::PropertyDefinition* foo = materialType.FindProperty("general", "fooC"); - const MaterialTypeSourceData::PropertyDefinition* bar = materialType.FindProperty("general", "barC"); - const MaterialTypeSourceData::PropertyDefinition* baz = materialType.FindProperty("otherGroup", "bazB"); - const MaterialTypeSourceData::PropertyDefinition* bop = materialType.FindProperty("otherGroup", "bopB"); - - EXPECT_TRUE(foo); - EXPECT_TRUE(bar); - EXPECT_TRUE(baz); - EXPECT_TRUE(bop); - EXPECT_EQ(foo->m_name, "fooC"); - EXPECT_EQ(bar->m_name, "barC"); - EXPECT_EQ(baz->m_name, "bazB"); - EXPECT_EQ(bop->m_name, "bopB"); - - // Now try doing the property lookup using old versions of the name and make sure the same property can be found - - EXPECT_EQ(foo, materialType.FindProperty("general", "fooA")); - EXPECT_EQ(foo, materialType.FindProperty("general", "fooB")); - EXPECT_EQ(bar, materialType.FindProperty("general", "barA")); - EXPECT_EQ(bar, materialType.FindProperty("general", "barB")); - EXPECT_EQ(baz, materialType.FindProperty("general", "bazA")); - EXPECT_EQ(bop, materialType.FindProperty("onlyOneProperty", "bopA")); - - EXPECT_EQ(nullptr, materialType.FindProperty("general", "fooX")); - EXPECT_EQ(nullptr, materialType.FindProperty("general", "barX")); - EXPECT_EQ(nullptr, materialType.FindProperty("general", "bazX")); - EXPECT_EQ(nullptr, materialType.FindProperty("general", "bazB")); - EXPECT_EQ(nullptr, materialType.FindProperty("otherGroup", "bazA")); - EXPECT_EQ(nullptr, materialType.FindProperty("onlyOneProperty", "bopB")); - EXPECT_EQ(nullptr, materialType.FindProperty("otherGroup", "bopA")); - } - - TEST_F(MaterialTypeSourceDataTests, FindPropertyUsingOldName_Error_UnsupportedVersionUpdate) - { - const AZStd::string inputJson = R"( - { - "version": 10, - "versionUpdates": [ - { - "toVersion": 2, - "actions": [ - { "op": "notRename", "from": "general.fooA", "to": "general.fooB" } - ] - } - ], - "propertyLayout": { - "properties": { - "general": [ - { - "name": "fooB", - "type": "Bool" - } - ] - } - } - } - )"; - - MaterialTypeSourceData materialType; - JsonTestResult loadResult = LoadTestDataFromJson(materialType, inputJson); - - ErrorMessageFinder errorMessageFinder; - errorMessageFinder.AddExpectedErrorMessage("Unsupported material version update operation 'notRename'"); - - - const MaterialTypeSourceData::PropertyDefinition* foo = materialType.FindProperty("general", "fooA"); - - EXPECT_EQ(nullptr, foo); - - errorMessageFinder.CheckExpectedErrorsFound(); - } - TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_Error_UnsupportedVersionUpdate) { MaterialTypeSourceData sourceData; - - MaterialTypeSourceData::PropertyDefinition propertySource; - propertySource.m_name = "a"; - propertySource.m_dataType = MaterialPropertyDataType::Int; - propertySource.m_value = 0; - sourceData.m_propertyLayout.m_properties["general"].push_back(propertySource); + + MaterialTypeSourceData::PropertyDefinition* propertySource = sourceData.AddPropertySet("general")->AddProperty("a"); + propertySource->m_dataType = MaterialPropertyDataType::Int; + propertySource->m_value = 0; sourceData.m_version = 2; From 9b8bebbd70d96282ca06fbf0b8e64e0afee2a4e0 Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Wed, 26 Jan 2022 12:21:43 -0800 Subject: [PATCH 08/53] Bumped the MaterialBuilder version number. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp index cadb182d03..d55f202c03 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp @@ -52,7 +52,7 @@ namespace AZ { AssetBuilderSDK::AssetBuilderDesc materialBuilderDescriptor; materialBuilderDescriptor.m_name = JobKey; - materialBuilderDescriptor.m_version = 116; // more material dependency improvements + materialBuilderDescriptor.m_version = 117; // new material type file format materialBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.material", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); materialBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.materialtype", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); materialBuilderDescriptor.m_busId = azrtti_typeid(); From aeb43c4012fd23ebf9cbe1a4758250c86d15a8f9 Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Wed, 26 Jan 2022 13:04:28 -0800 Subject: [PATCH 09/53] Fixed up a few small things to get Material Editor working again. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Materials/Special/ShadowCatcher.materialtype | 1 - .../RPI.Edit/Material/MaterialTypeSourceData.h | 11 ++++------- .../Code/Source/Document/MaterialDocument.cpp | 14 +++++++------- .../Material/EditorMaterialComponentUtil.cpp | 6 +++--- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.materialtype index 937abe656d..973f8b2147 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.materialtype @@ -2,7 +2,6 @@ "description": "Base material for the reflection probe visualization model.", "version": 1, "propertyLayout": { - "version": 1, "propertySets": [ { "name": "settings", diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h index 4fae3f710e..b5e7eecd0d 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h @@ -76,6 +76,7 @@ namespace AZ static const float DefaultMax; static const float DefaultStep; + // TODO: Consider making this private and readonly because it is used as the key for lookups and collision validation. AZStd::string m_name; //!< The name of the property within the property group. The full property ID will be groupName.propertyName. MaterialPropertyVisibility m_visibility = MaterialPropertyVisibility::Default; @@ -213,9 +214,9 @@ namespace AZ AZStd::string m_description; //< TODO: Make this private //! Version 1 is the default and should not contain any version update. - uint32_t m_version = 1; + uint32_t m_version = 1; //< TODO: Make this private - VersionUpdates m_versionUpdates; + VersionUpdates m_versionUpdates; //< TODO: Make this private //! A list of shader variants that are always used at runtime; they cannot be turned off AZStd::vector m_shaderCollection; //< TODO: Make this private @@ -283,11 +284,7 @@ namespace AZ MaterialTypeAssetCreator& materialTypeAssetCreator, AZStd::vector& propertyNameContext, const MaterialTypeSourceData::PropertySet* propertySet) const; - - //! Possibly renames @propertyId based on the material version update steps. - //! @return true if the property was renamed - bool ApplyPropertyRenames(MaterialPropertyId& propertyId) const; - + //! Construct a complete list of group definitions, including implicit groups, arranged in the same order as the source data. //! Groups with the same name will be consolidated into a single entry. //! Operates on the old format PropertyLayout::m_groups, used for conversion to the new format. diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp index 8d6eaf0633..c9ae970215 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp @@ -585,11 +585,11 @@ namespace MaterialEditor bool result = true; // populate sourceData with properties that meet the filter - m_materialTypeSourceData.EnumerateProperties([this, &sourceData, &propertyFilter, &result](const AZStd::string& propertyIdContext, const auto& propertyDefinition) { + m_materialTypeSourceData.EnumerateProperties([&](const AZStd::string& propertyIdContext, const auto& propertyDefinition) { - const AZStd::string propertyId = propertyIdContext + propertyDefinition->m_name; + Name propertyId{propertyIdContext + propertyDefinition->m_name}; - const auto it = m_properties.find(Name{propertyId}); + const auto it = m_properties.find(propertyId); if (it != m_properties.end() && propertyFilter(it->second)) { MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(it->second.GetValue()); @@ -603,7 +603,7 @@ namespace MaterialEditor } // TODO: Support populating the Material Editor with nested property sets, not just the top level. - const AZStd::string groupName = propertyId.substr(0, propertyId.size() - propertyDefinition->m_name.size() - 1); + const AZStd::string groupName = propertyId.GetStringView().substr(0, propertyId.GetStringView().size() - propertyDefinition->m_name.size() - 1); sourceData.m_properties[groupName][propertyDefinition->m_name].m_value = propertyValue; } } @@ -897,12 +897,12 @@ namespace MaterialEditor return false; } } - + bool enumerateResult = m_materialTypeSourceData.EnumeratePropertySets( - [this, &materialTypeSourceFilePath](const AZStd::string&, const MaterialTypeSourceData::PropertySet* propertySet) + [this](const AZStd::string&, const MaterialTypeSourceData::PropertySet* propertySet) { const MaterialFunctorSourceData::EditorContext editorContext = MaterialFunctorSourceData::EditorContext( - materialTypeSourceFilePath, m_materialAsset->GetMaterialPropertiesLayout()); + m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout()); for (Ptr functorData : propertySet->GetFunctors()) { diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp index 96cf02c6f9..d6342a2507 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp @@ -122,7 +122,7 @@ namespace AZ AZ::RPI::MaterialPropertyValue propertyValue = editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]; - AZ::RPI::MaterialPropertyValue propertyValueDefault = propertyDefinition.m_value; + AZ::RPI::MaterialPropertyValue propertyValueDefault = propertyDefinition->m_value; if (editData.m_materialParentAsset.IsReady()) { propertyValueDefault = editData.m_materialParentAsset->GetPropertyValues()[propertyIndex.GetIndex()]; @@ -135,7 +135,7 @@ namespace AZ propertyValue = AZ::RPI::MaterialPropertyValue::FromAny(propertyOverrideItr->second); } - if (!AtomToolsFramework::ConvertToExportFormat(path, propertyId, propertyDefinition, propertyValue)) + if (!AtomToolsFramework::ConvertToExportFormat(path, propertyId, *propertyDefinition, propertyValue)) { AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Failed to export: %s", path.c_str()); result = false; @@ -151,7 +151,7 @@ namespace AZ // TODO: Support populating the Material Editor with nested property sets, not just the top level. const AZStd::string groupName = propertyId.GetStringView().substr(0, propertyId.GetStringView().size() - propertyDefinition->m_name.size() - 1); - exportData.m_properties[groupName][propertyDefinition.m_name].m_value = propertyValue; + exportData.m_properties[groupName][propertyDefinition->m_name].m_value = propertyValue; return true; }); From 048b068c8991501a476f06578a9a37bcff107f1c Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Thu, 30 Sep 2021 01:08:54 -0700 Subject: [PATCH 10/53] Updated StringFunc::Tokenize to support returning a list of string_view instead of string, which should be more efficient. string is still supported as well, but users should prefer the string_view version. Testing: Updated unit tests. Reprocessed Atom material assets. Ran AtomSampleViewer material screenshot test. Opened, edited, saved materail in the Material Editor. Opened a level, edited material property overrides, saved and reloaded. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../AzCore/AzCore/StringFunc/StringFunc.cpp | 24 ++++++++++---- .../AzCore/AzCore/StringFunc/StringFunc.h | 12 ++++--- Code/Framework/AzCore/Tests/StringFunc.cpp | 31 +++++++++++++++---- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp b/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp index 8405424f7d..61a9096d5e 100644 --- a/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp +++ b/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp @@ -757,13 +757,18 @@ namespace AZ::StringFunc } return value; } - - void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings) + + template + void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings) { return Tokenize(in, tokens, { &delimiter, 1 }, keepEmptyStrings, keepSpaceStrings); } - void Tokenize(AZStd::string_view in, AZStd::vector& tokens, AZStd::string_view delimiters, bool keepEmptyStrings, bool keepSpaceStrings) + template void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings); + template void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings); + + template + void Tokenize(AZStd::string_view in, AZStd::vector& tokens, AZStd::string_view delimiters, bool keepEmptyStrings, bool keepSpaceStrings) { auto insertVisitor = [&tokens](AZStd::string_view token) { @@ -771,6 +776,9 @@ namespace AZ::StringFunc }; return TokenizeVisitor(in, insertVisitor, delimiters, keepEmptyStrings, keepSpaceStrings); } + + template void Tokenize(AZStd::string_view in, AZStd::vector& tokens, AZStd::string_view delimiters, bool keepEmptyStrings, bool keepSpaceStrings); + template void Tokenize(AZStd::string_view in, AZStd::vector& tokens, AZStd::string_view delimiters, bool keepEmptyStrings, bool keepSpaceStrings); void TokenizeVisitor(AZStd::string_view in, const TokenVisitor& tokenVisitor, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings) { @@ -918,8 +926,9 @@ namespace AZ::StringFunc return found; } - - void Tokenize(AZStd::string_view input, AZStd::vector& tokens, const AZStd::vector& delimiters, bool keepEmptyStrings /*= false*/, bool keepSpaceStrings /*= false*/) + + template + void Tokenize(AZStd::string_view input, AZStd::vector& tokens, const AZStd::vector& delimiters, bool keepEmptyStrings /*= false*/, bool keepSpaceStrings /*= false*/) { if (input.empty()) { @@ -939,7 +948,7 @@ namespace AZ::StringFunc } // Take the substring, not including the separator, and increment our offset - AZStd::string nextSubstring = input.substr(offset, nextOffset - offset); + AZStd::string_view nextSubstring = input.substr(offset, nextOffset - offset); if (keepEmptyStrings || keepSpaceStrings || !nextSubstring.empty()) { tokens.push_back(nextSubstring); @@ -948,6 +957,9 @@ namespace AZ::StringFunc offset = nextOffset + delimiters[nextMatch].size(); } } + + template void Tokenize(AZStd::string_view input, AZStd::vector& tokens, const AZStd::vector& delimiters, bool keepEmptyStrings /*= false*/, bool keepSpaceStrings /*= false*/); + template void Tokenize(AZStd::string_view input, AZStd::vector& tokens, const AZStd::vector& delimiters, bool keepEmptyStrings /*= false*/, bool keepSpaceStrings /*= false*/); int ToInt(const char* in) { diff --git a/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.h b/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.h index 55236a0fff..db26b364aa 100644 --- a/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.h +++ b/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.h @@ -258,17 +258,21 @@ namespace AZ bool Strip(AZStd::string& inout, const char* stripCharacters = " ", bool bCaseSensitive = false, bool bStripBeginning = false, bool bStripEnding = false); //! Tokenize - /*! Tokenize a c-string, into a vector of AZStd::string(s) optionally keeping empty string + /*! Tokenize a c-string, into a vector of strings optionally keeping empty string *! and optionally keeping space only strings + *! (The string type may be AZStd::string or AZStd::string_view. New code should use AZStd::string_view for better performance. AZStd::string version is preserved for compatibility.) Example: Tokenize the words of a sentence. StringFunc::Tokenize("Hello World", d, ' '); s[0] == "Hello", s[1] == "World" Example: Tokenize a comma and end line delimited string StringFunc::Tokenize("Hello,World\nHello,World", d, ' '); s[0] == "Hello", s[1] == "World" s[2] == "Hello", s[3] == "World" */ - void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const char delimiter, bool keepEmptyStrings = false, bool keepSpaceStrings = false); - void Tokenize(AZStd::string_view in, AZStd::vector& tokens, AZStd::string_view delimiters = "\\//, \t\n", bool keepEmptyStrings = false, bool keepSpaceStrings = false); - void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const AZStd::vector& delimiters, bool keepEmptyStrings = false, bool keepSpaceStrings = false); + template + void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const char delimiter, bool keepEmptyStrings = false, bool keepSpaceStrings = false); + template + void Tokenize(AZStd::string_view in, AZStd::vector& tokens, AZStd::string_view delimiters = "\\//, \t\n", bool keepEmptyStrings = false, bool keepSpaceStrings = false); + template + void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const AZStd::vector& delimiters, bool keepEmptyStrings = false, bool keepSpaceStrings = false); //! TokenizeVisitor /*! Tokenize a string_view and invoke a handler for each token found. diff --git a/Code/Framework/AzCore/Tests/StringFunc.cpp b/Code/Framework/AzCore/Tests/StringFunc.cpp index dc4bc40e5b..fd6fe7839d 100644 --- a/Code/Framework/AzCore/Tests/StringFunc.cpp +++ b/Code/Framework/AzCore/Tests/StringFunc.cpp @@ -199,7 +199,7 @@ namespace AZ TEST_F(StringFuncTest, Tokenize_SingleDelimeter_Empty) { AZStd::string input = ""; - AZStd::vector tokens; + AZStd::vector tokens; AZ::StringFunc::Tokenize(input.c_str(), tokens, ' '); ASSERT_EQ(tokens.size(), 0); } @@ -207,7 +207,7 @@ namespace AZ TEST_F(StringFuncTest, Tokenize_SingleDelimeter) { AZStd::string input = "a b,c"; - AZStd::vector tokens; + AZStd::vector tokens; AZ::StringFunc::Tokenize(input.c_str(), tokens, ' '); ASSERT_EQ(tokens.size(), 2); @@ -218,7 +218,7 @@ namespace AZ TEST_F(StringFuncTest, Tokenize_MultiDelimeter_Empty) { AZStd::string input = ""; - AZStd::vector tokens; + AZStd::vector tokens; AZ::StringFunc::Tokenize(input.c_str(), tokens, " ,"); ASSERT_EQ(tokens.size(), 0); } @@ -226,7 +226,7 @@ namespace AZ TEST_F(StringFuncTest, Tokenize_MultiDelimeters) { AZStd::string input = " -a +b +c -d-e"; - AZStd::vector tokens; + AZStd::vector tokens; AZ::StringFunc::Tokenize(input.c_str(), tokens, "-+"); ASSERT_EQ(tokens.size(), 5); @@ -240,7 +240,7 @@ namespace AZ TEST_F(StringFuncTest, Tokenize_SubstringDelimeters_Empty) { AZStd::string input = ""; - AZStd::vector tokens; + AZStd::vector tokens; AZStd::vector delimeters = {" -", " +"}; AZ::StringFunc::Tokenize(input.c_str(), tokens, delimeters); ASSERT_EQ(tokens.size(), 0); @@ -249,7 +249,7 @@ namespace AZ TEST_F(StringFuncTest, Tokenize_SubstringDelimeters) { AZStd::string input = " -a +b +c -d-e"; - AZStd::vector tokens; + AZStd::vector tokens; AZStd::vector delimeters = { " -", " +" }; AZ::StringFunc::Tokenize(input.c_str(), tokens, delimeters); @@ -259,6 +259,25 @@ namespace AZ ASSERT_TRUE(tokens[2] == "c"); ASSERT_TRUE(tokens[3] == "d-e"); // Test for something like a guid, which contain typical separator characters } + + TEST_F(StringFuncTest, Tokenize_MultiDelimeters_String) + { + // Test with AZStd::string for backward compatibility. The functions + // use to only work with AZStd::string, and now they are templatized + // to support both AZStd::string and AZStd::string_view (the latter + // being perferred for performance). + + AZStd::string input = " -a +b +c -d-e"; + AZStd::vector tokens; + AZ::StringFunc::Tokenize(input.c_str(), tokens, "-+"); + + ASSERT_EQ(tokens.size(), 5); + ASSERT_TRUE(tokens[0] == "a "); + ASSERT_TRUE(tokens[1] == "b "); + ASSERT_TRUE(tokens[2] == "c "); + ASSERT_TRUE(tokens[3] == "d"); + ASSERT_TRUE(tokens[4] == "e"); + } TEST_F(StringFuncTest, TokenizeVisitor_EmptyString_DoesNotInvokeVisitor) { From 68e4970a2d5e71b4ce038aeeefb481f5257051da Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Wed, 26 Jan 2022 16:47:10 -0800 Subject: [PATCH 11/53] Reverted the new Tokenize function I added, and used TokenizeVisitor instead. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../AzCore/AzCore/StringFunc/StringFunc.cpp | 24 ++++---------- .../AzCore/AzCore/StringFunc/StringFunc.h | 12 +++---- Code/Framework/AzCore/Tests/StringFunc.cpp | 33 ++++--------------- .../Material/MaterialTypeSourceData.cpp | 8 ++++- 4 files changed, 24 insertions(+), 53 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp b/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp index 61a9096d5e..e4d132afef 100644 --- a/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp +++ b/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp @@ -758,17 +758,12 @@ namespace AZ::StringFunc return value; } - template - void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings) + void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings) { return Tokenize(in, tokens, { &delimiter, 1 }, keepEmptyStrings, keepSpaceStrings); } - - template void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings); - template void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings); - - template - void Tokenize(AZStd::string_view in, AZStd::vector& tokens, AZStd::string_view delimiters, bool keepEmptyStrings, bool keepSpaceStrings) + + void Tokenize(AZStd::string_view in, AZStd::vector& tokens, AZStd::string_view delimiters, bool keepEmptyStrings, bool keepSpaceStrings) { auto insertVisitor = [&tokens](AZStd::string_view token) { @@ -776,10 +771,7 @@ namespace AZ::StringFunc }; return TokenizeVisitor(in, insertVisitor, delimiters, keepEmptyStrings, keepSpaceStrings); } - - template void Tokenize(AZStd::string_view in, AZStd::vector& tokens, AZStd::string_view delimiters, bool keepEmptyStrings, bool keepSpaceStrings); - template void Tokenize(AZStd::string_view in, AZStd::vector& tokens, AZStd::string_view delimiters, bool keepEmptyStrings, bool keepSpaceStrings); - + void TokenizeVisitor(AZStd::string_view in, const TokenVisitor& tokenVisitor, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings) { return TokenizeVisitor(in, tokenVisitor, { &delimiter, 1 }, keepEmptyStrings, keepSpaceStrings); @@ -927,8 +919,7 @@ namespace AZ::StringFunc return found; } - template - void Tokenize(AZStd::string_view input, AZStd::vector& tokens, const AZStd::vector& delimiters, bool keepEmptyStrings /*= false*/, bool keepSpaceStrings /*= false*/) + void Tokenize(AZStd::string_view input, AZStd::vector& tokens, const AZStd::vector& delimiters, bool keepEmptyStrings /*= false*/, bool keepSpaceStrings /*= false*/) { if (input.empty()) { @@ -948,7 +939,7 @@ namespace AZ::StringFunc } // Take the substring, not including the separator, and increment our offset - AZStd::string_view nextSubstring = input.substr(offset, nextOffset - offset); + AZStd::string nextSubstring = input.substr(offset, nextOffset - offset); if (keepEmptyStrings || keepSpaceStrings || !nextSubstring.empty()) { tokens.push_back(nextSubstring); @@ -958,9 +949,6 @@ namespace AZ::StringFunc } } - template void Tokenize(AZStd::string_view input, AZStd::vector& tokens, const AZStd::vector& delimiters, bool keepEmptyStrings /*= false*/, bool keepSpaceStrings /*= false*/); - template void Tokenize(AZStd::string_view input, AZStd::vector& tokens, const AZStd::vector& delimiters, bool keepEmptyStrings /*= false*/, bool keepSpaceStrings /*= false*/); - int ToInt(const char* in) { if (!in) diff --git a/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.h b/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.h index db26b364aa..55236a0fff 100644 --- a/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.h +++ b/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.h @@ -258,21 +258,17 @@ namespace AZ bool Strip(AZStd::string& inout, const char* stripCharacters = " ", bool bCaseSensitive = false, bool bStripBeginning = false, bool bStripEnding = false); //! Tokenize - /*! Tokenize a c-string, into a vector of strings optionally keeping empty string + /*! Tokenize a c-string, into a vector of AZStd::string(s) optionally keeping empty string *! and optionally keeping space only strings - *! (The string type may be AZStd::string or AZStd::string_view. New code should use AZStd::string_view for better performance. AZStd::string version is preserved for compatibility.) Example: Tokenize the words of a sentence. StringFunc::Tokenize("Hello World", d, ' '); s[0] == "Hello", s[1] == "World" Example: Tokenize a comma and end line delimited string StringFunc::Tokenize("Hello,World\nHello,World", d, ' '); s[0] == "Hello", s[1] == "World" s[2] == "Hello", s[3] == "World" */ - template - void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const char delimiter, bool keepEmptyStrings = false, bool keepSpaceStrings = false); - template - void Tokenize(AZStd::string_view in, AZStd::vector& tokens, AZStd::string_view delimiters = "\\//, \t\n", bool keepEmptyStrings = false, bool keepSpaceStrings = false); - template - void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const AZStd::vector& delimiters, bool keepEmptyStrings = false, bool keepSpaceStrings = false); + void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const char delimiter, bool keepEmptyStrings = false, bool keepSpaceStrings = false); + void Tokenize(AZStd::string_view in, AZStd::vector& tokens, AZStd::string_view delimiters = "\\//, \t\n", bool keepEmptyStrings = false, bool keepSpaceStrings = false); + void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const AZStd::vector& delimiters, bool keepEmptyStrings = false, bool keepSpaceStrings = false); //! TokenizeVisitor /*! Tokenize a string_view and invoke a handler for each token found. diff --git a/Code/Framework/AzCore/Tests/StringFunc.cpp b/Code/Framework/AzCore/Tests/StringFunc.cpp index fd6fe7839d..760cadb2c6 100644 --- a/Code/Framework/AzCore/Tests/StringFunc.cpp +++ b/Code/Framework/AzCore/Tests/StringFunc.cpp @@ -199,15 +199,15 @@ namespace AZ TEST_F(StringFuncTest, Tokenize_SingleDelimeter_Empty) { AZStd::string input = ""; - AZStd::vector tokens; + AZStd::vector tokens; AZ::StringFunc::Tokenize(input.c_str(), tokens, ' '); ASSERT_EQ(tokens.size(), 0); - } + } TEST_F(StringFuncTest, Tokenize_SingleDelimeter) { AZStd::string input = "a b,c"; - AZStd::vector tokens; + AZStd::vector tokens; AZ::StringFunc::Tokenize(input.c_str(), tokens, ' '); ASSERT_EQ(tokens.size(), 2); @@ -218,7 +218,7 @@ namespace AZ TEST_F(StringFuncTest, Tokenize_MultiDelimeter_Empty) { AZStd::string input = ""; - AZStd::vector tokens; + AZStd::vector tokens; AZ::StringFunc::Tokenize(input.c_str(), tokens, " ,"); ASSERT_EQ(tokens.size(), 0); } @@ -226,7 +226,7 @@ namespace AZ TEST_F(StringFuncTest, Tokenize_MultiDelimeters) { AZStd::string input = " -a +b +c -d-e"; - AZStd::vector tokens; + AZStd::vector tokens; AZ::StringFunc::Tokenize(input.c_str(), tokens, "-+"); ASSERT_EQ(tokens.size(), 5); @@ -240,7 +240,7 @@ namespace AZ TEST_F(StringFuncTest, Tokenize_SubstringDelimeters_Empty) { AZStd::string input = ""; - AZStd::vector tokens; + AZStd::vector tokens; AZStd::vector delimeters = {" -", " +"}; AZ::StringFunc::Tokenize(input.c_str(), tokens, delimeters); ASSERT_EQ(tokens.size(), 0); @@ -249,7 +249,7 @@ namespace AZ TEST_F(StringFuncTest, Tokenize_SubstringDelimeters) { AZStd::string input = " -a +b +c -d-e"; - AZStd::vector tokens; + AZStd::vector tokens; AZStd::vector delimeters = { " -", " +" }; AZ::StringFunc::Tokenize(input.c_str(), tokens, delimeters); @@ -260,25 +260,6 @@ namespace AZ ASSERT_TRUE(tokens[3] == "d-e"); // Test for something like a guid, which contain typical separator characters } - TEST_F(StringFuncTest, Tokenize_MultiDelimeters_String) - { - // Test with AZStd::string for backward compatibility. The functions - // use to only work with AZStd::string, and now they are templatized - // to support both AZStd::string and AZStd::string_view (the latter - // being perferred for performance). - - AZStd::string input = " -a +b +c -d-e"; - AZStd::vector tokens; - AZ::StringFunc::Tokenize(input.c_str(), tokens, "-+"); - - ASSERT_EQ(tokens.size(), 5); - ASSERT_TRUE(tokens[0] == "a "); - ASSERT_TRUE(tokens[1] == "b "); - ASSERT_TRUE(tokens[2] == "c "); - ASSERT_TRUE(tokens[3] == "d"); - ASSERT_TRUE(tokens[4] == "e"); - } - TEST_F(StringFuncTest, TokenizeVisitor_EmptyString_DoesNotInvokeVisitor) { int visitedCount{}; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp index 2504981273..bf24b98ac8 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp @@ -327,7 +327,13 @@ namespace AZ AZStd::vector MaterialTypeSourceData::TokenizeId(AZStd::string_view id) { AZStd::vector tokens; - AzFramework::StringFunc::Tokenize(id, tokens, "./", true, true); + + AzFramework::StringFunc::TokenizeVisitor(id, [&tokens](AZStd::string_view t) + { + tokens.push_back(t); + }, + "./", true, true); + return tokens; } From ed21e97dcd07f8543e707e0c028e3f1d26542bdd Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Wed, 26 Jan 2022 16:55:45 -0800 Subject: [PATCH 12/53] Fixed StringFunc whitespace differences. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp | 10 +++++----- Code/Framework/AzCore/Tests/StringFunc.cpp | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp b/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp index e4d132afef..8405424f7d 100644 --- a/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp +++ b/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp @@ -757,12 +757,12 @@ namespace AZ::StringFunc } return value; } - + void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings) { return Tokenize(in, tokens, { &delimiter, 1 }, keepEmptyStrings, keepSpaceStrings); } - + void Tokenize(AZStd::string_view in, AZStd::vector& tokens, AZStd::string_view delimiters, bool keepEmptyStrings, bool keepSpaceStrings) { auto insertVisitor = [&tokens](AZStd::string_view token) @@ -771,7 +771,7 @@ namespace AZ::StringFunc }; return TokenizeVisitor(in, insertVisitor, delimiters, keepEmptyStrings, keepSpaceStrings); } - + void TokenizeVisitor(AZStd::string_view in, const TokenVisitor& tokenVisitor, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings) { return TokenizeVisitor(in, tokenVisitor, { &delimiter, 1 }, keepEmptyStrings, keepSpaceStrings); @@ -918,7 +918,7 @@ namespace AZ::StringFunc return found; } - + void Tokenize(AZStd::string_view input, AZStd::vector& tokens, const AZStd::vector& delimiters, bool keepEmptyStrings /*= false*/, bool keepSpaceStrings /*= false*/) { if (input.empty()) @@ -948,7 +948,7 @@ namespace AZ::StringFunc offset = nextOffset + delimiters[nextMatch].size(); } } - + int ToInt(const char* in) { if (!in) diff --git a/Code/Framework/AzCore/Tests/StringFunc.cpp b/Code/Framework/AzCore/Tests/StringFunc.cpp index 760cadb2c6..dc4bc40e5b 100644 --- a/Code/Framework/AzCore/Tests/StringFunc.cpp +++ b/Code/Framework/AzCore/Tests/StringFunc.cpp @@ -202,7 +202,7 @@ namespace AZ AZStd::vector tokens; AZ::StringFunc::Tokenize(input.c_str(), tokens, ' '); ASSERT_EQ(tokens.size(), 0); - } + } TEST_F(StringFuncTest, Tokenize_SingleDelimeter) { @@ -259,7 +259,7 @@ namespace AZ ASSERT_TRUE(tokens[2] == "c"); ASSERT_TRUE(tokens[3] == "d-e"); // Test for something like a guid, which contain typical separator characters } - + TEST_F(StringFuncTest, TokenizeVisitor_EmptyString_DoesNotInvokeVisitor) { int visitedCount{}; From e0cee13747befb882d405b6ce61d68d153331348 Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Wed, 26 Jan 2022 18:10:02 -0800 Subject: [PATCH 13/53] Code cleanup. Made PropertyDefinition::m_name private. Moved code around for a cleaner diff in MaterialTypeSourceData.h. Added API comments. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Material/MaterialTypeSourceData.h | 156 +++++++++++------- .../Material/MaterialTypeSourceData.cpp | 15 +- .../MaterialPropertySerializerTests.cpp | 16 +- .../Material/MaterialTypeSourceDataTests.cpp | 20 +-- 4 files changed, 118 insertions(+), 89 deletions(-) diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h index b5e7eecd0d..0daa0a4c24 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h @@ -23,8 +23,10 @@ namespace AZ { class MaterialTypeAsset; class MaterialFunctorSourceDataHolder; + class JsonMaterialPropertySerializer; //! This is a simple data structure for serializing in/out material type source files. + //! Note that there may be a mixture of public and private members, as we are gradually introducing a proper API. class MaterialTypeSourceData final { public: @@ -69,15 +71,22 @@ namespace AZ struct PropertyDefinition { + friend class JsonMaterialPropertySerializer; + AZ_CLASS_ALLOCATOR(PropertyDefinition, SystemAllocator, 0); AZ_TYPE_INFO(AZ::RPI::MaterialTypeSourceData::PropertyDefinition, "{E0DB3C0D-75DB-4ADB-9E79-30DA63FA18B7}"); static const float DefaultMin; static const float DefaultMax; static const float DefaultStep; + + PropertyDefinition() = default; - // TODO: Consider making this private and readonly because it is used as the key for lookups and collision validation. - AZStd::string m_name; //!< The name of the property within the property group. The full property ID will be groupName.propertyName. + explicit PropertyDefinition(AZStd::string_view name) : m_name(name) + { + } + + const AZStd::string& GetName() const { return m_name; } MaterialPropertyVisibility m_visibility = MaterialPropertyVisibility::Default; @@ -99,6 +108,51 @@ namespace AZ MaterialPropertyValue m_softMin; MaterialPropertyValue m_softMax; MaterialPropertyValue m_step; + + private: + + // We are gradually moving toward having a more proper API for MaterialTypeSourceData code, but we still some public members + // like above. However, it's important for m_name to be private because it is used as the key for lookups, collision validation, etc. + AZStd::string m_name; //!< The name of the property within the property group. The full property ID will be groupName.propertyName. + }; + + using PropertyList = AZStd::vector>; + + struct PropertySet + { + friend class MaterialTypeSourceData; + + AZ_CLASS_ALLOCATOR(PropertySet, SystemAllocator, 0); + AZ_TYPE_INFO(AZ::RPI::MaterialTypeSourceData::PropertySet, "{BA3AA0E4-C74D-4FD0-ADB2-00B060F06314}"); + + public: + + PropertySet() = default; + AZ_DISABLE_COPY(PropertySet) + + const AZStd::string& GetName() const { return m_name; } + const AZStd::string& GetDisplayName() const { return m_displayName; } + const AZStd::string& GetDescription() const { return m_description; } + const PropertyList& GetProperties() const { return m_properties; } + const AZStd::vector>& GetPropertySets() const { return m_propertySets; } + const AZStd::vector>& GetFunctors() const { return m_materialFunctorSourceData; } + + void SetDisplayName(AZStd::string_view displayName) { m_displayName = displayName; } + void SetDescription(AZStd::string_view description) { m_description = description; } + + PropertyDefinition* AddProperty(AZStd::string_view name); + PropertySet* AddPropertySet(AZStd::string_view name); + + private: + + static PropertySet* AddPropertySet(AZStd::string_view name, AZStd::vector>& toPropertySetList); + + AZStd::string m_name; + AZStd::string m_displayName; + AZStd::string m_description; + PropertyList m_properties; + AZStd::vector> m_propertySets; + AZStd::vector> m_materialFunctorSourceData; }; struct ShaderVariantReferenceData @@ -144,47 +198,6 @@ namespace AZ using VersionUpdates = AZStd::vector; - using PropertyList = AZStd::vector>; - - struct PropertySet - { - friend class MaterialTypeSourceData; - - AZ_CLASS_ALLOCATOR(PropertySet, SystemAllocator, 0); - AZ_TYPE_INFO(AZ::RPI::MaterialTypeSourceData::PropertySet, "{BA3AA0E4-C74D-4FD0-ADB2-00B060F06314}"); - - public: - - PropertySet() = default; - AZ_DISABLE_COPY(PropertySet) - - const AZStd::string& GetName() const { return m_name; } - const AZStd::string& GetDisplayName() const { return m_displayName; } - const AZStd::string& GetDescription() const { return m_description; } - const PropertyList& GetProperties() const { return m_properties; } - const AZStd::vector>& GetPropertySets() const { return m_propertySets; } - const AZStd::vector>& GetFunctors() const { return m_materialFunctorSourceData; } - - void SetDisplayName(AZStd::string_view displayName) { m_displayName = displayName; } - void SetDescription(AZStd::string_view description) { m_description = description; } - - PropertyDefinition* AddProperty(AZStd::string_view name); - PropertySet* AddPropertySet(AZStd::string_view name); - - private: - - static PropertySet* AddPropertySet(AZStd::string_view name, AZStd::vector>& toPropertySetList); - - AZStd::string m_name; - AZStd::string m_displayName; - AZStd::string m_description; - PropertyList m_properties; - AZStd::vector> m_propertySets; - AZStd::vector> m_materialFunctorSourceData; - }; - - using VersionUpdates = AZStd::vector; - struct PropertyLayout { AZ_TYPE_INFO(AZ::RPI::MaterialTypeSourceData::PropertyLayout, "{AE53CF3F-5C3B-44F5-B2FB-306F0EB06393}"); @@ -206,52 +219,69 @@ namespace AZ AZStd::vector> m_propertySets; }; - PropertySet* AddPropertySet(AZStd::string_view propertySetId); - PropertyDefinition* AddProperty(AZStd::string_view propertyId); - - const PropertyLayout& GetPropertyLayout() const { return m_propertyLayout; } - - AZStd::string m_description; //< TODO: Make this private + AZStd::string m_description; //! Version 1 is the default and should not contain any version update. - uint32_t m_version = 1; //< TODO: Make this private - - VersionUpdates m_versionUpdates; //< TODO: Make this private + uint32_t m_version = 1; + + VersionUpdates m_versionUpdates; //! A list of shader variants that are always used at runtime; they cannot be turned off - AZStd::vector m_shaderCollection; //< TODO: Make this private + AZStd::vector m_shaderCollection; //! Material functors provide custom logic and calculations to configure shaders, render states, and more. See MaterialFunctor.h for details. - AZStd::vector> m_materialFunctorSourceData; //< TODO: Make this private + AZStd::vector> m_materialFunctorSourceData; //! Override names for UV input in the shaders of this material type. //! Using ordered map to sort names on loading. using UvNameMap = AZStd::map; - UvNameMap m_uvNameMap; //< TODO: Make this private + UvNameMap m_uvNameMap; //! Copy over UV custom names to the properties enum values. void ResolveUvEnums(); - - const PropertySet* FindPropertySet(AZStd::string_view propertySetId) const; + //! Add a new PropertySet for containing properties or other PropertySets. + //! @param propertySetId The ID of the new property set. To add as a nested PropertySet, use a full path ID like "levelA.levelB.levelC"; in this case a property set "levelA.levelB" must already exist. + //! @return a pointer to the new PropertySet or null if there was a problem (an AZ_Error will be reported). + PropertySet* AddPropertySet(AZStd::string_view propertySetId); + + //! Add a new property to a PropertySet. + //! @param propertyId The ID of the new property, like "layerBlend.factor" or "layer2.roughness.texture". The indicated property set must already exist. + //! @return a pointer to the new PropertyDefinition or null if there was a problem (an AZ_Error will be reported). + PropertyDefinition* AddProperty(AZStd::string_view propertyId); + + //! Return the PropertyLayout containing the tree of property sets and property definitions. + const PropertyLayout& GetPropertyLayout() const { return m_propertyLayout; } + + //! Find the PropertySet with the given ID. + //! @param propertySetId The full ID of a property set to find, like "levelA.levelB.levelC". + //! @return the found PropertySet or null if it doesn't exist. + const PropertySet* FindPropertySet(AZStd::string_view propertySetId) const; + + //! Find the definition for a property with the given ID. + //! @param propertyId The full ID of a property to find, like "baseColor.texture". + //! @return the found PropertyDefinition or null if it doesn't exist. const PropertyDefinition* FindProperty(AZStd::string_view propertyId) const; - //! Tokenizes an ID string like "itemA.itemB.itemC" into a vector like ["itemA", "itemB", "itemC"] + //! Tokenizes an ID string like "itemA.itemB.itemC" into a vector like ["itemA", "itemB", "itemC"]. static AZStd::vector TokenizeId(AZStd::string_view id); - //! Splits an ID string like "itemA.itemB.itemC" into a vector like ["itemA.itemB", "itemC"] + //! Splits an ID string like "itemA.itemB.itemC" into a vector like ["itemA.itemB", "itemC"]. static AZStd::vector SplitId(AZStd::string_view id); - //! Call back function type used with the enumeration functions + //! Call back function type used with the enumeration functions. + //! Return false to terminate the traversal. using EnumeratePropertySetsCallback = AZStd::function; + //! Recursively traverses all of the property sets contained in the material type, executing a callback function for each. //! @return false if the enumeration was terminated early by the callback returning false. bool EnumeratePropertySets(const EnumeratePropertySetsCallback& callback) const; - //! Call back function type used with the numeration functions + //! Call back function type used with the numeration functions. + //! Return false to terminate the traversal. using EnumeratePropertiesCallback = AZStd::function> CreateMaterialTypeAsset(Data::AssetId assetId, AZStd::string_view materialTypeSourceFilePath = "", bool elevateWarnings = true) const; + //! If the data was loaded from an old format file (i.e. where "groups" and "properties" were separate sections), + //! this converts to the new format where properties are listed inside property sets. bool ConvertToNewDataFormat(); private: diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp index bf24b98ac8..046dee9507 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp @@ -160,7 +160,7 @@ namespace AZ { auto propertyIter = AZStd::find_if(m_properties.begin(), m_properties.end(), [name](const AZStd::unique_ptr& existingProperty) { - return existingProperty->m_name == name; + return existingProperty->GetName() == name; }); if (propertyIter != m_properties.end()) @@ -186,8 +186,7 @@ namespace AZ return nullptr; } - m_properties.emplace_back(AZStd::make_unique()); - m_properties.back()->m_name = name; + m_properties.emplace_back(AZStd::make_unique(name)); return m_properties.back().get(); } @@ -195,7 +194,7 @@ namespace AZ { auto iter = AZStd::find_if(m_properties.begin(), m_properties.end(), [name](const AZStd::unique_ptr& existingProperty) { - return existingProperty->m_name == name; + return existingProperty->GetName() == name; }); if (iter != m_properties.end()) @@ -298,7 +297,7 @@ namespace AZ { for (AZStd::unique_ptr& property : propertySet->m_properties) { - if (property->m_name == subPath[0]) + if (property->GetName() == subPath[0]) { return property.get(); } @@ -440,7 +439,7 @@ namespace AZ propertySet = m_propertyLayout.m_propertySets.back().get(); } - PropertyDefinition* newProperty = propertySet->AddProperty(propertyDefinition.m_name); + PropertyDefinition* newProperty = propertySet->AddProperty(propertyDefinition.GetName()); *newProperty = propertyDefinition; } @@ -518,7 +517,7 @@ namespace AZ { // Register the property... - MaterialPropertyId propertyId{propertyNameContext, property->m_name}; + MaterialPropertyId propertyId{propertyNameContext, property->GetName()}; if (!propertyId.IsValid()) { @@ -529,7 +528,7 @@ namespace AZ auto propertySetIter = AZStd::find_if(propertySet->GetPropertySets().begin(), propertySet->GetPropertySets().end(), [&property](const AZStd::unique_ptr& existingPropertySet) { - return existingPropertySet->GetName() == property->m_name; + return existingPropertySet->GetName() == property->GetName(); }); if (propertySetIter != propertySet->GetPropertySets().end()) diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertySerializerTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertySerializerTests.cpp index 9afc05a237..904860d4fc 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertySerializerTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertySerializerTests.cpp @@ -45,8 +45,7 @@ namespace JsonSerializationTests AZStd::shared_ptr CreatePartialDefaultInstance() override { - auto result = AZStd::make_shared(); - result->m_name = "testProperty"; + auto result = AZStd::make_shared("testProperty"); result->m_dataType = AZ::RPI::MaterialPropertyDataType::Float; result->m_step = 1.0f; result->m_value = 0.0f; @@ -65,8 +64,7 @@ namespace JsonSerializationTests AZStd::shared_ptr CreateFullySetInstance() override { - auto result = AZStd::make_shared(); - result->m_name = "testProperty"; + auto result = AZStd::make_shared("testProperty"); result->m_description = "description"; result->m_displayName = "display_name"; result->m_dataType = AZ::RPI::MaterialPropertyDataType::Float; @@ -135,7 +133,7 @@ namespace JsonSerializationTests const AZ::RPI::MaterialTypeSourceData::PropertyDefinition& lhs, const AZ::RPI::MaterialTypeSourceData::PropertyDefinition& rhs) override { - if (lhs.m_name != rhs.m_name) { return false; } + if (lhs.GetName() != rhs.GetName()) { return false; } if (lhs.m_description != rhs.m_description) { return false; } if (lhs.m_displayName != rhs.m_displayName) { return false; } if (lhs.m_dataType != rhs.m_dataType) { return false; } @@ -216,7 +214,7 @@ namespace UnitTest EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing()); EXPECT_EQ(AZ::JsonSerializationResult::Outcomes::PartialDefaults, loadResult.m_jsonResultCode.GetOutcome()); - EXPECT_EQ("testProperty", propertyData.m_name); + EXPECT_EQ("testProperty", propertyData.GetName()); EXPECT_EQ("Test Property", propertyData.m_displayName); EXPECT_EQ("This is a property description", propertyData.m_description); EXPECT_EQ(MaterialPropertyDataType::Float, propertyData.m_dataType); @@ -851,7 +849,7 @@ namespace UnitTest EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask()); EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing()); - EXPECT_EQ("testProperty", propertyData.m_name); + EXPECT_EQ("testProperty", propertyData.GetName()); EXPECT_EQ(1, propertyData.m_outputConnections.size()); EXPECT_EQ(MaterialPropertyOutputType::ShaderOption, propertyData.m_outputConnections[0].m_type); @@ -934,7 +932,7 @@ namespace UnitTest EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask()); EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing()); - EXPECT_EQ(propertyData.m_name, "testProperty"); + EXPECT_EQ(propertyData.GetName(), "testProperty"); EXPECT_EQ(propertyData.m_dataType, MaterialPropertyDataType::Float); EXPECT_EQ(propertyData.m_outputConnections.size(), 0); @@ -964,7 +962,7 @@ namespace UnitTest EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask()); EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing()); - EXPECT_EQ(propertyData.m_name, "testProperty"); + EXPECT_EQ(propertyData.GetName(), "testProperty"); EXPECT_EQ(propertyData.m_dataType, MaterialPropertyDataType::Float); EXPECT_EQ(propertyData.m_outputConnections.size(), 1); EXPECT_EQ(propertyData.m_outputConnections[0].m_fieldName, "o_foo"); diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp index f07e1da6c2..2ecf2369f9 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp @@ -1645,12 +1645,12 @@ namespace UnitTest EXPECT_NE(material.FindProperty("groupC.groupD.foo"), nullptr); EXPECT_NE(material.FindProperty("groupC.groupE.bar"), nullptr); - EXPECT_EQ(material.FindProperty("groupA.foo")->m_name, "foo"); - EXPECT_EQ(material.FindProperty("groupA.bar")->m_name, "bar"); - EXPECT_EQ(material.FindProperty("groupB.foo")->m_name, "foo"); - EXPECT_EQ(material.FindProperty("groupB.bar")->m_name, "bar"); - EXPECT_EQ(material.FindProperty("groupC.groupD.foo")->m_name, "foo"); - EXPECT_EQ(material.FindProperty("groupC.groupE.bar")->m_name, "bar"); + EXPECT_EQ(material.FindProperty("groupA.foo")->GetName(), "foo"); + EXPECT_EQ(material.FindProperty("groupA.bar")->GetName(), "bar"); + EXPECT_EQ(material.FindProperty("groupB.foo")->GetName(), "foo"); + EXPECT_EQ(material.FindProperty("groupB.bar")->GetName(), "bar"); + EXPECT_EQ(material.FindProperty("groupC.groupD.foo")->GetName(), "foo"); + EXPECT_EQ(material.FindProperty("groupC.groupE.bar")->GetName(), "bar"); EXPECT_EQ(material.FindProperty("groupA.foo")->m_dataType, MaterialPropertyDataType::Bool); EXPECT_EQ(material.FindProperty("groupA.bar")->m_dataType, MaterialPropertyDataType::Image); EXPECT_EQ(material.FindProperty("groupB.foo")->m_dataType, MaterialPropertyDataType::Float); @@ -1823,10 +1823,10 @@ namespace UnitTest EXPECT_TRUE(material.FindProperty("groupB.foo") != nullptr); EXPECT_TRUE(material.FindProperty("groupB.bar") != nullptr); - EXPECT_EQ(material.FindProperty("groupA.foo")->m_name, "foo"); - EXPECT_EQ(material.FindProperty("groupA.bar")->m_name, "bar"); - EXPECT_EQ(material.FindProperty("groupB.foo")->m_name, "foo"); - EXPECT_EQ(material.FindProperty("groupB.bar")->m_name, "bar"); + EXPECT_EQ(material.FindProperty("groupA.foo")->GetName(), "foo"); + EXPECT_EQ(material.FindProperty("groupA.bar")->GetName(), "bar"); + EXPECT_EQ(material.FindProperty("groupB.foo")->GetName(), "foo"); + EXPECT_EQ(material.FindProperty("groupB.bar")->GetName(), "bar"); EXPECT_EQ(material.FindProperty("groupA.foo")->m_dataType, MaterialPropertyDataType::Bool); EXPECT_EQ(material.FindProperty("groupA.bar")->m_dataType, MaterialPropertyDataType::Image); EXPECT_EQ(material.FindProperty("groupB.foo")->m_dataType, MaterialPropertyDataType::Float); From a4346de65845b26fc827f3e5aea036c27f872f03 Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Wed, 26 Jan 2022 23:54:25 -0800 Subject: [PATCH 14/53] Code cleanup. New comments. Added some non-const find functions to MaterialTypeSourceData. Fixed places where I forgot to change m_name to GetName(). Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Material/MaterialTypeSourceData.h | 16 ++++++++-- .../Material/MaterialTypeSourceData.cpp | 30 ++++++++++++++++--- .../Code/Tests/Common/ErrorMessageFinder.cpp | 2 +- .../Code/Source/Util/MaterialPropertyUtil.cpp | 2 +- .../Code/Source/Document/MaterialDocument.cpp | 12 ++++---- .../MaterialInspector/MaterialInspector.cpp | 2 +- .../EditorMaterialComponentInspector.cpp | 3 +- .../Material/EditorMaterialComponentUtil.cpp | 7 ++--- 8 files changed, 54 insertions(+), 20 deletions(-) diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h index 0daa0a4c24..6c439195f1 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h @@ -140,7 +140,14 @@ namespace AZ void SetDisplayName(AZStd::string_view displayName) { m_displayName = displayName; } void SetDescription(AZStd::string_view description) { m_description = description; } + //! Add a new property to this PropertySet. + //! @param name a unique for the property. Must be a C-style identifier. + //! @return the new PropertyDefinition, or null if the name was not valid. PropertyDefinition* AddProperty(AZStd::string_view name); + + //! Add a new nested PropertySet to this PropertySet. + //! @param name a unique for the property set. Must be a C-style identifier. + //! @return the new PropertySet, or null if the name was not valid. PropertySet* AddPropertySet(AZStd::string_view name); private: @@ -213,9 +220,9 @@ namespace AZ AZStd::vector m_groupsOld; //! [Deprecated] Use m_propertySets instead - //! Collection of all available user-facing properties AZStd::map> m_propertiesOld; - + + //! Collection of all available user-facing properties AZStd::vector> m_propertySets; }; @@ -257,11 +264,13 @@ namespace AZ //! @param propertySetId The full ID of a property set to find, like "levelA.levelB.levelC". //! @return the found PropertySet or null if it doesn't exist. const PropertySet* FindPropertySet(AZStd::string_view propertySetId) const; + PropertySet* FindPropertySet(AZStd::string_view propertySetId); //! Find the definition for a property with the given ID. //! @param propertyId The full ID of a property to find, like "baseColor.texture". //! @return the found PropertyDefinition or null if it doesn't exist. const PropertyDefinition* FindProperty(AZStd::string_view propertyId) const; + PropertyDefinition* FindProperty(AZStd::string_view propertyId); //! Tokenizes an ID string like "itemA.itemB.itemC" into a vector like ["itemA", "itemB", "itemC"]. static AZStd::vector TokenizeId(AZStd::string_view id); @@ -300,7 +309,10 @@ namespace AZ private: const PropertySet* FindPropertySet(AZStd::array_view parsedPropertySetId, AZStd::array_view> inPropertySetList) const; + PropertySet* FindPropertySet(AZStd::array_view parsedPropertySetId, AZStd::array_view> inPropertySetList); + const PropertyDefinition* FindProperty(AZStd::array_view parsedPropertyId, AZStd::array_view> inPropertySetList) const; + PropertyDefinition* FindProperty(AZStd::array_view parsedPropertyId, AZStd::array_view> inPropertySetList); // Function overloads for recursion, returns false to indicate that recursion should end. bool EnumeratePropertySets(const EnumeratePropertySetsCallback& callback, AZStd::string propertyIdContext, const AZStd::vector>& inPropertySetList) const; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp index 046dee9507..032d36636e 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp @@ -215,7 +215,7 @@ namespace AZ return PropertySet::AddPropertySet(propertySetId, m_propertyLayout.m_propertySets); } - PropertySet* parentPropertySet = const_cast(const_cast(this)->FindPropertySet(splitPropertySetId[0])); + PropertySet* parentPropertySet = FindPropertySet(splitPropertySetId[0]); if (!parentPropertySet) { @@ -235,8 +235,8 @@ namespace AZ AZ_Error("Material source data", false, "Property id '%.*s' is invalid. Properties must be added to a PropertySet (i.e. \"general.%.*s\").", AZ_STRING_ARG(propertyId), AZ_STRING_ARG(propertyId)); return nullptr; } - - PropertySet* parentPropertySet = const_cast(const_cast(this)->FindPropertySet(splitPropertyId[0])); + + PropertySet* parentPropertySet = FindPropertySet(splitPropertyId[0]); if (!parentPropertySet) { @@ -276,12 +276,23 @@ namespace AZ return nullptr; } + + MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::FindPropertySet(AZStd::array_view parsedPropertySetId, AZStd::array_view> inPropertySetList) + { + return const_cast(const_cast(this)->FindPropertySet(parsedPropertySetId, inPropertySetList)); + } const MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::FindPropertySet(AZStd::string_view propertySetId) const { AZStd::vector tokens = TokenizeId(propertySetId); return FindPropertySet(tokens, m_propertyLayout.m_propertySets); } + + MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::FindPropertySet(AZStd::string_view propertySetId) + { + AZStd::vector tokens = TokenizeId(propertySetId); + return FindPropertySet(tokens, m_propertyLayout.m_propertySets); + } const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty( AZStd::array_view parsedPropertyId, @@ -316,12 +327,23 @@ namespace AZ return nullptr; } + + MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::array_view parsedPropertyId, AZStd::array_view> inPropertySetList) + { + return const_cast(const_cast(this)->FindProperty(parsedPropertyId, inPropertySetList)); + } const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::string_view propertyId) const { AZStd::vector tokens = TokenizeId(propertyId); return FindProperty(tokens, m_propertyLayout.m_propertySets); } + + MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::string_view propertyId) + { + AZStd::vector tokens = TokenizeId(propertyId); + return FindProperty(tokens, m_propertyLayout.m_propertySets); + } AZStd::vector MaterialTypeSourceData::TokenizeId(AZStd::string_view id) { @@ -428,7 +450,7 @@ namespace AZ const auto& propertyList = propertyListItr->second; for (auto& propertyDefinition : propertyList) { - PropertySet* propertySet = const_cast(const_cast(this)->FindPropertySet(group.m_name)); + PropertySet* propertySet = FindPropertySet(group.m_name); if (!propertySet) { diff --git a/Gems/Atom/RPI/Code/Tests/Common/ErrorMessageFinder.cpp b/Gems/Atom/RPI/Code/Tests/Common/ErrorMessageFinder.cpp index 06cdb073e9..fa88f145a6 100644 --- a/Gems/Atom/RPI/Code/Tests/Common/ErrorMessageFinder.cpp +++ b/Gems/Atom/RPI/Code/Tests/Common/ErrorMessageFinder.cpp @@ -84,7 +84,7 @@ namespace UnitTest } } - m_checked = true; + m_checked = true; } void ErrorMessageFinder::ReportFailure(const AZStd::string& failureMessage) diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/MaterialPropertyUtil.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/MaterialPropertyUtil.cpp index 568e701e3c..377fd46d69 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/MaterialPropertyUtil.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/MaterialPropertyUtil.cpp @@ -78,7 +78,7 @@ namespace AtomToolsFramework void ConvertToPropertyConfig(AtomToolsFramework::DynamicPropertyConfig& propertyConfig, const AZ::RPI::MaterialTypeSourceData::PropertyDefinition& propertyDefinition) { propertyConfig.m_dataType = ConvertToEditableType(propertyDefinition.m_dataType); - propertyConfig.m_name = propertyDefinition.m_name; + propertyConfig.m_name = propertyDefinition.GetName(); propertyConfig.m_displayName = propertyDefinition.m_displayName; propertyConfig.m_description = propertyDefinition.m_description; propertyConfig.m_defaultValue = ConvertToEditableType(propertyDefinition.m_value); diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp index c9ae970215..aa14b02bbe 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp @@ -587,7 +587,7 @@ namespace MaterialEditor // populate sourceData with properties that meet the filter m_materialTypeSourceData.EnumerateProperties([&](const AZStd::string& propertyIdContext, const auto& propertyDefinition) { - Name propertyId{propertyIdContext + propertyDefinition->m_name}; + Name propertyId{propertyIdContext + propertyDefinition->GetName()}; const auto it = m_properties.find(propertyId); if (it != m_properties.end() && propertyFilter(it->second)) @@ -603,8 +603,8 @@ namespace MaterialEditor } // TODO: Support populating the Material Editor with nested property sets, not just the top level. - const AZStd::string groupName = propertyId.GetStringView().substr(0, propertyId.GetStringView().size() - propertyDefinition->m_name.size() - 1); - sourceData.m_properties[groupName][propertyDefinition->m_name].m_value = propertyValue; + const AZStd::string groupName = propertyId.GetStringView().substr(0, propertyId.GetStringView().size() - propertyDefinition->GetName().size() - 1); + sourceData.m_properties[groupName][propertyDefinition->GetName()].m_value = propertyValue; } } return true; @@ -788,7 +788,7 @@ namespace MaterialEditor for (const auto& propertyDefinition : propertySet->GetProperties()) { // Assign id before conversion so it can be used in dynamic description - propertyConfig.m_id = propertyIdContext + propertySet->GetName() + "." + propertyDefinition->m_name; + propertyConfig.m_id = propertyIdContext + propertySet->GetName() + "." + propertyDefinition->GetName(); const auto& propertyIndex = m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id); const bool propertyIndexInBounds = propertyIndex.IsValid() && propertyIndex.GetIndex() < m_materialAsset->GetPropertyValues().size(); @@ -877,6 +877,7 @@ namespace MaterialEditor m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig); } + // Add material functors that are in the top-level functors list. const MaterialFunctorSourceData::EditorContext editorContext = MaterialFunctorSourceData::EditorContext(m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout()); for (Ptr functorData : m_materialTypeSourceData.m_materialFunctorSourceData) @@ -897,7 +898,8 @@ namespace MaterialEditor return false; } } - + + // Add any material functors that are located inside each property set. bool enumerateResult = m_materialTypeSourceData.EnumeratePropertySets( [this](const AZStd::string&, const MaterialTypeSourceData::PropertySet* propertySet) { diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp index 98e4ede2a9..3208683bf0 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp @@ -185,7 +185,7 @@ namespace MaterialEditor AtomToolsFramework::DynamicProperty property; AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult( property, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetProperty, - AZ::RPI::MaterialPropertyId(groupName, propertyDefinition->m_name)); + AZ::RPI::MaterialPropertyId(groupName, propertyDefinition->GetName())); group.m_properties.push_back(property); } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp index 9e990e2fc8..37fba346b8 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp @@ -307,7 +307,7 @@ namespace AZ AtomToolsFramework::DynamicPropertyConfig propertyConfig; // Assign id before conversion so it can be used in dynamic description - propertyConfig.m_id = AZ::RPI::MaterialPropertyId(groupName, propertyDefinition->m_name); + propertyConfig.m_id = AZ::RPI::MaterialPropertyId(groupName, propertyDefinition->GetName()); AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, *propertyDefinition.get()); @@ -323,7 +323,6 @@ namespace AZ // assigned material asset. Its values should be treated as parent, for comparison, in this case. propertyConfig.m_parentValue = AtomToolsFramework::ConvertToEditableType( m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]); - propertyConfig.m_originalValue = AtomToolsFramework::ConvertToEditableType( m_editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]); group.m_properties.emplace_back(propertyConfig); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp index d6342a2507..62982e4b4d 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp @@ -115,7 +115,7 @@ namespace AZ bool result = true; editData.m_materialTypeSourceData.EnumerateProperties([&](const AZStd::string& propertyIdContext, const AZ::RPI::MaterialTypeSourceData::PropertyDefinition* propertyDefinition) { - AZ::Name propertyId(propertyIdContext + propertyDefinition->m_name); + AZ::Name propertyId(propertyIdContext + propertyDefinition->GetName()); const AZ::RPI::MaterialPropertyIndex propertyIndex = editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId); @@ -148,10 +148,9 @@ namespace AZ return true; } - // TODO: Support populating the Material Editor with nested property sets, not just the top level. - const AZStd::string groupName = propertyId.GetStringView().substr(0, propertyId.GetStringView().size() - propertyDefinition->m_name.size() - 1); - exportData.m_properties[groupName][propertyDefinition->m_name].m_value = propertyValue; + const AZStd::string groupName = propertyId.GetStringView().substr(0, propertyId.GetStringView().size() - propertyDefinition->GetName().size() - 1); + exportData.m_properties[groupName][propertyDefinition->GetName()].m_value = propertyValue; return true; }); From 5e2bf1a46822a613d4db7ae8736c77f96345b99d Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Thu, 27 Jan 2022 14:14:50 -0800 Subject: [PATCH 15/53] Reverted changes to most of the material type files. I'll update those in a separate PR. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Special/ShadowCatcher.materialtype | 58 +- .../Materials/Types/EnhancedPBR.materialtype | 2763 +++++++++-------- .../Assets/Materials/Types/Skin.materialtype | 1846 +++++------ .../Materials/Types/StandardPBR.materialtype | 1907 ++++++------ .../Materials/Types/AutoBrick.materialtype | 327 +- 5 files changed, 3456 insertions(+), 3445 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.materialtype index 973f8b2147..d05f03c9a9 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.materialtype @@ -2,40 +2,38 @@ "description": "Base material for the reflection probe visualization model.", "version": 1, "propertyLayout": { - "propertySets": [ - { - "name": "settings", - "properties": [ - { - "name": "opacity", - "displayName": "Opacity", - "description": "Opacity of the shadow effect.", - "type": "Float", - "defaultValue": 0.5, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_opacity" - } - }, - { - "name": "shadeAll", - "displayName": "Shade All", - "description": "Shades the entire geometry with the shadow color, not just what's in shadow. For debugging.", - "type": "Bool", - "connection": { - "type": "ShaderOption", - "name": "o_shadeAll" - } + "properties": { + "settings": [ + { + "name": "opacity", + "displayName": "Opacity", + "description": "Opacity of the shadow effect.", + "type": "Float", + "defaultValue": 0.5, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_opacity" } - ] - } - ] + }, + { + "name": "shadeAll", + "displayName": "Shade All", + "description": "Shades the entire geometry with the shadow color, not just what's in shadow. For debugging.", + "type": "Bool", + "connection": { + "type": "ShaderOption", + "name": "o_shadeAll" + } + } + ] + } }, "shaders": [ { "file": "ShadowCatcher.shader" } ] -} \ No newline at end of file +} + diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype index a6af04fb89..eee9a0fc88 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype @@ -10,1453 +10,1456 @@ } ], "propertyLayout": { - "propertySets": [ + "groups": [ { "name": "baseColor", "displayName": "Base Color", - "description": "Properties for configuring the surface reflected color for dielectrics or reflectance values for metals.", - "properties": [ - { - "name": "color", - "displayName": "Color", - "description": "Color is displayed as sRGB but the values are stored as linear color.", - "type": "Color", - "defaultValue": [ 1.0, 1.0, 1.0 ], - "connection": { - "type": "ShaderInput", - "name": "m_baseColor" - } - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the base color values. Zero (0.0) is black, white (1.0) is full color.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_baseColorFactor" - } - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "Base color texture map", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_baseColorMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Base color map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_baseColorMapUvIndex" - } - }, - { - "name": "textureBlendMode", - "displayName": "Texture Blend Mode", - "description": "Selects the equation to use when combining Color, Factor, and Texture.", - "type": "Enum", - "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], - "defaultValue": "Multiply", - "connection": { - "type": "ShaderOption", - "name": "o_baseColorTextureBlendMode" - } - } - ] + "description": "Properties for configuring the surface reflected color for dielectrics or reflectance values for metals." }, { "name": "metallic", "displayName": "Metallic", - "description": "Properties for configuring whether the surface is metallic or not.", - "properties": [ - { - "name": "factor", - "displayName": "Factor", - "description": "This value is linear, black is non-metal and white means raw metal.", - "type": "Float", - "defaultValue": 0.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_metallicFactor" - } - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_metallicMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture, or just default to the Factor value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Metallic map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_metallicMapUvIndex" - } - } - ] + "description": "Properties for configuring whether the surface is metallic or not." }, { "name": "roughness", "displayName": "Roughness", - "description": "Properties for configuring how rough the surface appears.", - "properties": [ - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface roughness.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_roughnessMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture, or just default to the Factor value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Roughness map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_roughnessMapUvIndex" - } - }, - { - // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. - "name": "lowerBound", - "displayName": "Lower Bound", - "description": "The roughness value that corresponds to black in the texture.", - "type": "Float", - "defaultValue": 0.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_roughnessLowerBound" - } - }, - { - // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. - "name": "upperBound", - "displayName": "Upper Bound", - "description": "The roughness value that corresponds to white in the texture.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_roughnessUpperBound" - } - }, - { - // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. - "name": "factor", - "displayName": "Factor", - "description": "Controls the roughness value", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_roughnessFactor" - } - } - ] + "description": "Properties for configuring how rough the surface appears." }, { "name": "specularF0", "displayName": "Specular Reflectance f0", - "description": "The constant f0 represents the specular reflectance at normal incidence (Fresnel 0 Angle). Used to adjust reflectance of non-metal surfaces.", - "properties": [ - { - "name": "factor", - "displayName": "Factor", - "description": "The default IOR is 1.5, which gives you 0.04 (4% of light reflected at 0 degree angle for dielectric materials). F0 values lie in the range 0-0.08, so that is why the default F0 slider is set on 0.5.", - "type": "Float", - "defaultValue": 0.5, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_specularF0Factor" - } - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface reflectance.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_specularF0Map" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture, or just default to the Factor value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Specular reflection map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_specularF0MapUvIndex" - } - }, - // Consider moving this to the "general" group to be consistent with StandardMultilayerPBR - { - "name": "enableMultiScatterCompensation", - "displayName": "Multiscattering Compensation", - "description": "Whether to enable multiple scattering compensation.", - "type": "Bool", - "connection": { - "type": "ShaderOption", - "name": "o_specularF0_enableMultiScatterCompensation" - } - } - ] + "description": "The constant f0 represents the specular reflectance at normal incidence (Fresnel 0 Angle). Used to adjust reflectance of non-metal surfaces." }, { "name": "normal", "displayName": "Normal", - "description": "Properties related to configuring surface normal.", - "properties": [ - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface normal direction.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_normalMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture, or just rely on vertex normals.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Normal map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_normalMapUvIndex" - } - }, - { - "name": "flipX", - "displayName": "Flip X Channel", - "description": "Flip tangent direction for this normal map.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderInput", - "name": "m_flipNormalX" - } - }, - { - "name": "flipY", - "displayName": "Flip Y Channel", - "description": "Flip bitangent direction for this normal map.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderInput", - "name": "m_flipNormalY" - } - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the values", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_normalFactor" - } - } - ] + "description": "Properties related to configuring surface normal." }, { "name": "detailLayerGroup", "displayName": "Detail Layer", - "description": "Properties for Fine Details Layer.", - "properties": [ - { - "name": "enableDetailLayer", - "displayName": "Enable Detail Layer", - "description": "Enable detail layer for fine details and scratches", - "type": "Bool", - "defaultValue": false - }, - { - "name": "blendDetailFactor", - "displayName": "Blend Factor", - "description": "Scales the overall impact of the detail layer.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_detail_blendFactor" - } - }, - { - "name": "blendDetailMask", - "displayName": "Blend Mask", - "description": "Detailed blend mask for application of the detail maps.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_detail_blendMask_texture" - } - }, - { - "name": "enableDetailMaskTexture", - "displayName": " Use Texture", - "description": "Enable detail blend mask", - "type": "Bool", - "defaultValue": true - }, - { - "name": "blendDetailMaskUv", - "displayName": " Blend Mask UV", - "description": "Which UV set to use for sampling the detail blend mask", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_detail_blendMask_uvIndex" - } - }, - { - "name": "textureMapUv", - "displayName": "Detail Map UVs", - "description": "Which UV set to use for detail map sampling", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_detail_allMapsUvIndex" - } - }, - { - "name": "enableBaseColor", - "displayName": "Enable Base Color", - "description": "Enable detail blending for base color", - "type": "Bool", - "defaultValue": false - }, - { - "name": "baseColorDetailMap", - "displayName": " Texture", - "description": "Detailed Base Color Texture", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_detail_baseColor_texture" - } - }, - { - "name": "baseColorDetailBlend", - "displayName": " Blend Factor", - "description": "How much to blend the detail layer into the base color.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_detail_baseColor_factor" - } - }, - { - "name": "enableNormals", - "displayName": "Enable Normal", - "description": "Enable detail normal map to be used for fine detail normal such as scratches and small dents", - "type": "Bool", - "defaultValue": false - }, - { - "name": "normalDetailStrength", - "displayName": " Factor", - "description": "Strength factor for scaling the Detail Normal", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_detail_normal_factor" - } - }, - { - "name": "normalDetailMap", - "displayName": " Texture", - "description": "Detailed Normal map", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_detail_normal_texture" - } - }, - { - "name": "normalDetailFlipX", - "displayName": " Flip X Channel", - "description": "Flip Detail tangent direction for this normal map.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderInput", - "name": "m_detail_normal_flipX" - } - }, - { - "name": "normalDetailFlipY", - "displayName": " Flip Y Channel", - "description": "Flip Detail bitangent direction for this normal map.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderInput", - "name": "m_detail_normal_flipY" - } - } - ] + "description": "Properties for Fine Details Layer." }, { "name": "detailUV", "displayName": "Detail Layer UV", - "description": "Properties for modifying detail layer UV.", - "properties": [ - { - "name": "center", - "displayName": "Center", - "description": "Center point for scaling and rotation transformations.", - "type": "vector2", - "vectorLabels": [ "U", "V" ], - "defaultValue": [ 0.5, 0.5 ] - }, - { - "name": "tileU", - "displayName": "Tile U", - "description": "Scales texture coordinates in V.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - }, - { - "name": "tileV", - "displayName": "Tile V", - "description": "Scales texture coordinates in V.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - }, - { - "name": "offsetU", - "displayName": "Offset U", - "description": "Offsets texture coordinates in the U direction.", - "type": "float", - "defaultValue": 0.0, - "min": -1.0, - "max": 1.0 - }, - { - "name": "offsetV", - "displayName": "Offset V", - "description": "Offsets texture coordinates in the V direction.", - "type": "float", - "defaultValue": 0.0, - "min": -1.0, - "max": 1.0 - }, - { - "name": "rotateDegrees", - "displayName": "Rotate", - "description": "Rotates the texture coordinates (degrees).", - "type": "float", - "defaultValue": 0.0, - "min": -180.0, - "max": 180.0, - "step": 1.0 - }, - { - "name": "scale", - "displayName": "Scale", - "description": "Scales texture coordinates in both U and V.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - } - ] + "description": "Properties for modifying detail layer UV." }, { "name": "anisotropy", "displayName": "Anisotropic Material Response", - "description": "How much is this material response anisotropic.", - "properties": [ - { - "name": "enableAnisotropy", - "displayName": "Enable Anisotropy", - "description": "Enable anisotropic surface response for non uniform reflection along the axis", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_enableAnisotropy" - } - }, - { - "name": "factor", - "displayName": "Anisotropy Factor", - "description": "Strength factor for the anisotropy: negative = along v, positive = along u", - "type": "Float", - "defaultValue": 0.0, - "min": -0.95, - "max": 0.95, - "connection": { - "type": "ShaderInput", - "name": "m_anisotropicFactor" - } - }, - { - "name": "anisotropyAngle", - "displayName": "Anisotropy Angle", - "description": "Anisotropy direction of major reflection axis: 0 = 0 degrees, 1.0 = 180 degrees", - "type": "Float", - "defaultValue": 0.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_anisotropicAngle" - } - } - ] + "description": "How much is this material response anisotropic." }, { "name": "occlusion", "displayName": "Occlusion", - "description": "Properties for baked textures that represent geometric occlusion of light.", - "properties": [ - { - "name": "diffuseTextureMap", - "displayName": "Diffuse AO", - "description": "Texture for defining occlusion area for diffuse ambient lighting.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_diffuseOcclusionMap" - } - }, - { - "name": "diffuseUseTexture", - "displayName": " Use Texture", - "description": "Whether to use the Diffuse AO map.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "diffuseTextureMapUv", - "displayName": " UV", - "description": "Diffuse AO map UV set.", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_diffuseOcclusionMapUvIndex" - } - }, - { - "name": "diffuseFactor", - "displayName": " Factor", - "description": "Strength factor for scaling the values of Diffuse AO", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_diffuseOcclusionFactor" - } - }, - { - "name": "specularTextureMap", - "displayName": "Specular Cavity", - "description": "Texture for defining occlusion area for specular lighting.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_specularOcclusionMap" - } - }, - { - "name": "specularUseTexture", - "displayName": " Use Texture", - "description": "Whether to use the Specular Cavity map.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "specularTextureMapUv", - "displayName": " UV", - "description": "Specular Cavity map UV set.", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_specularOcclusionMapUvIndex" - } - }, - { - "name": "specularFactor", - "displayName": " Factor", - "description": "Strength factor for scaling the values of Specular Cavity", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_specularOcclusionFactor" - } - } - ] + "description": "Properties for baked textures that represent geometric occlusion of light." }, { "name": "emissive", "displayName": "Emissive", - "description": "Properties to add light emission, independent of other lights in the scene.", - "properties": [ - { - "name": "enable", - "displayName": "Enable", - "description": "Enable the emissive group", - "type": "Bool", - "defaultValue": false - }, - { - "name": "unit", - "displayName": "Units", - "description": "The photometric units of the Intensity property.", - "type": "Enum", - "enumValues": ["Ev100"], - "defaultValue": "Ev100" - }, - { - "name": "color", - "displayName": "Color", - "description": "Color is displayed as sRGB but the values are stored as linear color.", - "type": "Color", - "defaultValue": [ 1.0, 1.0, 1.0 ], - "connection": { - "type": "ShaderInput", - "name": "m_emissiveColor" - } - }, - { - "name": "intensity", - "displayName": "Intensity", - "description": "The amount of energy emitted.", - "type": "Float", - "defaultValue": 4, - "min": -10, - "max": 20, - "softMin": -6, - "softMax": 16 - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining emissive area.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_emissiveMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Emissive map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_emissiveMapUvIndex" - } - } - ] + "description": "Properties to add light emission, independent of other lights in the scene." }, { "name": "subsurfaceScattering", "displayName": "Subsurface Scattering", - "description": "Properties for configuring subsurface scattering effects.", - "properties": [ - { - "name": "enableSubsurfaceScattering", - "displayName": "Subsurface Scattering", - "description": "Enable subsurface scattering feature, this will disable metallic and parallax mapping property due to incompatibility", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_enableSubsurfaceScattering" - } - }, - { - "name": "subsurfaceScatterFactor", - "displayName": " Factor", - "description": "Strength factor for scaling percentage of subsurface scattering effect applied", - "type": "float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_subsurfaceScatteringFactor" - } - }, - { - "name": "influenceMap", - "displayName": " Influence Map", - "description": "Texture for controlling the strength of subsurface scattering", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_subsurfaceScatteringInfluenceMap" - } - }, - { - "name": "useInfluenceMap", - "displayName": " Use Influence Map", - "description": "Whether to use the influence map.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "influenceMapUv", - "displayName": " UV", - "description": "Influence map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_subsurfaceScatteringInfluenceMapUvIndex" - } - }, - { - "name": "scatterColor", - "displayName": " Scatter color", - "description": "Color of volume light traveled through", - "type": "Color", - "defaultValue": [ 1.0, 0.27, 0.13 ] - }, - { - "name": "scatterDistance", - "displayName": " Scatter distance", - "description": "How far light traveled inside the volume", - "type": "float", - "defaultValue": 8, - "min": 0.0, - "softMax": 20.0 - }, - { - "name": "quality", - "displayName": " Quality", - "description": "How much percent of sample will be used for each pixel, more samples improve quality and reduce artifacts, especially when the scatter distance is relatively large, but slow down computation time, 1.0 = full set 200 samples per pixel", - "type": "float", - "defaultValue": 0.4, - "min": 0.2, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_subsurfaceScatteringQuality" - } - }, - { - "name": "transmissionMode", - "displayName": "Transmission", - "description": "Algorithm used for calculating transmission", - "type": "Enum", - "enumValues": [ "None", "ThickObject", "ThinObject" ], - "defaultValue": "None", - "connection": { - "type": "ShaderOption", - "name": "o_transmission_mode" - } - }, - { - "name": "thickness", - "displayName": " Thickness", - "description": "Normalized global thickness, the maxima between this value (multiplied by thickness map if enabled) and thickness from shadow map (if applicable) will be used as final thickness of pixel", - "type": "float", - "defaultValue": 0.5, - "min": 0.0, - "max": 1.0 - }, - { - "name": "thicknessMap", - "displayName": " Thickness Map", - "description": "Texture for controlling per pixel thickness", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_transmissionThicknessMap" - } - }, - { - "name": "useThicknessMap", - "displayName": " Use Thickness Map", - "description": "Whether to use the thickness map", - "type": "Bool", - "defaultValue": true - }, - { - "name": "thicknessMapUv", - "displayName": " UV", - "description": "Thickness map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_transmissionThicknessMapUvIndex" - } - }, - { - "name": "transmissionTint", - "displayName": " Transmission Tint", - "description": "Color of the volume light traveling through", - "type": "Color", - "defaultValue": [ 1.0, 0.8, 0.6 ] - }, - { - "name": "transmissionPower", - "displayName": " Power", - "description": "How much transmitted light scatter radially ", - "type": "float", - "defaultValue": 6.0, - "min": 0.0, - "softMax": 20.0 - }, - { - "name": "transmissionDistortion", - "displayName": " Distortion", - "description": "How much light direction distorted towards surface normal", - "type": "float", - "defaultValue": 0.1, - "min": 0.0, - "max": 1.0 - }, - { - "name": "transmissionAttenuation", - "displayName": " Attenuation", - "description": "How fast transmitted light fade with thickness", - "type": "float", - "defaultValue": 4.0, - "min": 0.0, - "softMax": 20.0 - }, - { - "name": "transmissionScale", - "displayName": " Scale", - "description": "Strength of transmission", - "type": "float", - "defaultValue": 3.0, - "min": 0.0, - "softMax": 20.0 - } - ] + "description": "Properties for configuring subsurface scattering effects." }, { "name": "clearCoat", "displayName": "Clear Coat", - "description": "Properties for configuring gloss clear coat", - "properties": [ - { - "name": "enable", - "displayName": "Enable", - "description": "Enable clear coat", - "type": "Bool", - "defaultValue": false - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the percentage of effect applied", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatFactor" - } - }, - { - "name": "influenceMap", - "displayName": " Influence Map", - "description": "Strength factor texture", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatInfluenceMap" - } - }, - { - "name": "useInfluenceMap", - "displayName": " Use Texture", - "description": "Whether to use the texture, or just default to the Factor value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "influenceMapUv", - "displayName": " UV", - "description": "Strength factor map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatInfluenceMapUvIndex" - } - }, - { - "name": "roughness", - "displayName": "Roughness", - "description": "Clear coat layer roughness", - "type": "Float", - "defaultValue": 0.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatRoughness" - } - }, - { - "name": "roughnessMap", - "displayName": " Roughness Map", - "description": "Texture for defining surface roughness", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatRoughnessMap" - } - }, - { - "name": "useRoughnessMap", - "displayName": " Use Texture", - "description": "Whether to use the texture, or just default to the roughness value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "roughnessMapUv", - "displayName": " UV", - "description": "Roughness map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatRoughnessMapUvIndex" - } - }, - { - "name": "normalStrength", - "displayName": "Normal Strength", - "description": "Scales the impact of the clear coat normal map", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatNormalStrength" - } - }, - { - "name": "normalMap", - "displayName": "Normal Map", - "description": "Normal map for clear coat layer, as top layer material clear coat doesn't affect by base layer normal map", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatNormalMap" - } - }, - { - "name": "useNormalMap", - "displayName": " Use Texture", - "description": "Whether to use the normal map", - "type": "Bool", - "defaultValue": true - }, - { - "name": "normalMapUv", - "displayName": " UV", - "description": "Normal map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatNormalMapUvIndex" - } - } - ] - }, + "description": "Properties for configuring gloss clear coat" + }, { "name": "parallax", "displayName": "Displacement", - "description": "Properties for parallax effect produced by a height map.", - "properties": [ - { - "name": "textureMap", - "displayName": "Height Map", - "description": "Displacement height map to create parallax effect.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_heightmap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the height map.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Height map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_parallaxUvIndex" - } - }, - { - "name": "factor", - "displayName": "Height Map Scale", - "description": "The total height of the height map in local model units.", - "type": "Float", - "defaultValue": 0.05, - "min": 0.0, - "softMax": 0.1, - "connection": { - "type": "ShaderInput", - "name": "m_heightmapScale" - } - }, - { - "name": "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", - "name": "m_heightmapOffset" - } - }, - { - "name": "algorithm", - "displayName": "Algorithm", - "description": "Select the algorithm to use for parallax mapping.", - "type": "Enum", - "enumValues": [ "Basic", "Steep", "POM", "Relief", "ContactRefinement" ], - "defaultValue": "POM", - "connection": { - "type": "ShaderOption", - "name": "o_parallax_algorithm" - } - }, - { - "name": "quality", - "displayName": "Quality", - "description": "Quality of parallax mapping.", - "type": "Enum", - "enumValues": [ "Low", "Medium", "High", "Ultra" ], - "defaultValue": "Low", - "connection": { - "type": "ShaderOption", - "name": "o_parallax_quality" - } - }, - { - "name": "pdo", - "displayName": "Pixel Depth Offset", - "description": "Enable PDO to offset the original pixel depths. This will affect any shaders using depth, for example, when receiving shadows.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_parallax_enablePixelDepthOffset" - } - }, - { - "name": "showClipping", - "displayName": "Show Clipping", - "description": "Highlight areas where the height map is clipped by the mesh surface.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_parallax_highlightClipping" - } - } - ] + "description": "Properties for parallax effect produced by a height map." }, { "name": "opacity", "displayName": "Opacity", - "description": "Properties for configuring the materials transparency.", - "properties": [ - { - "name": "mode", - "displayName": "Opacity Mode", - "description": "Indicates the general approach how transparency is to be applied.", - "type": "Enum", - "enumValues": [ "Opaque", "Cutout", "Blended", "TintedTransparent" ], - "defaultValue": "Opaque", - "connection": { - "type": "ShaderOption", - "name": "o_opacity_mode" - } - }, - { - "name": "alphaSource", - "displayName": "Alpha Source", - "description": "Indicates whether to get the opacity texture from the Base Color map (Packed) or from a separate greyscale texture (Split).", - "type": "Enum", - "enumValues": [ "Packed", "Split", "None" ], - "defaultValue": "Packed", - "connection": { - "type": "ShaderOption", - "name": "o_opacity_source" - } - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface opacity.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_opacityMap" - } - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Opacity map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_opacityMapUvIndex" - } - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Factor for cutout threshold and blending", - "type": "Float", - "min": 0.0, - "max": 1.0, - "defaultValue": 0.5, - "connection": { - "type": "ShaderInput", - "name": "m_opacityFactor" - } - }, - { - "name": "alphaAffectsSpecular", - "displayName": "Alpha affects specular", - "description": "How much the alpha value should also affect specular reflection. This should be 0.0 for materials where light can transmit through their physical surface (like glass), but 1.0 when alpha determines the very presence of a surface (like hair or grass)", - "type": "float", - "min": 0.0, - "max": 1.0, - "defaultValue": 0.0, - "connection": { - "type": "ShaderInput", - "name": "m_opacityAffectsSpecularFactor" - } - } - ] + "description": "Properties for configuring the materials transparency." }, { "name": "uv", "displayName": "UVs", - "description": "Properties for configuring UV transforms.", - "properties": [ - { - "name": "center", - "displayName": "Center", - "description": "Center point for scaling and rotation transformations.", - "type": "vector2", - "vectorLabels": [ "U", "V" ], - "defaultValue": [ 0.5, 0.5 ] - }, - { - "name": "tileU", - "displayName": "Tile U", - "description": "Scales texture coordinates in U.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - }, - { - "name": "tileV", - "displayName": "Tile V", - "description": "Scales texture coordinates in V.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - }, - { - "name": "offsetU", - "displayName": "Offset U", - "description": "Offsets texture coordinates in the U direction.", - "type": "float", - "defaultValue": 0.0, - "min": -1.0, - "max": 1.0 - }, - { - "name": "offsetV", - "displayName": "Offset V", - "description": "Offsets texture coordinates in the V direction.", - "type": "float", - "defaultValue": 0.0, - "min": -1.0, - "max": 1.0 - }, - { - "name": "rotateDegrees", - "displayName": "Rotate", - "description": "Rotates the texture coordinates (degrees).", - "type": "float", - "defaultValue": 0.0, - "min": -180.0, - "max": 180.0, - "step": 1.0 - }, - { - "name": "scale", - "displayName": "Scale", - "description": "Scales texture coordinates in both U and V.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - } - ] + "description": "Properties for configuring UV transforms." }, { // Note: this property group is used in the DiffuseGlobalIllumination pass and not by the main forward shader "name": "irradiance", "displayName": "Irradiance", - "description": "Properties for configuring the irradiance used in global illumination.", - "properties": [ - { - "name": "color", - "displayName": "Color", - "description": "Color is displayed as sRGB but the values are stored as linear color.", - "type": "Color", - "defaultValue": [ 1.0, 1.0, 1.0 ] - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the irradiance color values. Zero (0.0) is black, white (1.0) is full color.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0 - } - ] + "description": "Properties for configuring the irradiance used in global illumination." }, { "name": "general", "displayName": "General Settings", - "description": "General settings.", - "properties": [ - { - "name": "doubleSided", - "displayName": "Double-sided", - "description": "Whether to render back-faces or just front-faces.", - "type": "Bool" - }, - { - "name": "applySpecularAA", - "displayName": "Apply Specular AA", - "description": "Whether to apply specular anti-aliasing in the shader.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_applySpecularAA" - } - }, - { - "name": "enableShadows", - "displayName": "Enable Shadows", - "description": "Whether to use the shadow maps.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableShadows" - } - }, - { - "name": "enableDirectionalLights", - "displayName": "Enable Directional Lights", - "description": "Whether to use directional lights.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableDirectionalLights" - } - }, - { - "name": "enablePunctualLights", - "displayName": "Enable Punctual Lights", - "description": "Whether to use punctual lights.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enablePunctualLights" - } - }, - { - "name": "enableAreaLights", - "displayName": "Enable Area Lights", - "description": "Whether to use area lights.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableAreaLights" - } - }, - { - "name": "enableIBL", - "displayName": "Enable IBL", - "description": "Whether to use Image Based Lighting (IBL).", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableIBL" - } - }, - { - "name": "forwardPassIBLSpecular", - "displayName": "Forward Pass IBL Specular", - "description": "Whether to apply IBL specular in the forward pass.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_materialUseForwardPassIBLSpecular" - } - } - ] + "description": "General settings." } - ] + ], + "properties": { + "general": [ + { + "name": "doubleSided", + "displayName": "Double-sided", + "description": "Whether to render back-faces or just front-faces.", + "type": "Bool" + }, + { + "name": "applySpecularAA", + "displayName": "Apply Specular AA", + "description": "Whether to apply specular anti-aliasing in the shader.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_applySpecularAA" + } + }, + { + "name": "enableShadows", + "displayName": "Enable Shadows", + "description": "Whether to use the shadow maps.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableShadows" + } + }, + { + "name": "enableDirectionalLights", + "displayName": "Enable Directional Lights", + "description": "Whether to use directional lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableDirectionalLights" + } + }, + { + "name": "enablePunctualLights", + "displayName": "Enable Punctual Lights", + "description": "Whether to use punctual lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enablePunctualLights" + } + }, + { + "name": "enableAreaLights", + "displayName": "Enable Area Lights", + "description": "Whether to use area lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableAreaLights" + } + }, + { + "name": "enableIBL", + "displayName": "Enable IBL", + "description": "Whether to use Image Based Lighting (IBL).", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableIBL" + } + }, + { + "name": "forwardPassIBLSpecular", + "displayName": "Forward Pass IBL Specular", + "description": "Whether to apply IBL specular in the forward pass.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_materialUseForwardPassIBLSpecular" + } + } + ], + "baseColor": [ + { + "name": "color", + "displayName": "Color", + "description": "Color is displayed as sRGB but the values are stored as linear color.", + "type": "Color", + "defaultValue": [ 1.0, 1.0, 1.0 ], + "connection": { + "type": "ShaderInput", + "name": "m_baseColor" + } + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Strength factor for scaling the base color values. Zero (0.0) is black, white (1.0) is full color.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_baseColorFactor" + } + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "Base color texture map", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_baseColorMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Base color map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_baseColorMapUvIndex" + } + }, + { + "name": "textureBlendMode", + "displayName": "Texture Blend Mode", + "description": "Selects the equation to use when combining Color, Factor, and Texture.", + "type": "Enum", + "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], + "defaultValue": "Multiply", + "connection": { + "type": "ShaderOption", + "name": "o_baseColorTextureBlendMode" + } + } + ], + "metallic": [ + { + "name": "factor", + "displayName": "Factor", + "description": "This value is linear, black is non-metal and white means raw metal.", + "type": "Float", + "defaultValue": 0.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_metallicFactor" + } + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_metallicMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just default to the Factor value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Metallic map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_metallicMapUvIndex" + } + } + ], + "roughness": [ + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface roughness.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_roughnessMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just default to the Factor value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Roughness map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_roughnessMapUvIndex" + } + }, + { + // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. + "name": "lowerBound", + "displayName": "Lower Bound", + "description": "The roughness value that corresponds to black in the texture.", + "type": "Float", + "defaultValue": 0.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_roughnessLowerBound" + } + }, + { + // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. + "name": "upperBound", + "displayName": "Upper Bound", + "description": "The roughness value that corresponds to white in the texture.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_roughnessUpperBound" + } + }, + { + // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. + "name": "factor", + "displayName": "Factor", + "description": "Controls the roughness value", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_roughnessFactor" + } + } + ], + "anisotropy": [ + { + "name": "enableAnisotropy", + "displayName": "Enable Anisotropy", + "description": "Enable anisotropic surface response for non uniform reflection along the axis", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_enableAnisotropy" + } + }, + { + "name": "factor", + "displayName": "Anisotropy Factor", + "description": "Strength factor for the anisotropy: negative = along v, positive = along u", + "type": "Float", + "defaultValue": 0.0, + "min": -0.95, + "max": 0.95, + "connection": { + "type": "ShaderInput", + "name": "m_anisotropicFactor" + } + }, + { + "name": "anisotropyAngle", + "displayName": "Anisotropy Angle", + "description": "Anisotropy direction of major reflection axis: 0 = 0 degrees, 1.0 = 180 degrees", + "type": "Float", + "defaultValue": 0.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_anisotropicAngle" + } + } + ], + "specularF0": [ + { + "name": "factor", + "displayName": "Factor", + "description": "The default IOR is 1.5, which gives you 0.04 (4% of light reflected at 0 degree angle for dielectric materials). F0 values lie in the range 0-0.08, so that is why the default F0 slider is set on 0.5.", + "type": "Float", + "defaultValue": 0.5, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_specularF0Factor" + } + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface reflectance.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_specularF0Map" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just default to the Factor value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Specular reflection map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_specularF0MapUvIndex" + } + }, + // Consider moving this to the "general" group to be consistent with StandardMultilayerPBR + { + "name": "enableMultiScatterCompensation", + "displayName": "Multiscattering Compensation", + "description": "Whether to enable multiple scattering compensation.", + "type": "Bool", + "connection": { + "type": "ShaderOption", + "name": "o_specularF0_enableMultiScatterCompensation" + } + } + ], + "clearCoat": [ + { + "name": "enable", + "displayName": "Enable", + "description": "Enable clear coat", + "type": "Bool", + "defaultValue": false + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Strength factor for scaling the percentage of effect applied", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatFactor" + } + }, + { + "name": "influenceMap", + "displayName": " Influence Map", + "description": "Strength factor texture", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatInfluenceMap" + } + }, + { + "name": "useInfluenceMap", + "displayName": " Use Texture", + "description": "Whether to use the texture, or just default to the Factor value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "influenceMapUv", + "displayName": " UV", + "description": "Strength factor map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatInfluenceMapUvIndex" + } + }, + { + "name": "roughness", + "displayName": "Roughness", + "description": "Clear coat layer roughness", + "type": "Float", + "defaultValue": 0.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatRoughness" + } + }, + { + "name": "roughnessMap", + "displayName": " Roughness Map", + "description": "Texture for defining surface roughness", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatRoughnessMap" + } + }, + { + "name": "useRoughnessMap", + "displayName": " Use Texture", + "description": "Whether to use the texture, or just default to the roughness value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "roughnessMapUv", + "displayName": " UV", + "description": "Roughness map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatRoughnessMapUvIndex" + } + }, + { + "name": "normalStrength", + "displayName": "Normal Strength", + "description": "Scales the impact of the clear coat normal map", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatNormalStrength" + } + }, + { + "name": "normalMap", + "displayName": "Normal Map", + "description": "Normal map for clear coat layer, as top layer material clear coat doesn't affect by base layer normal map", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatNormalMap" + } + }, + { + "name": "useNormalMap", + "displayName": " Use Texture", + "description": "Whether to use the normal map", + "type": "Bool", + "defaultValue": true + }, + { + "name": "normalMapUv", + "displayName": " UV", + "description": "Normal map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatNormalMapUvIndex" + } + } + ], + "normal": [ + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface normal direction.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_normalMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just rely on vertex normals.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Normal map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_normalMapUvIndex" + } + }, + { + "name": "flipX", + "displayName": "Flip X Channel", + "description": "Flip tangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "name": "m_flipNormalX" + } + }, + { + "name": "flipY", + "displayName": "Flip Y Channel", + "description": "Flip bitangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "name": "m_flipNormalY" + } + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Strength factor for scaling the values", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_normalFactor" + } + } + ], + "opacity": [ + { + "name": "mode", + "displayName": "Opacity Mode", + "description": "Indicates the general approach how transparency is to be applied.", + "type": "Enum", + "enumValues": [ "Opaque", "Cutout", "Blended", "TintedTransparent" ], + "defaultValue": "Opaque", + "connection": { + "type": "ShaderOption", + "name": "o_opacity_mode" + } + }, + { + "name": "alphaSource", + "displayName": "Alpha Source", + "description": "Indicates whether to get the opacity texture from the Base Color map (Packed) or from a separate greyscale texture (Split).", + "type": "Enum", + "enumValues": [ "Packed", "Split", "None" ], + "defaultValue": "Packed", + "connection": { + "type": "ShaderOption", + "name": "o_opacity_source" + } + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface opacity.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_opacityMap" + } + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Opacity map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_opacityMapUvIndex" + } + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Factor for cutout threshold and blending", + "type": "Float", + "min": 0.0, + "max": 1.0, + "defaultValue": 0.5, + "connection": { + "type": "ShaderInput", + "name": "m_opacityFactor" + } + }, + { + "name": "alphaAffectsSpecular", + "displayName": "Alpha affects specular", + "description": "How much the alpha value should also affect specular reflection. This should be 0.0 for materials where light can transmit through their physical surface (like glass), but 1.0 when alpha determines the very presence of a surface (like hair or grass)", + "type": "float", + "min": 0.0, + "max": 1.0, + "defaultValue": 0.0, + "connection": { + "type": "ShaderInput", + "name": "m_opacityAffectsSpecularFactor" + } + } + ], + "uv": [ + { + "name": "center", + "displayName": "Center", + "description": "Center point for scaling and rotation transformations.", + "type": "vector2", + "vectorLabels": [ "U", "V" ], + "defaultValue": [ 0.5, 0.5 ] + }, + { + "name": "tileU", + "displayName": "Tile U", + "description": "Scales texture coordinates in U.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + }, + { + "name": "tileV", + "displayName": "Tile V", + "description": "Scales texture coordinates in V.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + }, + { + "name": "offsetU", + "displayName": "Offset U", + "description": "Offsets texture coordinates in the U direction.", + "type": "float", + "defaultValue": 0.0, + "min": -1.0, + "max": 1.0 + }, + { + "name": "offsetV", + "displayName": "Offset V", + "description": "Offsets texture coordinates in the V direction.", + "type": "float", + "defaultValue": 0.0, + "min": -1.0, + "max": 1.0 + }, + { + "name": "rotateDegrees", + "displayName": "Rotate", + "description": "Rotates the texture coordinates (degrees).", + "type": "float", + "defaultValue": 0.0, + "min": -180.0, + "max": 180.0, + "step": 1.0 + }, + { + "name": "scale", + "displayName": "Scale", + "description": "Scales texture coordinates in both U and V.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + } + ], + "occlusion": [ + { + "name": "diffuseTextureMap", + "displayName": "Diffuse AO", + "description": "Texture for defining occlusion area for diffuse ambient lighting.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_diffuseOcclusionMap" + } + }, + { + "name": "diffuseUseTexture", + "displayName": " Use Texture", + "description": "Whether to use the Diffuse AO map.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "diffuseTextureMapUv", + "displayName": " UV", + "description": "Diffuse AO map UV set.", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_diffuseOcclusionMapUvIndex" + } + }, + { + "name": "diffuseFactor", + "displayName": " Factor", + "description": "Strength factor for scaling the values of Diffuse AO", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_diffuseOcclusionFactor" + } + }, + { + "name": "specularTextureMap", + "displayName": "Specular Cavity", + "description": "Texture for defining occlusion area for specular lighting.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_specularOcclusionMap" + } + }, + { + "name": "specularUseTexture", + "displayName": " Use Texture", + "description": "Whether to use the Specular Cavity map.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "specularTextureMapUv", + "displayName": " UV", + "description": "Specular Cavity map UV set.", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_specularOcclusionMapUvIndex" + } + }, + { + "name": "specularFactor", + "displayName": " Factor", + "description": "Strength factor for scaling the values of Specular Cavity", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_specularOcclusionFactor" + } + } + ], + "emissive": [ + { + "name": "enable", + "displayName": "Enable", + "description": "Enable the emissive group", + "type": "Bool", + "defaultValue": false + }, + { + "name": "unit", + "displayName": "Units", + "description": "The photometric units of the Intensity property.", + "type": "Enum", + "enumValues": ["Ev100"], + "defaultValue": "Ev100" + }, + { + "name": "color", + "displayName": "Color", + "description": "Color is displayed as sRGB but the values are stored as linear color.", + "type": "Color", + "defaultValue": [ 1.0, 1.0, 1.0 ], + "connection": { + "type": "ShaderInput", + "name": "m_emissiveColor" + } + }, + { + "name": "intensity", + "displayName": "Intensity", + "description": "The amount of energy emitted.", + "type": "Float", + "defaultValue": 4, + "min": -10, + "max": 20, + "softMin": -6, + "softMax": 16 + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining emissive area.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_emissiveMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Emissive map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_emissiveMapUvIndex" + } + } + ], + "parallax": [ + { + "name": "textureMap", + "displayName": "Height Map", + "description": "Displacement height map to create parallax effect.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_heightmap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the height map.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Height map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_parallaxUvIndex" + } + }, + { + "name": "factor", + "displayName": "Height Map Scale", + "description": "The total height of the height map in local model units.", + "type": "Float", + "defaultValue": 0.05, + "min": 0.0, + "softMax": 0.1, + "connection": { + "type": "ShaderInput", + "name": "m_heightmapScale" + } + }, + { + "name": "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", + "name": "m_heightmapOffset" + } + }, + { + "name": "algorithm", + "displayName": "Algorithm", + "description": "Select the algorithm to use for parallax mapping.", + "type": "Enum", + "enumValues": [ "Basic", "Steep", "POM", "Relief", "ContactRefinement" ], + "defaultValue": "POM", + "connection": { + "type": "ShaderOption", + "name": "o_parallax_algorithm" + } + }, + { + "name": "quality", + "displayName": "Quality", + "description": "Quality of parallax mapping.", + "type": "Enum", + "enumValues": [ "Low", "Medium", "High", "Ultra" ], + "defaultValue": "Low", + "connection": { + "type": "ShaderOption", + "name": "o_parallax_quality" + } + }, + { + "name": "pdo", + "displayName": "Pixel Depth Offset", + "description": "Enable PDO to offset the original pixel depths. This will affect any shaders using depth, for example, when receiving shadows.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_parallax_enablePixelDepthOffset" + } + }, + { + "name": "showClipping", + "displayName": "Show Clipping", + "description": "Highlight areas where the height map is clipped by the mesh surface.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_parallax_highlightClipping" + } + } + ], + "subsurfaceScattering": [ + { + "name": "enableSubsurfaceScattering", + "displayName": "Subsurface Scattering", + "description": "Enable subsurface scattering feature, this will disable metallic and parallax mapping property due to incompatibility", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_enableSubsurfaceScattering" + } + }, + { + "name": "subsurfaceScatterFactor", + "displayName": " Factor", + "description": "Strength factor for scaling percentage of subsurface scattering effect applied", + "type": "float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_subsurfaceScatteringFactor" + } + }, + { + "name": "influenceMap", + "displayName": " Influence Map", + "description": "Texture for controlling the strength of subsurface scattering", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_subsurfaceScatteringInfluenceMap" + } + }, + { + "name": "useInfluenceMap", + "displayName": " Use Influence Map", + "description": "Whether to use the influence map.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "influenceMapUv", + "displayName": " UV", + "description": "Influence map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_subsurfaceScatteringInfluenceMapUvIndex" + } + }, + { + "name": "scatterColor", + "displayName": " Scatter color", + "description": "Color of volume light traveled through", + "type": "Color", + "defaultValue": [ 1.0, 0.27, 0.13 ] + }, + { + "name": "scatterDistance", + "displayName": " Scatter distance", + "description": "How far light traveled inside the volume", + "type": "float", + "defaultValue": 8, + "min": 0.0, + "softMax": 20.0 + }, + { + "name": "quality", + "displayName": " Quality", + "description": "How much percent of sample will be used for each pixel, more samples improve quality and reduce artifacts, especially when the scatter distance is relatively large, but slow down computation time, 1.0 = full set 200 samples per pixel", + "type": "float", + "defaultValue": 0.4, + "min": 0.2, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_subsurfaceScatteringQuality" + } + }, + { + "name": "transmissionMode", + "displayName": "Transmission", + "description": "Algorithm used for calculating transmission", + "type": "Enum", + "enumValues": [ "None", "ThickObject", "ThinObject" ], + "defaultValue": "None", + "connection": { + "type": "ShaderOption", + "name": "o_transmission_mode" + } + }, + { + "name": "thickness", + "displayName": " Thickness", + "description": "Normalized global thickness, the maxima between this value (multiplied by thickness map if enabled) and thickness from shadow map (if applicable) will be used as final thickness of pixel", + "type": "float", + "defaultValue": 0.5, + "min": 0.0, + "max": 1.0 + }, + { + "name": "thicknessMap", + "displayName": " Thickness Map", + "description": "Texture for controlling per pixel thickness", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_transmissionThicknessMap" + } + }, + { + "name": "useThicknessMap", + "displayName": " Use Thickness Map", + "description": "Whether to use the thickness map", + "type": "Bool", + "defaultValue": true + }, + { + "name": "thicknessMapUv", + "displayName": " UV", + "description": "Thickness map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_transmissionThicknessMapUvIndex" + } + }, + { + "name": "transmissionTint", + "displayName": " Transmission Tint", + "description": "Color of the volume light traveling through", + "type": "Color", + "defaultValue": [ 1.0, 0.8, 0.6 ] + }, + { + "name": "transmissionPower", + "displayName": " Power", + "description": "How much transmitted light scatter radially ", + "type": "float", + "defaultValue": 6.0, + "min": 0.0, + "softMax": 20.0 + }, + { + "name": "transmissionDistortion", + "displayName": " Distortion", + "description": "How much light direction distorted towards surface normal", + "type": "float", + "defaultValue": 0.1, + "min": 0.0, + "max": 1.0 + }, + { + "name": "transmissionAttenuation", + "displayName": " Attenuation", + "description": "How fast transmitted light fade with thickness", + "type": "float", + "defaultValue": 4.0, + "min": 0.0, + "softMax": 20.0 + }, + { + "name": "transmissionScale", + "displayName": " Scale", + "description": "Strength of transmission", + "type": "float", + "defaultValue": 3.0, + "min": 0.0, + "softMax": 20.0 + } + ], + "detailLayerGroup": [ + { + "name": "enableDetailLayer", + "displayName": "Enable Detail Layer", + "description": "Enable detail layer for fine details and scratches", + "type": "Bool", + "defaultValue": false + }, + { + "name": "blendDetailFactor", + "displayName": "Blend Factor", + "description": "Scales the overall impact of the detail layer.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_detail_blendFactor" + } + }, + { + "name": "blendDetailMask", + "displayName": "Blend Mask", + "description": "Detailed blend mask for application of the detail maps.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_detail_blendMask_texture" + } + }, + { + "name": "enableDetailMaskTexture", + "displayName": " Use Texture", + "description": "Enable detail blend mask", + "type": "Bool", + "defaultValue": true + }, + { + "name": "blendDetailMaskUv", + "displayName": " Blend Mask UV", + "description": "Which UV set to use for sampling the detail blend mask", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_detail_blendMask_uvIndex" + } + }, + { + "name": "textureMapUv", + "displayName": "Detail Map UVs", + "description": "Which UV set to use for detail map sampling", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_detail_allMapsUvIndex" + } + }, + { + "name": "enableBaseColor", + "displayName": "Enable Base Color", + "description": "Enable detail blending for base color", + "type": "Bool", + "defaultValue": false + }, + { + "name": "baseColorDetailMap", + "displayName": " Texture", + "description": "Detailed Base Color Texture", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_detail_baseColor_texture" + } + }, + { + "name": "baseColorDetailBlend", + "displayName": " Blend Factor", + "description": "How much to blend the detail layer into the base color.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_detail_baseColor_factor" + } + }, + { + "name": "enableNormals", + "displayName": "Enable Normal", + "description": "Enable detail normal map to be used for fine detail normal such as scratches and small dents", + "type": "Bool", + "defaultValue": false + }, + { + "name": "normalDetailStrength", + "displayName": " Factor", + "description": "Strength factor for scaling the Detail Normal", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_detail_normal_factor" + } + }, + { + "name": "normalDetailMap", + "displayName": " Texture", + "description": "Detailed Normal map", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_detail_normal_texture" + } + }, + { + "name": "normalDetailFlipX", + "displayName": " Flip X Channel", + "description": "Flip Detail tangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "name": "m_detail_normal_flipX" + } + }, + { + "name": "normalDetailFlipY", + "displayName": " Flip Y Channel", + "description": "Flip Detail bitangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "name": "m_detail_normal_flipY" + } + } + ], + "detailUV": [ + { + "name": "center", + "displayName": "Center", + "description": "Center point for scaling and rotation transformations.", + "type": "vector2", + "vectorLabels": [ "U", "V" ], + "defaultValue": [ 0.5, 0.5 ] + }, + { + "name": "tileU", + "displayName": "Tile U", + "description": "Scales texture coordinates in V.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + }, + { + "name": "tileV", + "displayName": "Tile V", + "description": "Scales texture coordinates in V.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + }, + { + "name": "offsetU", + "displayName": "Offset U", + "description": "Offsets texture coordinates in the U direction.", + "type": "float", + "defaultValue": 0.0, + "min": -1.0, + "max": 1.0 + }, + { + "name": "offsetV", + "displayName": "Offset V", + "description": "Offsets texture coordinates in the V direction.", + "type": "float", + "defaultValue": 0.0, + "min": -1.0, + "max": 1.0 + }, + { + "name": "rotateDegrees", + "displayName": "Rotate", + "description": "Rotates the texture coordinates (degrees).", + "type": "float", + "defaultValue": 0.0, + "min": -180.0, + "max": 180.0, + "step": 1.0 + }, + { + "name": "scale", + "displayName": "Scale", + "description": "Scales texture coordinates in both U and V.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + } + ], + "irradiance": [ + // Note: this property group is used in the DiffuseGlobalIllumination pass and not by the main forward shader + { + "name": "color", + "displayName": "Color", + "description": "Color is displayed as sRGB but the values are stored as linear color.", + "type": "Color", + "defaultValue": [ 1.0, 1.0, 1.0 ] + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Strength factor for scaling the irradiance color values. Zero (0.0) is black, white (1.0) is full color.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0 + } + ] + } }, "shaders": [ { @@ -1684,4 +1687,4 @@ "UV0": "Tiled", "UV1": "Unwrapped" } -} +} \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype index 6366bddff0..15eaf94df6 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype @@ -2,968 +2,970 @@ "description": "Material Type tailored for rendering skin, with support for blended wrinkle maps that work with animated vertex blend shapes.", "version": 3, "propertyLayout": { - "propertySets": [ + "groups": [ { "name": "baseColor", "displayName": "Base Color", - "description": "Properties for configuring the surface reflected color for dielectrics or reflectance values for metals.", - "properties": [ - { - "name": "color", - "displayName": "Color", - "description": "Color is displayed as sRGB but the values are stored as linear color.", - "type": "Color", - "defaultValue": [ 1.0, 1.0, 1.0 ], - "connection": { - "type": "ShaderInput", - "name": "m_baseColor" - } - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the base color values. Zero (0.0) is black, white (1.0) is full color.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_baseColorFactor" - } - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "Base color texture map", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_baseColorMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Base color map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Unwrapped", - "connection": { - "type": "ShaderInput", - "name": "m_baseColorMapUvIndex" - } - }, - { - "name": "textureBlendMode", - "displayName": "Texture Blend Mode", - "description": "Selects the equation to use when combining Color, Factor, and Texture.", - "type": "Enum", - "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], - "defaultValue": "Multiply", - "connection": { - "type": "ShaderOption", - "name": "o_baseColorTextureBlendMode" - } - } - ] + "description": "Properties for configuring the surface reflected color for dielectrics or reflectance values for metals." }, { "name": "roughness", "displayName": "Roughness", - "description": "Properties for configuring how rough the surface appears.", - "properties": [ - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface roughness.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_roughnessMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture, or just default to the Factor value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Roughness map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Unwrapped", - "connection": { - "type": "ShaderInput", - "name": "m_roughnessMapUvIndex" - } - }, - { - // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. - "name": "lowerBound", - "displayName": "Lower Bound", - "description": "The roughness value that corresponds to black in the texture.", - "type": "Float", - "defaultValue": 0.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_roughnessLowerBound" - } - }, - { - // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. - "name": "upperBound", - "displayName": "Upper Bound", - "description": "The roughness value that corresponds to white in the texture.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_roughnessUpperBound" - } - }, - { - // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. - "name": "factor", - "displayName": "Factor", - "description": "Controls the roughness value", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_roughnessFactor" - } - } - ] + "description": "Properties for configuring how rough the surface appears." }, { "name": "specularF0", "displayName": "Specular Reflectance f0", - "description": "The constant f0 represents the specular reflectance at normal incidence (Fresnel 0 Angle). Used to adjust reflectance of non-metal surfaces.", - "properties": [ - { - "name": "factor", - "displayName": "Factor", - "description": "The default IOR is 1.5, which gives you 0.04 (4% of light reflected at 0 degree angle for dielectric materials). F0 values lie in the range 0-0.08, so that is why the default F0 slider is set on 0.5.", - "type": "Float", - "defaultValue": 0.5, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_specularF0Factor" - } - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface reflectance.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_specularF0Map" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture, or just default to the Factor value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Specular reflection map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Unwrapped", - "connection": { - "type": "ShaderInput", - "name": "m_specularF0MapUvIndex" - } - }, - // Consider moving this to the "general" group to be consistent with StandardMultilayerPBR - { - "name": "enableMultiScatterCompensation", - "displayName": "Multiscattering Compensation", - "description": "Whether to enable multiple scattering compensation.", - "type": "Bool", - "connection": { - "type": "ShaderOption", - "name": "o_specularF0_enableMultiScatterCompensation" - } - } - ] + "description": "The constant f0 represents the specular reflectance at normal incidence (Fresnel 0 Angle). Used to adjust reflectance of non-metal surfaces." }, { "name": "normal", "displayName": "Normal", - "description": "Properties related to configuring surface normal.", - "properties": [ - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface normal direction.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_normalMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture, or just rely on vertex normals.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Normal map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Unwrapped", - "connection": { - "type": "ShaderInput", - "name": "m_normalMapUvIndex" - } - }, - { - "name": "flipX", - "displayName": "Flip X Channel", - "description": "Flip tangent direction for this normal map.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderInput", - "name": "m_flipNormalX" - } - }, - { - "name": "flipY", - "displayName": "Flip Y Channel", - "description": "Flip bitangent direction for this normal map.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderInput", - "name": "m_flipNormalY" - } - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the values", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_normalFactor" - } - } - ] + "description": "Properties related to configuring surface normal." }, { "name": "detailLayerGroup", "displayName": "Detail Layer", - "description": "Properties for Fine Details Layer.", - "properties": [ - { - "name": "enableDetailLayer", - "displayName": "Enable Detail Layer", - "description": "Enable detail layer for fine details and scratches", - "type": "Bool", - "defaultValue": false - }, - { - "name": "blendDetailFactor", - "displayName": "Blend Factor", - "description": "Scales the overall impact of the detail layer.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_detail_blendFactor" - } - }, - { - "name": "blendDetailMask", - "displayName": "Blend Mask", - "description": "Detailed blend mask for application of the detail maps.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_detail_blendMask_texture" - } - }, - { - "name": "enableDetailMaskTexture", - "displayName": " Use Texture", - "description": "Enable detail blend mask", - "type": "Bool", - "defaultValue": true - }, - { - "name": "blendDetailMaskUv", - "displayName": " Blend Mask UV", - "description": "Which UV set to use for sampling the detail blend mask", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Unwrapped", - "connection": { - "type": "ShaderInput", - "name": "m_detail_blendMask_uvIndex" - } - }, - { - "name": "textureMapUv", - "displayName": "Detail Map UVs", - "description": "Which UV set to use for detail map sampling", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Unwrapped", - "connection": { - "type": "ShaderInput", - "name": "m_detail_allMapsUvIndex" - } - }, - { - "name": "enableBaseColor", - "displayName": "Enable Base Color", - "description": "Enable detail blending for base color", - "type": "Bool", - "defaultValue": false - }, - { - "name": "baseColorDetailMap", - "displayName": " Texture", - "description": "Detailed Base Color Texture", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_detail_baseColor_texture" - } - }, - { - "name": "baseColorDetailBlend", - "displayName": " Blend Factor", - "description": "How much to blend the detail layer into the base color.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_detail_baseColor_factor" - } - }, - { - "name": "enableNormals", - "displayName": "Enable Normal", - "description": "Enable detail normal map to be used for fine detail normal such as scratches and small dents", - "type": "Bool", - "defaultValue": false - }, - { - "name": "normalDetailStrength", - "displayName": " Factor", - "description": "Strength factor for scaling the Detail Normal", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_detail_normal_factor" - } - }, - { - "name": "normalDetailMap", - "displayName": " Texture", - "description": "Detailed Normal map", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_detail_normal_texture" - } - }, - { - "name": "normalDetailFlipX", - "displayName": " Flip X Channel", - "description": "Flip Detail tangent direction for this normal map.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderInput", - "name": "m_detail_normal_flipX" - } - }, - { - "name": "normalDetailFlipY", - "displayName": " Flip Y Channel", - "description": "Flip Detail bitangent direction for this normal map.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderInput", - "name": "m_detail_normal_flipY" - } - } - ] + "description": "Properties for Fine Details Layer." }, { "name": "detailUV", "displayName": "Detail Layer UV", - "description": "Properties for modifying detail layer UV.", - "properties": [ - { - "name": "center", - "displayName": "Center", - "description": "Center point for scaling and rotation transformations.", - "type": "vector2", - "vectorLabels": [ "U", "V" ], - "defaultValue": [ 0.5, 0.5 ] - }, - { - "name": "tileU", - "displayName": "Tile U", - "description": "Scales texture coordinates in V.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - }, - { - "name": "tileV", - "displayName": "Tile V", - "description": "Scales texture coordinates in V.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - }, - { - "name": "offsetU", - "displayName": "Offset U", - "description": "Offsets texture coordinates in the U direction.", - "type": "float", - "defaultValue": 0.0, - "min": -1.0, - "max": 1.0 - }, - { - "name": "offsetV", - "displayName": "Offset V", - "description": "Offsets texture coordinates in the V direction.", - "type": "float", - "defaultValue": 0.0, - "min": -1.0, - "max": 1.0 - }, - { - "name": "rotateDegrees", - "displayName": "Rotate", - "description": "Rotates the texture coordinates (degrees).", - "type": "float", - "defaultValue": 0.0, - "min": -180.0, - "max": 180.0, - "step": 1.0 - }, - { - "name": "scale", - "displayName": "Scale", - "description": "Scales texture coordinates in both U and V.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - } - ] + "description": "Properties for modifying detail layer UV." }, { "name": "occlusion", "displayName": "Occlusion", - "description": "Properties for baked textures that represent geometric occlusion of light.", - "properties": [ - { - "name": "diffuseTextureMap", - "displayName": "Diffuse AO", - "description": "Texture for defining occlusion area for diffuse ambient lighting.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_diffuseOcclusionMap" - } - }, - { - "name": "diffuseUseTexture", - "displayName": " Use Texture", - "description": "Whether to use the Diffuse AO map.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "diffuseTextureMapUv", - "displayName": " UV", - "description": "Diffuse AO map UV set.", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_diffuseOcclusionMapUvIndex" - } - }, - { - "name": "diffuseFactor", - "displayName": " Factor", - "description": "Strength factor for scaling the values of Diffuse AO", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_diffuseOcclusionFactor" - } - }, - { - "name": "specularTextureMap", - "displayName": "Specular Cavity", - "description": "Texture for defining occlusion area for specular lighting.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_specularOcclusionMap" - } - }, - { - "name": "specularUseTexture", - "displayName": " Use Texture", - "description": "Whether to use the Specular Cavity map.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "specularTextureMapUv", - "displayName": " UV", - "description": "Specular Cavity map UV set.", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_specularOcclusionMapUvIndex" - } - }, - { - "name": "specularFactor", - "displayName": " Factor", - "description": "Strength factor for scaling the values of Specular Cavity", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_specularOcclusionFactor" - } - } - ] + "description": "Properties for baked textures that represent geometric occlusion of light." }, { "name": "subsurfaceScattering", "displayName": "Subsurface Scattering", - "description": "Properties for configuring subsurface scattering effects.", - "properties": [ - { - "name": "enableSubsurfaceScattering", - "displayName": "Subsurface Scattering", - "description": "Enable subsurface scattering feature, this will disable metallic and parallax mapping property due to incompatibility", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_enableSubsurfaceScattering" - } - }, - { - "name": "subsurfaceScatterFactor", - "displayName": " Factor", - "description": "Strength factor for scaling percentage of subsurface scattering effect applied", - "type": "float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_subsurfaceScatteringFactor" - } - }, - { - "name": "influenceMap", - "displayName": " Influence Map", - "description": "Texture for controlling the strength of subsurface scattering", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_subsurfaceScatteringInfluenceMap" - } - }, - { - "name": "useInfluenceMap", - "displayName": " Use Influence Map", - "description": "Whether to use the influence map.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "influenceMapUv", - "displayName": " UV", - "description": "Influence map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Unwrapped", - "connection": { - "type": "ShaderInput", - "name": "m_subsurfaceScatteringInfluenceMapUvIndex" - } - }, - { - "name": "scatterColor", - "displayName": " Scatter color", - "description": "Color of volume light traveled through", - "type": "Color", - "defaultValue": [ 1.0, 0.27, 0.13 ] - }, - { - "name": "scatterDistance", - "displayName": " Scatter distance", - "description": "How far light traveled inside the volume", - "type": "float", - "defaultValue": 8, - "min": 0.0, - "softMax": 20.0 - }, - { - "name": "quality", - "displayName": " Quality", - "description": "How much percent of sample will be used for each pixel, more samples improve quality and reduce artifacts, especially when the scatter distance is relatively large, but slow down computation time, 1.0 = full set 200 samples per pixel", - "type": "float", - "defaultValue": 0.4, - "min": 0.2, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_subsurfaceScatteringQuality" - } - }, - { - "name": "transmissionMode", - "displayName": "Transmission", - "description": "Algorithm used for calculating transmission", - "type": "Enum", - "enumValues": [ "None", "ThickObject", "ThinObject" ], - "defaultValue": "None", - "connection": { - "type": "ShaderOption", - "name": "o_transmission_mode" - } - }, - { - "name": "thickness", - "displayName": " Thickness", - "description": "Normalized global thickness, the maxima between this value (multiplied by thickness map if enabled) and thickness from shadow map (if applicable) will be used as final thickness of pixel", - "type": "float", - "defaultValue": 0.5, - "min": 0.0, - "max": 1.0 - }, - { - "name": "thicknessMap", - "displayName": " Thickness Map", - "description": "Texture for controlling per pixel thickness", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_transmissionThicknessMap" - } - }, - { - "name": "useThicknessMap", - "displayName": " Use Thickness Map", - "description": "Whether to use the thickness map", - "type": "Bool", - "defaultValue": true - }, - { - "name": "thicknessMapUv", - "displayName": " UV", - "description": "Thickness map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Unwrapped", - "connection": { - "type": "ShaderInput", - "name": "m_transmissionThicknessMapUvIndex" - } - }, - { - "name": "transmissionTint", - "displayName": " Transmission Tint", - "description": "Color of the volume light traveling through", - "type": "Color", - "defaultValue": [ 1.0, 0.8, 0.6 ] - }, - { - "name": "transmissionPower", - "displayName": " Power", - "description": "How much transmitted light scatter radially ", - "type": "float", - "defaultValue": 6.0, - "min": 0.0, - "softMax": 20.0 - }, - { - "name": "transmissionDistortion", - "displayName": " Distortion", - "description": "How much light direction distorted towards surface normal", - "type": "float", - "defaultValue": 0.1, - "min": 0.0, - "max": 1.0 - }, - { - "name": "transmissionAttenuation", - "displayName": " Attenuation", - "description": "How fast transmitted light fade with thickness", - "type": "float", - "defaultValue": 4.0, - "min": 0.0, - "softMax": 20.0 - }, - { - "name": "transmissionScale", - "displayName": " Scale", - "description": "Strength of transmission", - "type": "float", - "defaultValue": 3.0, - "min": 0.0, - "softMax": 20.0 - } - ] + "description": "Properties for configuring subsurface scattering effects." }, { "name": "wrinkleLayers", "displayName": "Wrinkle Layers", - "description": "Properties for wrinkle maps to support morph animation, using vertex color blend weights.", - "properties": [ - { - "name": "enable", - "displayName": "Enable Wrinkle Layers", - "description": "Enable wrinkle layers for morph animations, using vertex color blend weights.", - "type": "Bool", - "defaultValue": false - }, - { - "name": "count", - "displayName": "Number Of Layers", - "description": "The number of wrinkle map layers to use. The blend values come from the 'COLOR0' vertex stream, where R/G/B/A correspond to wrinkle layers 1/2/3/4 respectively.", - "type": "UInt", - "defaultValue": 4, - "min": 1, - "max": 4 - }, - { - "name": "showBlendValues", - "displayName": "Show Blend Values", - "description": "Enable a debug mode that draws the blend values as red, green, blue, and white overlays.", - "type": "Bool", - "defaultValue": false - }, - { - "name": "enableBaseColor", - "displayName": "Enable Base Color Maps", - "description": "Enable support for blending the base color according to morph animations.", - "type": "Bool", - "defaultValue": false - }, - { - "name": "baseColorMap1", - "displayName": " Base Color 1", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_wrinkle_baseColor_texture1" - } - }, - { - "name": "baseColorMap2", - "displayName": " Base Color 2", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_wrinkle_baseColor_texture2" - } - }, - { - "name": "baseColorMap3", - "displayName": " Base Color 3", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_wrinkle_baseColor_texture3" - } - }, - { - "name": "baseColorMap4", - "displayName": " Base Color 4", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_wrinkle_baseColor_texture4" - } - }, - { - "name": "enableNormal", - "displayName": "Enable Normal Maps", - "description": "Enable support for blending the normal maps according to morph animations.", - "type": "Bool", - "defaultValue": false - }, - { - "name": "normalMap1", - "displayName": " Normals 1", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_wrinkle_normal_texture1" - } - }, - { - "name": "normalMap2", - "displayName": " Normals 2", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_wrinkle_normal_texture2" - } - }, - { - "name": "normalMap3", - "displayName": " Normals 3", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_wrinkle_normal_texture3" - } - }, - { - "name": "normalMap4", - "displayName": " Normals 4", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_wrinkle_normal_texture4" - } - } - ] + "description": "Properties for wrinkle maps to support morph animation, using vertex color blend weights." }, { "name": "general", "displayName": "General Settings", - "description": "General settings.", - "properties": [ - { - "name": "applySpecularAA", - "displayName": "Apply Specular AA", - "description": "Whether to apply specular anti-aliasing in the shader.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_applySpecularAA" - } - }, - { - "name": "enableShadows", - "displayName": "Enable Shadows", - "description": "Whether to use the shadow maps.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableShadows" - } - }, - { - "name": "enableDirectionalLights", - "displayName": "Enable Directional Lights", - "description": "Whether to use directional lights.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableDirectionalLights" - } - }, - { - "name": "enablePunctualLights", - "displayName": "Enable Punctual Lights", - "description": "Whether to use punctual lights.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enablePunctualLights" - } - }, - { - "name": "enableAreaLights", - "displayName": "Enable Area Lights", - "description": "Whether to use area lights.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableAreaLights" - } - }, - { - "name": "enableIBL", - "displayName": "Enable IBL", - "description": "Whether to use Image Based Lighting (IBL).", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableIBL" - } - } - ] + "description": "General settings." } - ] + ], + "properties": { + "general": [ + { + "name": "applySpecularAA", + "displayName": "Apply Specular AA", + "description": "Whether to apply specular anti-aliasing in the shader.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_applySpecularAA" + } + }, + { + "name": "enableShadows", + "displayName": "Enable Shadows", + "description": "Whether to use the shadow maps.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableShadows" + } + }, + { + "name": "enableDirectionalLights", + "displayName": "Enable Directional Lights", + "description": "Whether to use directional lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableDirectionalLights" + } + }, + { + "name": "enablePunctualLights", + "displayName": "Enable Punctual Lights", + "description": "Whether to use punctual lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enablePunctualLights" + } + }, + { + "name": "enableAreaLights", + "displayName": "Enable Area Lights", + "description": "Whether to use area lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableAreaLights" + } + }, + { + "name": "enableIBL", + "displayName": "Enable IBL", + "description": "Whether to use Image Based Lighting (IBL).", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableIBL" + } + } + ], + "baseColor": [ + { + "name": "color", + "displayName": "Color", + "description": "Color is displayed as sRGB but the values are stored as linear color.", + "type": "Color", + "defaultValue": [ 1.0, 1.0, 1.0 ], + "connection": { + "type": "ShaderInput", + "name": "m_baseColor" + } + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Strength factor for scaling the base color values. Zero (0.0) is black, white (1.0) is full color.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_baseColorFactor" + } + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "Base color texture map", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_baseColorMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Base color map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Unwrapped", + "connection": { + "type": "ShaderInput", + "name": "m_baseColorMapUvIndex" + } + }, + { + "name": "textureBlendMode", + "displayName": "Texture Blend Mode", + "description": "Selects the equation to use when combining Color, Factor, and Texture.", + "type": "Enum", + "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], + "defaultValue": "Multiply", + "connection": { + "type": "ShaderOption", + "name": "o_baseColorTextureBlendMode" + } + } + ], + "roughness": [ + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface roughness.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_roughnessMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just default to the Factor value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Roughness map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Unwrapped", + "connection": { + "type": "ShaderInput", + "name": "m_roughnessMapUvIndex" + } + }, + { + // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. + "name": "lowerBound", + "displayName": "Lower Bound", + "description": "The roughness value that corresponds to black in the texture.", + "type": "Float", + "defaultValue": 0.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_roughnessLowerBound" + } + }, + { + // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. + "name": "upperBound", + "displayName": "Upper Bound", + "description": "The roughness value that corresponds to white in the texture.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_roughnessUpperBound" + } + }, + { + // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. + "name": "factor", + "displayName": "Factor", + "description": "Controls the roughness value", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_roughnessFactor" + } + } + ], + "specularF0": [ + { + "name": "factor", + "displayName": "Factor", + "description": "The default IOR is 1.5, which gives you 0.04 (4% of light reflected at 0 degree angle for dielectric materials). F0 values lie in the range 0-0.08, so that is why the default F0 slider is set on 0.5.", + "type": "Float", + "defaultValue": 0.5, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_specularF0Factor" + } + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface reflectance.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_specularF0Map" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just default to the Factor value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Specular reflection map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Unwrapped", + "connection": { + "type": "ShaderInput", + "name": "m_specularF0MapUvIndex" + } + }, + // Consider moving this to the "general" group to be consistent with StandardMultilayerPBR + { + "name": "enableMultiScatterCompensation", + "displayName": "Multiscattering Compensation", + "description": "Whether to enable multiple scattering compensation.", + "type": "Bool", + "connection": { + "type": "ShaderOption", + "name": "o_specularF0_enableMultiScatterCompensation" + } + } + ], + "normal": [ + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface normal direction.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_normalMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just rely on vertex normals.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Normal map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Unwrapped", + "connection": { + "type": "ShaderInput", + "name": "m_normalMapUvIndex" + } + }, + { + "name": "flipX", + "displayName": "Flip X Channel", + "description": "Flip tangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "name": "m_flipNormalX" + } + }, + { + "name": "flipY", + "displayName": "Flip Y Channel", + "description": "Flip bitangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "name": "m_flipNormalY" + } + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Strength factor for scaling the values", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_normalFactor" + } + } + ], + "occlusion": [ + { + "name": "diffuseTextureMap", + "displayName": "Diffuse AO", + "description": "Texture for defining occlusion area for diffuse ambient lighting.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_diffuseOcclusionMap" + } + }, + { + "name": "diffuseUseTexture", + "displayName": " Use Texture", + "description": "Whether to use the Diffuse AO map.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "diffuseTextureMapUv", + "displayName": " UV", + "description": "Diffuse AO map UV set.", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_diffuseOcclusionMapUvIndex" + } + }, + { + "name": "diffuseFactor", + "displayName": " Factor", + "description": "Strength factor for scaling the values of Diffuse AO", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_diffuseOcclusionFactor" + } + }, + { + "name": "specularTextureMap", + "displayName": "Specular Cavity", + "description": "Texture for defining occlusion area for specular lighting.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_specularOcclusionMap" + } + }, + { + "name": "specularUseTexture", + "displayName": " Use Texture", + "description": "Whether to use the Specular Cavity map.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "specularTextureMapUv", + "displayName": " UV", + "description": "Specular Cavity map UV set.", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_specularOcclusionMapUvIndex" + } + }, + { + "name": "specularFactor", + "displayName": " Factor", + "description": "Strength factor for scaling the values of Specular Cavity", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_specularOcclusionFactor" + } + } + ], + "subsurfaceScattering": [ + { + "name": "enableSubsurfaceScattering", + "displayName": "Subsurface Scattering", + "description": "Enable subsurface scattering feature, this will disable metallic and parallax mapping property due to incompatibility", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_enableSubsurfaceScattering" + } + }, + { + "name": "subsurfaceScatterFactor", + "displayName": " Factor", + "description": "Strength factor for scaling percentage of subsurface scattering effect applied", + "type": "float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_subsurfaceScatteringFactor" + } + }, + { + "name": "influenceMap", + "displayName": " Influence Map", + "description": "Texture for controlling the strength of subsurface scattering", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_subsurfaceScatteringInfluenceMap" + } + }, + { + "name": "useInfluenceMap", + "displayName": " Use Influence Map", + "description": "Whether to use the influence map.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "influenceMapUv", + "displayName": " UV", + "description": "Influence map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Unwrapped", + "connection": { + "type": "ShaderInput", + "name": "m_subsurfaceScatteringInfluenceMapUvIndex" + } + }, + { + "name": "scatterColor", + "displayName": " Scatter color", + "description": "Color of volume light traveled through", + "type": "Color", + "defaultValue": [ 1.0, 0.27, 0.13 ] + }, + { + "name": "scatterDistance", + "displayName": " Scatter distance", + "description": "How far light traveled inside the volume", + "type": "float", + "defaultValue": 8, + "min": 0.0, + "softMax": 20.0 + }, + { + "name": "quality", + "displayName": " Quality", + "description": "How much percent of sample will be used for each pixel, more samples improve quality and reduce artifacts, especially when the scatter distance is relatively large, but slow down computation time, 1.0 = full set 200 samples per pixel", + "type": "float", + "defaultValue": 0.4, + "min": 0.2, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_subsurfaceScatteringQuality" + } + }, + { + "name": "transmissionMode", + "displayName": "Transmission", + "description": "Algorithm used for calculating transmission", + "type": "Enum", + "enumValues": [ "None", "ThickObject", "ThinObject" ], + "defaultValue": "None", + "connection": { + "type": "ShaderOption", + "name": "o_transmission_mode" + } + }, + { + "name": "thickness", + "displayName": " Thickness", + "description": "Normalized global thickness, the maxima between this value (multiplied by thickness map if enabled) and thickness from shadow map (if applicable) will be used as final thickness of pixel", + "type": "float", + "defaultValue": 0.5, + "min": 0.0, + "max": 1.0 + }, + { + "name": "thicknessMap", + "displayName": " Thickness Map", + "description": "Texture for controlling per pixel thickness", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_transmissionThicknessMap" + } + }, + { + "name": "useThicknessMap", + "displayName": " Use Thickness Map", + "description": "Whether to use the thickness map", + "type": "Bool", + "defaultValue": true + }, + { + "name": "thicknessMapUv", + "displayName": " UV", + "description": "Thickness map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Unwrapped", + "connection": { + "type": "ShaderInput", + "name": "m_transmissionThicknessMapUvIndex" + } + }, + { + "name": "transmissionTint", + "displayName": " Transmission Tint", + "description": "Color of the volume light traveling through", + "type": "Color", + "defaultValue": [ 1.0, 0.8, 0.6 ] + }, + { + "name": "transmissionPower", + "displayName": " Power", + "description": "How much transmitted light scatter radially ", + "type": "float", + "defaultValue": 6.0, + "min": 0.0, + "softMax": 20.0 + }, + { + "name": "transmissionDistortion", + "displayName": " Distortion", + "description": "How much light direction distorted towards surface normal", + "type": "float", + "defaultValue": 0.1, + "min": 0.0, + "max": 1.0 + }, + { + "name": "transmissionAttenuation", + "displayName": " Attenuation", + "description": "How fast transmitted light fade with thickness", + "type": "float", + "defaultValue": 4.0, + "min": 0.0, + "softMax": 20.0 + }, + { + "name": "transmissionScale", + "displayName": " Scale", + "description": "Strength of transmission", + "type": "float", + "defaultValue": 3.0, + "min": 0.0, + "softMax": 20.0 + } + ], + "wrinkleLayers": [ + { + "name": "enable", + "displayName": "Enable Wrinkle Layers", + "description": "Enable wrinkle layers for morph animations, using vertex color blend weights.", + "type": "Bool", + "defaultValue": false + }, + { + "name": "count", + "displayName": "Number Of Layers", + "description": "The number of wrinkle map layers to use. The blend values come from the 'COLOR0' vertex stream, where R/G/B/A correspond to wrinkle layers 1/2/3/4 respectively.", + "type": "UInt", + "defaultValue": 4, + "min": 1, + "max": 4 + }, + { + "name": "showBlendValues", + "displayName": "Show Blend Values", + "description": "Enable a debug mode that draws the blend values as red, green, blue, and white overlays.", + "type": "Bool", + "defaultValue": false + }, + { + "name": "enableBaseColor", + "displayName": "Enable Base Color Maps", + "description": "Enable support for blending the base color according to morph animations.", + "type": "Bool", + "defaultValue": false + }, + { + "name": "baseColorMap1", + "displayName": " Base Color 1", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_wrinkle_baseColor_texture1" + } + }, + { + "name": "baseColorMap2", + "displayName": " Base Color 2", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_wrinkle_baseColor_texture2" + } + }, + { + "name": "baseColorMap3", + "displayName": " Base Color 3", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_wrinkle_baseColor_texture3" + } + }, + { + "name": "baseColorMap4", + "displayName": " Base Color 4", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_wrinkle_baseColor_texture4" + } + }, + { + "name": "enableNormal", + "displayName": "Enable Normal Maps", + "description": "Enable support for blending the normal maps according to morph animations.", + "type": "Bool", + "defaultValue": false + }, + { + "name": "normalMap1", + "displayName": " Normals 1", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_wrinkle_normal_texture1" + } + }, + { + "name": "normalMap2", + "displayName": " Normals 2", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_wrinkle_normal_texture2" + } + }, + { + "name": "normalMap3", + "displayName": " Normals 3", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_wrinkle_normal_texture3" + } + }, + { + "name": "normalMap4", + "displayName": " Normals 4", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_wrinkle_normal_texture4" + } + } + ], + "detailLayerGroup": [ + { + "name": "enableDetailLayer", + "displayName": "Enable Detail Layer", + "description": "Enable detail layer for fine details and scratches", + "type": "Bool", + "defaultValue": false + }, + { + "name": "blendDetailFactor", + "displayName": "Blend Factor", + "description": "Scales the overall impact of the detail layer.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_detail_blendFactor" + } + }, + { + "name": "blendDetailMask", + "displayName": "Blend Mask", + "description": "Detailed blend mask for application of the detail maps.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_detail_blendMask_texture" + } + }, + { + "name": "enableDetailMaskTexture", + "displayName": " Use Texture", + "description": "Enable detail blend mask", + "type": "Bool", + "defaultValue": true + }, + { + "name": "blendDetailMaskUv", + "displayName": " Blend Mask UV", + "description": "Which UV set to use for sampling the detail blend mask", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Unwrapped", + "connection": { + "type": "ShaderInput", + "name": "m_detail_blendMask_uvIndex" + } + }, + { + "name": "textureMapUv", + "displayName": "Detail Map UVs", + "description": "Which UV set to use for detail map sampling", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Unwrapped", + "connection": { + "type": "ShaderInput", + "name": "m_detail_allMapsUvIndex" + } + }, + { + "name": "enableBaseColor", + "displayName": "Enable Base Color", + "description": "Enable detail blending for base color", + "type": "Bool", + "defaultValue": false + }, + { + "name": "baseColorDetailMap", + "displayName": " Texture", + "description": "Detailed Base Color Texture", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_detail_baseColor_texture" + } + }, + { + "name": "baseColorDetailBlend", + "displayName": " Blend Factor", + "description": "How much to blend the detail layer into the base color.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_detail_baseColor_factor" + } + }, + { + "name": "enableNormals", + "displayName": "Enable Normal", + "description": "Enable detail normal map to be used for fine detail normal such as scratches and small dents", + "type": "Bool", + "defaultValue": false + }, + { + "name": "normalDetailStrength", + "displayName": " Factor", + "description": "Strength factor for scaling the Detail Normal", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_detail_normal_factor" + } + }, + { + "name": "normalDetailMap", + "displayName": " Texture", + "description": "Detailed Normal map", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_detail_normal_texture" + } + }, + { + "name": "normalDetailFlipX", + "displayName": " Flip X Channel", + "description": "Flip Detail tangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "name": "m_detail_normal_flipX" + } + }, + { + "name": "normalDetailFlipY", + "displayName": " Flip Y Channel", + "description": "Flip Detail bitangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "name": "m_detail_normal_flipY" + } + } + ], + "detailUV": [ + { + "name": "center", + "displayName": "Center", + "description": "Center point for scaling and rotation transformations.", + "type": "vector2", + "vectorLabels": [ "U", "V" ], + "defaultValue": [ 0.5, 0.5 ] + }, + { + "name": "tileU", + "displayName": "Tile U", + "description": "Scales texture coordinates in V.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + }, + { + "name": "tileV", + "displayName": "Tile V", + "description": "Scales texture coordinates in V.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + }, + { + "name": "offsetU", + "displayName": "Offset U", + "description": "Offsets texture coordinates in the U direction.", + "type": "float", + "defaultValue": 0.0, + "min": -1.0, + "max": 1.0 + }, + { + "name": "offsetV", + "displayName": "Offset V", + "description": "Offsets texture coordinates in the V direction.", + "type": "float", + "defaultValue": 0.0, + "min": -1.0, + "max": 1.0 + }, + { + "name": "rotateDegrees", + "displayName": "Rotate", + "description": "Rotates the texture coordinates (degrees).", + "type": "float", + "defaultValue": 0.0, + "min": -180.0, + "max": 180.0, + "step": 1.0 + }, + { + "name": "scale", + "displayName": "Scale", + "description": "Scales texture coordinates in both U and V.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + } + ] + } }, "shaders": [ { diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype index 0789d758b8..88e845ddf0 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype @@ -10,1010 +10,1013 @@ } ], "propertyLayout": { - "propertySets": [ + "groups": [ { "name": "baseColor", "displayName": "Base Color", - "description": "Properties for configuring the surface reflected color for dielectrics or reflectance values for metals.", - "properties": [ - { - "name": "color", - "displayName": "Color", - "description": "Color is displayed as sRGB but the values are stored as linear color.", - "type": "Color", - "defaultValue": [ 1.0, 1.0, 1.0 ], - "connection": { - "type": "ShaderInput", - "name": "m_baseColor" - } - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the base color values. Zero (0.0) is black, white (1.0) is full color.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_baseColorFactor" - } - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "Base color texture map", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_baseColorMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Base color map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_baseColorMapUvIndex" - } - }, - { - "name": "textureBlendMode", - "displayName": "Texture Blend Mode", - "description": "Selects the equation to use when combining Color, Factor, and Texture.", - "type": "Enum", - "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], - "defaultValue": "Multiply", - "connection": { - "type": "ShaderOption", - "name": "o_baseColorTextureBlendMode" - } - } - ] + "description": "Properties for configuring the surface reflected color for dielectrics or reflectance values for metals." }, { "name": "metallic", "displayName": "Metallic", - "description": "Properties for configuring whether the surface is metallic or not.", - "properties": [ - { - "name": "factor", - "displayName": "Factor", - "description": "This value is linear, black is non-metal and white means raw metal.", - "type": "Float", - "defaultValue": 0.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_metallicFactor" - } - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_metallicMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture, or just default to the Factor value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Metallic map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_metallicMapUvIndex" - } - } - ] + "description": "Properties for configuring whether the surface is metallic or not." }, { "name": "roughness", "displayName": "Roughness", - "description": "Properties for configuring how rough the surface appears.", - "properties": [ - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface roughness.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_roughnessMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture, or just default to the Factor value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Roughness map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_roughnessMapUvIndex" - } - }, - { - // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. - "name": "lowerBound", - "displayName": "Lower Bound", - "description": "The roughness value that corresponds to black in the texture.", - "type": "Float", - "defaultValue": 0.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_roughnessLowerBound" - } - }, - { - // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. - "name": "upperBound", - "displayName": "Upper Bound", - "description": "The roughness value that corresponds to white in the texture.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_roughnessUpperBound" - } - }, - { - // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. - "name": "factor", - "displayName": "Factor", - "description": "Controls the roughness value", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_roughnessFactor" - } - } - ] + "description": "Properties for configuring how rough the surface appears." }, { "name": "specularF0", "displayName": "Specular Reflectance f0", - "description": "The constant f0 represents the specular reflectance at normal incidence (Fresnel 0 Angle). Used to adjust reflectance of non-metal surfaces.", - "properties": [ - { - "name": "factor", - "displayName": "Factor", - "description": "The default IOR is 1.5, which gives you 0.04 (4% of light reflected at 0 degree angle for dielectric materials). F0 values lie in the range 0-0.08, so that is why the default F0 slider is set on 0.5.", - "type": "Float", - "defaultValue": 0.5, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_specularF0Factor" - } - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface reflectance.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_specularF0Map" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture, or just default to the Factor value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Specular reflection map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_specularF0MapUvIndex" - } - }, - // Consider moving this to the "general" group to be consistent with StandardMultilayerPBR - { - "name": "enableMultiScatterCompensation", - "displayName": "Multiscattering Compensation", - "description": "Whether to enable multiple scattering compensation.", - "type": "Bool", - "connection": { - "type": "ShaderOption", - "name": "o_specularF0_enableMultiScatterCompensation" - } - } - ] + "description": "The constant f0 represents the specular reflectance at normal incidence (Fresnel 0 Angle). Used to adjust reflectance of non-metal surfaces." }, { "name": "normal", "displayName": "Normal", - "description": "Properties related to configuring surface normal.", - "properties": [ - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface normal direction.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_normalMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture, or just rely on vertex normals.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Normal map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_normalMapUvIndex" - } - }, - { - "name": "flipX", - "displayName": "Flip X Channel", - "description": "Flip tangent direction for this normal map.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderInput", - "name": "m_flipNormalX" - } - }, - { - "name": "flipY", - "displayName": "Flip Y Channel", - "description": "Flip bitangent direction for this normal map.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderInput", - "name": "m_flipNormalY" - } - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the values", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_normalFactor" - } - } - ] + "description": "Properties related to configuring surface normal." }, { "name": "occlusion", "displayName": "Occlusion", - "description": "Properties for baked textures that represent geometric occlusion of light.", - "properties": [ - { - "name": "diffuseTextureMap", - "displayName": "Diffuse AO", - "description": "Texture for defining occlusion area for diffuse ambient lighting.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_diffuseOcclusionMap" - } - }, - { - "name": "diffuseUseTexture", - "displayName": " Use Texture", - "description": "Whether to use the Diffuse AO map.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "diffuseTextureMapUv", - "displayName": " UV", - "description": "Diffuse AO map UV set.", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_diffuseOcclusionMapUvIndex" - } - }, - { - "name": "diffuseFactor", - "displayName": " Factor", - "description": "Strength factor for scaling the values of Diffuse AO", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_diffuseOcclusionFactor" - } - }, - { - "name": "specularTextureMap", - "displayName": "Specular Cavity", - "description": "Texture for defining occlusion area for specular lighting.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_specularOcclusionMap" - } - }, - { - "name": "specularUseTexture", - "displayName": " Use Texture", - "description": "Whether to use the Specular Cavity map.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "specularTextureMapUv", - "displayName": " UV", - "description": "Specular Cavity map UV set.", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_specularOcclusionMapUvIndex" - } - }, - { - "name": "specularFactor", - "displayName": " Factor", - "description": "Strength factor for scaling the values of Specular Cavity", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "softMax": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_specularOcclusionFactor" - } - } - ] + "description": "Properties for baked textures that represent geometric occlusion of light." }, { "name": "emissive", "displayName": "Emissive", - "description": "Properties to add light emission, independent of other lights in the scene.", - "properties": [ - { - "name": "enable", - "displayName": "Enable", - "description": "Enable the emissive group", - "type": "Bool", - "defaultValue": false - }, - { - "name": "unit", - "displayName": "Units", - "description": "The photometric units of the Intensity property.", - "type": "Enum", - "enumValues": ["Ev100"], - "defaultValue": "Ev100" - }, - { - "name": "color", - "displayName": "Color", - "description": "Color is displayed as sRGB but the values are stored as linear color.", - "type": "Color", - "defaultValue": [ 1.0, 1.0, 1.0 ], - "connection": { - "type": "ShaderInput", - "name": "m_emissiveColor" - } - }, - { - "name": "intensity", - "displayName": "Intensity", - "description": "The amount of energy emitted.", - "type": "Float", - "defaultValue": 4, - "min": -10, - "max": 20, - "softMin": -6, - "softMax": 16 - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining emissive area.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_emissiveMap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the texture.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Emissive map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_emissiveMapUvIndex" - } - } - ] + "description": "Properties to add light emission, independent of other lights in the scene." }, { "name": "clearCoat", "displayName": "Clear Coat", - "description": "Properties for configuring gloss clear coat", - "properties": [ - { - "name": "enable", - "displayName": "Enable", - "description": "Enable clear coat", - "type": "Bool", - "defaultValue": false - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the percentage of effect applied", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatFactor" - } - }, - { - "name": "influenceMap", - "displayName": " Influence Map", - "description": "Strength factor texture", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatInfluenceMap" - } - }, - { - "name": "useInfluenceMap", - "displayName": " Use Texture", - "description": "Whether to use the texture, or just default to the Factor value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "influenceMapUv", - "displayName": " UV", - "description": "Strength factor map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatInfluenceMapUvIndex" - } - }, - { - "name": "roughness", - "displayName": "Roughness", - "description": "Clear coat layer roughness", - "type": "Float", - "defaultValue": 0.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatRoughness" - } - }, - { - "name": "roughnessMap", - "displayName": " Roughness Map", - "description": "Texture for defining surface roughness", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatRoughnessMap" - } - }, - { - "name": "useRoughnessMap", - "displayName": " Use Texture", - "description": "Whether to use the texture, or just default to the roughness value.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "roughnessMapUv", - "displayName": " UV", - "description": "Roughness map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatRoughnessMapUvIndex" - } - }, - { - "name": "normalStrength", - "displayName": "Normal Strength", - "description": "Scales the impact of the clear coat normal map", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 2.0, - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatNormalStrength" - } - }, - { - "name": "normalMap", - "displayName": "Normal Map", - "description": "Normal map for clear coat layer, as top layer material clear coat doesn't affect by base layer normal map", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatNormalMap" - } - }, - { - "name": "useNormalMap", - "displayName": " Use Texture", - "description": "Whether to use the normal map", - "type": "Bool", - "defaultValue": true - }, - { - "name": "normalMapUv", - "displayName": " UV", - "description": "Normal map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_clearCoatNormalMapUvIndex" - } - } - ] - }, + "description": "Properties for configuring gloss clear coat" + }, { "name": "parallax", "displayName": "Displacement", - "description": "Properties for parallax effect produced by a height map.", - "properties": [ - { - "name": "textureMap", - "displayName": "Height Map", - "description": "Displacement height map to create parallax effect.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_heightmap" - } - }, - { - "name": "useTexture", - "displayName": "Use Texture", - "description": "Whether to use the height map.", - "type": "Bool", - "defaultValue": true - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Height map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_parallaxUvIndex" - } - }, - { - "name": "factor", - "displayName": "Height Map Scale", - "description": "The total height of the height map in local model units.", - "type": "Float", - "defaultValue": 0.05, - "min": 0.0, - "softMax": 0.1, - "connection": { - "type": "ShaderInput", - "name": "m_heightmapScale" - } - }, - { - "name": "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", - "name": "m_heightmapOffset" - } - }, - { - "name": "algorithm", - "displayName": "Algorithm", - "description": "Select the algorithm to use for parallax mapping.", - "type": "Enum", - "enumValues": [ "Basic", "Steep", "POM", "Relief", "ContactRefinement" ], - "defaultValue": "POM", - "connection": { - "type": "ShaderOption", - "name": "o_parallax_algorithm" - } - }, - { - "name": "quality", - "displayName": "Quality", - "description": "Quality of parallax mapping.", - "type": "Enum", - "enumValues": [ "Low", "Medium", "High", "Ultra" ], - "defaultValue": "Low", - "connection": { - "type": "ShaderOption", - "name": "o_parallax_quality" - } - }, - { - "name": "pdo", - "displayName": "Pixel Depth Offset", - "description": "Enable PDO to offset the original pixel depths. This will affect any shaders using depth, for example, when receiving shadows.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_parallax_enablePixelDepthOffset" - } - }, - { - "name": "showClipping", - "displayName": "Show Clipping", - "description": "Highlight areas where the height map is clipped by the mesh surface.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_parallax_highlightClipping" - } - } - ] + "description": "Properties for parallax effect produced by a height map." }, { "name": "opacity", "displayName": "Opacity", - "description": "Properties for configuring the materials transparency.", - "properties": [ - { - "name": "mode", - "displayName": "Opacity Mode", - "description": "Indicates the general approach how transparency is to be applied.", - "type": "Enum", - "enumValues": [ "Opaque", "Cutout", "Blended", "TintedTransparent" ], - "defaultValue": "Opaque", - "connection": { - "type": "ShaderOption", - "name": "o_opacity_mode" - } - }, - { - "name": "alphaSource", - "displayName": "Alpha Source", - "description": "Indicates whether to get the opacity texture from the Base Color map (Packed) or from a separate greyscale texture (Split).", - "type": "Enum", - "enumValues": [ "Packed", "Split", "None" ], - "defaultValue": "Packed", - "connection": { - "type": "ShaderOption", - "name": "o_opacity_source" - } - }, - { - "name": "textureMap", - "displayName": "Texture", - "description": "Texture for defining surface opacity.", - "type": "Image", - "connection": { - "type": "ShaderInput", - "name": "m_opacityMap" - } - }, - { - "name": "textureMapUv", - "displayName": "UV", - "description": "Opacity map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "name": "m_opacityMapUvIndex" - } - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Factor for cutout threshold and blending", - "type": "Float", - "min": 0.0, - "max": 1.0, - "defaultValue": 0.5, - "connection": { - "type": "ShaderInput", - "name": "m_opacityFactor" - } - }, - { - "name": "alphaAffectsSpecular", - "displayName": "Alpha affects specular", - "description": "How much the alpha value should also affect specular reflection. This should be 0.0 for materials where light can transmit through their physical surface (like glass), but 1.0 when alpha determines the very presence of a surface (like hair or grass)", - "type": "float", - "min": 0.0, - "max": 1.0, - "defaultValue": 0.0, - "connection": { - "type": "ShaderInput", - "name": "m_opacityAffectsSpecularFactor" - } - } - ] + "description": "Properties for configuring the materials transparency." }, { "name": "uv", "displayName": "UVs", - "description": "Properties for configuring UV transforms.", - "properties": [ - { - "name": "center", - "displayName": "Center", - "description": "Center point for scaling and rotation transformations.", - "type": "vector2", - "vectorLabels": [ "U", "V" ], - "defaultValue": [ 0.5, 0.5 ] - }, - { - "name": "tileU", - "displayName": "Tile U", - "description": "Scales texture coordinates in U.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - }, - { - "name": "tileV", - "displayName": "Tile V", - "description": "Scales texture coordinates in V.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - }, - { - "name": "offsetU", - "displayName": "Offset U", - "description": "Offsets texture coordinates in the U direction.", - "type": "float", - "defaultValue": 0.0, - "min": -1.0, - "max": 1.0 - }, - { - "name": "offsetV", - "displayName": "Offset V", - "description": "Offsets texture coordinates in the V direction.", - "type": "float", - "defaultValue": 0.0, - "min": -1.0, - "max": 1.0 - }, - { - "name": "rotateDegrees", - "displayName": "Rotate", - "description": "Rotates the texture coordinates (degrees).", - "type": "float", - "defaultValue": 0.0, - "min": -180.0, - "max": 180.0, - "step": 1.0 - }, - { - "name": "scale", - "displayName": "Scale", - "description": "Scales texture coordinates in both U and V.", - "type": "float", - "defaultValue": 1.0, - "step": 0.1 - } - ] + "description": "Properties for configuring UV transforms." }, { // Note: this property group is used in the DiffuseGlobalIllumination pass, it is not read by the StandardPBR shader "name": "irradiance", "displayName": "Irradiance", - "description": "Properties for configuring the irradiance used in global illumination.", - "properties": [ - { - "name": "color", - "displayName": "Color", - "description": "Color is displayed as sRGB but the values are stored as linear color.", - "type": "Color", - "defaultValue": [ 1.0, 1.0, 1.0 ] - }, - { - "name": "factor", - "displayName": "Factor", - "description": "Strength factor for scaling the irradiance color values. Zero (0.0) is black, white (1.0) is full color.", - "type": "Float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0 - } - ] + "description": "Properties for configuring the irradiance used in global illumination." }, { "name": "general", "displayName": "General Settings", - "description": "General settings.", - "properties": [ - { - "name": "doubleSided", - "displayName": "Double-sided", - "description": "Whether to render back-faces or just front-faces.", - "type": "Bool" - }, - { - "name": "applySpecularAA", - "displayName": "Apply Specular AA", - "description": "Whether to apply specular anti-aliasing in the shader.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_applySpecularAA" - } - }, - { - "name": "enableShadows", - "displayName": "Enable Shadows", - "description": "Whether to use the shadow maps.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableShadows" - } - }, - { - "name": "enableDirectionalLights", - "displayName": "Enable Directional Lights", - "description": "Whether to use directional lights.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableDirectionalLights" - } - }, - { - "name": "enablePunctualLights", - "displayName": "Enable Punctual Lights", - "description": "Whether to use punctual lights.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enablePunctualLights" - } - }, - { - "name": "enableAreaLights", - "displayName": "Enable Area Lights", - "description": "Whether to use area lights.", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableAreaLights" - } - }, - { - "name": "enableIBL", - "displayName": "Enable IBL", - "description": "Whether to use Image Based Lighting (IBL).", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderOption", - "name": "o_enableIBL" - } - }, - { - "name": "forwardPassIBLSpecular", - "displayName": "Forward Pass IBL Specular", - "description": "Whether to apply IBL specular in the forward pass.", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "name": "o_materialUseForwardPassIBLSpecular" - } - } - ] + "description": "General settings." } - ] + ], + "properties": { + "general": [ + { + "name": "doubleSided", + "displayName": "Double-sided", + "description": "Whether to render back-faces or just front-faces.", + "type": "Bool" + }, + { + "name": "applySpecularAA", + "displayName": "Apply Specular AA", + "description": "Whether to apply specular anti-aliasing in the shader.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_applySpecularAA" + } + }, + { + "name": "enableShadows", + "displayName": "Enable Shadows", + "description": "Whether to use the shadow maps.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableShadows" + } + }, + { + "name": "enableDirectionalLights", + "displayName": "Enable Directional Lights", + "description": "Whether to use directional lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableDirectionalLights" + } + }, + { + "name": "enablePunctualLights", + "displayName": "Enable Punctual Lights", + "description": "Whether to use punctual lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enablePunctualLights" + } + }, + { + "name": "enableAreaLights", + "displayName": "Enable Area Lights", + "description": "Whether to use area lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableAreaLights" + } + }, + { + "name": "enableIBL", + "displayName": "Enable IBL", + "description": "Whether to use Image Based Lighting (IBL).", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "name": "o_enableIBL" + } + }, + { + "name": "forwardPassIBLSpecular", + "displayName": "Forward Pass IBL Specular", + "description": "Whether to apply IBL specular in the forward pass.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_materialUseForwardPassIBLSpecular" + } + } + ], + "baseColor": [ + { + "name": "color", + "displayName": "Color", + "description": "Color is displayed as sRGB but the values are stored as linear color.", + "type": "Color", + "defaultValue": [ 1.0, 1.0, 1.0 ], + "connection": { + "type": "ShaderInput", + "name": "m_baseColor" + } + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Strength factor for scaling the base color values. Zero (0.0) is black, white (1.0) is full color.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_baseColorFactor" + } + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "Base color texture map", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_baseColorMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Base color map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_baseColorMapUvIndex" + } + }, + { + "name": "textureBlendMode", + "displayName": "Texture Blend Mode", + "description": "Selects the equation to use when combining Color, Factor, and Texture.", + "type": "Enum", + "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], + "defaultValue": "Multiply", + "connection": { + "type": "ShaderOption", + "name": "o_baseColorTextureBlendMode" + } + } + ], + "metallic": [ + { + "name": "factor", + "displayName": "Factor", + "description": "This value is linear, black is non-metal and white means raw metal.", + "type": "Float", + "defaultValue": 0.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_metallicFactor" + } + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_metallicMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just default to the Factor value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Metallic map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_metallicMapUvIndex" + } + } + ], + "roughness": [ + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface roughness.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_roughnessMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just default to the Factor value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Roughness map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_roughnessMapUvIndex" + } + }, + { + // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. + "name": "lowerBound", + "displayName": "Lower Bound", + "description": "The roughness value that corresponds to black in the texture.", + "type": "Float", + "defaultValue": 0.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_roughnessLowerBound" + } + }, + { + // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. + "name": "upperBound", + "displayName": "Upper Bound", + "description": "The roughness value that corresponds to white in the texture.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_roughnessUpperBound" + } + }, + { + // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. + "name": "factor", + "displayName": "Factor", + "description": "Controls the roughness value", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_roughnessFactor" + } + } + ], + "specularF0": [ + { + "name": "factor", + "displayName": "Factor", + "description": "The default IOR is 1.5, which gives you 0.04 (4% of light reflected at 0 degree angle for dielectric materials). F0 values lie in the range 0-0.08, so that is why the default F0 slider is set on 0.5.", + "type": "Float", + "defaultValue": 0.5, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_specularF0Factor" + } + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface reflectance.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_specularF0Map" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just default to the Factor value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Specular reflection map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_specularF0MapUvIndex" + } + }, + // Consider moving this to the "general" group to be consistent with StandardMultilayerPBR + { + "name": "enableMultiScatterCompensation", + "displayName": "Multiscattering Compensation", + "description": "Whether to enable multiple scattering compensation.", + "type": "Bool", + "connection": { + "type": "ShaderOption", + "name": "o_specularF0_enableMultiScatterCompensation" + } + } + ], + "clearCoat": [ + { + "name": "enable", + "displayName": "Enable", + "description": "Enable clear coat", + "type": "Bool", + "defaultValue": false + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Strength factor for scaling the percentage of effect applied", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatFactor" + } + }, + { + "name": "influenceMap", + "displayName": " Influence Map", + "description": "Strength factor texture", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatInfluenceMap" + } + }, + { + "name": "useInfluenceMap", + "displayName": " Use Texture", + "description": "Whether to use the texture, or just default to the Factor value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "influenceMapUv", + "displayName": " UV", + "description": "Strength factor map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatInfluenceMapUvIndex" + } + }, + { + "name": "roughness", + "displayName": "Roughness", + "description": "Clear coat layer roughness", + "type": "Float", + "defaultValue": 0.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatRoughness" + } + }, + { + "name": "roughnessMap", + "displayName": " Roughness Map", + "description": "Texture for defining surface roughness", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatRoughnessMap" + } + }, + { + "name": "useRoughnessMap", + "displayName": " Use Texture", + "description": "Whether to use the texture, or just default to the roughness value.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "roughnessMapUv", + "displayName": " UV", + "description": "Roughness map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatRoughnessMapUvIndex" + } + }, + { + "name": "normalStrength", + "displayName": "Normal Strength", + "description": "Scales the impact of the clear coat normal map", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatNormalStrength" + } + }, + { + "name": "normalMap", + "displayName": "Normal Map", + "description": "Normal map for clear coat layer, as top layer material clear coat doesn't affect by base layer normal map", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatNormalMap" + } + }, + { + "name": "useNormalMap", + "displayName": " Use Texture", + "description": "Whether to use the normal map", + "type": "Bool", + "defaultValue": true + }, + { + "name": "normalMapUv", + "displayName": " UV", + "description": "Normal map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_clearCoatNormalMapUvIndex" + } + } + ], + "normal": [ + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface normal direction.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_normalMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture, or just rely on vertex normals.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Normal map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_normalMapUvIndex" + } + }, + { + "name": "flipX", + "displayName": "Flip X Channel", + "description": "Flip tangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "name": "m_flipNormalX" + } + }, + { + "name": "flipY", + "displayName": "Flip Y Channel", + "description": "Flip bitangent direction for this normal map.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderInput", + "name": "m_flipNormalY" + } + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Strength factor for scaling the values", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_normalFactor" + } + } + ], + "opacity": [ + { + "name": "mode", + "displayName": "Opacity Mode", + "description": "Indicates the general approach how transparency is to be applied.", + "type": "Enum", + "enumValues": [ "Opaque", "Cutout", "Blended", "TintedTransparent" ], + "defaultValue": "Opaque", + "connection": { + "type": "ShaderOption", + "name": "o_opacity_mode" + } + }, + { + "name": "alphaSource", + "displayName": "Alpha Source", + "description": "Indicates whether to get the opacity texture from the Base Color map (Packed) or from a separate greyscale texture (Split).", + "type": "Enum", + "enumValues": [ "Packed", "Split", "None" ], + "defaultValue": "Packed", + "connection": { + "type": "ShaderOption", + "name": "o_opacity_source" + } + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining surface opacity.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_opacityMap" + } + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Opacity map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_opacityMapUvIndex" + } + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Factor for cutout threshold and blending", + "type": "Float", + "min": 0.0, + "max": 1.0, + "defaultValue": 0.5, + "connection": { + "type": "ShaderInput", + "name": "m_opacityFactor" + } + }, + { + "name": "alphaAffectsSpecular", + "displayName": "Alpha affects specular", + "description": "How much the alpha value should also affect specular reflection. This should be 0.0 for materials where light can transmit through their physical surface (like glass), but 1.0 when alpha determines the very presence of a surface (like hair or grass)", + "type": "float", + "min": 0.0, + "max": 1.0, + "defaultValue": 0.0, + "connection": { + "type": "ShaderInput", + "name": "m_opacityAffectsSpecularFactor" + } + } + ], + "uv": [ + { + "name": "center", + "displayName": "Center", + "description": "Center point for scaling and rotation transformations.", + "type": "vector2", + "vectorLabels": [ "U", "V" ], + "defaultValue": [ 0.5, 0.5 ] + }, + { + "name": "tileU", + "displayName": "Tile U", + "description": "Scales texture coordinates in U.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + }, + { + "name": "tileV", + "displayName": "Tile V", + "description": "Scales texture coordinates in V.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + }, + { + "name": "offsetU", + "displayName": "Offset U", + "description": "Offsets texture coordinates in the U direction.", + "type": "float", + "defaultValue": 0.0, + "min": -1.0, + "max": 1.0 + }, + { + "name": "offsetV", + "displayName": "Offset V", + "description": "Offsets texture coordinates in the V direction.", + "type": "float", + "defaultValue": 0.0, + "min": -1.0, + "max": 1.0 + }, + { + "name": "rotateDegrees", + "displayName": "Rotate", + "description": "Rotates the texture coordinates (degrees).", + "type": "float", + "defaultValue": 0.0, + "min": -180.0, + "max": 180.0, + "step": 1.0 + }, + { + "name": "scale", + "displayName": "Scale", + "description": "Scales texture coordinates in both U and V.", + "type": "float", + "defaultValue": 1.0, + "step": 0.1 + } + ], + "occlusion": [ + { + "name": "diffuseTextureMap", + "displayName": "Diffuse AO", + "description": "Texture for defining occlusion area for diffuse ambient lighting.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_diffuseOcclusionMap" + } + }, + { + "name": "diffuseUseTexture", + "displayName": " Use Texture", + "description": "Whether to use the Diffuse AO map.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "diffuseTextureMapUv", + "displayName": " UV", + "description": "Diffuse AO map UV set.", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_diffuseOcclusionMapUvIndex" + } + }, + { + "name": "diffuseFactor", + "displayName": " Factor", + "description": "Strength factor for scaling the values of Diffuse AO", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_diffuseOcclusionFactor" + } + }, + { + "name": "specularTextureMap", + "displayName": "Specular Cavity", + "description": "Texture for defining occlusion area for specular lighting.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_specularOcclusionMap" + } + }, + { + "name": "specularUseTexture", + "displayName": " Use Texture", + "description": "Whether to use the Specular Cavity map.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "specularTextureMapUv", + "displayName": " UV", + "description": "Specular Cavity map UV set.", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_specularOcclusionMapUvIndex" + } + }, + { + "name": "specularFactor", + "displayName": " Factor", + "description": "Strength factor for scaling the values of Specular Cavity", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "softMax": 2.0, + "connection": { + "type": "ShaderInput", + "name": "m_specularOcclusionFactor" + } + } + ], + "emissive": [ + { + "name": "enable", + "displayName": "Enable", + "description": "Enable the emissive group", + "type": "Bool", + "defaultValue": false + }, + { + "name": "unit", + "displayName": "Units", + "description": "The photometric units of the Intensity property.", + "type": "Enum", + "enumValues": ["Ev100"], + "defaultValue": "Ev100" + }, + { + "name": "color", + "displayName": "Color", + "description": "Color is displayed as sRGB but the values are stored as linear color.", + "type": "Color", + "defaultValue": [ 1.0, 1.0, 1.0 ], + "connection": { + "type": "ShaderInput", + "name": "m_emissiveColor" + } + }, + { + "name": "intensity", + "displayName": "Intensity", + "description": "The amount of energy emitted.", + "type": "Float", + "defaultValue": 4, + "min": -10, + "max": 20, + "softMin": -6, + "softMax": 16 + }, + { + "name": "textureMap", + "displayName": "Texture", + "description": "Texture for defining emissive area.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_emissiveMap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Emissive map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_emissiveMapUvIndex" + } + } + ], + "parallax": [ + { + "name": "textureMap", + "displayName": "Height Map", + "description": "Displacement height map to create parallax effect.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "name": "m_heightmap" + } + }, + { + "name": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the height map.", + "type": "Bool", + "defaultValue": true + }, + { + "name": "textureMapUv", + "displayName": "UV", + "description": "Height map UV set", + "type": "Enum", + "enumIsUv": true, + "defaultValue": "Tiled", + "connection": { + "type": "ShaderInput", + "name": "m_parallaxUvIndex" + } + }, + { + "name": "factor", + "displayName": "Height Map Scale", + "description": "The total height of the height map in local model units.", + "type": "Float", + "defaultValue": 0.05, + "min": 0.0, + "softMax": 0.1, + "connection": { + "type": "ShaderInput", + "name": "m_heightmapScale" + } + }, + { + "name": "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", + "name": "m_heightmapOffset" + } + }, + { + "name": "algorithm", + "displayName": "Algorithm", + "description": "Select the algorithm to use for parallax mapping.", + "type": "Enum", + "enumValues": [ "Basic", "Steep", "POM", "Relief", "ContactRefinement" ], + "defaultValue": "POM", + "connection": { + "type": "ShaderOption", + "name": "o_parallax_algorithm" + } + }, + { + "name": "quality", + "displayName": "Quality", + "description": "Quality of parallax mapping.", + "type": "Enum", + "enumValues": [ "Low", "Medium", "High", "Ultra" ], + "defaultValue": "Low", + "connection": { + "type": "ShaderOption", + "name": "o_parallax_quality" + } + }, + { + "name": "pdo", + "displayName": "Pixel Depth Offset", + "description": "Enable PDO to offset the original pixel depths. This will affect any shaders using depth, for example, when receiving shadows.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_parallax_enablePixelDepthOffset" + } + }, + { + "name": "showClipping", + "displayName": "Show Clipping", + "description": "Highlight areas where the height map is clipped by the mesh surface.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "name": "o_parallax_highlightClipping" + } + } + ], + "irradiance": [ + // Note: this property group is used in the DiffuseGlobalIllumination pass and not by the main forward shader + { + "name": "color", + "displayName": "Color", + "description": "Color is displayed as sRGB but the values are stored as linear color.", + "type": "Color", + "defaultValue": [ 1.0, 1.0, 1.0 ] + }, + { + "name": "factor", + "displayName": "Factor", + "description": "Strength factor for scaling the irradiance color values. Zero (0.0) is black, white (1.0) is full color.", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0 + } + ] + } }, "shaders": [ { diff --git a/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick.materialtype b/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick.materialtype index e29599c45c..cf0bffa058 100644 --- a/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick.materialtype +++ b/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick.materialtype @@ -2,174 +2,176 @@ "description": "This is an example of a custom material type using Atom's PBR shading model: procedurally generated brick or tile.", "version": 3, "propertyLayout": { - "propertySets": [ + "groups": [ { "name": "shape", "displayName": "Shape", - "description": "Properties for configuring size, shape, and position of the bricks.", - "properties": [ - { - "name": "brickWidth", - "displayName": "Brick Width", - "description": "The width of each brick.", - "type": "Float", - "defaultValue": 0.1, - "min": 0.0, - "softMax": 0.2, - "step": 0.001, - "connection": { - "type": "ShaderInput", - "name": "m_brickWidth" - } - }, - { - "name": "brickHeight", - "displayName": "Brick Height", - "description": "The height of each brick.", - "type": "Float", - "defaultValue": 0.05, - "min": 0.0, - "softMax": 0.2, - "step": 0.001, - "connection": { - "type": "ShaderInput", - "name": "m_brickHeight" - } - }, - { - "name": "brickOffset", - "displayName": "Offset", - "description": "The offset of each stack of bricks as a percentage of brick width.", - "type": "Float", - "defaultValue": 0.5, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_brickOffset" - } - }, - { - "name": "lineWidth", - "displayName": "Line Width", - "description": "The width of the grout lines.", - "type": "Float", - "defaultValue": 0.01, - "min": 0.0, - "softMax": 0.02, - "step": 0.0001, - "connection": { - "type": "ShaderInput", - "name": "m_lineWidth" - } - }, - { - "name": "lineDepth", - "displayName": "Line Depth", - "description": "The depth of the grout lines.", - "type": "Float", - "defaultValue": 0.01, - "min": 0.0, - "softMax": 0.02, - "connection": { - "type": "ShaderInput", - "name": "m_lineDepth" - } - } - ] + "description": "Properties for configuring size, shape, and position of the bricks." }, { "name": "appearance", "displayName": "Appearance", - "description": "Properties for configuring the appearance of the bricks and grout lines.", - "properties": [ - { - "name": "noiseTexture", - "type": "Image", - "defaultValue": "TestData/Textures/noise512.png", - "visibility": "Hidden", - "connection": { - "type": "ShaderInput", - "name": "m_noise" - } - }, - { - "name": "brickColor", - "displayName": "Brick Color", - "description": "The color of the bricks.", - "type": "Color", - "defaultValue": [1.0,1.0,1.0], - "connection": { - "type": "ShaderInput", - "name": "m_brickColor" - } - }, - { - "name": "brickColorNoise", - "displayName": "Brick Color Noise", - "description": "Scale the variation of brick color.", - "type": "Float", - "defaultValue": 0.25, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_brickNoiseFactor" - } - }, - { - "name": "lineColor", - "displayName": "Line Color", - "description": "The color of the grout lines.", - "type": "Color", - "defaultValue": [0.5,0.5,0.5], - "connection": { - "type": "ShaderInput", - "name": "m_lineColor" - } - }, - { - "name": "lineColorNoise", - "displayName": "Line Color Noise", - "description": "Scale the variation of grout line color.", - "type": "Float", - "defaultValue": 0.25, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_lineNoiseFactor" - } - }, - { - "name": "brickColorBleed", - "displayName": "Brick Color Bleed", - "description": "Distance into the grout line that the brick color will continue.", - "type": "Float", - "defaultValue": 0.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_brickColorBleed" - } - }, - { - "name": "ao", - "displayName": "Ambient Occlusion", - "description": "The strength of baked ambient occlusion in the grout lines.", - "type": "Float", - "defaultValue": 0.5, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "name": "m_aoFactor" - } - } - ] + "description": "Properties for configuring the appearance of the bricks and grout lines." } - ] + ], + "properties": { + "shape": [ + { + "name": "brickWidth", + "displayName": "Brick Width", + "description": "The width of each brick.", + "type": "Float", + "defaultValue": 0.1, + "min": 0.0, + "softMax": 0.2, + "step": 0.001, + "connection": { + "type": "ShaderInput", + "name": "m_brickWidth" + } + }, + { + "name": "brickHeight", + "displayName": "Brick Height", + "description": "The height of each brick.", + "type": "Float", + "defaultValue": 0.05, + "min": 0.0, + "softMax": 0.2, + "step": 0.001, + "connection": { + "type": "ShaderInput", + "name": "m_brickHeight" + } + }, + { + "name": "brickOffset", + "displayName": "Offset", + "description": "The offset of each stack of bricks as a percentage of brick width.", + "type": "Float", + "defaultValue": 0.5, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_brickOffset" + } + }, + { + "name": "lineWidth", + "displayName": "Line Width", + "description": "The width of the grout lines.", + "type": "Float", + "defaultValue": 0.01, + "min": 0.0, + "softMax": 0.02, + "step": 0.0001, + "connection": { + "type": "ShaderInput", + "name": "m_lineWidth" + } + }, + { + "name": "lineDepth", + "displayName": "Line Depth", + "description": "The depth of the grout lines.", + "type": "Float", + "defaultValue": 0.01, + "min": 0.0, + "softMax": 0.02, + "connection": { + "type": "ShaderInput", + "name": "m_lineDepth" + } + } + ], + "appearance": [ + { + "name": "noiseTexture", + "type": "Image", + "defaultValue": "TestData/Textures/noise512.png", + "visibility": "Hidden", + "connection": { + "type": "ShaderInput", + "name": "m_noise" + } + }, + { + "name": "brickColor", + "displayName": "Brick Color", + "description": "The color of the bricks.", + "type": "Color", + "defaultValue": [1.0,1.0,1.0], + "connection": { + "type": "ShaderInput", + "name": "m_brickColor" + } + }, + { + "name": "brickColorNoise", + "displayName": "Brick Color Noise", + "description": "Scale the variation of brick color.", + "type": "Float", + "defaultValue": 0.25, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_brickNoiseFactor" + } + }, + { + "name": "lineColor", + "displayName": "Line Color", + "description": "The color of the grout lines.", + "type": "Color", + "defaultValue": [0.5,0.5,0.5], + "connection": { + "type": "ShaderInput", + "name": "m_lineColor" + } + }, + { + "name": "lineColorNoise", + "displayName": "Line Color Noise", + "description": "Scale the variation of grout line color.", + "type": "Float", + "defaultValue": 0.25, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_lineNoiseFactor" + } + }, + { + "name": "brickColorBleed", + "displayName": "Brick Color Bleed", + "description": "Distance into the grout line that the brick color will continue.", + "type": "Float", + "defaultValue": 0.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_brickColorBleed" + } + }, + { + "name": "ao", + "displayName": "Ambient Occlusion", + "description": "The strength of baked ambient occlusion in the grout lines.", + "type": "Float", + "defaultValue": 0.5, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "name": "m_aoFactor" + } + } + ] + } }, "shaders": [ { @@ -185,5 +187,8 @@ { "file": "Shaders/Depth/DepthPass.shader" } + ], + "functors": [ ] -} \ No newline at end of file +} + From b9a80dee325164cc035cbd1728de7f4a364905b3 Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Thu, 27 Jan 2022 14:17:09 -0800 Subject: [PATCH 16/53] Fixed newline at end of file. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Common/Assets/Materials/Types/EnhancedPBR.materialtype | 2 +- .../Feature/Common/Assets/Materials/Types/Skin.materialtype | 2 +- .../Common/Assets/Materials/Types/StandardPBR.materialtype | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype index eee9a0fc88..d4b5882f21 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype @@ -1687,4 +1687,4 @@ "UV0": "Tiled", "UV1": "Unwrapped" } -} \ No newline at end of file +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype index 15eaf94df6..a49eba7975 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype @@ -1101,4 +1101,4 @@ "UV0": "Tiled", "UV1": "Unwrapped" } -} \ No newline at end of file +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype index 88e845ddf0..f324394309 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype @@ -1199,4 +1199,4 @@ "UV0": "Tiled", "UV1": "Unwrapped" } -} \ No newline at end of file +} From 1de540ae3f2adb73650a740a8251bd37b8b0d8a2 Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Thu, 27 Jan 2022 14:48:50 -0800 Subject: [PATCH 17/53] Updated code to use span instead of array_view as this was replaced on the development branch. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../RPI.Edit/Material/MaterialTypeSourceData.h | 8 ++++---- .../RPI.Edit/Material/MaterialPropertyId.cpp | 4 ++-- .../RPI.Edit/Material/MaterialTypeSourceData.cpp | 14 +++++++------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h index 6c439195f1..35cc7fe09a 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h @@ -308,11 +308,11 @@ namespace AZ private: - const PropertySet* FindPropertySet(AZStd::array_view parsedPropertySetId, AZStd::array_view> inPropertySetList) const; - PropertySet* FindPropertySet(AZStd::array_view parsedPropertySetId, AZStd::array_view> inPropertySetList); + const PropertySet* FindPropertySet(AZStd::span parsedPropertySetId, AZStd::span> inPropertySetList) const; + PropertySet* FindPropertySet(AZStd::span parsedPropertySetId, AZStd::span> inPropertySetList); - const PropertyDefinition* FindProperty(AZStd::array_view parsedPropertyId, AZStd::array_view> inPropertySetList) const; - PropertyDefinition* FindProperty(AZStd::array_view parsedPropertyId, AZStd::array_view> inPropertySetList); + const PropertyDefinition* FindProperty(AZStd::span parsedPropertyId, AZStd::span> inPropertySetList) const; + PropertyDefinition* FindProperty(AZStd::span parsedPropertyId, AZStd::span> inPropertySetList); // Function overloads for recursion, returns false to indicate that recursion should end. bool EnumeratePropertySets(const EnumeratePropertySetsCallback& callback, AZStd::string propertyIdContext, const AZStd::vector>& inPropertySetList) const; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyId.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyId.cpp index 1a9b91f431..1f5581e417 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyId.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyId.cpp @@ -88,7 +88,7 @@ namespace AZ { } - MaterialPropertyId::MaterialPropertyId(const AZStd::array_view groupNames, AZStd::string_view propertyName) + MaterialPropertyId::MaterialPropertyId(const AZStd::span groupNames, AZStd::string_view propertyName) { for (const auto& name : groupNames) { @@ -118,7 +118,7 @@ namespace AZ } } - MaterialPropertyId::MaterialPropertyId(const AZStd::array_view names) + MaterialPropertyId::MaterialPropertyId(const AZStd::span names) { for (const auto& name : names) { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp index 032d36636e..4aa0a77370 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp @@ -247,7 +247,7 @@ namespace AZ return parentPropertySet->AddProperty(splitPropertyId[1]); } - const MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::FindPropertySet(AZStd::array_view parsedPropertySetId, AZStd::array_view> inPropertySetList) const + const MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::FindPropertySet(AZStd::span parsedPropertySetId, AZStd::span> inPropertySetList) const { for (const auto& propertySet : inPropertySetList) { @@ -261,7 +261,7 @@ namespace AZ } else { - AZStd::array_view subPath{parsedPropertySetId.begin() + 1, parsedPropertySetId.end()}; + AZStd::span subPath{parsedPropertySetId.begin() + 1, parsedPropertySetId.end()}; if (!subPath.empty()) { @@ -277,7 +277,7 @@ namespace AZ return nullptr; } - MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::FindPropertySet(AZStd::array_view parsedPropertySetId, AZStd::array_view> inPropertySetList) + MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::FindPropertySet(AZStd::span parsedPropertySetId, AZStd::span> inPropertySetList) { return const_cast(const_cast(this)->FindPropertySet(parsedPropertySetId, inPropertySetList)); } @@ -295,14 +295,14 @@ namespace AZ } const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty( - AZStd::array_view parsedPropertyId, - AZStd::array_view> inPropertySetList) const + AZStd::span parsedPropertyId, + AZStd::span> inPropertySetList) const { for (const auto& propertySet : inPropertySetList) { if (propertySet->m_name == parsedPropertyId[0]) { - AZStd::array_view subPath {parsedPropertyId.begin() + 1, parsedPropertyId.end()}; + AZStd::span subPath {parsedPropertyId.begin() + 1, parsedPropertyId.end()}; if (subPath.size() == 1) { @@ -328,7 +328,7 @@ namespace AZ return nullptr; } - MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::array_view parsedPropertyId, AZStd::array_view> inPropertySetList) + MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::span parsedPropertyId, AZStd::span> inPropertySetList) { return const_cast(const_cast(this)->FindProperty(parsedPropertyId, inPropertySetList)); } From 1a8fdd9c84d3fd60867b26d3648f566599e63c01 Mon Sep 17 00:00:00 2001 From: lsemp3d <58790905+lsemp3d@users.noreply.github.com> Date: Thu, 27 Jan 2022 15:45:33 -0800 Subject: [PATCH 18/53] Script Canvas: Added take screenshot button to toolbar Signed-off-by: lsemp3d <58790905+lsemp3d@users.noreply.github.com> --- .../Code/Editor/View/Windows/MainWindow.cpp | 14 +++++++++++++- .../Code/Editor/View/Windows/MainWindow.h | 1 + .../Windows/Resources/scriptcanvas_screenshot.png | 3 +++ .../View/Windows/ScriptCanvasEditorResources.qrc | 1 + 4 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 Gems/ScriptCanvas/Code/Editor/View/Windows/Resources/scriptcanvas_screenshot.png diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.cpp index c0b6758147..c6ac85d314 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.cpp +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.cpp @@ -513,7 +513,6 @@ namespace ScriptCanvasEditor m_editorToolbar->AddCustomAction(m_createFunctionOutput); connect(m_createFunctionOutput, &QToolButton::clicked, this, &MainWindow::CreateFunctionOutput); - { m_validateGraphToolButton = new QToolButton(); m_validateGraphToolButton->setToolTip("Will run a validation check on the current graph and report any warnings/errors discovered."); @@ -523,6 +522,18 @@ namespace ScriptCanvasEditor m_editorToolbar->AddCustomAction(m_validateGraphToolButton); + // Screenshot + { + m_takeScreenshot = new QToolButton(); + m_takeScreenshot->setToolTip("Captures a full resolution screenshot of the entire graph or selected nodes into the clipboard"); + m_takeScreenshot->setIcon(QIcon(":/ScriptCanvasEditorResources/Resources/scriptcanvas_screenshot.png")); + m_takeScreenshot->setEnabled(false); + } + + m_editorToolbar->AddCustomAction(m_takeScreenshot); + connect(m_takeScreenshot, &QToolButton::clicked, this, &MainWindow::OnScreenshot); + + connect(m_validateGraphToolButton, &QToolButton::clicked, this, &MainWindow::OnValidateCurrentGraph); m_layout->addWidget(m_editorToolbar); @@ -3211,6 +3222,7 @@ namespace ScriptCanvasEditor m_createFunctionOutput->setEnabled(enabled); m_createFunctionInput->setEnabled(enabled); + m_takeScreenshot->setEnabled(enabled); // File Menu ui->action_Close->setEnabled(enabled); diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.h index 75c798a0d3..d05792c4ec 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.h +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.h @@ -648,6 +648,7 @@ namespace ScriptCanvasEditor QToolButton* m_createFunctionInput = nullptr; QToolButton* m_createFunctionOutput = nullptr; + QToolButton* m_takeScreenshot = nullptr; QToolButton* m_createScriptCanvas = nullptr; diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Resources/scriptcanvas_screenshot.png b/Gems/ScriptCanvas/Code/Editor/View/Windows/Resources/scriptcanvas_screenshot.png new file mode 100644 index 0000000000..7daa11d945 --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Resources/scriptcanvas_screenshot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef1d048b1ef82137424b6c346e7f87f156e97402ecc89eca8c0dc0e2b6acc395 +size 400 diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/ScriptCanvasEditorResources.qrc b/Gems/ScriptCanvas/Code/Editor/View/Windows/ScriptCanvasEditorResources.qrc index be90d229dc..9244f71ca3 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/ScriptCanvasEditorResources.qrc +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/ScriptCanvasEditorResources.qrc @@ -34,6 +34,7 @@ Resources/scriptcanvas_nodes.png Resources/scriptcanvas_outliner.png Resources/scriptcanvas_properties.png + Resources/scriptcanvas_screenshot.png Resources/scriptcanvas_variables.png Resources/settings_icon.png Resources/settings_dropdown_icon.png From c2e220ce491f69b5764e2199e5b223b3dc088e4d Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Thu, 27 Jan 2022 16:11:26 -0800 Subject: [PATCH 19/53] Renamed property 'set' to property 'group' for consistency with the prior naming. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Material/MaterialTypeSourceData.h | 86 ++--- .../Material/MaterialTypeSourceData.cpp | 200 +++++----- .../Material/MaterialSourceDataTests.cpp | 4 +- .../Material/MaterialTypeSourceDataTests.cpp | 346 +++++++++--------- .../Materials/Types/MinimalPBR.materialtype | 2 +- .../Code/Source/Document/MaterialDocument.cpp | 26 +- .../MaterialInspector/MaterialInspector.cpp | 14 +- .../EditorMaterialComponentInspector.cpp | 14 +- .../Material/EditorMaterialComponentUtil.cpp | 2 +- 9 files changed, 347 insertions(+), 347 deletions(-) diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h index 35cc7fe09a..40f82c8ec7 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h @@ -118,47 +118,47 @@ namespace AZ using PropertyList = AZStd::vector>; - struct PropertySet + struct PropertyGroup { friend class MaterialTypeSourceData; - AZ_CLASS_ALLOCATOR(PropertySet, SystemAllocator, 0); - AZ_TYPE_INFO(AZ::RPI::MaterialTypeSourceData::PropertySet, "{BA3AA0E4-C74D-4FD0-ADB2-00B060F06314}"); + AZ_CLASS_ALLOCATOR(PropertyGroup, SystemAllocator, 0); + AZ_TYPE_INFO(AZ::RPI::MaterialTypeSourceData::PropertyGroup, "{BA3AA0E4-C74D-4FD0-ADB2-00B060F06314}"); public: - PropertySet() = default; - AZ_DISABLE_COPY(PropertySet) + PropertyGroup() = default; + AZ_DISABLE_COPY(PropertyGroup) const AZStd::string& GetName() const { return m_name; } const AZStd::string& GetDisplayName() const { return m_displayName; } const AZStd::string& GetDescription() const { return m_description; } const PropertyList& GetProperties() const { return m_properties; } - const AZStd::vector>& GetPropertySets() const { return m_propertySets; } + const AZStd::vector>& GetPropertyGroups() const { return m_propertyGroups; } const AZStd::vector>& GetFunctors() const { return m_materialFunctorSourceData; } void SetDisplayName(AZStd::string_view displayName) { m_displayName = displayName; } void SetDescription(AZStd::string_view description) { m_description = description; } - //! Add a new property to this PropertySet. + //! Add a new property to this PropertyGroup. //! @param name a unique for the property. Must be a C-style identifier. //! @return the new PropertyDefinition, or null if the name was not valid. PropertyDefinition* AddProperty(AZStd::string_view name); - //! Add a new nested PropertySet to this PropertySet. - //! @param name a unique for the property set. Must be a C-style identifier. - //! @return the new PropertySet, or null if the name was not valid. - PropertySet* AddPropertySet(AZStd::string_view name); + //! Add a new nested PropertyGroup to this PropertyGroup. + //! @param name a unique for the property group. Must be a C-style identifier. + //! @return the new PropertyGroup, or null if the name was not valid. + PropertyGroup* AddPropertyGroup(AZStd::string_view name); private: - static PropertySet* AddPropertySet(AZStd::string_view name, AZStd::vector>& toPropertySetList); + static PropertyGroup* AddPropertyGroup(AZStd::string_view name, AZStd::vector>& toPropertyGroupList); AZStd::string m_name; AZStd::string m_displayName; AZStd::string m_description; PropertyList m_properties; - AZStd::vector> m_propertySets; + AZStd::vector> m_propertyGroups; AZStd::vector> m_materialFunctorSourceData; }; @@ -215,15 +215,15 @@ namespace AZ //! This field is unused, and has been replaced by MaterialTypeSourceData::m_version below. It is kept for legacy file compatibility to suppress warnings and errors. uint32_t m_versionOld = 0; - //! [Deprecated] Use m_propertySets instead + //! [Deprecated] Use m_propertyGroups instead //! List of groups that will contain the available properties AZStd::vector m_groupsOld; - //! [Deprecated] Use m_propertySets instead + //! [Deprecated] Use m_propertyGroups instead AZStd::map> m_propertiesOld; //! Collection of all available user-facing properties - AZStd::vector> m_propertySets; + AZStd::vector> m_propertyGroups; }; AZStd::string m_description; @@ -247,24 +247,24 @@ namespace AZ //! Copy over UV custom names to the properties enum values. void ResolveUvEnums(); - //! Add a new PropertySet for containing properties or other PropertySets. - //! @param propertySetId The ID of the new property set. To add as a nested PropertySet, use a full path ID like "levelA.levelB.levelC"; in this case a property set "levelA.levelB" must already exist. - //! @return a pointer to the new PropertySet or null if there was a problem (an AZ_Error will be reported). - PropertySet* AddPropertySet(AZStd::string_view propertySetId); + //! Add a new PropertyGroup for containing properties or other PropertyGroups. + //! @param propertyGroupId The ID of the new property group. To add as a nested PropertyGroup, use a full path ID like "levelA.levelB.levelC"; in this case a property group "levelA.levelB" must already exist. + //! @return a pointer to the new PropertyGroup or null if there was a problem (an AZ_Error will be reported). + PropertyGroup* AddPropertyGroup(AZStd::string_view propertyGroupId); - //! Add a new property to a PropertySet. - //! @param propertyId The ID of the new property, like "layerBlend.factor" or "layer2.roughness.texture". The indicated property set must already exist. + //! Add a new property to a PropertyGroup. + //! @param propertyId The ID of the new property, like "layerBlend.factor" or "layer2.roughness.texture". The indicated property group must already exist. //! @return a pointer to the new PropertyDefinition or null if there was a problem (an AZ_Error will be reported). PropertyDefinition* AddProperty(AZStd::string_view propertyId); - //! Return the PropertyLayout containing the tree of property sets and property definitions. + //! Return the PropertyLayout containing the tree of property groups and property definitions. const PropertyLayout& GetPropertyLayout() const { return m_propertyLayout; } - //! Find the PropertySet with the given ID. - //! @param propertySetId The full ID of a property set to find, like "levelA.levelB.levelC". - //! @return the found PropertySet or null if it doesn't exist. - const PropertySet* FindPropertySet(AZStd::string_view propertySetId) const; - PropertySet* FindPropertySet(AZStd::string_view propertySetId); + //! Find the PropertyGroup with the given ID. + //! @param propertyGroupId The full ID of a property group to find, like "levelA.levelB.levelC". + //! @return the found PropertyGroup or null if it doesn't exist. + const PropertyGroup* FindPropertyGroup(AZStd::string_view propertyGroupId) const; + PropertyGroup* FindPropertyGroup(AZStd::string_view propertyGroupId); //! Find the definition for a property with the given ID. //! @param propertyId The full ID of a property to find, like "baseColor.texture". @@ -280,14 +280,14 @@ namespace AZ //! Call back function type used with the enumeration functions. //! Return false to terminate the traversal. - using EnumeratePropertySetsCallback = AZStd::function; - //! Recursively traverses all of the property sets contained in the material type, executing a callback function for each. + //! Recursively traverses all of the property groups contained in the material type, executing a callback function for each. //! @return false if the enumeration was terminated early by the callback returning false. - bool EnumeratePropertySets(const EnumeratePropertySetsCallback& callback) const; + bool EnumeratePropertyGroups(const EnumeratePropertyGroupsCallback& callback) const; //! Call back function type used with the numeration functions. //! Return false to terminate the traversal. @@ -303,31 +303,31 @@ namespace AZ Outcome> CreateMaterialTypeAsset(Data::AssetId assetId, AZStd::string_view materialTypeSourceFilePath = "", bool elevateWarnings = true) const; //! If the data was loaded from an old format file (i.e. where "groups" and "properties" were separate sections), - //! this converts to the new format where properties are listed inside property sets. + //! this converts to the new format where properties are listed inside property groups. bool ConvertToNewDataFormat(); private: - const PropertySet* FindPropertySet(AZStd::span parsedPropertySetId, AZStd::span> inPropertySetList) const; - PropertySet* FindPropertySet(AZStd::span parsedPropertySetId, AZStd::span> inPropertySetList); + const PropertyGroup* FindPropertyGroup(AZStd::span parsedPropertyGroupId, AZStd::span> inPropertyGroupList) const; + PropertyGroup* FindPropertyGroup(AZStd::span parsedPropertyGroupId, AZStd::span> inPropertyGroupList); - const PropertyDefinition* FindProperty(AZStd::span parsedPropertyId, AZStd::span> inPropertySetList) const; - PropertyDefinition* FindProperty(AZStd::span parsedPropertyId, AZStd::span> inPropertySetList); + const PropertyDefinition* FindProperty(AZStd::span parsedPropertyId, AZStd::span> inPropertyGroupList) const; + PropertyDefinition* FindProperty(AZStd::span parsedPropertyId, AZStd::span> inPropertyGroupList); // Function overloads for recursion, returns false to indicate that recursion should end. - bool EnumeratePropertySets(const EnumeratePropertySetsCallback& callback, AZStd::string propertyIdContext, const AZStd::vector>& inPropertySetList) const; - bool EnumerateProperties(const EnumeratePropertiesCallback& callback, AZStd::string propertyIdContext, const AZStd::vector>& inPropertySetList) const; + bool EnumeratePropertyGroups(const EnumeratePropertyGroupsCallback& callback, AZStd::string propertyIdContext, const AZStd::vector>& inPropertyGroupList) const; + bool EnumerateProperties(const EnumeratePropertiesCallback& callback, AZStd::string propertyIdContext, const AZStd::vector>& inPropertyGroupList) const; - //! Recursively populates a material asset with properties from the tree of material property sets. + //! Recursively populates a material asset with properties from the tree of material property groups. //! @param materialTypeSourceFilePath path to the material type file that is being processed, used to look up relative paths - //! @param propertyNameContext the accumulated prefix that should be applied to any property names encountered in the current @propertySet - //! @param propertySet the current PropertySet that is being processed + //! @param propertyNameContext the accumulated prefix that should be applied to any property names encountered in the current @propertyGroup + //! @param propertyGroup the current PropertyGroup that is being processed //! @return false if errors are detected and processing should abort bool BuildPropertyList( const AZStd::string& materialTypeSourceFilePath, MaterialTypeAssetCreator& materialTypeAssetCreator, AZStd::vector& propertyNameContext, - const MaterialTypeSourceData::PropertySet* propertySet) const; + const MaterialTypeSourceData::PropertyGroup* propertyGroup) const; //! Construct a complete list of group definitions, including implicit groups, arranged in the same order as the source data. //! Groups with the same name will be consolidated into a single entry. diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp index 4aa0a77370..b9a5754732 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp @@ -58,9 +58,9 @@ namespace AZ serializeContext->Class()->Version(4); serializeContext->Class()->Version(1); - serializeContext->RegisterGenericType>(); + serializeContext->RegisterGenericType>(); serializeContext->RegisterGenericType>(); - serializeContext->RegisterGenericType>>(); + serializeContext->RegisterGenericType>>(); serializeContext->RegisterGenericType>>(); serializeContext->RegisterGenericType(); @@ -88,22 +88,22 @@ namespace AZ ->Field("options", &ShaderVariantReferenceData::m_shaderOptionValues) ; - serializeContext->Class() + serializeContext->Class() ->Version(1) - ->Field("name", &PropertySet::m_name) - ->Field("displayName", &PropertySet::m_displayName) - ->Field("description", &PropertySet::m_description) - ->Field("properties", &PropertySet::m_properties) - ->Field("propertySets", &PropertySet::m_propertySets) - ->Field("functors", &PropertySet::m_materialFunctorSourceData) + ->Field("name", &PropertyGroup::m_name) + ->Field("displayName", &PropertyGroup::m_displayName) + ->Field("description", &PropertyGroup::m_description) + ->Field("properties", &PropertyGroup::m_properties) + ->Field("propertyGroups", &PropertyGroup::m_propertyGroups) + ->Field("functors", &PropertyGroup::m_materialFunctorSourceData) ; serializeContext->Class() - ->Version(3) // Added propertySets + ->Version(3) // Added propertyGroups ->Field("version", &PropertyLayout::m_versionOld) //< Deprecated, preserved for backward compatibility, replaced by MaterialTypeSourceData::version - ->Field("groups", &PropertyLayout::m_groupsOld) //< Deprecated, preserved for backward compatibility, replaced by propertySets - ->Field("properties", &PropertyLayout::m_propertiesOld) //< Deprecated, preserved for backward compatibility, replaced by propertySets - ->Field("propertySets", &PropertyLayout::m_propertySets) + ->Field("groups", &PropertyLayout::m_groupsOld) //< Deprecated, preserved for backward compatibility, replaced by propertyGroups + ->Field("properties", &PropertyLayout::m_propertiesOld) //< Deprecated, preserved for backward compatibility, replaced by propertyGroups + ->Field("propertyGroups", &PropertyLayout::m_propertyGroups) ; serializeContext->RegisterGenericType(); @@ -132,16 +132,16 @@ namespace AZ const float MaterialTypeSourceData::PropertyDefinition::DefaultMax = std::numeric_limits::max(); const float MaterialTypeSourceData::PropertyDefinition::DefaultStep = 0.1f; - /*static*/ MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::PropertySet::AddPropertySet(AZStd::string_view name, AZStd::vector>& toPropertySetList) + /*static*/ MaterialTypeSourceData::PropertyGroup* MaterialTypeSourceData::PropertyGroup::AddPropertyGroup(AZStd::string_view name, AZStd::vector>& toPropertyGroupList) { - auto iter = AZStd::find_if(toPropertySetList.begin(), toPropertySetList.end(), [name](const AZStd::unique_ptr& existingPropertySet) + auto iter = AZStd::find_if(toPropertyGroupList.begin(), toPropertyGroupList.end(), [name](const AZStd::unique_ptr& existingPropertyGroup) { - return existingPropertySet->m_name == name; + return existingPropertyGroup->m_name == name; }); - if (iter != toPropertySetList.end()) + if (iter != toPropertyGroupList.end()) { - AZ_Error("Material source data", false, "PropertySet named '%.*s' already exists", AZ_STRING_ARG(name)); + AZ_Error("Material source data", false, "PropertyGroup named '%.*s' already exists", AZ_STRING_ARG(name)); return nullptr; } @@ -151,12 +151,12 @@ namespace AZ return nullptr; } - toPropertySetList.push_back(AZStd::make_unique()); - toPropertySetList.back()->m_name = name; - return toPropertySetList.back().get(); + toPropertyGroupList.push_back(AZStd::make_unique()); + toPropertyGroupList.back()->m_name = name; + return toPropertyGroupList.back().get(); } - MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::PropertySet::AddProperty(AZStd::string_view name) + MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::PropertyGroup::AddProperty(AZStd::string_view name) { auto propertyIter = AZStd::find_if(m_properties.begin(), m_properties.end(), [name](const AZStd::unique_ptr& existingProperty) { @@ -165,18 +165,18 @@ namespace AZ if (propertyIter != m_properties.end()) { - AZ_Error("Material source data", false, "PropertySet '%s' already contains a property named '%.*s'", m_name.c_str(), AZ_STRING_ARG(name)); + AZ_Error("Material source data", false, "PropertyGroup '%s' already contains a property named '%.*s'", m_name.c_str(), AZ_STRING_ARG(name)); return nullptr; } - auto propertySetIter = AZStd::find_if(m_propertySets.begin(), m_propertySets.end(), [name](const AZStd::unique_ptr& existingPropertySet) + auto propertyGroupIter = AZStd::find_if(m_propertyGroups.begin(), m_propertyGroups.end(), [name](const AZStd::unique_ptr& existingPropertyGroup) { - return existingPropertySet->m_name == name; + return existingPropertyGroup->m_name == name; }); - if (propertySetIter != m_propertySets.end()) + if (propertyGroupIter != m_propertyGroups.end()) { - AZ_Error("Material source data", false, "Property name '%.*s' collides with a PropertySet of the same name", AZ_STRING_ARG(name)); + AZ_Error("Material source data", false, "Property name '%.*s' collides with a PropertyGroup of the same name", AZ_STRING_ARG(name)); return nullptr; } @@ -190,7 +190,7 @@ namespace AZ return m_properties.back().get(); } - MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::PropertySet::AddPropertySet(AZStd::string_view name) + MaterialTypeSourceData::PropertyGroup* MaterialTypeSourceData::PropertyGroup::AddPropertyGroup(AZStd::string_view name) { auto iter = AZStd::find_if(m_properties.begin(), m_properties.end(), [name](const AZStd::unique_ptr& existingProperty) { @@ -199,31 +199,31 @@ namespace AZ if (iter != m_properties.end()) { - AZ_Error("Material source data", false, "PropertySet name '%.*s' collides with a Property of the same name", AZ_STRING_ARG(name)); + AZ_Error("Material source data", false, "PropertyGroup name '%.*s' collides with a Property of the same name", AZ_STRING_ARG(name)); return nullptr; } - return AddPropertySet(name, m_propertySets); + return AddPropertyGroup(name, m_propertyGroups); } - MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::AddPropertySet(AZStd::string_view propertySetId) + MaterialTypeSourceData::PropertyGroup* MaterialTypeSourceData::AddPropertyGroup(AZStd::string_view propertyGroupId) { - AZStd::vector splitPropertySetId = SplitId(propertySetId); + AZStd::vector splitPropertyGroupId = SplitId(propertyGroupId); - if (splitPropertySetId.size() == 1) + if (splitPropertyGroupId.size() == 1) { - return PropertySet::AddPropertySet(propertySetId, m_propertyLayout.m_propertySets); + return PropertyGroup::AddPropertyGroup(propertyGroupId, m_propertyLayout.m_propertyGroups); } - PropertySet* parentPropertySet = FindPropertySet(splitPropertySetId[0]); + PropertyGroup* parentPropertyGroup = FindPropertyGroup(splitPropertyGroupId[0]); - if (!parentPropertySet) + if (!parentPropertyGroup) { - AZ_Error("Material source data", false, "PropertySet '%.*s' does not exists", AZ_STRING_ARG(splitPropertySetId[0])); + AZ_Error("Material source data", false, "PropertyGroup '%.*s' does not exists", AZ_STRING_ARG(splitPropertyGroupId[0])); return nullptr; } - return parentPropertySet->AddPropertySet(splitPropertySetId[1]); + return parentPropertyGroup->AddPropertyGroup(splitPropertyGroupId[1]); } MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::AddProperty(AZStd::string_view propertyId) @@ -232,40 +232,40 @@ namespace AZ if (splitPropertyId.size() == 1) { - AZ_Error("Material source data", false, "Property id '%.*s' is invalid. Properties must be added to a PropertySet (i.e. \"general.%.*s\").", AZ_STRING_ARG(propertyId), AZ_STRING_ARG(propertyId)); + AZ_Error("Material source data", false, "Property id '%.*s' is invalid. Properties must be added to a PropertyGroup (i.e. \"general.%.*s\").", AZ_STRING_ARG(propertyId), AZ_STRING_ARG(propertyId)); return nullptr; } - PropertySet* parentPropertySet = FindPropertySet(splitPropertyId[0]); + PropertyGroup* parentPropertyGroup = FindPropertyGroup(splitPropertyId[0]); - if (!parentPropertySet) + if (!parentPropertyGroup) { - AZ_Error("Material source data", false, "PropertySet '%.*s' does not exists", AZ_STRING_ARG(splitPropertyId[0])); + AZ_Error("Material source data", false, "PropertyGroup '%.*s' does not exists", AZ_STRING_ARG(splitPropertyId[0])); return nullptr; } - return parentPropertySet->AddProperty(splitPropertyId[1]); + return parentPropertyGroup->AddProperty(splitPropertyId[1]); } - const MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::FindPropertySet(AZStd::span parsedPropertySetId, AZStd::span> inPropertySetList) const + const MaterialTypeSourceData::PropertyGroup* MaterialTypeSourceData::FindPropertyGroup(AZStd::span parsedPropertyGroupId, AZStd::span> inPropertyGroupList) const { - for (const auto& propertySet : inPropertySetList) + for (const auto& propertyGroup : inPropertyGroupList) { - if (propertySet->m_name != parsedPropertySetId[0]) + if (propertyGroup->m_name != parsedPropertyGroupId[0]) { continue; } - else if (parsedPropertySetId.size() == 1) + else if (parsedPropertyGroupId.size() == 1) { - return propertySet.get(); + return propertyGroup.get(); } else { - AZStd::span subPath{parsedPropertySetId.begin() + 1, parsedPropertySetId.end()}; + AZStd::span subPath{parsedPropertyGroupId.begin() + 1, parsedPropertyGroupId.end()}; if (!subPath.empty()) { - const MaterialTypeSourceData::PropertySet* propertySubset = FindPropertySet(subPath, propertySet->m_propertySets); + const MaterialTypeSourceData::PropertyGroup* propertySubset = FindPropertyGroup(subPath, propertyGroup->m_propertyGroups); if (propertySubset) { return propertySubset; @@ -277,36 +277,36 @@ namespace AZ return nullptr; } - MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::FindPropertySet(AZStd::span parsedPropertySetId, AZStd::span> inPropertySetList) + MaterialTypeSourceData::PropertyGroup* MaterialTypeSourceData::FindPropertyGroup(AZStd::span parsedPropertyGroupId, AZStd::span> inPropertyGroupList) { - return const_cast(const_cast(this)->FindPropertySet(parsedPropertySetId, inPropertySetList)); + return const_cast(const_cast(this)->FindPropertyGroup(parsedPropertyGroupId, inPropertyGroupList)); } - const MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::FindPropertySet(AZStd::string_view propertySetId) const + const MaterialTypeSourceData::PropertyGroup* MaterialTypeSourceData::FindPropertyGroup(AZStd::string_view propertyGroupId) const { - AZStd::vector tokens = TokenizeId(propertySetId); - return FindPropertySet(tokens, m_propertyLayout.m_propertySets); + AZStd::vector tokens = TokenizeId(propertyGroupId); + return FindPropertyGroup(tokens, m_propertyLayout.m_propertyGroups); } - MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::FindPropertySet(AZStd::string_view propertySetId) + MaterialTypeSourceData::PropertyGroup* MaterialTypeSourceData::FindPropertyGroup(AZStd::string_view propertyGroupId) { - AZStd::vector tokens = TokenizeId(propertySetId); - return FindPropertySet(tokens, m_propertyLayout.m_propertySets); + AZStd::vector tokens = TokenizeId(propertyGroupId); + return FindPropertyGroup(tokens, m_propertyLayout.m_propertyGroups); } const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty( AZStd::span parsedPropertyId, - AZStd::span> inPropertySetList) const + AZStd::span> inPropertyGroupList) const { - for (const auto& propertySet : inPropertySetList) + for (const auto& propertyGroup : inPropertyGroupList) { - if (propertySet->m_name == parsedPropertyId[0]) + if (propertyGroup->m_name == parsedPropertyId[0]) { AZStd::span subPath {parsedPropertyId.begin() + 1, parsedPropertyId.end()}; if (subPath.size() == 1) { - for (AZStd::unique_ptr& property : propertySet->m_properties) + for (AZStd::unique_ptr& property : propertyGroup->m_properties) { if (property->GetName() == subPath[0]) { @@ -316,7 +316,7 @@ namespace AZ } else if(subPath.size() > 1) { - const MaterialTypeSourceData::PropertyDefinition* property = FindProperty(subPath, propertySet->m_propertySets); + const MaterialTypeSourceData::PropertyDefinition* property = FindProperty(subPath, propertyGroup->m_propertyGroups); if (property) { return property; @@ -328,21 +328,21 @@ namespace AZ return nullptr; } - MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::span parsedPropertyId, AZStd::span> inPropertySetList) + MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::span parsedPropertyId, AZStd::span> inPropertyGroupList) { - return const_cast(const_cast(this)->FindProperty(parsedPropertyId, inPropertySetList)); + return const_cast(const_cast(this)->FindProperty(parsedPropertyId, inPropertyGroupList)); } const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::string_view propertyId) const { AZStd::vector tokens = TokenizeId(propertyId); - return FindProperty(tokens, m_propertyLayout.m_propertySets); + return FindProperty(tokens, m_propertyLayout.m_propertyGroups); } MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::string_view propertyId) { AZStd::vector tokens = TokenizeId(propertyId); - return FindProperty(tokens, m_propertyLayout.m_propertySets); + return FindProperty(tokens, m_propertyLayout.m_propertyGroups); } AZStd::vector MaterialTypeSourceData::TokenizeId(AZStd::string_view id) @@ -376,18 +376,18 @@ namespace AZ return parts; } - bool MaterialTypeSourceData::EnumeratePropertySets(const EnumeratePropertySetsCallback& callback, AZStd::string propertyNameContext, const AZStd::vector>& inPropertySetList) const + bool MaterialTypeSourceData::EnumeratePropertyGroups(const EnumeratePropertyGroupsCallback& callback, AZStd::string propertyNameContext, const AZStd::vector>& inPropertyGroupList) const { - for (auto& propertySet : inPropertySetList) + for (auto& propertyGroup : inPropertyGroupList) { - if (!callback(propertyNameContext, propertySet.get())) + if (!callback(propertyNameContext, propertyGroup.get())) { return false; // Stop processing } - const AZStd::string propertyNameContext2 = propertyNameContext + propertySet->m_name + "."; + const AZStd::string propertyNameContext2 = propertyNameContext + propertyGroup->m_name + "."; - if (!EnumeratePropertySets(callback, propertyNameContext2, propertySet->m_propertySets)) + if (!EnumeratePropertyGroups(callback, propertyNameContext2, propertyGroup->m_propertyGroups)) { return false; // Stop processing } @@ -396,24 +396,24 @@ namespace AZ return true; } - bool MaterialTypeSourceData::EnumeratePropertySets(const EnumeratePropertySetsCallback& callback) const + bool MaterialTypeSourceData::EnumeratePropertyGroups(const EnumeratePropertyGroupsCallback& callback) const { if (!callback) { return false; } - return EnumeratePropertySets(callback, {}, m_propertyLayout.m_propertySets); + return EnumeratePropertyGroups(callback, {}, m_propertyLayout.m_propertyGroups); } - bool MaterialTypeSourceData::EnumerateProperties(const EnumeratePropertiesCallback& callback, AZStd::string propertyNameContext, const AZStd::vector>& inPropertySetList) const + bool MaterialTypeSourceData::EnumerateProperties(const EnumeratePropertiesCallback& callback, AZStd::string propertyNameContext, const AZStd::vector>& inPropertyGroupList) const { - for (auto& propertySet : inPropertySetList) + for (auto& propertyGroup : inPropertyGroupList) { - const AZStd::string propertyNameContext2 = propertyNameContext + propertySet->m_name + "."; + const AZStd::string propertyNameContext2 = propertyNameContext + propertyGroup->m_name + "."; - for (auto& property : propertySet->m_properties) + for (auto& property : propertyGroup->m_properties) { if (!callback(propertyNameContext2, property.get())) { @@ -421,7 +421,7 @@ namespace AZ } } - if (!EnumerateProperties(callback, propertyNameContext2, propertySet->m_propertySets)) + if (!EnumerateProperties(callback, propertyNameContext2, propertyGroup->m_propertyGroups)) { return false; // Stop processing } @@ -437,7 +437,7 @@ namespace AZ return false; } - return EnumerateProperties(callback, {}, m_propertyLayout.m_propertySets); + return EnumerateProperties(callback, {}, m_propertyLayout.m_propertyGroups); } bool MaterialTypeSourceData::ConvertToNewDataFormat() @@ -450,18 +450,18 @@ namespace AZ const auto& propertyList = propertyListItr->second; for (auto& propertyDefinition : propertyList) { - PropertySet* propertySet = FindPropertySet(group.m_name); + PropertyGroup* propertyGroup = FindPropertyGroup(group.m_name); - if (!propertySet) + if (!propertyGroup) { - m_propertyLayout.m_propertySets.emplace_back(AZStd::make_unique()); - m_propertyLayout.m_propertySets.back()->m_name = group.m_name; - m_propertyLayout.m_propertySets.back()->m_displayName = group.m_displayName; - m_propertyLayout.m_propertySets.back()->m_description = group.m_description; - propertySet = m_propertyLayout.m_propertySets.back().get(); + m_propertyLayout.m_propertyGroups.emplace_back(AZStd::make_unique()); + m_propertyLayout.m_propertyGroups.back()->m_name = group.m_name; + m_propertyLayout.m_propertyGroups.back()->m_displayName = group.m_displayName; + m_propertyLayout.m_propertyGroups.back()->m_description = group.m_description; + propertyGroup = m_propertyLayout.m_propertyGroups.back().get(); } - PropertyDefinition* newProperty = propertySet->AddProperty(propertyDefinition.GetName()); + PropertyDefinition* newProperty = propertyGroup->AddProperty(propertyDefinition.GetName()); *newProperty = propertyDefinition; } @@ -533,9 +533,9 @@ namespace AZ const AZStd::string& materialTypeSourceFilePath, MaterialTypeAssetCreator& materialTypeAssetCreator, AZStd::vector& propertyNameContext, - const MaterialTypeSourceData::PropertySet* propertySet) const + const MaterialTypeSourceData::PropertyGroup* propertyGroup) const { - for (const AZStd::unique_ptr& property : propertySet->m_properties) + for (const AZStd::unique_ptr& property : propertyGroup->m_properties) { // Register the property... @@ -547,15 +547,15 @@ namespace AZ return false; } - auto propertySetIter = AZStd::find_if(propertySet->GetPropertySets().begin(), propertySet->GetPropertySets().end(), - [&property](const AZStd::unique_ptr& existingPropertySet) + auto propertyGroupIter = AZStd::find_if(propertyGroup->GetPropertyGroups().begin(), propertyGroup->GetPropertyGroups().end(), + [&property](const AZStd::unique_ptr& existingPropertyGroup) { - return existingPropertySet->GetName() == property->GetName(); + return existingPropertyGroup->GetName() == property->GetName(); }); - if (propertySetIter != propertySet->GetPropertySets().end()) + if (propertyGroupIter != propertyGroup->GetPropertyGroups().end()) { - AZ_Error("Material source data", false, "Material property '%s' collides with a PropertySet with the same ID.", propertyId.GetCStr()); + AZ_Error("Material source data", false, "Material property '%s' collides with a PropertyGroup with the same ID.", propertyId.GetCStr()); return false; } @@ -650,7 +650,7 @@ namespace AZ } } - for (const AZStd::unique_ptr& propertySubset : propertySet->m_propertySets) + for (const AZStd::unique_ptr& propertySubset : propertyGroup->m_propertyGroups) { propertyNameContext.push_back(propertySubset->m_name); @@ -670,7 +670,7 @@ namespace AZ // We cannot create the MaterialFunctor until after all the properties are added because // CreateFunctor() may need to look up properties in the MaterialPropertiesLayout - for (auto& functorData : propertySet->m_materialFunctorSourceData) + for (auto& functorData : propertyGroup->m_materialFunctorSourceData) { MaterialFunctorSourceData::FunctorResult result = functorData->CreateFunctor( MaterialFunctorSourceData::RuntimeContext( @@ -795,11 +795,11 @@ namespace AZ } } - for (const AZStd::unique_ptr& propertySet : m_propertyLayout.m_propertySets) + for (const AZStd::unique_ptr& propertyGroup : m_propertyLayout.m_propertyGroups) { AZStd::vector propertyNameContext; - propertyNameContext.push_back(propertySet->m_name); - bool success = BuildPropertyList(materialTypeSourceFilePath, materialTypeAssetCreator, propertyNameContext, propertySet.get()); + propertyNameContext.push_back(propertyGroup->m_name); + bool success = BuildPropertyList(materialTypeSourceFilePath, materialTypeAssetCreator, propertyNameContext, propertyGroup.get()); if (!success) { diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp index 7b8475bf69..2bd257d55b 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp @@ -93,7 +93,7 @@ namespace UnitTest { "version": 10, "propertyLayout": { - "propertySets": [ + "propertyGroups": [ { "name": "general", "properties": [ @@ -471,7 +471,7 @@ namespace UnitTest const AZStd::string simpleMaterialTypeJson = R"( { "propertyLayout": { - "propertySets": + "propertyGroups": [ { "name": "general", diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp index 2ecf2369f9..206073e96a 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp @@ -360,18 +360,18 @@ namespace UnitTest { MaterialTypeSourceData sourceData; - // Here we are building up multiple layers of property sets and properties, using a variety of different Add functions, - // going through the MaterialTypeSourceData or going to the PropertySet directly. + // Here we are building up multiple layers of property groups and properties, using a variety of different Add functions, + // going through the MaterialTypeSourceData or going to the PropertyGroup directly. - MaterialTypeSourceData::PropertySet* layer1 = sourceData.AddPropertySet("layer1"); - MaterialTypeSourceData::PropertySet* layer2 = sourceData.AddPropertySet("layer2"); - MaterialTypeSourceData::PropertySet* blend = sourceData.AddPropertySet("blend"); + MaterialTypeSourceData::PropertyGroup* layer1 = sourceData.AddPropertyGroup("layer1"); + MaterialTypeSourceData::PropertyGroup* layer2 = sourceData.AddPropertyGroup("layer2"); + MaterialTypeSourceData::PropertyGroup* blend = sourceData.AddPropertyGroup("blend"); - MaterialTypeSourceData::PropertySet* layer1_baseColor = layer1->AddPropertySet("baseColor"); - MaterialTypeSourceData::PropertySet* layer2_baseColor = layer2->AddPropertySet("baseColor"); + MaterialTypeSourceData::PropertyGroup* layer1_baseColor = layer1->AddPropertyGroup("baseColor"); + MaterialTypeSourceData::PropertyGroup* layer2_baseColor = layer2->AddPropertyGroup("baseColor"); - MaterialTypeSourceData::PropertySet* layer1_roughness = sourceData.AddPropertySet("layer1.roughness"); - MaterialTypeSourceData::PropertySet* layer2_roughness = sourceData.AddPropertySet("layer2.roughness"); + MaterialTypeSourceData::PropertyGroup* layer1_roughness = sourceData.AddPropertyGroup("layer1.roughness"); + MaterialTypeSourceData::PropertyGroup* layer2_roughness = sourceData.AddPropertyGroup("layer2.roughness"); MaterialTypeSourceData::PropertyDefinition* layer1_baseColor_texture = layer1_baseColor->AddProperty("texture"); MaterialTypeSourceData::PropertyDefinition* layer2_baseColor_texture = layer2_baseColor->AddProperty("texture"); @@ -380,9 +380,9 @@ namespace UnitTest MaterialTypeSourceData::PropertyDefinition* layer2_roughness_texture = sourceData.AddProperty("layer2.roughness.texture"); // We're doing clear coat only on layer2, for brevity - MaterialTypeSourceData::PropertySet* layer2_clearCoat = layer2->AddPropertySet("clearCoat"); - MaterialTypeSourceData::PropertySet* layer2_clearCoat_roughness = layer2_clearCoat->AddPropertySet("roughness"); - MaterialTypeSourceData::PropertySet* layer2_clearCoat_normal = layer2_clearCoat->AddPropertySet("normal"); + MaterialTypeSourceData::PropertyGroup* layer2_clearCoat = layer2->AddPropertyGroup("clearCoat"); + MaterialTypeSourceData::PropertyGroup* layer2_clearCoat_roughness = layer2_clearCoat->AddPropertyGroup("roughness"); + MaterialTypeSourceData::PropertyGroup* layer2_clearCoat_normal = layer2_clearCoat->AddPropertyGroup("normal"); MaterialTypeSourceData::PropertyDefinition* layer2_clearCoat_enabled = layer2_clearCoat->AddProperty("enabled"); MaterialTypeSourceData::PropertyDefinition* layer2_clearCoat_roughness_texture = layer2_clearCoat_roughness->AddProperty("texture"); MaterialTypeSourceData::PropertyDefinition* layer2_clearCoat_normal_texture = layer2_clearCoat_normal->AddProperty("texture"); @@ -396,27 +396,27 @@ namespace UnitTest EXPECT_EQ(nullptr, sourceData.FindProperty("layer1.DoesNotExist")); EXPECT_EQ(nullptr, sourceData.FindProperty("layer1.baseColor.DoesNotExist")); EXPECT_EQ(nullptr, sourceData.FindProperty("baseColor.texture")); - EXPECT_EQ(nullptr, sourceData.FindProperty("baseColor")); // This is a property set, not a property - EXPECT_EQ(nullptr, sourceData.FindPropertySet("baseColor.texture")); // This is a property, not a property set + EXPECT_EQ(nullptr, sourceData.FindProperty("baseColor")); // This is a property group, not a property + EXPECT_EQ(nullptr, sourceData.FindPropertyGroup("baseColor.texture")); // This is a property, not a property group - EXPECT_EQ(layer1, sourceData.FindPropertySet("layer1")); - EXPECT_EQ(layer2, sourceData.FindPropertySet("layer2")); - EXPECT_EQ(blend, sourceData.FindPropertySet("blend")); + EXPECT_EQ(layer1, sourceData.FindPropertyGroup("layer1")); + EXPECT_EQ(layer2, sourceData.FindPropertyGroup("layer2")); + EXPECT_EQ(blend, sourceData.FindPropertyGroup("blend")); - EXPECT_EQ(layer1_baseColor, sourceData.FindPropertySet("layer1.baseColor")); - EXPECT_EQ(layer2_baseColor, sourceData.FindPropertySet("layer2.baseColor")); + EXPECT_EQ(layer1_baseColor, sourceData.FindPropertyGroup("layer1.baseColor")); + EXPECT_EQ(layer2_baseColor, sourceData.FindPropertyGroup("layer2.baseColor")); - EXPECT_EQ(layer1_roughness, sourceData.FindPropertySet("layer1.roughness")); - EXPECT_EQ(layer2_roughness, sourceData.FindPropertySet("layer2.roughness")); + EXPECT_EQ(layer1_roughness, sourceData.FindPropertyGroup("layer1.roughness")); + EXPECT_EQ(layer2_roughness, sourceData.FindPropertyGroup("layer2.roughness")); EXPECT_EQ(layer1_baseColor_texture, sourceData.FindProperty("layer1.baseColor.texture")); EXPECT_EQ(layer2_baseColor_texture, sourceData.FindProperty("layer2.baseColor.texture")); EXPECT_EQ(layer1_roughness_texture, sourceData.FindProperty("layer1.roughness.texture")); EXPECT_EQ(layer2_roughness_texture, sourceData.FindProperty("layer2.roughness.texture")); - EXPECT_EQ(layer2_clearCoat, sourceData.FindPropertySet("layer2.clearCoat")); - EXPECT_EQ(layer2_clearCoat_roughness, sourceData.FindPropertySet("layer2.clearCoat.roughness")); - EXPECT_EQ(layer2_clearCoat_normal, sourceData.FindPropertySet("layer2.clearCoat.normal")); + EXPECT_EQ(layer2_clearCoat, sourceData.FindPropertyGroup("layer2.clearCoat")); + EXPECT_EQ(layer2_clearCoat_roughness, sourceData.FindPropertyGroup("layer2.clearCoat.roughness")); + EXPECT_EQ(layer2_clearCoat_normal, sourceData.FindPropertyGroup("layer2.clearCoat.normal")); EXPECT_EQ(layer2_clearCoat_enabled, sourceData.FindProperty("layer2.clearCoat.enabled")); EXPECT_EQ(layer2_clearCoat_roughness_texture, sourceData.FindProperty("layer2.clearCoat.roughness.texture")); @@ -425,39 +425,39 @@ namespace UnitTest EXPECT_EQ(blend_factor, sourceData.FindProperty("blend.factor")); - // Check EnumeratePropertySets + // Check EnumeratePropertyGroups - struct EnumeratePropertySetsResult + struct EnumeratePropertyGroupsResult { AZStd::string m_propertyIdContext; - const MaterialTypeSourceData::PropertySet* m_propertySet; + const MaterialTypeSourceData::PropertyGroup* m_propertyGroup; - void Check(AZStd::string expectedIdContext, const MaterialTypeSourceData::PropertySet* expectedPropertySet) + void Check(AZStd::string expectedIdContext, const MaterialTypeSourceData::PropertyGroup* expectedPropertyGroup) { EXPECT_EQ(expectedIdContext, m_propertyIdContext); - EXPECT_EQ(expectedPropertySet, m_propertySet); + EXPECT_EQ(expectedPropertyGroup, m_propertyGroup); } }; - AZStd::vector enumeratePropertySetsResults; + AZStd::vector enumeratePropertyGroupsResults; - sourceData.EnumeratePropertySets([&enumeratePropertySetsResults](const AZStd::string& propertyIdContext, const MaterialTypeSourceData::PropertySet* propertySet) + sourceData.EnumeratePropertyGroups([&enumeratePropertyGroupsResults](const AZStd::string& propertyIdContext, const MaterialTypeSourceData::PropertyGroup* propertyGroup) { - enumeratePropertySetsResults.push_back(EnumeratePropertySetsResult{propertyIdContext, propertySet}); + enumeratePropertyGroupsResults.push_back(EnumeratePropertyGroupsResult{propertyIdContext, propertyGroup}); return true; }); int resultIndex = 0; - enumeratePropertySetsResults[resultIndex++].Check("", layer1); - enumeratePropertySetsResults[resultIndex++].Check("layer1.", layer1_baseColor); - enumeratePropertySetsResults[resultIndex++].Check("layer1.", layer1_roughness); - enumeratePropertySetsResults[resultIndex++].Check("", layer2); - enumeratePropertySetsResults[resultIndex++].Check("layer2.", layer2_baseColor); - enumeratePropertySetsResults[resultIndex++].Check("layer2.", layer2_roughness); - enumeratePropertySetsResults[resultIndex++].Check("layer2.", layer2_clearCoat); - enumeratePropertySetsResults[resultIndex++].Check("layer2.clearCoat.", layer2_clearCoat_roughness); - enumeratePropertySetsResults[resultIndex++].Check("layer2.clearCoat.", layer2_clearCoat_normal); - enumeratePropertySetsResults[resultIndex++].Check("", blend); - EXPECT_EQ(resultIndex, enumeratePropertySetsResults.size()); + enumeratePropertyGroupsResults[resultIndex++].Check("", layer1); + enumeratePropertyGroupsResults[resultIndex++].Check("layer1.", layer1_baseColor); + enumeratePropertyGroupsResults[resultIndex++].Check("layer1.", layer1_roughness); + enumeratePropertyGroupsResults[resultIndex++].Check("", layer2); + enumeratePropertyGroupsResults[resultIndex++].Check("layer2.", layer2_baseColor); + enumeratePropertyGroupsResults[resultIndex++].Check("layer2.", layer2_roughness); + enumeratePropertyGroupsResults[resultIndex++].Check("layer2.", layer2_clearCoat); + enumeratePropertyGroupsResults[resultIndex++].Check("layer2.clearCoat.", layer2_clearCoat_roughness); + enumeratePropertyGroupsResults[resultIndex++].Check("layer2.clearCoat.", layer2_clearCoat_normal); + enumeratePropertyGroupsResults[resultIndex++].Check("", blend); + EXPECT_EQ(resultIndex, enumeratePropertyGroupsResults.size()); // Check EnumerateProperties @@ -497,39 +497,39 @@ namespace UnitTest { MaterialTypeSourceData sourceData; - MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("main"); + MaterialTypeSourceData::PropertyGroup* propertyGroup = sourceData.AddPropertyGroup("main"); ErrorMessageFinder errorMessageFinder; errorMessageFinder.AddExpectedErrorMessage("'' is not a valid identifier"); errorMessageFinder.AddExpectedErrorMessage("'main.' is not a valid identifier"); errorMessageFinder.AddExpectedErrorMessage("'base-color' is not a valid identifier"); - EXPECT_FALSE(propertySet->AddProperty("")); - EXPECT_FALSE(propertySet->AddProperty("main.")); + EXPECT_FALSE(propertyGroup->AddProperty("")); + EXPECT_FALSE(propertyGroup->AddProperty("main.")); EXPECT_FALSE(sourceData.AddProperty("main.base-color")); - EXPECT_TRUE(propertySet->GetProperties().empty()); + EXPECT_TRUE(propertyGroup->GetProperties().empty()); errorMessageFinder.CheckExpectedErrorsFound(); } - TEST_F(MaterialTypeSourceDataTests, AddPropertySet_Error_InvalidName) + TEST_F(MaterialTypeSourceDataTests, AddPropertyGroup_Error_InvalidName) { MaterialTypeSourceData sourceData; - MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("general"); + MaterialTypeSourceData::PropertyGroup* propertyGroup = sourceData.AddPropertyGroup("general"); ErrorMessageFinder errorMessageFinder; errorMessageFinder.AddExpectedErrorMessage("'' is not a valid identifier", 2); errorMessageFinder.AddExpectedErrorMessage("'base-color' is not a valid identifier"); errorMessageFinder.AddExpectedErrorMessage("'look@it' is not a valid identifier"); - EXPECT_FALSE(propertySet->AddPropertySet("")); - EXPECT_FALSE(sourceData.AddPropertySet("")); - EXPECT_FALSE(sourceData.AddPropertySet("base-color")); - EXPECT_FALSE(sourceData.AddPropertySet("general.look@it")); + EXPECT_FALSE(propertyGroup->AddPropertyGroup("")); + EXPECT_FALSE(sourceData.AddPropertyGroup("")); + EXPECT_FALSE(sourceData.AddPropertyGroup("base-color")); + EXPECT_FALSE(sourceData.AddPropertyGroup("general.look@it")); - EXPECT_TRUE(propertySet->GetProperties().empty()); + EXPECT_TRUE(propertyGroup->GetProperties().empty()); errorMessageFinder.CheckExpectedErrorsFound(); } @@ -538,16 +538,16 @@ namespace UnitTest { MaterialTypeSourceData sourceData; - MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("main"); + MaterialTypeSourceData::PropertyGroup* propertyGroup = sourceData.AddPropertyGroup("main"); ErrorMessageFinder errorMessageFinder; - errorMessageFinder.AddExpectedErrorMessage("PropertySet 'main' already contains a property named 'foo'", 2); + errorMessageFinder.AddExpectedErrorMessage("PropertyGroup 'main' already contains a property named 'foo'", 2); - EXPECT_TRUE(propertySet->AddProperty("foo")); - EXPECT_FALSE(propertySet->AddProperty("foo")); + EXPECT_TRUE(propertyGroup->AddProperty("foo")); + EXPECT_FALSE(propertyGroup->AddProperty("foo")); EXPECT_FALSE(sourceData.AddProperty("main.foo")); - EXPECT_EQ(propertySet->GetProperties().size(), 1); + EXPECT_EQ(propertyGroup->GetProperties().size(), 1); errorMessageFinder.CheckExpectedErrorsFound(); } @@ -555,66 +555,66 @@ namespace UnitTest TEST_F(MaterialTypeSourceDataTests, AddProperty_Error_AddLooseProperty) { MaterialTypeSourceData sourceData; - ErrorMessageFinder errorMessageFinder("Property id 'foo' is invalid. Properties must be added to a PropertySet"); + ErrorMessageFinder errorMessageFinder("Property id 'foo' is invalid. Properties must be added to a PropertyGroup"); EXPECT_FALSE(sourceData.AddProperty("foo")); errorMessageFinder.CheckExpectedErrorsFound(); } - TEST_F(MaterialTypeSourceDataTests, AddProperty_Error_PropertySetDoesNotExist ) + TEST_F(MaterialTypeSourceDataTests, AddProperty_Error_PropertyGroupDoesNotExist ) { MaterialTypeSourceData sourceData; - ErrorMessageFinder errorMessageFinder("PropertySet 'DNE' does not exists"); + ErrorMessageFinder errorMessageFinder("PropertyGroup 'DNE' does not exists"); EXPECT_FALSE(sourceData.AddProperty("DNE.foo")); errorMessageFinder.CheckExpectedErrorsFound(); } - TEST_F(MaterialTypeSourceDataTests, AddPropertySet_Error_PropertySetDoesNotExist ) + TEST_F(MaterialTypeSourceDataTests, AddPropertyGroup_Error_PropertyGroupDoesNotExist ) { MaterialTypeSourceData sourceData; - ErrorMessageFinder errorMessageFinder("PropertySet 'DNE' does not exists"); - EXPECT_FALSE(sourceData.AddPropertySet("DNE.foo")); + ErrorMessageFinder errorMessageFinder("PropertyGroup 'DNE' does not exists"); + EXPECT_FALSE(sourceData.AddPropertyGroup("DNE.foo")); errorMessageFinder.CheckExpectedErrorsFound(); } - TEST_F(MaterialTypeSourceDataTests, AddPropertySet_Error_AddDuplicatePropertySet) + TEST_F(MaterialTypeSourceDataTests, AddPropertyGroup_Error_AddDuplicatePropertyGroup) { MaterialTypeSourceData sourceData; - MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("main"); - sourceData.AddPropertySet("main.level2"); + MaterialTypeSourceData::PropertyGroup* propertyGroup = sourceData.AddPropertyGroup("main"); + sourceData.AddPropertyGroup("main.level2"); ErrorMessageFinder errorMessageFinder; - errorMessageFinder.AddExpectedErrorMessage("PropertySet named 'main' already exists", 1); - errorMessageFinder.AddExpectedErrorMessage("PropertySet named 'level2' already exists", 2); + errorMessageFinder.AddExpectedErrorMessage("PropertyGroup named 'main' already exists", 1); + errorMessageFinder.AddExpectedErrorMessage("PropertyGroup named 'level2' already exists", 2); - EXPECT_FALSE(sourceData.AddPropertySet("main")); - EXPECT_FALSE(sourceData.AddPropertySet("main.level2")); - EXPECT_FALSE(propertySet->AddPropertySet("level2")); + EXPECT_FALSE(sourceData.AddPropertyGroup("main")); + EXPECT_FALSE(sourceData.AddPropertyGroup("main.level2")); + EXPECT_FALSE(propertyGroup->AddPropertyGroup("level2")); errorMessageFinder.CheckExpectedErrorsFound(); - EXPECT_EQ(sourceData.GetPropertyLayout().m_propertySets.size(), 1); - EXPECT_EQ(propertySet->GetPropertySets().size(), 1); + EXPECT_EQ(sourceData.GetPropertyLayout().m_propertyGroups.size(), 1); + EXPECT_EQ(propertyGroup->GetPropertyGroups().size(), 1); } - TEST_F(MaterialTypeSourceDataTests, AddPropertySet_Error_NameCollidesWithProperty ) + TEST_F(MaterialTypeSourceDataTests, AddPropertyGroup_Error_NameCollidesWithProperty ) { MaterialTypeSourceData sourceData; - sourceData.AddPropertySet("main"); + sourceData.AddPropertyGroup("main"); sourceData.AddProperty("main.foo"); - ErrorMessageFinder errorMessageFinder("PropertySet name 'foo' collides with a Property of the same name"); - EXPECT_FALSE(sourceData.AddPropertySet("main.foo")); + ErrorMessageFinder errorMessageFinder("PropertyGroup name 'foo' collides with a Property of the same name"); + EXPECT_FALSE(sourceData.AddPropertyGroup("main.foo")); errorMessageFinder.CheckExpectedErrorsFound(); } - TEST_F(MaterialTypeSourceDataTests, AddProperty_Error_NameCollidesWithPropertySet ) + TEST_F(MaterialTypeSourceDataTests, AddProperty_Error_NameCollidesWithPropertyGroup ) { MaterialTypeSourceData sourceData; - sourceData.AddPropertySet("main"); - sourceData.AddPropertySet("main.foo"); + sourceData.AddPropertyGroup("main"); + sourceData.AddPropertyGroup("main.foo"); - ErrorMessageFinder errorMessageFinder("Property name 'foo' collides with a PropertySet of the same name"); + ErrorMessageFinder errorMessageFinder("Property name 'foo' collides with a PropertyGroup of the same name"); EXPECT_FALSE(sourceData.AddProperty("main.foo")); errorMessageFinder.CheckExpectedErrorsFound(); } @@ -627,11 +627,11 @@ namespace UnitTest sourceData.m_uvNameMap["UV1"] = "Unwrapped"; sourceData.m_uvNameMap["UV2"] = "Other"; - sourceData.AddPropertySet("a"); - sourceData.AddPropertySet("a.b"); - sourceData.AddPropertySet("c"); - sourceData.AddPropertySet("c.d"); - sourceData.AddPropertySet("c.d.e"); + sourceData.AddPropertyGroup("a"); + sourceData.AddPropertyGroup("a.b"); + sourceData.AddPropertyGroup("c"); + sourceData.AddPropertyGroup("c.d"); + sourceData.AddPropertyGroup("c.d.e"); MaterialTypeSourceData::PropertyDefinition* enum1 = sourceData.AddProperty("a.enum1"); MaterialTypeSourceData::PropertyDefinition* enum2 = sourceData.AddProperty("a.b.enum2"); @@ -820,8 +820,8 @@ namespace UnitTest sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{ TestShaderFilename }); - MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("general"); - MaterialTypeSourceData::PropertyDefinition* property = propertySet->AddProperty("MyBool"); + MaterialTypeSourceData::PropertyGroup* propertyGroup = sourceData.AddPropertyGroup("general"); + MaterialTypeSourceData::PropertyDefinition* property = propertyGroup->AddProperty("MyBool"); property->m_displayName = "My Bool"; property->m_description = "This is a bool"; property->m_dataType = MaterialPropertyDataType::Bool; @@ -845,8 +845,8 @@ namespace UnitTest sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{ TestShaderFilename }); - MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("general"); - MaterialTypeSourceData::PropertyDefinition* property = propertySet->AddProperty("MyFloat"); + MaterialTypeSourceData::PropertyGroup* propertyGroup = sourceData.AddPropertyGroup("general"); + MaterialTypeSourceData::PropertyDefinition* property = propertyGroup->AddProperty("MyFloat"); property->m_displayName = "My Float"; property->m_description = "This is a float"; property->m_min = 0.0f; @@ -874,8 +874,8 @@ namespace UnitTest sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{ TestShaderFilename }); - MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("general"); - MaterialTypeSourceData::PropertyDefinition* property = propertySet->AddProperty("MyImage"); + MaterialTypeSourceData::PropertyGroup* propertyGroup = sourceData.AddPropertyGroup("general"); + MaterialTypeSourceData::PropertyDefinition* property = propertyGroup->AddProperty("MyImage"); property->m_displayName = "My Image"; property->m_description = "This is an image"; property->m_dataType = MaterialPropertyDataType::Image; @@ -898,8 +898,8 @@ namespace UnitTest sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{TestShaderFilename}); - MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("general"); - MaterialTypeSourceData::PropertyDefinition* property = propertySet->AddProperty("MyInt"); + MaterialTypeSourceData::PropertyGroup* propertyGroup = sourceData.AddPropertyGroup("general"); + MaterialTypeSourceData::PropertyDefinition* property = propertyGroup->AddProperty("MyInt"); property->m_displayName = "My Integer"; property->m_dataType = MaterialPropertyDataType::Int; property->m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{MaterialPropertyOutputType::ShaderOption, AZStd::string("o_foo"), 0}); @@ -920,8 +920,8 @@ namespace UnitTest sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{TestShaderFilename}); - MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("general"); - MaterialTypeSourceData::PropertyDefinition* property = propertySet->AddProperty("MyInt"); + MaterialTypeSourceData::PropertyGroup* propertyGroup = sourceData.AddPropertyGroup("general"); + MaterialTypeSourceData::PropertyDefinition* property = propertyGroup->AddProperty("MyInt"); property->m_dataType = MaterialPropertyDataType::Int; property->m_outputConnections.push_back(MaterialTypeSourceData::PropertyConnection{MaterialPropertyOutputType::ShaderOption, AZStd::string("DoesNotExist"), 0}); @@ -937,7 +937,7 @@ namespace UnitTest const AZStd::string inputJson = R"( { "propertyLayout": { - "propertySets": [ + "propertyGroups": [ { "name": "not a valid name because it has spaces", "properties": [ @@ -967,7 +967,7 @@ namespace UnitTest const AZStd::string inputJson = R"( { "propertyLayout": { - "propertySets": [ + "propertyGroups": [ { "name": "general", "properties": [ @@ -997,7 +997,7 @@ namespace UnitTest const AZStd::string inputJson = R"( { "propertyLayout": { - "propertySets": [ + "propertyGroups": [ { "name": "general", "properties": [ @@ -1027,12 +1027,12 @@ namespace UnitTest errorMessageFinder.CheckExpectedErrorsFound(); } - TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_Error_PropertyAndPropertySetNameCollision) + TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_Error_PropertyAndPropertyGroupNameCollision) { const AZStd::string inputJson = R"( { "propertyLayout": { - "propertySets": [ + "propertyGroups": [ { "name": "general", "properties": [ @@ -1041,7 +1041,7 @@ namespace UnitTest "type": "Bool" } ], - "propertySets": [ + "propertyGroups": [ { "name": "foo", "properties": [ @@ -1062,7 +1062,7 @@ namespace UnitTest JsonTestResult loadResult = LoadTestDataFromJson(sourceData, inputJson); EXPECT_EQ(loadResult.m_jsonResultCode.GetProcessing(), JsonSerializationResult::Processing::Completed); - ErrorMessageFinder errorMessageFinder("Material property 'general.foo' collides with a PropertySet with the same ID"); + ErrorMessageFinder errorMessageFinder("Material property 'general.foo' collides with a PropertyGroup with the same ID"); auto materialTypeOutcome = sourceData.CreateMaterialTypeAsset(Uuid::CreateRandom()); EXPECT_FALSE(materialTypeOutcome.IsSuccess()); errorMessageFinder.CheckExpectedErrorsFound(); @@ -1117,8 +1117,8 @@ namespace UnitTest sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{ "shaderB.shader" }); sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{ "shaderC.shader" }); - MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("general"); - MaterialTypeSourceData::PropertyDefinition* property = propertySet->AddProperty("MyInt"); + MaterialTypeSourceData::PropertyGroup* propertyGroup = sourceData.AddPropertyGroup("general"); + MaterialTypeSourceData::PropertyDefinition* property = propertyGroup->AddProperty("MyInt"); property->m_displayName = "Integer"; property->m_description = "Integer property that is connected to multiple shader settings"; @@ -1176,8 +1176,8 @@ namespace UnitTest { MaterialTypeSourceData sourceData; - MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("general"); - MaterialTypeSourceData::PropertyDefinition* property = propertySet->AddProperty("floatForFunctor"); + MaterialTypeSourceData::PropertyGroup* propertyGroup = sourceData.AddPropertyGroup("general"); + MaterialTypeSourceData::PropertyDefinition* property = propertyGroup->AddProperty("floatForFunctor"); property->m_displayName = "Float for Functor"; property->m_description = "This float is processed by a functor, not with a direct connection"; @@ -1221,9 +1221,9 @@ namespace UnitTest sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{TestShaderFilename}); sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{TestShaderFilename}); - MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("general"); - MaterialTypeSourceData::PropertyDefinition* property1 = propertySet->AddProperty("EnableSpecialPassA"); - MaterialTypeSourceData::PropertyDefinition* property2 = propertySet->AddProperty("EnableSpecialPassB"); + MaterialTypeSourceData::PropertyGroup* propertyGroup = sourceData.AddPropertyGroup("general"); + MaterialTypeSourceData::PropertyDefinition* property1 = propertyGroup->AddProperty("EnableSpecialPassA"); + MaterialTypeSourceData::PropertyDefinition* property2 = propertyGroup->AddProperty("EnableSpecialPassB"); property1->m_displayName = property2->m_displayName = "Enable Special Pass"; property1->m_description = property2->m_description = "This is a bool to enable an extra shader/pass"; @@ -1279,8 +1279,8 @@ namespace UnitTest sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{TestShaderFilename}); sourceData.m_shaderCollection.push_back(MaterialTypeSourceData::ShaderVariantReferenceData{TestShaderFilename}); - MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("general"); - MaterialTypeSourceData::PropertyDefinition* property = propertySet->AddProperty("MyProperty"); + MaterialTypeSourceData::PropertyGroup* propertyGroup = sourceData.AddPropertyGroup("general"); + MaterialTypeSourceData::PropertyDefinition* property = propertyGroup->AddProperty("MyProperty"); property->m_dataType = MaterialPropertyDataType::Bool; // Note that we don't fill property->m_outputConnections because this is not a direct-connected property @@ -1307,12 +1307,12 @@ namespace UnitTest EXPECT_TRUE(materialTypeAsset->GetShaderCollection()[0].MaterialOwnsShaderOption(Name{"o_bar"})); } - TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_FunctorIsInsidePropertySet) + TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_FunctorIsInsidePropertyGroup) { MaterialTypeSourceData sourceData; - MaterialTypeSourceData::PropertySet* propertySet = sourceData.AddPropertySet("general"); - MaterialTypeSourceData::PropertyDefinition* property = propertySet->AddProperty("floatForFunctor"); + MaterialTypeSourceData::PropertyGroup* propertyGroup = sourceData.AddPropertyGroup("general"); + MaterialTypeSourceData::PropertyDefinition* property = propertyGroup->AddProperty("floatForFunctor"); property->m_dataType = MaterialPropertyDataType::Float; @@ -1360,7 +1360,7 @@ namespace UnitTest property->m_value = value; }; - sourceData.AddPropertySet("general"); + sourceData.AddPropertyGroup("general"); addProperty(MaterialPropertyDataType::Bool, "general.MyBool", "m_bool", true); addProperty(MaterialPropertyDataType::Float, "general.MyFloat", "m_float", 1.2f); @@ -1387,7 +1387,7 @@ namespace UnitTest CheckPropertyValue>(materialTypeAsset, Name{"general.MyImage"}, m_testImageAsset); } - TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_NestedPropertySets) + TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_NestedPropertyGroups) { RHI::Ptr layeredMaterialSrgLayout = RHI::ShaderResourceGroupLayout::Create(); layeredMaterialSrgLayout->SetName(Name{"MaterialSrg"}); @@ -1428,16 +1428,16 @@ namespace UnitTest property->m_value = value; }; - sourceData.AddPropertySet("layer1"); - sourceData.AddPropertySet("layer2"); - sourceData.AddPropertySet("blend"); - sourceData.AddPropertySet("layer1.baseColor"); - sourceData.AddPropertySet("layer2.baseColor"); - sourceData.AddPropertySet("layer1.roughness"); - sourceData.AddPropertySet("layer2.roughness"); - sourceData.AddPropertySet("layer2.clearCoat"); - sourceData.AddPropertySet("layer2.clearCoat.roughness"); - sourceData.AddPropertySet("layer2.clearCoat.normal"); + sourceData.AddPropertyGroup("layer1"); + sourceData.AddPropertyGroup("layer2"); + sourceData.AddPropertyGroup("blend"); + sourceData.AddPropertyGroup("layer1.baseColor"); + sourceData.AddPropertyGroup("layer2.baseColor"); + sourceData.AddPropertyGroup("layer1.roughness"); + sourceData.AddPropertyGroup("layer2.roughness"); + sourceData.AddPropertyGroup("layer2.clearCoat"); + sourceData.AddPropertyGroup("layer2.clearCoat.roughness"); + sourceData.AddPropertyGroup("layer2.clearCoat.normal"); addSrgProperty(MaterialPropertyDataType::Image, MaterialPropertyOutputType::ShaderInput, "layer1.baseColor.texture", "m_layer1_baseColor_texture", AZStd::string{TestImageFilename}); addSrgProperty(MaterialPropertyDataType::Image, MaterialPropertyOutputType::ShaderInput, "layer1.roughness.texture", "m_layer1_roughness_texture", AZStd::string{TestImageFilename}); @@ -1487,7 +1487,7 @@ namespace UnitTest } ], "propertyLayout": { - "propertySets": [ + "propertyGroups": [ { "name": "groupA", "displayName": "Property Group A", @@ -1545,8 +1545,8 @@ namespace UnitTest { "name": "groupC", "displayName": "Property Group C", - "description": "Property group C has a nested property set", - "propertySets": [ + "description": "Property group C has a nested property group", + "propertyGroups": [ { "name": "groupD", "displayName": "Property Group D", @@ -1616,27 +1616,27 @@ namespace UnitTest EXPECT_EQ(material.m_versionUpdates[0].m_actions[0].m_renameTo, "groupA.foo"); - EXPECT_EQ(material.GetPropertyLayout().m_propertySets.size(), 3); - EXPECT_TRUE(material.FindPropertySet("groupA") != nullptr); - EXPECT_TRUE(material.FindPropertySet("groupB") != nullptr); - EXPECT_TRUE(material.FindPropertySet("groupC") != nullptr); - EXPECT_TRUE(material.FindPropertySet("groupC.groupD") != nullptr); - EXPECT_TRUE(material.FindPropertySet("groupC.groupE") != nullptr); - EXPECT_EQ(material.FindPropertySet("groupA")->GetDisplayName(), "Property Group A"); - EXPECT_EQ(material.FindPropertySet("groupB")->GetDisplayName(), "Property Group B"); - EXPECT_EQ(material.FindPropertySet("groupC")->GetDisplayName(), "Property Group C"); - EXPECT_EQ(material.FindPropertySet("groupC.groupD")->GetDisplayName(), "Property Group D"); - EXPECT_EQ(material.FindPropertySet("groupC.groupE")->GetDisplayName(), "Property Group E"); - EXPECT_EQ(material.FindPropertySet("groupA")->GetDescription(), "Description of property group A"); - EXPECT_EQ(material.FindPropertySet("groupB")->GetDescription(), "Description of property group B"); - EXPECT_EQ(material.FindPropertySet("groupC")->GetDescription(), "Property group C has a nested property set"); - EXPECT_EQ(material.FindPropertySet("groupC.groupD")->GetDescription(), "Description of property group D"); - EXPECT_EQ(material.FindPropertySet("groupC.groupE")->GetDescription(), "Description of property group E"); - EXPECT_EQ(material.FindPropertySet("groupA")->GetProperties().size(), 2); - EXPECT_EQ(material.FindPropertySet("groupB")->GetProperties().size(), 2); - EXPECT_EQ(material.FindPropertySet("groupC")->GetProperties().size(), 0); - EXPECT_EQ(material.FindPropertySet("groupC.groupD")->GetProperties().size(), 1); - EXPECT_EQ(material.FindPropertySet("groupC.groupE")->GetProperties().size(), 1); + EXPECT_EQ(material.GetPropertyLayout().m_propertyGroups.size(), 3); + EXPECT_TRUE(material.FindPropertyGroup("groupA") != nullptr); + EXPECT_TRUE(material.FindPropertyGroup("groupB") != nullptr); + EXPECT_TRUE(material.FindPropertyGroup("groupC") != nullptr); + EXPECT_TRUE(material.FindPropertyGroup("groupC.groupD") != nullptr); + EXPECT_TRUE(material.FindPropertyGroup("groupC.groupE") != nullptr); + EXPECT_EQ(material.FindPropertyGroup("groupA")->GetDisplayName(), "Property Group A"); + EXPECT_EQ(material.FindPropertyGroup("groupB")->GetDisplayName(), "Property Group B"); + EXPECT_EQ(material.FindPropertyGroup("groupC")->GetDisplayName(), "Property Group C"); + EXPECT_EQ(material.FindPropertyGroup("groupC.groupD")->GetDisplayName(), "Property Group D"); + EXPECT_EQ(material.FindPropertyGroup("groupC.groupE")->GetDisplayName(), "Property Group E"); + EXPECT_EQ(material.FindPropertyGroup("groupA")->GetDescription(), "Description of property group A"); + EXPECT_EQ(material.FindPropertyGroup("groupB")->GetDescription(), "Description of property group B"); + EXPECT_EQ(material.FindPropertyGroup("groupC")->GetDescription(), "Property group C has a nested property group"); + EXPECT_EQ(material.FindPropertyGroup("groupC.groupD")->GetDescription(), "Description of property group D"); + EXPECT_EQ(material.FindPropertyGroup("groupC.groupE")->GetDescription(), "Description of property group E"); + EXPECT_EQ(material.FindPropertyGroup("groupA")->GetProperties().size(), 2); + EXPECT_EQ(material.FindPropertyGroup("groupB")->GetProperties().size(), 2); + EXPECT_EQ(material.FindPropertyGroup("groupC")->GetProperties().size(), 0); + EXPECT_EQ(material.FindPropertyGroup("groupC.groupD")->GetProperties().size(), 1); + EXPECT_EQ(material.FindPropertyGroup("groupC.groupE")->GetProperties().size(), 1); EXPECT_NE(material.FindProperty("groupA.foo"), nullptr); EXPECT_NE(material.FindProperty("groupA.bar"), nullptr); @@ -1670,10 +1670,10 @@ namespace UnitTest EXPECT_EQ(material.FindProperty("groupC.groupD.foo")->m_value, -1); EXPECT_EQ(material.FindProperty("groupC.groupE.bar")->m_value, 0u); - EXPECT_EQ(material.FindPropertySet("groupA")->GetFunctors().size(), 1); - EXPECT_EQ(material.FindPropertySet("groupB")->GetFunctors().size(), 1); - Ptr functorA = material.FindPropertySet("groupA")->GetFunctors()[0]->GetActualSourceData(); - Ptr functorB = material.FindPropertySet("groupB")->GetFunctors()[0]->GetActualSourceData(); + EXPECT_EQ(material.FindPropertyGroup("groupA")->GetFunctors().size(), 1); + EXPECT_EQ(material.FindPropertyGroup("groupB")->GetFunctors().size(), 1); + Ptr functorA = material.FindPropertyGroup("groupA")->GetFunctors()[0]->GetActualSourceData(); + Ptr functorB = material.FindPropertyGroup("groupB")->GetFunctors()[0]->GetActualSourceData(); EXPECT_TRUE(azrtti_cast(functorA.get())); EXPECT_EQ(azrtti_cast(functorA.get())->m_enablePassPropertyId, "foo"); EXPECT_EQ(azrtti_cast(functorA.get())->m_shaderIndex, 1); @@ -1708,7 +1708,7 @@ namespace UnitTest // (The "store" part of the test was not included because the saved data will be the new format). // Notable differences include: // 1) the key "id" is used instead of "name" - // 2) the group metadata, property definitions, and functors are all defined in different sections rather than in a property set + // 2) the group metadata, property definitions, and functors are all defined in different sections rather than in a unified property group definition const AZStd::string inputJson = R"( { @@ -1798,25 +1798,25 @@ namespace UnitTest // Before conversion to the new format, the data is in the old place EXPECT_EQ(material.GetPropertyLayout().m_groupsOld.size(), 2); EXPECT_EQ(material.GetPropertyLayout().m_propertiesOld.size(), 2); - EXPECT_EQ(material.GetPropertyLayout().m_propertySets.size(), 0); + EXPECT_EQ(material.GetPropertyLayout().m_propertyGroups.size(), 0); material.ConvertToNewDataFormat(); // After conversion to the new format, the data is in the new place EXPECT_EQ(material.GetPropertyLayout().m_groupsOld.size(), 0); EXPECT_EQ(material.GetPropertyLayout().m_propertiesOld.size(), 0); - EXPECT_EQ(material.GetPropertyLayout().m_propertySets.size(), 2); + EXPECT_EQ(material.GetPropertyLayout().m_propertyGroups.size(), 2); EXPECT_EQ(material.m_description, "This is a general description about the material"); - EXPECT_TRUE(material.FindPropertySet("groupA") != nullptr); - EXPECT_TRUE(material.FindPropertySet("groupB") != nullptr); - EXPECT_EQ(material.FindPropertySet("groupA")->GetDisplayName(), "Property Group A"); - EXPECT_EQ(material.FindPropertySet("groupB")->GetDisplayName(), "Property Group B"); - EXPECT_EQ(material.FindPropertySet("groupA")->GetDescription(), "Description of property group A"); - EXPECT_EQ(material.FindPropertySet("groupB")->GetDescription(), "Description of property group B"); - EXPECT_EQ(material.FindPropertySet("groupA")->GetProperties().size(), 2); - EXPECT_EQ(material.FindPropertySet("groupB")->GetProperties().size(), 2); + EXPECT_TRUE(material.FindPropertyGroup("groupA") != nullptr); + EXPECT_TRUE(material.FindPropertyGroup("groupB") != nullptr); + EXPECT_EQ(material.FindPropertyGroup("groupA")->GetDisplayName(), "Property Group A"); + EXPECT_EQ(material.FindPropertyGroup("groupB")->GetDisplayName(), "Property Group B"); + EXPECT_EQ(material.FindPropertyGroup("groupA")->GetDescription(), "Description of property group A"); + EXPECT_EQ(material.FindPropertyGroup("groupB")->GetDescription(), "Description of property group B"); + EXPECT_EQ(material.FindPropertyGroup("groupA")->GetProperties().size(), 2); + EXPECT_EQ(material.FindPropertyGroup("groupB")->GetProperties().size(), 2); EXPECT_TRUE(material.FindProperty("groupA.foo") != nullptr); EXPECT_TRUE(material.FindProperty("groupA.bar") != nullptr); @@ -1840,10 +1840,10 @@ namespace UnitTest EXPECT_EQ(material.FindProperty("groupB.foo")->m_value, 0.5f); EXPECT_EQ(material.FindProperty("groupB.bar")->m_value, AZ::Color(0.5f, 0.5f, 0.5f, 1.0f)); - // The functors can appear either at the top level or within each property set. The format conversion + // The functors can appear either at the top level or within each property group. The format conversion // function doesn't know how to move the functors, and they will be left at the top level. - EXPECT_EQ(material.FindPropertySet("groupA")->GetFunctors().size(), 0); - EXPECT_EQ(material.FindPropertySet("groupB")->GetFunctors().size(), 0); + EXPECT_EQ(material.FindPropertyGroup("groupA")->GetFunctors().size(), 0); + EXPECT_EQ(material.FindPropertyGroup("groupB")->GetFunctors().size(), 0); EXPECT_EQ(material.m_shaderCollection.size(), 2); EXPECT_EQ(material.m_shaderCollection[0].m_shaderFilePath, "ForwardPass.shader"); @@ -1873,7 +1873,7 @@ namespace UnitTest { "description": "", "propertyLayout": { - "propertySets": [ + "propertyGroups": [ { "name": "general", "displayName": "General", @@ -1915,7 +1915,7 @@ namespace UnitTest { MaterialTypeSourceData sourceData; - MaterialTypeSourceData::PropertyDefinition* propertySource = sourceData.AddPropertySet("general")->AddProperty("a"); + MaterialTypeSourceData::PropertyDefinition* propertySource = sourceData.AddPropertyGroup("general")->AddProperty("a"); propertySource->m_dataType = MaterialPropertyDataType::Int; propertySource->m_value = 0; diff --git a/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR.materialtype b/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR.materialtype index 5876a3a858..641b0089a5 100644 --- a/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR.materialtype +++ b/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR.materialtype @@ -2,7 +2,7 @@ "description": "Base Material with properties used to define Standard PBR, a metallic-roughness Physically-Based Rendering (PBR) material shading model.", "version": 3, "propertyLayout": { - "propertySets": [ + "propertyGroups": [ { "name": "settings", "displayName": "Settings", diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp index 96b98cef51..c441762710 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp @@ -602,7 +602,7 @@ namespace MaterialEditor return false; } - // TODO: Support populating the Material Editor with nested property sets, not just the top level. + // TODO: Support populating the Material Editor with nested property groups, not just the top level. const AZStd::string groupName = propertyId.GetStringView().substr(0, propertyId.GetStringView().size() - propertyDefinition->GetName().size() - 1); sourceData.m_properties[groupName][propertyDefinition->GetName()].m_value = propertyValue; } @@ -781,14 +781,14 @@ namespace MaterialEditor // Populate the property map from a combination of source data and assets // Assets must still be used for now because they contain the final accumulated value after all other materials // in the hierarchy are applied - m_materialTypeSourceData.EnumeratePropertySets([this, &parentPropertyValues](const AZStd::string& propertyIdContext, const MaterialTypeSourceData::PropertySet* propertySet) + m_materialTypeSourceData.EnumeratePropertyGroups([this, &parentPropertyValues](const AZStd::string& propertyIdContext, const MaterialTypeSourceData::PropertyGroup* propertyGroup) { AtomToolsFramework::DynamicPropertyConfig propertyConfig; - for (const auto& propertyDefinition : propertySet->GetProperties()) + for (const auto& propertyDefinition : propertyGroup->GetProperties()) { // Assign id before conversion so it can be used in dynamic description - propertyConfig.m_id = propertyIdContext + propertySet->GetName() + "." + propertyDefinition->GetName(); + propertyConfig.m_id = propertyIdContext + propertyGroup->GetName() + "." + propertyDefinition->GetName(); const auto& propertyIndex = m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id); const bool propertyIndexInBounds = propertyIndex.IsValid() && propertyIndex.GetIndex() < m_materialAsset->GetPropertyValues().size(); @@ -801,9 +801,9 @@ namespace MaterialEditor propertyConfig.m_originalValue = AtomToolsFramework::ConvertToEditableType(m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]); propertyConfig.m_parentValue = AtomToolsFramework::ConvertToEditableType(parentPropertyValues[propertyIndex.GetIndex()]); - // TODO: Support populating the Material Editor with nested property sets, not just the top level. + // TODO: Support populating the Material Editor with nested property groups, not just the top level. // (Does DynamicPropertyConfig really even need m_groupName?) - propertyConfig.m_groupName = propertySet->GetDisplayName(); + propertyConfig.m_groupName = propertyGroup->GetDisplayName(); m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig); } } @@ -812,10 +812,10 @@ namespace MaterialEditor }); // Populate the property group visibility map - // TODO: Support populating the Material Editor with nested property sets, not just the top level. - for (const AZStd::unique_ptr& propertySet : m_materialTypeSourceData.GetPropertyLayout().m_propertySets) + // TODO: Support populating the Material Editor with nested property groups, not just the top level. + for (const AZStd::unique_ptr& propertyGroup : m_materialTypeSourceData.GetPropertyLayout().m_propertyGroups) { - m_propertyGroupVisibility[AZ::Name{propertySet->GetName()}] = true; + m_propertyGroupVisibility[AZ::Name{propertyGroup->GetName()}] = true; } // Adding properties for material type and parent as part of making dynamic @@ -899,14 +899,14 @@ namespace MaterialEditor } } - // Add any material functors that are located inside each property set. - bool enumerateResult = m_materialTypeSourceData.EnumeratePropertySets( - [this](const AZStd::string&, const MaterialTypeSourceData::PropertySet* propertySet) + // Add any material functors that are located inside each property group. + bool enumerateResult = m_materialTypeSourceData.EnumeratePropertyGroups( + [this](const AZStd::string&, const MaterialTypeSourceData::PropertyGroup* propertyGroup) { const MaterialFunctorSourceData::EditorContext editorContext = MaterialFunctorSourceData::EditorContext( m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout()); - for (Ptr functorData : propertySet->GetFunctors()) + for (Ptr functorData : propertyGroup->GetFunctors()) { MaterialFunctorSourceData::FunctorResult result = functorData->CreateFunctor(editorContext); diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp index 3208683bf0..1402ad9f24 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp @@ -171,16 +171,16 @@ namespace MaterialEditor MaterialDocumentRequestBus::EventResult( materialTypeSourceData, m_documentId, &MaterialDocumentRequestBus::Events::GetMaterialTypeSourceData); - // TODO: Support populating the Material Editor with nested property sets, not just the top level. - for (const AZStd::unique_ptr& propertySet : materialTypeSourceData->GetPropertyLayout().m_propertySets) + // TODO: Support populating the Material Editor with nested property groups, not just the top level. + for (const AZStd::unique_ptr& propertyGroup : materialTypeSourceData->GetPropertyLayout().m_propertyGroups) { - const AZStd::string& groupName = propertySet->GetName(); - const AZStd::string& groupDisplayName = !propertySet->GetDisplayName().empty() ? propertySet->GetDisplayName() : groupName; - const AZStd::string& groupDescription = !propertySet->GetDescription().empty() ? propertySet->GetDescription() : groupDisplayName; + const AZStd::string& groupName = propertyGroup->GetName(); + const AZStd::string& groupDisplayName = !propertyGroup->GetDisplayName().empty() ? propertyGroup->GetDisplayName() : groupName; + const AZStd::string& groupDescription = !propertyGroup->GetDescription().empty() ? propertyGroup->GetDescription() : groupDisplayName; auto& group = m_groups[groupName]; - group.m_properties.reserve(propertySet->GetProperties().size()); - for (const auto& propertyDefinition : propertySet->GetProperties()) + group.m_properties.reserve(propertyGroup->GetProperties().size()); + for (const auto& propertyDefinition : propertyGroup->GetProperties()) { AtomToolsFramework::DynamicProperty property; AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult( diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp index 37fba346b8..dde81e77b5 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp @@ -293,16 +293,16 @@ namespace AZ void MaterialPropertyInspector::AddPropertiesGroup() { // Copy all of the properties from the material asset to the source data that will be exported - // TODO: Support populating the Material Editor with nested property sets, not just the top level. - for (const AZStd::unique_ptr& propertySet : m_editData.m_materialTypeSourceData.GetPropertyLayout().m_propertySets) + // TODO: Support populating the Material Editor with nested property groups, not just the top level. + for (const AZStd::unique_ptr& propertyGroup : m_editData.m_materialTypeSourceData.GetPropertyLayout().m_propertyGroups) { - const AZStd::string& groupName = propertySet->GetName(); - const AZStd::string& groupDisplayName = !propertySet->GetDisplayName().empty() ? propertySet->GetDisplayName() : groupName; - const AZStd::string& groupDescription = !propertySet->GetDescription().empty() ? propertySet->GetDescription() : groupDisplayName; + const AZStd::string& groupName = propertyGroup->GetName(); + const AZStd::string& groupDisplayName = !propertyGroup->GetDisplayName().empty() ? propertyGroup->GetDisplayName() : groupName; + const AZStd::string& groupDescription = !propertyGroup->GetDescription().empty() ? propertyGroup->GetDescription() : groupDisplayName; auto& group = m_groups[groupName]; - group.m_properties.reserve(propertySet->GetProperties().size()); - for (const auto& propertyDefinition : propertySet->GetProperties()) + group.m_properties.reserve(propertyGroup->GetProperties().size()); + for (const auto& propertyDefinition : propertyGroup->GetProperties()) { AtomToolsFramework::DynamicPropertyConfig propertyConfig; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp index 62982e4b4d..dde9644c50 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp @@ -148,7 +148,7 @@ namespace AZ return true; } - // TODO: Support populating the Material Editor with nested property sets, not just the top level. + // TODO: Support populating the Material Editor with nested property groups, not just the top level. const AZStd::string groupName = propertyId.GetStringView().substr(0, propertyId.GetStringView().size() - propertyDefinition->GetName().size() - 1); exportData.m_properties[groupName][propertyDefinition->GetName()].m_value = propertyValue; return true; From fa037d5d7dad0228377729ffc34bb77e767303cd Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Thu, 27 Jan 2022 16:49:17 -0800 Subject: [PATCH 20/53] Updated MaterialSourceData::CreateMaterialAssetFromSourceData to use MaterialUtils::LoadMaterialTypeSourceData which calls ConvertToNewDataFormat(). This was needed by Material Editor to succesfully load old-format material types. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Code/Source/RPI.Edit/Material/MaterialSourceData.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp index 2fa4ff648e..ae7889aa93 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp @@ -217,14 +217,14 @@ namespace AZ return Failure(); } - MaterialTypeSourceData materialTypeSourceData; - if (!AZ::RPI::JsonUtils::LoadObjectFromFile(materialTypeSourcePath, materialTypeSourceData)) + auto materialTypeLoadOutcome = MaterialUtils::LoadMaterialTypeSourceData(materialTypeSourcePath); + if (!materialTypeLoadOutcome) { AZ_Error("MaterialSourceData", false, "Failed to load MaterialTypeSourceData: '%s'.", materialTypeSourcePath.c_str()); return Failure(); } - materialTypeSourceData.ResolveUvEnums(); + MaterialTypeSourceData materialTypeSourceData = materialTypeLoadOutcome.TakeValue(); const auto materialTypeAsset = materialTypeSourceData.CreateMaterialTypeAsset(materialTypeAssetId.GetValue(), materialTypeSourcePath, elevateWarnings); From fba7d73c57d79bbf6b6dd34715364b122542d1af Mon Sep 17 00:00:00 2001 From: Chris Galvan Date: Fri, 28 Jan 2022 11:10:33 -0600 Subject: [PATCH 21/53] Extended sub image pixel API to support component indexing. Signed-off-by: Chris Galvan --- .../RPI/Code/Source/RPI.Public/RPIUtils.cpp | 57 +++++++++++++------ 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp index 2dd2885dff..e13db253f3 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp @@ -111,10 +111,14 @@ namespace AZ { case AZ::RHI::Format::R8_UNORM: case AZ::RHI::Format::A8_UNORM: + case AZ::RHI::Format::R8G8_UNORM: + case AZ::RHI::Format::R8G8B8A8_UNORM: { return mem[index] / static_cast(std::numeric_limits::max()); } case AZ::RHI::Format::R8_SNORM: + case AZ::RHI::Format::R8G8_SNORM: + case AZ::RHI::Format::R8G8B8A8_SNORM: { // Scale the value from AZ::s8 min/max to -1 to 1 // We need to treat -128 and -127 the same, so that we get a symmetric @@ -126,10 +130,14 @@ namespace AZ } case AZ::RHI::Format::D16_UNORM: case AZ::RHI::Format::R16_UNORM: + case AZ::RHI::Format::R16G16_UNORM: + case AZ::RHI::Format::R16G16B16A16_UNORM: { return mem[index] / static_cast(std::numeric_limits::max()); } case AZ::RHI::Format::R16_SNORM: + case AZ::RHI::Format::R16G16_SNORM: + case AZ::RHI::Format::R16G16B16A16_SNORM: { // Scale the value from AZ::s16 min/max to -1 to 1 // We need to treat -32768 and -32767 the same, so that we get a symmetric @@ -140,18 +148,23 @@ namespace AZ return ScaleValue(AZStd::max(actualMem[index], signedMin), signedMin, signedMax, -1.0f, 1.0f); } case AZ::RHI::Format::R16_FLOAT: + case AZ::RHI::Format::R16G16_FLOAT: + case AZ::RHI::Format::R16G16B16A16_FLOAT: { auto actualMem = reinterpret_cast(mem); return SHalf(actualMem[index]); } case AZ::RHI::Format::D32_FLOAT: case AZ::RHI::Format::R32_FLOAT: + case AZ::RHI::Format::R32G32_FLOAT: + case AZ::RHI::Format::R32G32B32_FLOAT: + case AZ::RHI::Format::R32G32B32A32_FLOAT: { auto actualMem = reinterpret_cast(mem); return actualMem[index]; } default: - AZ_Assert(false, "Unsupported pixel format"); + AZ_Assert(false, "Unsupported pixel format: %s", AZ::RHI::ToString(format)); return 0.0f; } } @@ -161,21 +174,28 @@ namespace AZ switch (format) { case AZ::RHI::Format::R8_UINT: + case AZ::RHI::Format::R8G8_UINT: + case AZ::RHI::Format::R8G8B8A8_UINT: { return mem[index] / static_cast(std::numeric_limits::max()); } case AZ::RHI::Format::R16_UINT: + case AZ::RHI::Format::R16G16_UINT: + case AZ::RHI::Format::R16G16B16A16_UINT: { auto actualMem = reinterpret_cast(mem); return actualMem[index] / static_cast(std::numeric_limits::max()); } case AZ::RHI::Format::R32_UINT: + case AZ::RHI::Format::R32G32_UINT: + case AZ::RHI::Format::R32G32B32_UINT: + case AZ::RHI::Format::R32G32B32A32_UINT: { auto actualMem = reinterpret_cast(mem); return actualMem[index]; } default: - AZ_Assert(false, "Unsupported pixel format"); + AZ_Assert(false, "Unsupported pixel format: %s", AZ::RHI::ToString(format)); return 0; } } @@ -185,21 +205,28 @@ namespace AZ switch (format) { case AZ::RHI::Format::R8_SINT: + case AZ::RHI::Format::R8G8_SINT: + case AZ::RHI::Format::R8G8B8A8_SINT: { return mem[index] / static_cast(std::numeric_limits::max()); } case AZ::RHI::Format::R16_SINT: + case AZ::RHI::Format::R16G16_SINT: + case AZ::RHI::Format::R16G16B16A16_SINT: { auto actualMem = reinterpret_cast(mem); return actualMem[index] / static_cast(std::numeric_limits::max()); } case AZ::RHI::Format::R32_SINT: + case AZ::RHI::Format::R32G32_SINT: + case AZ::RHI::Format::R32G32B32_SINT: + case AZ::RHI::Format::R32G32B32A32_SINT: { auto actualMem = reinterpret_cast(mem); return actualMem[index]; } default: - AZ_Assert(false, "Unsupported pixel format"); + AZ_Assert(false, "Unsupported pixel format: %s", AZ::RHI::ToString(format)); return 0; } } @@ -439,9 +466,6 @@ namespace AZ bool GetSubImagePixelValues(const AZ::Data::Asset& imageAsset, AZStd::pair topLeft, AZStd::pair bottomRight, AZStd::span outValues, uint32_t componentIndex, uint32_t mip, uint32_t slice) { - // TODO: Use the component index - (void)componentIndex; - if (!imageAsset.IsReady()) { return false; @@ -455,14 +479,15 @@ namespace AZ const AZ::RHI::ImageDescriptor imageDescriptor = imageAsset->GetImageDescriptor(); auto width = imageDescriptor.m_size.m_width; - const uint32_t pixelSize = AZ::RHI::GetFormatSize(imageDescriptor.m_format); + const uint32_t numComponents = AZ::RHI::GetFormatComponentCount(imageDescriptor.m_format); + const uint32_t pixelSize = AZ::RHI::GetFormatSize(imageDescriptor.m_format) / numComponents; size_t outValuesIndex = 0; for (uint32_t y = topLeft.second; y < bottomRight.second; ++y) { for (uint32_t x = topLeft.first; x < bottomRight.first; ++x) { - size_t imageDataIndex = (y * width + x) * pixelSize; + size_t imageDataIndex = (y * width + x) * pixelSize + componentIndex; auto& outValue = outValues[outValuesIndex++]; outValue = Internal::RetrieveFloatValue(imageData.data(), imageDataIndex, imageDescriptor.m_format); @@ -474,9 +499,6 @@ namespace AZ bool GetSubImagePixelValues(const AZ::Data::Asset& imageAsset, AZStd::pair topLeft, AZStd::pair bottomRight, AZStd::span outValues, uint32_t componentIndex, uint32_t mip, uint32_t slice) { - // TODO: Use the component index - (void)componentIndex; - if (!imageAsset.IsReady()) { return false; @@ -490,14 +512,15 @@ namespace AZ const AZ::RHI::ImageDescriptor imageDescriptor = imageAsset->GetImageDescriptor(); auto width = imageDescriptor.m_size.m_width; - const uint32_t pixelSize = AZ::RHI::GetFormatSize(imageDescriptor.m_format); + const uint32_t numComponents = AZ::RHI::GetFormatComponentCount(imageDescriptor.m_format); + const uint32_t pixelSize = AZ::RHI::GetFormatSize(imageDescriptor.m_format) / numComponents; size_t outValuesIndex = 0; for (uint32_t y = topLeft.second; y < bottomRight.second; ++y) { for (uint32_t x = topLeft.first; x < bottomRight.first; ++x) { - size_t imageDataIndex = (y * width + x) * pixelSize; + size_t imageDataIndex = (y * width + x) * pixelSize + componentIndex; auto& outValue = outValues[outValuesIndex++]; outValue = Internal::RetrieveUintValue(imageData.data(), imageDataIndex, imageDescriptor.m_format); @@ -509,9 +532,6 @@ namespace AZ bool GetSubImagePixelValues(const AZ::Data::Asset& imageAsset, AZStd::pair topLeft, AZStd::pair bottomRight, AZStd::span outValues, uint32_t componentIndex, uint32_t mip, uint32_t slice) { - // TODO: Use the component index - (void)componentIndex; - if (!imageAsset.IsReady()) { return false; @@ -525,14 +545,15 @@ namespace AZ const AZ::RHI::ImageDescriptor imageDescriptor = imageAsset->GetImageDescriptor(); auto width = imageDescriptor.m_size.m_width; - const uint32_t pixelSize = AZ::RHI::GetFormatSize(imageDescriptor.m_format); + const uint32_t numComponents = AZ::RHI::GetFormatComponentCount(imageDescriptor.m_format); + const uint32_t pixelSize = AZ::RHI::GetFormatSize(imageDescriptor.m_format) / numComponents; size_t outValuesIndex = 0; for (uint32_t y = topLeft.second; y < bottomRight.second; ++y) { for (uint32_t x = topLeft.first; x < bottomRight.first; ++x) { - size_t imageDataIndex = (y * width + x) * pixelSize; + size_t imageDataIndex = (y * width + x) * pixelSize + componentIndex; auto& outValue = outValues[outValuesIndex++]; outValue = Internal::RetrieveIntValue(imageData.data(), imageDataIndex, imageDescriptor.m_format); From fc027832003e4b1a8ea3eea800225b069819e490 Mon Sep 17 00:00:00 2001 From: Sergey Pereslavtsev Date: Fri, 28 Jan 2022 17:14:21 +0000 Subject: [PATCH 22/53] Fixed crash and asserts when heightfield is used without Terrain World Signed-off-by: Sergey Pereslavtsev --- Gems/PhysX/Code/Editor/DebugDraw.cpp | 7 ++++++- .../Code/Source/EditorHeightfieldColliderComponent.cpp | 7 ++++++- Gems/PhysX/Code/Source/HeightfieldColliderComponent.cpp | 8 +++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Gems/PhysX/Code/Editor/DebugDraw.cpp b/Gems/PhysX/Code/Editor/DebugDraw.cpp index 0af5822edf..78d216a21a 100644 --- a/Gems/PhysX/Code/Editor/DebugDraw.cpp +++ b/Gems/PhysX/Code/Editor/DebugDraw.cpp @@ -694,7 +694,12 @@ namespace PhysX const float minYBounds = -(numRows * heightfieldShapeConfig.GetGridResolution().GetY()) / 2.0f; auto heights = heightfieldShapeConfig.GetSamples(); - + + if (heights.empty()) + { + return; + } + for (int xIndex = 0; xIndex < numColumns - 1; xIndex++) { for (int yIndex = 0; yIndex < numRows - 1; yIndex++) diff --git a/Gems/PhysX/Code/Source/EditorHeightfieldColliderComponent.cpp b/Gems/PhysX/Code/Source/EditorHeightfieldColliderComponent.cpp index c6def5f0ab..292267cc5b 100644 --- a/Gems/PhysX/Code/Source/EditorHeightfieldColliderComponent.cpp +++ b/Gems/PhysX/Code/Source/EditorHeightfieldColliderComponent.cpp @@ -211,7 +211,12 @@ namespace PhysX { ClearHeightfield(); InitHeightfieldShapeConfiguration(); - InitStaticRigidBody(); + + if (!m_shapeConfig->GetSamples().empty()) + { + InitStaticRigidBody(); + } + Physics::ColliderComponentEventBus::Event(GetEntityId(), &Physics::ColliderComponentEvents::OnColliderChanged); } diff --git a/Gems/PhysX/Code/Source/HeightfieldColliderComponent.cpp b/Gems/PhysX/Code/Source/HeightfieldColliderComponent.cpp index b219253b6f..9076afeab9 100644 --- a/Gems/PhysX/Code/Source/HeightfieldColliderComponent.cpp +++ b/Gems/PhysX/Code/Source/HeightfieldColliderComponent.cpp @@ -150,7 +150,13 @@ namespace PhysX { ClearHeightfield(); InitHeightfieldShapeConfiguration(); - InitStaticRigidBody(); + + Physics::HeightfieldShapeConfiguration& configuration = static_cast(*m_shapeConfig.second); + if (!configuration.GetSamples().empty()) + { + InitStaticRigidBody(); + } + Physics::ColliderComponentEventBus::Event(GetEntityId(), &Physics::ColliderComponentEvents::OnColliderChanged); } From 0ba1cff08ed1b91c62dcf091ad3a8a95d49d1d86 Mon Sep 17 00:00:00 2001 From: Steve Pham <82231385+spham-amzn@users.noreply.github.com> Date: Fri, 28 Jan 2022 09:48:46 -0800 Subject: [PATCH 23/53] Remove legacy 'CrySetFileAttributes' (#7226) * Remove legacy function 'CrySetFileAttributes' and replace its only use with AZ::IO::SystemFile::* functions Signed-off-by: Steve Pham <82231385+spham-amzn@users.noreply.github.com> --- Code/Legacy/CryCommon/WinBase.cpp | 14 -------------- Code/Legacy/CryCommon/platform.h | 1 - Code/Legacy/CryCommon/platform_impl.cpp | 17 ----------------- Code/Legacy/CrySystem/XML/xml.cpp | 5 ++++- 4 files changed, 4 insertions(+), 33 deletions(-) diff --git a/Code/Legacy/CryCommon/WinBase.cpp b/Code/Legacy/CryCommon/WinBase.cpp index 771cde324e..6e6f5e210a 100644 --- a/Code/Legacy/CryCommon/WinBase.cpp +++ b/Code/Legacy/CryCommon/WinBase.cpp @@ -856,18 +856,4 @@ DLL_EXPORT void OutputDebugString(const char* outputString) #endif -// This code does not have a long life span and will be replaced soon -#if defined(APPLE) || defined(LINUX) || defined(DEFINE_LEGACY_CRY_FILE_OPERATIONS) - -bool CrySetFileAttributes(const char* lpFileName, uint32 dwFileAttributes) -{ - //TODO: implement - printf("CrySetFileAttributes not properly implemented yet\n"); - return false; -} - - - -#endif //defined(APPLE) || defined(LINUX) - #endif // AZ_TRAIT_LEGACY_CRYCOMMON_USE_WINDOWS_STUBS diff --git a/Code/Legacy/CryCommon/platform.h b/Code/Legacy/CryCommon/platform.h index d2251c7091..512f8b4892 100644 --- a/Code/Legacy/CryCommon/platform.h +++ b/Code/Legacy/CryCommon/platform.h @@ -336,7 +336,6 @@ void SetFlags(T& dest, U flags, bool b) #include AZ_RESTRICTED_FILE(platform_h) #endif -bool CrySetFileAttributes(const char* lpFileName, uint32 dwFileAttributes); threadID CryGetCurrentThreadId(); #ifdef __GNUC__ diff --git a/Code/Legacy/CryCommon/platform_impl.cpp b/Code/Legacy/CryCommon/platform_impl.cpp index 8cbc58ad95..3392c40771 100644 --- a/Code/Legacy/CryCommon/platform_impl.cpp +++ b/Code/Legacy/CryCommon/platform_impl.cpp @@ -24,7 +24,6 @@ #define PLATFORM_IMPL_H_SECTION_TRAITS 1 #define PLATFORM_IMPL_H_SECTION_CRYLOWLATENCYSLEEP 2 #define PLATFORM_IMPL_H_SECTION_CRYGETFILEATTRIBUTES 3 -#define PLATFORM_IMPL_H_SECTION_CRYSETFILEATTRIBUTES 4 #define PLATFORM_IMPL_H_SECTION_CRY_FILE_ATTRIBUTE_STUBS 5 #define PLATFORM_IMPL_H_SECTION_CRY_SYSTEM_FUNCTIONS 6 #define PLATFORM_IMPL_H_SECTION_VIRTUAL_ALLOCATORS 7 @@ -238,22 +237,6 @@ void InitRootDir(char szExeFileName[], uint nExeSize, char szExeRootName[], uint } } -////////////////////////////////////////////////////////////////////////// -bool CrySetFileAttributes(const char* lpFileName, uint32 dwFileAttributes) -{ -#if defined(AZ_RESTRICTED_PLATFORM) - #define AZ_RESTRICTED_SECTION PLATFORM_IMPL_H_SECTION_CRYSETFILEATTRIBUTES - #include AZ_RESTRICTED_FILE(platform_impl_h) -#endif -#if defined(AZ_RESTRICTED_SECTION_IMPLEMENTED) -#undef AZ_RESTRICTED_SECTION_IMPLEMENTED -#else - AZStd::wstring lpFileNameW; - AZStd::to_wstring(lpFileNameW, lpFileName); - return SetFileAttributes(lpFileNameW.c_str(), dwFileAttributes) != 0; -#endif -} - ////////////////////////////////////////////////////////////////////////// threadID CryGetCurrentThreadId() { diff --git a/Code/Legacy/CrySystem/XML/xml.cpp b/Code/Legacy/CrySystem/XML/xml.cpp index fb0714500c..2356e71518 100644 --- a/Code/Legacy/CrySystem/XML/xml.cpp +++ b/Code/Legacy/CrySystem/XML/xml.cpp @@ -1132,7 +1132,10 @@ bool CXmlNode::saveToFile(const char* fileName) bool CXmlNode::saveToFile([[maybe_unused]] const char* fileName, size_t chunkSize, AZ::IO::HandleType fileHandle) { - CrySetFileAttributes(fileName, FILE_ATTRIBUTE_NORMAL); + if (AZ::IO::SystemFile::Exists(fileName) && !AZ::IO::SystemFile::IsWritable(fileName)) + { + AZ::IO::SystemFile::SetWritable(fileName, true); + } if (chunkSize < 256 * 1024) // make at least 256k { From 24086ab3946d6d7257938f4643d81562349c8747 Mon Sep 17 00:00:00 2001 From: AMZN-nggieber <52797929+AMZN-nggieber@users.noreply.github.com> Date: Fri, 28 Jan 2022 10:05:03 -0800 Subject: [PATCH 24/53] Resizable Headers for Gem Catalog and Gem Repo Screen (#6885) * Initial mostly working attempt at resizable headers Signed-off-by: nggieber <52797929+AMZN-nggieber@users.noreply.github.com> * Add const and constexpr to variables Signed-off-by: nggieber <52797929+AMZN-nggieber@users.noreply.github.com> * Add header tracking for buttons, display items based on header scrollbar position, fix some spacing issues Signed-off-by: nggieber <52797929+AMZN-nggieber@users.noreply.github.com> * Correct styling for adjustable header, and intending header section and alingned item content with header text Signed-off-by: nggieber <52797929+AMZN-nggieber@users.noreply.github.com> * Prevent header resizing larger than table width. Signed-off-by: Alex Peterson <26804013+AMZN-alexpete@users.noreply.github.com> * Fix resize graphical glitching Signed-off-by: Alex Peterson <26804013+AMZN-alexpete@users.noreply.github.com> * Remove unecessary qss Signed-off-by: nggieber <52797929+AMZN-nggieber@users.noreply.github.com> * Removed necessary headers Signed-off-by: nggieber <52797929+AMZN-nggieber@users.noreply.github.com> * Remove extra nl and old comment Signed-off-by: nggieber <52797929+AMZN-nggieber@users.noreply.github.com> * Address PR feedback Signed-off-by: nggieber <52797929+AMZN-nggieber@users.noreply.github.com> * Removed unused variables Signed-off-by: nggieber <52797929+AMZN-nggieber@users.noreply.github.com> * Change variable to constexpr Signed-off-by: nggieber <52797929+AMZN-nggieber@users.noreply.github.com> * Remove AUTOMOC from headers, set background color of Gem Catalog to 333333 and adjustable header to transparent Signed-off-by: nggieber <52797929+AMZN-nggieber@users.noreply.github.com> * Change to using update instead of repaint when sections are resized Signed-off-by: nggieber <52797929+AMZN-nggieber@users.noreply.github.com> * Change temp directory creation on for gradle Signed-off-by: nggieber <52797929+AMZN-nggieber@users.noreply.github.com> Co-authored-by: Alex Peterson <26804013+AMZN-alexpete@users.noreply.github.com> --- .../Resources/ProjectManager.qss | 20 ++- .../Source/AdjustableHeaderWidget.cpp | 118 ++++++++++++++++++ .../Source/AdjustableHeaderWidget.h | 52 ++++++++ .../Source/ExternalLinkDialog.h | 2 +- .../GemCatalog/GemCatalogHeaderWidget.h | 2 +- .../Source/GemCatalog/GemCatalogScreen.cpp | 46 ++++++- .../Source/GemCatalog/GemDependenciesDialog.h | 2 +- .../Source/GemCatalog/GemFilterWidget.h | 2 +- .../Source/GemCatalog/GemInspector.h | 2 +- .../Source/GemCatalog/GemItemDelegate.cpp | 47 +++++-- .../Source/GemCatalog/GemItemDelegate.h | 29 ++++- .../Source/GemCatalog/GemListHeaderWidget.cpp | 30 +---- .../Source/GemCatalog/GemListHeaderWidget.h | 2 +- .../Source/GemCatalog/GemListView.cpp | 9 +- .../Source/GemCatalog/GemListView.h | 6 +- .../Source/GemCatalog/GemModel.h | 2 +- .../GemCatalog/GemRequirementDelegate.cpp | 8 +- .../GemCatalog/GemRequirementDelegate.h | 2 +- .../Source/GemCatalog/GemRequirementDialog.h | 2 +- .../GemRequirementFilterProxyModel.h | 2 +- .../GemCatalog/GemRequirementListView.h | 2 +- .../GemCatalog/GemSortFilterProxyModel.h | 2 +- .../Source/GemCatalog/GemUninstallDialog.h | 2 +- .../Source/GemCatalog/GemUpdateDialog.h | 2 +- .../Source/GemRepo/GemRepoInspector.h | 2 +- .../Source/GemRepo/GemRepoItemDelegate.cpp | 63 +++++++--- .../Source/GemRepo/GemRepoItemDelegate.h | 27 ++-- .../Source/GemRepo/GemRepoListView.cpp | 8 +- .../Source/GemRepo/GemRepoListView.h | 10 +- .../Source/GemRepo/GemRepoModel.h | 2 +- .../Source/GemRepo/GemRepoScreen.cpp | 58 +++++---- .../Source/GemRepo/GemRepoScreen.h | 3 +- .../ProjectManager/Source/GemsSubWidget.h | 2 +- Code/Tools/ProjectManager/Source/LinkWidget.h | 2 +- .../Source/ProjectButtonWidget.h | 2 +- .../Source/ProjectManagerDefs.h | 1 + .../Source/ScreenHeaderWidget.h | 2 +- Code/Tools/ProjectManager/Source/TagWidget.h | 2 +- .../Source/TemplateButtonWidget.h | 2 +- .../project_manager_files.cmake | 2 + .../build/Platform/Android/gradle_windows.cmd | 18 ++- 41 files changed, 439 insertions(+), 160 deletions(-) create mode 100644 Code/Tools/ProjectManager/Source/AdjustableHeaderWidget.cpp create mode 100644 Code/Tools/ProjectManager/Source/AdjustableHeaderWidget.h diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qss b/Code/Tools/ProjectManager/Resources/ProjectManager.qss index 3d100ec170..b2217435b2 100644 --- a/Code/Tools/ProjectManager/Resources/ProjectManager.qss +++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qss @@ -500,6 +500,10 @@ QProgressBar::chunk { /************** Gem Catalog **************/ +#GemCatalogScreen { + background-color: #333333; +} + #GemCatalogTitle { font-size: 18px; } @@ -546,9 +550,8 @@ QProgressBar::chunk { min-height:24px; } -#GemCatalogHeaderLabel { - font-size: 12px; - color: #FFFFFF; +#adjustableHeaderWidget QHeaderView::section { + background-color: transparent; } #GemCatalogHeaderShowCountLabel { @@ -732,15 +735,6 @@ QProgressBar::chunk { stop: 0 #555555, stop: 1.0 #777777); } -#gemRepoHeaderTable { - background-color: transparent; - max-height: 30px; -} - -#gemRepoListHeader { - background-color: transparent; -} - #gemRepoInspector { background: #444444; } @@ -774,4 +768,4 @@ QProgressBar::chunk { #gemRepoInspectorAddInfoTitleLabel { font-size: 16px; color: #FFFFFF; -} \ No newline at end of file +} diff --git a/Code/Tools/ProjectManager/Source/AdjustableHeaderWidget.cpp b/Code/Tools/ProjectManager/Source/AdjustableHeaderWidget.cpp new file mode 100644 index 0000000000..2b4732a168 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/AdjustableHeaderWidget.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include + +#include +#include + +namespace O3DE::ProjectManager +{ + AdjustableHeaderWidget::AdjustableHeaderWidget( const QStringList& headerLabels, + const QVector& defaultHeaderWidths, int minHeaderWidth, + const QVector& resizeModes, QWidget* parent) + : QTableWidget(parent) + { + setObjectName("adjustableHeaderWidget"); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + setFixedHeight(s_headerWidgetHeight); + + m_header = horizontalHeader(); + m_header->setDefaultAlignment(Qt::AlignLeft); + + setColumnCount(headerLabels.count()); + setHorizontalHeaderLabels(headerLabels); + + AZ_Assert(defaultHeaderWidths.count() == columnCount(), "Default header widths does not match number of columns"); + AZ_Assert(resizeModes.count() == columnCount(), "Resize modesdoes not match number of columns"); + + for (int column = 0; column < columnCount(); ++column) + { + m_header->resizeSection(column, defaultHeaderWidths[column]); + m_header->setSectionResizeMode(column, resizeModes[column]); + } + + m_header->setMinimumSectionSize(minHeaderWidth); + m_header->setCascadingSectionResizes(true); + + connect(m_header, &QHeaderView::sectionResized, this, &AdjustableHeaderWidget::OnSectionResized); + } + + void AdjustableHeaderWidget::OnSectionResized(int logicalIndex, int oldSize, int newSize) + { + const int headerCount = columnCount(); + const int headerWidth = m_header->width(); + const int totalSectionWidth = m_header->length(); + + if (totalSectionWidth > headerWidth && newSize > oldSize) + { + int xPos = 0; + int requiredWidth = 0; + + for (int i = 0; i < headerCount; i++) + { + if (i < logicalIndex) + { + xPos += m_header->sectionSize(i); + } + else if (i == logicalIndex) + { + xPos += newSize; + } + else if (i > logicalIndex) + { + if (m_header->sectionResizeMode(i) == QHeaderView::ResizeMode::Fixed) + { + requiredWidth += m_header->sectionSize(i); + } + else + { + requiredWidth += m_header->minimumSectionSize(); + } + } + } + + if (xPos + requiredWidth > headerWidth) + { + m_header->resizeSection(logicalIndex, oldSize); + } + } + + // wait till all columns resized + QTimer::singleShot(0, [&]() + { + // only re-paint when the header and section widths have settled + const int headerWidth = m_header->width(); + const int totalSectionWidth = m_header->length(); + if (totalSectionWidth == headerWidth) + { + emit sectionsResized(); + } + }); + } + + QPair AdjustableHeaderWidget::CalcColumnXBounds(int headerIndex) const + { + // Total the widths of all headers before this one in first and including it in second + QPair bounds(0, 0); + + for (int curIndex = 0; curIndex <= headerIndex; ++curIndex) + { + if (curIndex == headerIndex) + { + bounds.first = bounds.second; + } + bounds.second += m_header->sectionSize(curIndex); + } + + return bounds; + } + + +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/AdjustableHeaderWidget.h b/Code/Tools/ProjectManager/Source/AdjustableHeaderWidget.h new file mode 100644 index 0000000000..26a5ce13c7 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/AdjustableHeaderWidget.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include + +#include +#include +#include +#include +#endif + +namespace O3DE::ProjectManager +{ + // Using a QTableWidget for its header + // Using a seperate model allows the setup of a header exactly as needed + class AdjustableHeaderWidget + : public QTableWidget + { + Q_OBJECT + + public: + explicit AdjustableHeaderWidget(const QStringList& headerLabels, + const QVector& defaultHeaderWidths, int minHeaderWidth, + const QVector& resizeModes, + QWidget* parent = nullptr); + ~AdjustableHeaderWidget() = default; + + QPair CalcColumnXBounds(int headerIndex) const; + + inline constexpr static int s_headerTextIndent = 7; + inline constexpr static int s_headerWidgetHeight = 24; + + QHeaderView* m_header; + + signals: + void sectionsResized(); + + protected slots: + void OnSectionResized(int logicalIndex, int oldSize, int newSize); + + private: + inline constexpr static int s_headerIndentSection = 11; + }; +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ExternalLinkDialog.h b/Code/Tools/ProjectManager/Source/ExternalLinkDialog.h index 45d391e64e..f1b6574b67 100644 --- a/Code/Tools/ProjectManager/Source/ExternalLinkDialog.h +++ b/Code/Tools/ProjectManager/Source/ExternalLinkDialog.h @@ -17,7 +17,7 @@ namespace O3DE::ProjectManager class ExternalLinkDialog : public QDialog { - Q_OBJECT // AUTOMOC + Q_OBJECT public: explicit ExternalLinkDialog(const QUrl& url, QWidget* parent = nullptr); ~ExternalLinkDialog() = default; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h index b749e9831d..4e945f5c99 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h @@ -33,7 +33,7 @@ namespace O3DE::ProjectManager class GemCartWidget : public QScrollArea { - Q_OBJECT // AUTOMOC + Q_OBJECT public: GemCartWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent = nullptr); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index 5bc0b26ca5..ebabff45cc 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -19,8 +19,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -40,6 +42,13 @@ namespace O3DE::ProjectManager GemCatalogScreen::GemCatalogScreen(QWidget* parent) : ScreenWidget(parent) { + // The width of either side panel (filters, inspector) in the catalog + constexpr int sidePanelWidth = 240; + // Querying qApp about styling reports the scroll bar being larger than it is so define it manually + constexpr int verticalScrollBarWidth = 8; + + setObjectName("GemCatalogScreen"); + m_gemModel = new GemModel(this); m_proxyModel = new GemSortFilterProxyModel(m_gemModel, this); @@ -69,10 +78,8 @@ namespace O3DE::ProjectManager hLayout->setMargin(0); vLayout->addLayout(hLayout); - m_gemListView = new GemListView(m_proxyModel, m_proxyModel->GetSelectionModel(), this); - m_rightPanelStack = new QStackedWidget(this); - m_rightPanelStack->setFixedWidth(240); + m_rightPanelStack->setFixedWidth(sidePanelWidth); m_gemInspector = new GemInspector(m_gemModel, this); @@ -81,18 +88,45 @@ namespace O3DE::ProjectManager connect(m_gemInspector, &GemInspector::UninstallGem, this, &GemCatalogScreen::UninstallGem); QWidget* filterWidget = new QWidget(this); - filterWidget->setFixedWidth(240); + filterWidget->setFixedWidth(sidePanelWidth); m_filterWidgetLayout = new QVBoxLayout(); m_filterWidgetLayout->setMargin(0); m_filterWidgetLayout->setSpacing(0); filterWidget->setLayout(m_filterWidgetLayout); - GemListHeaderWidget* listHeaderWidget = new GemListHeaderWidget(m_proxyModel); + GemListHeaderWidget* catalogHeaderWidget = new GemListHeaderWidget(m_proxyModel); + + constexpr int minHeaderSectionWidth = 100; + AdjustableHeaderWidget* listHeaderWidget = new AdjustableHeaderWidget( + QStringList{ tr("Gem Name"), tr("Gem Summary"), tr("Status") }, + QVector{ + GemItemDelegate::s_defaultSummaryStartX - 30, + 0, // Section is set to stretch to fit + GemItemDelegate::s_buttonWidth + GemItemDelegate::s_itemMargins.left() + GemItemDelegate::s_itemMargins.right() + GemItemDelegate::s_contentMargins.right() + }, + minHeaderSectionWidth, + QVector + { + QHeaderView::ResizeMode::Interactive, + QHeaderView::ResizeMode::Stretch, + QHeaderView::ResizeMode::Fixed + }, + this); + + m_gemListView = new GemListView(m_proxyModel, m_proxyModel->GetSelectionModel(), listHeaderWidget, this); + + QHBoxLayout* listHeaderLayout = new QHBoxLayout(); + listHeaderLayout->setMargin(0); + listHeaderLayout->setSpacing(0); + listHeaderLayout->addSpacing(GemItemDelegate::s_itemMargins.left()); + listHeaderLayout->addWidget(listHeaderWidget); + listHeaderLayout->addSpacing(GemItemDelegate::s_itemMargins.right() + verticalScrollBarWidth); QVBoxLayout* middleVLayout = new QVBoxLayout(); middleVLayout->setMargin(0); middleVLayout->setSpacing(0); - middleVLayout->addWidget(listHeaderWidget); + middleVLayout->addWidget(catalogHeaderWidget); + middleVLayout->addLayout(listHeaderLayout); middleVLayout->addWidget(m_gemListView); hLayout->addWidget(filterWidget); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemDependenciesDialog.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemDependenciesDialog.h index df8ca6f8a2..1858637aa5 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemDependenciesDialog.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemDependenciesDialog.h @@ -19,7 +19,7 @@ namespace O3DE::ProjectManager class GemDependenciesDialog : public QDialog { - Q_OBJECT // AUTOMOC + Q_OBJECT public: explicit GemDependenciesDialog(GemModel* gemModel, QWidget *parent = nullptr); ~GemDependenciesDialog() = default; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.h index e422178d08..729a63af64 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.h @@ -26,7 +26,7 @@ namespace O3DE::ProjectManager class FilterCategoryWidget : public QWidget { - Q_OBJECT // AUTOMOC + Q_OBJECT public: explicit FilterCategoryWidget(const QString& header, diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h index 1713191623..c71d43eaac 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h @@ -28,7 +28,7 @@ namespace O3DE::ProjectManager class GemInspector : public QScrollArea { - Q_OBJECT // AUTOMOC + Q_OBJECT public: explicit GemInspector(GemModel* model, QWidget* parent = nullptr); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp index dd94e42fc4..1733257e3b 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp @@ -9,6 +9,8 @@ #include #include #include +#include + #include #include @@ -22,12 +24,14 @@ #include #include #include +#include namespace O3DE::ProjectManager { - GemItemDelegate::GemItemDelegate(QAbstractItemModel* model, QObject* parent) + GemItemDelegate::GemItemDelegate(QAbstractItemModel* model, AdjustableHeaderWidget* header, QObject* parent) : QStyledItemDelegate(parent) , m_model(model) + , m_headerWidget(header) { AddPlatformIcon(GemInfo::Android, ":/Android.svg"); AddPlatformIcon(GemInfo::iOS, ":/iOS.svg"); @@ -116,12 +120,15 @@ namespace O3DE::ProjectManager // Gem name QString gemName = GemModel::GetDisplayName(modelIndex); QFont gemNameFont(options.font); - const int firstColumnMaxTextWidth = s_summaryStartX - 30; + QPair nameXBounds = CalcColumnXBounds(HeaderOrder::Name); + const int nameStartX = nameXBounds.first; + const int firstColumnTextStartX = s_itemMargins.left() + nameStartX + AdjustableHeaderWidget::s_headerTextIndent; + const int firstColumnMaxTextWidth = nameXBounds.second - nameStartX - AdjustableHeaderWidget::s_headerTextIndent; gemNameFont.setPixelSize(static_cast(s_gemNameFontSize)); gemNameFont.setBold(true); gemName = QFontMetrics(gemNameFont).elidedText(gemName, Qt::TextElideMode::ElideRight, firstColumnMaxTextWidth); QRect gemNameRect = GetTextRect(gemNameFont, gemName, s_gemNameFontSize); - gemNameRect.moveTo(contentRect.left(), contentRect.top()); + gemNameRect.moveTo(firstColumnTextStartX, contentRect.top()); painter->setFont(gemNameFont); painter->setPen(m_textColor); gemNameRect = painter->boundingRect(gemNameRect, Qt::TextSingleLine, gemName); @@ -131,7 +138,7 @@ namespace O3DE::ProjectManager QString gemCreator = GemModel::GetCreator(modelIndex); gemCreator = standardFontMetrics.elidedText(gemCreator, Qt::TextElideMode::ElideRight, firstColumnMaxTextWidth); QRect gemCreatorRect = GetTextRect(standardFont, gemCreator, s_fontSize); - gemCreatorRect.moveTo(contentRect.left(), contentRect.top() + gemNameRect.height()); + gemCreatorRect.moveTo(firstColumnTextStartX, contentRect.top() + gemNameRect.height()); painter->setFont(standardFont); gemCreatorRect = painter->boundingRect(gemCreatorRect, Qt::TextSingleLine, gemCreator); @@ -157,10 +164,13 @@ namespace O3DE::ProjectManager const int featureTagAreaHeight = 30; const int summaryHeight = contentRect.height() - (hasTags * featureTagAreaHeight); - const int additionalSummarySpacing = s_itemMargins.right() * 3; - const QSize summarySize = QSize(contentRect.width() - s_summaryStartX - s_buttonWidth - additionalSummarySpacing, + const auto [summaryStartX, summaryEndX] = CalcColumnXBounds(HeaderOrder::Summary); + + const QSize summarySize = + QSize(summaryEndX - summaryStartX - AdjustableHeaderWidget::s_headerTextIndent - s_extraSummarySpacing, summaryHeight); - return QRect(QPoint(contentRect.left() + s_summaryStartX, contentRect.top()), summarySize); + return QRect( + QPoint(s_itemMargins.left() + summaryStartX + AdjustableHeaderWidget::s_headerTextIndent, contentRect.top()), summarySize); } QSize GemItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const @@ -169,7 +179,7 @@ namespace O3DE::ProjectManager initStyleOption(&options, modelIndex); int marginsHorizontal = s_itemMargins.left() + s_itemMargins.right() + s_contentMargins.left() + s_contentMargins.right(); - return QSize(marginsHorizontal + s_buttonWidth + s_summaryStartX, s_height); + return QSize(marginsHorizontal + s_buttonWidth + s_defaultSummaryStartX, s_height); } bool GemItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) @@ -299,9 +309,17 @@ namespace O3DE::ProjectManager return QFontMetrics(font).boundingRect(text); } + QPair GemItemDelegate::CalcColumnXBounds(HeaderOrder header) const + { + return m_headerWidget->CalcColumnXBounds(static_cast(header)); + } + QRect GemItemDelegate::CalcButtonRect(const QRect& contentRect) const { - const QPoint topLeft = QPoint(contentRect.right() - s_buttonWidth, contentRect.center().y() - s_buttonHeight / 2); + const QPoint topLeft = QPoint( + s_itemMargins.left() + CalcColumnXBounds(HeaderOrder::Status).first + AdjustableHeaderWidget::s_headerTextIndent + s_statusIconSize + + s_statusButtonSpacing, + contentRect.center().y() - s_buttonHeight / 2); const QSize size = QSize(s_buttonWidth, s_buttonHeight); return QRect(topLeft, size); } @@ -331,18 +349,23 @@ namespace O3DE::ProjectManager } } - void GemItemDelegate::DrawFeatureTags(QPainter* painter, const QRect& contentRect, const QStringList& featureTags, const QFont& standardFont, const QRect& summaryRect) const + void GemItemDelegate::DrawFeatureTags( + QPainter* painter, + const QRect& contentRect, + const QStringList& featureTags, + const QFont& standardFont, + const QRect& summaryRect) const { QFont gemFeatureTagFont(standardFont); gemFeatureTagFont.setPixelSize(s_featureTagFontSize); gemFeatureTagFont.setBold(false); painter->setFont(gemFeatureTagFont); - int x = s_summaryStartX; + int x = CalcColumnXBounds(HeaderOrder::Summary).first + AdjustableHeaderWidget::s_headerTextIndent; for (const QString& featureTag : featureTags) { QRect featureTagRect = GetTextRect(gemFeatureTagFont, featureTag, s_featureTagFontSize); - featureTagRect.moveTo(contentRect.left() + x + s_featureTagBorderMarginX, + featureTagRect.moveTo(s_itemMargins.left() + x + s_featureTagBorderMarginX, contentRect.top() + 47); featureTagRect = painter->boundingRect(featureTagRect, Qt::TextSingleLine, featureTag); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h index 107de6de15..a08fcb0a4b 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h @@ -19,13 +19,15 @@ QT_FORWARD_DECLARE_CLASS(QEvent) namespace O3DE::ProjectManager { + QT_FORWARD_DECLARE_CLASS(AdjustableHeaderWidget) + class GemItemDelegate : public QStyledItemDelegate { - Q_OBJECT // AUTOMOC + Q_OBJECT public: - explicit GemItemDelegate(QAbstractItemModel* model, QObject* parent = nullptr); + explicit GemItemDelegate(QAbstractItemModel* model, AdjustableHeaderWidget* header, QObject* parent = nullptr); ~GemItemDelegate() = default; void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const override; @@ -45,12 +47,13 @@ namespace O3DE::ProjectManager inline constexpr static int s_height = 105; // Gem item total height inline constexpr static qreal s_gemNameFontSize = 13.0; inline constexpr static qreal s_fontSize = 12.0; - inline constexpr static int s_summaryStartX = 150; + inline constexpr static int s_defaultSummaryStartX = 190; // Margin and borders inline constexpr static QMargins s_itemMargins = QMargins(/*left=*/16, /*top=*/8, /*right=*/16, /*bottom=*/8); // Item border distances inline constexpr static QMargins s_contentMargins = QMargins(/*left=*/20, /*top=*/12, /*right=*/20, /*bottom=*/12); // Distances of the elements within an item to the item borders inline constexpr static int s_borderWidth = 4; + inline constexpr static int s_extraSummarySpacing = s_itemMargins.right(); // Button inline constexpr static int s_buttonWidth = 32; @@ -65,6 +68,13 @@ namespace O3DE::ProjectManager inline constexpr static int s_featureTagBorderMarginY = 3; inline constexpr static int s_featureTagSpacing = 7; + enum class HeaderOrder + { + Name, + Summary, + Status + }; + signals: void MovieStartedPlaying(const QMovie* playingMovie) const; @@ -74,13 +84,20 @@ namespace O3DE::ProjectManager void CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const; QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const; + QPair CalcColumnXBounds(HeaderOrder header) const; QRect CalcButtonRect(const QRect& contentRect) const; QRect CalcSummaryRect(const QRect& contentRect, bool hasTags) const; void DrawPlatformIcons(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const; void DrawButton(QPainter* painter, const QRect& buttonRect, const QModelIndex& modelIndex) const; - void DrawFeatureTags(QPainter* painter, const QRect& contentRect, const QStringList& featureTags, const QFont& standardFont, const QRect& summaryRect) const; + void DrawFeatureTags( + QPainter* painter, + const QRect& contentRect, + const QStringList& featureTags, + const QFont& standardFont, + const QRect& summaryRect) const; void DrawText(const QString& text, QPainter* painter, const QRect& rect, const QFont& standardFont) const; - void DrawDownloadStatusIcon(QPainter* painter, const QRect& contentRect, const QRect& buttonRect, const QModelIndex& modelIndex) const; + void DrawDownloadStatusIcon( + QPainter* painter, const QRect& contentRect, const QRect& buttonRect, const QModelIndex& modelIndex) const; QAbstractItemModel* m_model = nullptr; @@ -100,5 +117,7 @@ namespace O3DE::ProjectManager QPixmap m_downloadSuccessfulPixmap; QPixmap m_downloadFailedPixmap; QMovie* m_downloadingMovie = nullptr; + + AdjustableHeaderWidget* m_headerWidget = nullptr; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemListHeaderWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemListHeaderWidget.cpp index 10ff31f33b..ec54b413b6 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemListHeaderWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemListHeaderWidget.cpp @@ -78,37 +78,9 @@ namespace O3DE::ProjectManager // Separating line QFrame* hLine = new QFrame(); hLine->setFrameShape(QFrame::HLine); - hLine->setStyleSheet("color: #666666;"); + hLine->setObjectName("horizontalSeparatingLine"); vLayout->addWidget(hLine); vLayout->addSpacing(GemItemDelegate::s_contentMargins.top()); - - // Bottom section - QHBoxLayout* columnHeaderLayout = new QHBoxLayout(); - columnHeaderLayout->setAlignment(Qt::AlignLeft); - - const int gemNameStartX = GemItemDelegate::s_itemMargins.left() + GemItemDelegate::s_contentMargins.left() - 1; - columnHeaderLayout->addSpacing(gemNameStartX); - - QLabel* gemNameLabel = new QLabel(tr("Gem Name")); - gemNameLabel->setObjectName("GemCatalogHeaderLabel"); - columnHeaderLayout->addWidget(gemNameLabel); - - columnHeaderLayout->addSpacing(89); - - QLabel* gemSummaryLabel = new QLabel(tr("Gem Summary")); - gemSummaryLabel->setObjectName("GemCatalogHeaderLabel"); - columnHeaderLayout->addWidget(gemSummaryLabel); - - QSpacerItem* horizontalSpacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum); - columnHeaderLayout->addSpacerItem(horizontalSpacer); - - QLabel* gemSelectedLabel = new QLabel(tr("Status")); - gemSelectedLabel->setObjectName("GemCatalogHeaderLabel"); - columnHeaderLayout->addWidget(gemSelectedLabel); - - columnHeaderLayout->addSpacing(72); - - vLayout->addLayout(columnHeaderLayout); } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemListHeaderWidget.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemListHeaderWidget.h index 537748c849..350c17bbf9 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemListHeaderWidget.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemListHeaderWidget.h @@ -19,7 +19,7 @@ namespace O3DE::ProjectManager class GemListHeaderWidget : public QFrame { - Q_OBJECT // AUTOMOC + Q_OBJECT public: explicit GemListHeaderWidget(GemSortFilterProxyModel* proxyModel, QWidget* parent = nullptr); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.cpp index cfdf7fa5b3..d68cbf511b 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.cpp @@ -8,12 +8,15 @@ #include #include +#include #include +#include namespace O3DE::ProjectManager { - GemListView::GemListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent) + GemListView::GemListView( + QAbstractItemModel* model, QItemSelectionModel* selectionModel, AdjustableHeaderWidget* header, QWidget* parent) : QListView(parent) { setObjectName("GemCatalogListView"); @@ -21,7 +24,7 @@ namespace O3DE::ProjectManager setModel(model); setSelectionModel(selectionModel); - GemItemDelegate* itemDelegate = new GemItemDelegate(model, this); + GemItemDelegate* itemDelegate = new GemItemDelegate(model, header, this); connect(itemDelegate, &GemItemDelegate::MovieStartedPlaying, [=](const QMovie* playingMovie) { @@ -31,6 +34,8 @@ namespace O3DE::ProjectManager this->viewport()->repaint(); }); }); + + connect(header, &AdjustableHeaderWidget::sectionsResized, [=] { update(); }); setItemDelegate(itemDelegate); } diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.h index b1b0e0c077..81f5255d9b 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.h @@ -16,13 +16,15 @@ namespace O3DE::ProjectManager { + QT_FORWARD_DECLARE_CLASS(AdjustableHeaderWidget) + class GemListView : public QListView { - Q_OBJECT // AUTOMOC + Q_OBJECT public: - explicit GemListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent = nullptr); + explicit GemListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, AdjustableHeaderWidget* header, QWidget* parent = nullptr); ~GemListView() = default; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h index cb99581468..50f406c97d 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h @@ -21,7 +21,7 @@ namespace O3DE::ProjectManager class GemModel : public QStandardItemModel { - Q_OBJECT // AUTOMOC + Q_OBJECT public: explicit GemModel(QObject* parent = nullptr); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDelegate.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDelegate.cpp index f4a7148d46..ae4bad8901 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDelegate.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDelegate.cpp @@ -16,7 +16,7 @@ namespace O3DE::ProjectManager { GemRequirementDelegate::GemRequirementDelegate(QAbstractItemModel* model, QObject* parent) - : GemItemDelegate(model, parent) + : GemItemDelegate(model, nullptr, parent) { } @@ -54,7 +54,7 @@ namespace O3DE::ProjectManager // Gem name QString gemName = GemModel::GetDisplayName(modelIndex); QFont gemNameFont(options.font); - const int firstColumnMaxTextWidth = s_summaryStartX - 30; + const int firstColumnMaxTextWidth = s_defaultSummaryStartX - 30; gemName = QFontMetrics(gemNameFont).elidedText(gemName, Qt::TextElideMode::ElideRight, firstColumnMaxTextWidth); gemNameFont.setPixelSize(static_cast(s_gemNameFontSize)); gemNameFont.setBold(true); @@ -75,8 +75,8 @@ namespace O3DE::ProjectManager QRect GemRequirementDelegate::CalcRequirementRect(const QRect& contentRect) const { - const QSize requirementSize = QSize(contentRect.width() - s_summaryStartX - s_itemMargins.right(), contentRect.height()); - return QRect(QPoint(contentRect.left() + s_summaryStartX, contentRect.top()), requirementSize); + const QSize requirementSize = QSize(contentRect.width() - s_defaultSummaryStartX - s_itemMargins.right(), contentRect.height()); + return QRect(QPoint(contentRect.left() + s_defaultSummaryStartX, contentRect.top()), requirementSize); } bool GemRequirementDelegate::editorEvent( diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDelegate.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDelegate.h index e9001df7fa..cbfb6b1838 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDelegate.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDelegate.h @@ -18,7 +18,7 @@ namespace O3DE::ProjectManager class GemRequirementDelegate : public GemItemDelegate { - Q_OBJECT // AUTOMOC + Q_OBJECT public: explicit GemRequirementDelegate(QAbstractItemModel* model, QObject* parent = nullptr); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDialog.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDialog.h index af8b1e2cc9..c1dff70ca2 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDialog.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDialog.h @@ -19,7 +19,7 @@ namespace O3DE::ProjectManager class GemRequirementDialog : public QDialog { - Q_OBJECT // AUTOMOC + Q_OBJECT public: explicit GemRequirementDialog(GemModel* model, QWidget *parent = nullptr); ~GemRequirementDialog() = default; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementFilterProxyModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementFilterProxyModel.h index 7df75f7d94..c527df2ec1 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementFilterProxyModel.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementFilterProxyModel.h @@ -22,7 +22,7 @@ namespace O3DE::ProjectManager class GemRequirementFilterProxyModel : public QSortFilterProxyModel { - Q_OBJECT // AUTOMOC + Q_OBJECT public: GemRequirementFilterProxyModel(GemModel* sourceModel, QObject* parent = nullptr); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementListView.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementListView.h index 1638fe3fc5..61b3356e06 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementListView.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementListView.h @@ -19,7 +19,7 @@ namespace O3DE::ProjectManager class GemRequirementListView : public QListView { - Q_OBJECT // AUTOMOC + Q_OBJECT public: explicit GemRequirementListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent = nullptr); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.h index 0c58d66ccf..6bdeaf828b 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.h @@ -22,7 +22,7 @@ namespace O3DE::ProjectManager class GemSortFilterProxyModel : public QSortFilterProxyModel { - Q_OBJECT // AUTOMOC + Q_OBJECT public: enum class GemSelected diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.h index 9e3f4c3f3b..391d247f90 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.h @@ -17,7 +17,7 @@ namespace O3DE::ProjectManager class GemUninstallDialog : public QDialog { - Q_OBJECT // AUTOMOC + Q_OBJECT public: explicit GemUninstallDialog(const QString& gemName, QWidget *parent = nullptr); ~GemUninstallDialog() = default; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.h index cf34abfb3d..a0996216fd 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.h @@ -17,7 +17,7 @@ namespace O3DE::ProjectManager class GemUpdateDialog : public QDialog { - Q_OBJECT // AUTOMOC + Q_OBJECT public : explicit GemUpdateDialog(const QString& gemName, bool updateAvaliable = true, QWidget* parent = nullptr); ~GemUpdateDialog() = default; diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInspector.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInspector.h index a14472e6a6..f7051d1704 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInspector.h +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInspector.h @@ -26,7 +26,7 @@ namespace O3DE::ProjectManager { class GemRepoInspector : public QScrollArea { - Q_OBJECT // AUTOMOC + Q_OBJECT public : explicit GemRepoInspector(GemRepoModel* model, QWidget* parent = nullptr); ~GemRepoInspector() = default; diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp index fdfcf02155..672cd509d3 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp @@ -9,16 +9,19 @@ #include #include #include +#include #include #include #include +#include namespace O3DE::ProjectManager { - GemRepoItemDelegate::GemRepoItemDelegate(QAbstractItemModel* model, QObject* parent) + GemRepoItemDelegate::GemRepoItemDelegate(QAbstractItemModel* model, AdjustableHeaderWidget* header, QObject* parent) : QStyledItemDelegate(parent) , m_model(model) + , m_headerWidget(header) { m_refreshIcon = QIcon(":/Refresh.svg").pixmap(s_refreshIconSize, s_refreshIconSize); m_editIcon = QIcon(":/Edit.svg").pixmap(s_iconSize, s_iconSize); @@ -69,44 +72,55 @@ namespace O3DE::ProjectManager painter->restore(); } + int currentHorizontalOffset = CalcColumnXBounds(HeaderOrder::Name).first; + // Repo name QString repoName = GemRepoModel::GetName(modelIndex); - repoName = QFontMetrics(standardFont).elidedText(repoName, Qt::TextElideMode::ElideRight, s_nameMaxWidth); + int sectionSize = m_headerWidget->m_header->sectionSize(static_cast(HeaderOrder::Name)); + repoName = standardFontMetrics.elidedText(repoName, Qt::TextElideMode::ElideRight, + sectionSize - AdjustableHeaderWidget::s_headerTextIndent); QRect repoNameRect = GetTextRect(standardFont, repoName, s_fontSize); - int currentHorizontalOffset = contentRect.left(); - repoNameRect.moveTo(currentHorizontalOffset, contentRect.center().y() - repoNameRect.height() / 2); + repoNameRect.moveTo(currentHorizontalOffset + AdjustableHeaderWidget::s_headerTextIndent, + contentRect.center().y() - repoNameRect.height() / 2); repoNameRect = painter->boundingRect(repoNameRect, Qt::TextSingleLine, repoName); painter->drawText(repoNameRect, Qt::TextSingleLine, repoName); // Rem repo creator + currentHorizontalOffset += sectionSize; + sectionSize = m_headerWidget->m_header->sectionSize(static_cast(HeaderOrder::Creator)); + QString repoCreator = GemRepoModel::GetCreator(modelIndex); - repoCreator = standardFontMetrics.elidedText(repoCreator, Qt::TextElideMode::ElideRight, s_creatorMaxWidth); + repoCreator = standardFontMetrics.elidedText(repoCreator, Qt::TextElideMode::ElideRight, + sectionSize - AdjustableHeaderWidget::s_headerTextIndent); QRect repoCreatorRect = GetTextRect(standardFont, repoCreator, s_fontSize); - currentHorizontalOffset += s_nameMaxWidth + s_contentSpacing; - repoCreatorRect.moveTo(currentHorizontalOffset, contentRect.center().y() - repoCreatorRect.height() / 2); + repoCreatorRect.moveTo(currentHorizontalOffset + AdjustableHeaderWidget::s_headerTextIndent, + contentRect.center().y() - repoCreatorRect.height() / 2); repoCreatorRect = painter->boundingRect(repoCreatorRect, Qt::TextSingleLine, repoCreator); painter->drawText(repoCreatorRect, Qt::TextSingleLine, repoCreator); // Repo update + currentHorizontalOffset += sectionSize; + sectionSize = m_headerWidget->m_header->sectionSize(static_cast(HeaderOrder::Update)); + QString repoUpdatedDate = GemRepoModel::GetLastUpdated(modelIndex).toString(RepoTimeFormat); - repoUpdatedDate = standardFontMetrics.elidedText(repoUpdatedDate, Qt::TextElideMode::ElideRight, s_updatedMaxWidth); + repoUpdatedDate = standardFontMetrics.elidedText( + repoUpdatedDate, Qt::TextElideMode::ElideRight, + sectionSize - GemRepoItemDelegate::s_refreshIconSpacing - GemRepoItemDelegate::s_refreshIconSize - AdjustableHeaderWidget::s_headerTextIndent); QRect repoUpdatedDateRect = GetTextRect(standardFont, repoUpdatedDate, s_fontSize); - currentHorizontalOffset += s_creatorMaxWidth + s_contentSpacing; - repoUpdatedDateRect.moveTo(currentHorizontalOffset, contentRect.center().y() - repoUpdatedDateRect.height() / 2); + repoUpdatedDateRect.moveTo(currentHorizontalOffset + AdjustableHeaderWidget::s_headerTextIndent, + contentRect.center().y() - repoUpdatedDateRect.height() / 2); repoUpdatedDateRect = painter->boundingRect(repoUpdatedDateRect, Qt::TextSingleLine, repoUpdatedDate); painter->drawText(repoUpdatedDateRect, Qt::TextSingleLine, repoUpdatedDate); // Draw refresh button - painter->drawPixmap( - repoUpdatedDateRect.left() + s_updatedMaxWidth + s_refreshIconSpacing, - contentRect.center().y() - s_refreshIconSize / 3, // Dividing size by 3 centers much better - m_refreshIcon); + const QRect refreshButtonRect = CalcRefreshButtonRect(contentRect); + painter->drawPixmap(refreshButtonRect.topLeft(), m_refreshIcon); if (options.state & QStyle::State_MouseOver) { @@ -121,8 +135,8 @@ namespace O3DE::ProjectManager QStyleOptionViewItem options(option); initStyleOption(&options, modelIndex); - int marginsHorizontal = s_itemMargins.left() + s_itemMargins.right() + s_contentMargins.left() + s_contentMargins.right(); - return QSize(marginsHorizontal + s_nameMaxWidth + s_creatorMaxWidth + s_updatedMaxWidth + s_contentSpacing * 3, s_height); + const int marginsHorizontal = s_itemMargins.left() + s_itemMargins.right() + s_contentMargins.left() + s_contentMargins.right(); + return QSize(marginsHorizontal + s_nameDefaultWidth + s_creatorDefaultWidth + s_updatedDefaultWidth, s_height); } bool GemRepoItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) @@ -185,22 +199,31 @@ namespace O3DE::ProjectManager return QFontMetrics(font).boundingRect(text); } + QPair GemRepoItemDelegate::CalcColumnXBounds(HeaderOrder header) const + { + return m_headerWidget->CalcColumnXBounds(static_cast(header)); + } + QRect GemRepoItemDelegate::CalcDeleteButtonRect(const QRect& contentRect) const { - const QPoint topLeft = QPoint(contentRect.right() - s_iconSize, contentRect.center().y() - s_iconSize / 2); + const int deleteHeaderEndX = CalcColumnXBounds(HeaderOrder::Delete).second; + const QPoint topLeft = QPoint(deleteHeaderEndX - s_iconSize - s_contentMargins.right(), contentRect.center().y() - s_iconSize / 2); return QRect(topLeft, QSize(s_iconSize, s_iconSize)); } QRect GemRepoItemDelegate::CalcRefreshButtonRect(const QRect& contentRect) const { - const int topLeftX = contentRect.left() + s_nameMaxWidth + s_creatorMaxWidth + s_updatedMaxWidth + s_contentSpacing * 2 + s_refreshIconSpacing; - const QPoint topLeft = QPoint(topLeftX, contentRect.center().y() - s_refreshIconSize / 3); + const int headerEndX = CalcColumnXBounds(HeaderOrder::Update).second; + const int leftX = headerEndX - s_refreshIconSize - s_refreshIconSpacing; + // Dividing size by 3 centers much better + const QPoint topLeft = QPoint(leftX, contentRect.center().y() - s_refreshIconSize / 3); return QRect(topLeft, QSize(s_refreshIconSize, s_refreshIconSize)); } void GemRepoItemDelegate::DrawEditButtons(QPainter* painter, const QRect& contentRect) const { - painter->drawPixmap(contentRect.right() - s_iconSize, contentRect.center().y() - s_iconSize / 2, m_deleteIcon); + const QRect deleteButtonRect = CalcDeleteButtonRect(contentRect); + painter->drawPixmap(deleteButtonRect, m_deleteIcon); } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h index 69d943001d..f8b53e47be 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h @@ -18,13 +18,15 @@ QT_FORWARD_DECLARE_CLASS(QEvent) namespace O3DE::ProjectManager { + QT_FORWARD_DECLARE_CLASS(AdjustableHeaderWidget) + class GemRepoItemDelegate : public QStyledItemDelegate { - Q_OBJECT // AUTOMOC + Q_OBJECT public: - explicit GemRepoItemDelegate(QAbstractItemModel* model, QObject* parent = nullptr); + explicit GemRepoItemDelegate(QAbstractItemModel* model, AdjustableHeaderWidget* header, QObject* parent = nullptr); ~GemRepoItemDelegate() = default; void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const override; @@ -42,15 +44,14 @@ namespace O3DE::ProjectManager inline constexpr static qreal s_fontSize = 12.0; // Margin and borders - inline constexpr static QMargins s_itemMargins = QMargins(/*left=*/0, /*top=*/8, /*right=*/60, /*bottom=*/8); // Item border distances + inline constexpr static QMargins s_itemMargins = QMargins(/*left=*/0, /*top=*/8, /*right=*/0, /*bottom=*/8); // Item border distances inline constexpr static QMargins s_contentMargins = QMargins(/*left=*/20, /*top=*/20, /*right=*/20, /*bottom=*/20); // Distances of the elements within an item to the item borders inline constexpr static int s_borderWidth = 4; // Content - inline constexpr static int s_contentSpacing = 5; - inline constexpr static int s_nameMaxWidth = 145; - inline constexpr static int s_creatorMaxWidth = 115; - inline constexpr static int s_updatedMaxWidth = 125; + inline constexpr static int s_nameDefaultWidth = 150; + inline constexpr static int s_creatorDefaultWidth = 120; + inline constexpr static int s_updatedDefaultWidth = 130; // Icon inline constexpr static int s_iconSize = 24; @@ -58,6 +59,14 @@ namespace O3DE::ProjectManager inline constexpr static int s_refreshIconSize = 14; inline constexpr static int s_refreshIconSpacing = 10; + enum class HeaderOrder + { + Name, + Creator, + Update, + Delete + }; + signals: void RemoveRepo(const QModelIndex& modelIndex); void RefreshRepo(const QModelIndex& modelIndex); @@ -65,13 +74,15 @@ namespace O3DE::ProjectManager protected: void CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const; QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const; - QRect CalcButtonRect(const QRect& contentRect) const; + QPair CalcColumnXBounds(HeaderOrder header) const; QRect CalcDeleteButtonRect(const QRect& contentRect) const; QRect CalcRefreshButtonRect(const QRect& contentRect) const; void DrawEditButtons(QPainter* painter, const QRect& contentRect) const; QAbstractItemModel* m_model = nullptr; + AdjustableHeaderWidget* m_headerWidget = nullptr; + QPixmap m_refreshIcon; QPixmap m_editIcon; QPixmap m_deleteIcon; diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.cpp index 9adf3e6e3f..cf877fd73a 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.cpp @@ -8,12 +8,15 @@ #include #include +#include #include +#include namespace O3DE::ProjectManager { - GemRepoListView::GemRepoListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent) + GemRepoListView::GemRepoListView( + QAbstractItemModel* model, QItemSelectionModel* selectionModel, AdjustableHeaderWidget* header, QWidget* parent) : QListView(parent) { setObjectName("gemRepoListView"); @@ -22,9 +25,10 @@ namespace O3DE::ProjectManager setModel(model); setSelectionModel(selectionModel); - GemRepoItemDelegate* itemDelegate = new GemRepoItemDelegate(model, this); + GemRepoItemDelegate* itemDelegate = new GemRepoItemDelegate(model, header, this); connect(itemDelegate, &GemRepoItemDelegate::RemoveRepo, this, &GemRepoListView::RemoveRepo); connect(itemDelegate, &GemRepoItemDelegate::RefreshRepo, this, &GemRepoListView::RefreshRepo); + connect(header, &AdjustableHeaderWidget::sectionsResized, [=] { update(); }); setItemDelegate(itemDelegate); } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.h index 50bcf8daa6..7062997f09 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.h +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.h @@ -17,13 +17,19 @@ QT_FORWARD_DECLARE_CLASS(QAbstractItemModel) namespace O3DE::ProjectManager { + QT_FORWARD_DECLARE_CLASS(AdjustableHeaderWidget) + class GemRepoListView : public QListView { - Q_OBJECT // AUTOMOC + Q_OBJECT public: - explicit GemRepoListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent = nullptr); + explicit GemRepoListView( + QAbstractItemModel* model, + QItemSelectionModel* selectionModel, + AdjustableHeaderWidget* header, + QWidget* parent = nullptr); ~GemRepoListView() = default; signals: diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.h index 68991a0509..d1e3975496 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.h +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.h @@ -21,7 +21,7 @@ namespace O3DE::ProjectManager class GemRepoModel : public QStandardItemModel { - Q_OBJECT // AUTOMOC + Q_OBJECT public: explicit GemRepoModel(QObject* parent = nullptr); diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp index 843538d9da..996d8873c5 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include #include @@ -248,6 +250,9 @@ namespace O3DE::ProjectManager QFrame* GemRepoScreen::CreateReposContent() { + constexpr int inspectorWidth = 240; + constexpr int middleLayoutIndent = 60; + QFrame* contentFrame = new QFrame(this); QHBoxLayout* hLayout = new QHBoxLayout(); @@ -255,7 +260,7 @@ namespace O3DE::ProjectManager hLayout->setSpacing(0); contentFrame->setLayout(hLayout); - hLayout->addSpacing(60); + hLayout->addSpacing(middleLayoutIndent); QVBoxLayout* middleVLayout = new QVBoxLayout(); middleVLayout->setMargin(0); @@ -287,37 +292,34 @@ namespace O3DE::ProjectManager connect(addRepoButton, &QPushButton::clicked, this, &GemRepoScreen::HandleAddRepoButton); - topMiddleHLayout->addSpacing(30); - middleVLayout->addLayout(topMiddleHLayout); middleVLayout->addSpacing(30); - // Create a QTableWidget just for its header - // Using a seperate model allows the setup of a header exactly as needed - m_gemRepoHeaderTable = new QTableWidget(this); - m_gemRepoHeaderTable->setObjectName("gemRepoHeaderTable"); - m_gemRepoListHeader = m_gemRepoHeaderTable->horizontalHeader(); - m_gemRepoListHeader->setObjectName("gemRepoListHeader"); - m_gemRepoListHeader->setDefaultAlignment(Qt::AlignLeft); - m_gemRepoListHeader->setSectionResizeMode(QHeaderView::ResizeMode::Fixed); - - // Insert columns so the header labels will show up - m_gemRepoHeaderTable->insertColumn(0); - m_gemRepoHeaderTable->insertColumn(1); - m_gemRepoHeaderTable->insertColumn(2); - m_gemRepoHeaderTable->setHorizontalHeaderLabels({ tr("Repository Name"), tr("Creator"), tr("Updated") }); - - const int headerExtraMargin = 18; - m_gemRepoListHeader->resizeSection(0, GemRepoItemDelegate::s_nameMaxWidth + GemRepoItemDelegate::s_contentSpacing + headerExtraMargin); - m_gemRepoListHeader->resizeSection(1, GemRepoItemDelegate::s_creatorMaxWidth + GemRepoItemDelegate::s_contentSpacing); - m_gemRepoListHeader->resizeSection(2, GemRepoItemDelegate::s_updatedMaxWidth + GemRepoItemDelegate::s_contentSpacing); - - // Required to set stylesheet in code as it will not be respected if set in qss - m_gemRepoHeaderTable->horizontalHeader()->setStyleSheet("QHeaderView::section { background-color:transparent; color:white; font-size:12px; border-style:none; }"); + constexpr int minHeaderSectionWidth = 120; + + m_gemRepoHeaderTable = new AdjustableHeaderWidget( + QStringList{ tr("Repository Name"), tr("Creator"), tr("Updated"), "" }, + QVector{ + GemRepoItemDelegate::s_nameDefaultWidth, + GemRepoItemDelegate::s_creatorDefaultWidth, + GemRepoItemDelegate::s_updatedDefaultWidth + GemRepoItemDelegate::s_refreshIconSpacing + GemRepoItemDelegate::s_refreshIconSize, + // Include invisible header for delete button + GemRepoItemDelegate::s_iconSize + GemRepoItemDelegate::s_contentMargins.right() + }, + minHeaderSectionWidth, + QVector + { + QHeaderView::ResizeMode::Interactive, + QHeaderView::ResizeMode::Stretch, + QHeaderView::ResizeMode::Fixed, + QHeaderView::ResizeMode::Fixed + }, + this); + middleVLayout->addWidget(m_gemRepoHeaderTable); - m_gemRepoListView = new GemRepoListView(m_gemRepoModel, m_gemRepoModel->GetSelectionModel(), this); + m_gemRepoListView = new GemRepoListView(m_gemRepoModel, m_gemRepoModel->GetSelectionModel(), m_gemRepoHeaderTable, this); middleVLayout->addWidget(m_gemRepoListView); connect(m_gemRepoListView, &GemRepoListView::RemoveRepo, this, &GemRepoScreen::HandleRemoveRepoButton); @@ -325,8 +327,10 @@ namespace O3DE::ProjectManager hLayout->addLayout(middleVLayout); + hLayout->addSpacing(middleLayoutIndent); + m_gemRepoInspector = new GemRepoInspector(m_gemRepoModel, this); - m_gemRepoInspector->setFixedWidth(240); + m_gemRepoInspector->setFixedWidth(inspectorWidth); hLayout->addWidget(m_gemRepoInspector); return contentFrame; diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h index eed9a5ec4a..643a9b91fc 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h @@ -24,6 +24,7 @@ namespace O3DE::ProjectManager QT_FORWARD_DECLARE_CLASS(GemRepoInspector) QT_FORWARD_DECLARE_CLASS(GemRepoListView) QT_FORWARD_DECLARE_CLASS(GemRepoModel) + QT_FORWARD_DECLARE_CLASS(AdjustableHeaderWidget) class GemRepoScreen : public ScreenWidget @@ -59,7 +60,7 @@ namespace O3DE::ProjectManager QFrame* m_noRepoContent; QFrame* m_repoContent; - QTableWidget* m_gemRepoHeaderTable = nullptr; + AdjustableHeaderWidget* m_gemRepoHeaderTable = nullptr; QHeaderView* m_gemRepoListHeader = nullptr; GemRepoListView* m_gemRepoListView = nullptr; GemRepoInspector* m_gemRepoInspector = nullptr; diff --git a/Code/Tools/ProjectManager/Source/GemsSubWidget.h b/Code/Tools/ProjectManager/Source/GemsSubWidget.h index 5e670b930a..130e6c4282 100644 --- a/Code/Tools/ProjectManager/Source/GemsSubWidget.h +++ b/Code/Tools/ProjectManager/Source/GemsSubWidget.h @@ -22,7 +22,7 @@ namespace O3DE::ProjectManager class GemsSubWidget : public QWidget { - Q_OBJECT // AUTOMOC + Q_OBJECT public: GemsSubWidget(QWidget* parent = nullptr); diff --git a/Code/Tools/ProjectManager/Source/LinkWidget.h b/Code/Tools/ProjectManager/Source/LinkWidget.h index eb0b9bb528..ab95c2ecdf 100644 --- a/Code/Tools/ProjectManager/Source/LinkWidget.h +++ b/Code/Tools/ProjectManager/Source/LinkWidget.h @@ -22,7 +22,7 @@ namespace O3DE::ProjectManager class LinkLabel : public QLabel { - Q_OBJECT // AUTOMOC + Q_OBJECT public: LinkLabel(const QString& text = {}, const QUrl& url = {}, int fontSize = 10, QWidget* parent = nullptr); diff --git a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h index 358c1f249a..c526f7864d 100644 --- a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h +++ b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h @@ -31,7 +31,7 @@ namespace O3DE::ProjectManager class LabelButton : public QLabel { - Q_OBJECT // AUTOMOC + Q_OBJECT public: explicit LabelButton(QWidget* parent = nullptr); diff --git a/Code/Tools/ProjectManager/Source/ProjectManagerDefs.h b/Code/Tools/ProjectManager/Source/ProjectManagerDefs.h index d784fcf5fd..f184fd2e17 100644 --- a/Code/Tools/ProjectManager/Source/ProjectManagerDefs.h +++ b/Code/Tools/ProjectManager/Source/ProjectManagerDefs.h @@ -11,6 +11,7 @@ namespace O3DE::ProjectManager { + inline constexpr static int MinWindowWidth = 1200; inline constexpr static int ProjectPreviewImageWidth = 210; inline constexpr static int ProjectPreviewImageHeight = 280; inline constexpr static int ProjectTemplateImageWidth = 92; diff --git a/Code/Tools/ProjectManager/Source/ScreenHeaderWidget.h b/Code/Tools/ProjectManager/Source/ScreenHeaderWidget.h index aca0d21fd3..5c000bf61d 100644 --- a/Code/Tools/ProjectManager/Source/ScreenHeaderWidget.h +++ b/Code/Tools/ProjectManager/Source/ScreenHeaderWidget.h @@ -20,7 +20,7 @@ namespace O3DE::ProjectManager class ScreenHeader : public QFrame { - Q_OBJECT // AUTOMOC + Q_OBJECT public: ScreenHeader(QWidget* parent = nullptr); diff --git a/Code/Tools/ProjectManager/Source/TagWidget.h b/Code/Tools/ProjectManager/Source/TagWidget.h index fce6eaf863..df817dc506 100644 --- a/Code/Tools/ProjectManager/Source/TagWidget.h +++ b/Code/Tools/ProjectManager/Source/TagWidget.h @@ -27,7 +27,7 @@ namespace O3DE::ProjectManager class TagWidget : public QLabel { - Q_OBJECT // AUTOMOC + Q_OBJECT public: explicit TagWidget(const Tag& id, QWidget* parent = nullptr); diff --git a/Code/Tools/ProjectManager/Source/TemplateButtonWidget.h b/Code/Tools/ProjectManager/Source/TemplateButtonWidget.h index 6216f0e3de..507219d2f2 100644 --- a/Code/Tools/ProjectManager/Source/TemplateButtonWidget.h +++ b/Code/Tools/ProjectManager/Source/TemplateButtonWidget.h @@ -17,7 +17,7 @@ namespace O3DE::ProjectManager class TemplateButton : public QPushButton { - Q_OBJECT // AUTOMOC + Q_OBJECT public: explicit TemplateButton(const QString& imagePath, const QString& labelText, QWidget* parent = nullptr); diff --git a/Code/Tools/ProjectManager/project_manager_files.cmake b/Code/Tools/ProjectManager/project_manager_files.cmake index 915b1a072f..87dfae85e8 100644 --- a/Code/Tools/ProjectManager/project_manager_files.cmake +++ b/Code/Tools/ProjectManager/project_manager_files.cmake @@ -81,6 +81,8 @@ set(FILES Source/TemplateButtonWidget.cpp Source/ExternalLinkDialog.h Source/ExternalLinkDialog.cpp + Source/AdjustableHeaderWidget.h + Source/AdjustableHeaderWidget.cpp Source/GemCatalog/GemCatalogHeaderWidget.h Source/GemCatalog/GemCatalogHeaderWidget.cpp Source/GemCatalog/GemCatalogScreen.h diff --git a/scripts/build/Platform/Android/gradle_windows.cmd b/scripts/build/Platform/Android/gradle_windows.cmd index bb98799444..f2d423282f 100644 --- a/scripts/build/Platform/Android/gradle_windows.cmd +++ b/scripts/build/Platform/Android/gradle_windows.cmd @@ -31,11 +31,19 @@ IF NOT EXIST %OUTPUT_DIRECTORY% ( mkdir %OUTPUT_DIRECTORY% ) -REM Jenkins reports MSB8029 when TMP/TEMP is not defined, define a dummy folder -SET TMP=%cd%/temp -SET TEMP=%cd%/temp -IF NOT EXIST %TMP% ( - mkdir %TMP% +REM Jenkins does not defined TMP +IF "%TMP%"=="" ( + IF "%WORKSPACE%"=="" ( + SET TMP=%APPDATA%\Local\Temp + SET TEMP=%APPDATA%\Local\Temp + ) ELSE ( + SET TMP=%WORKSPACE%\Temp + SET TEMP=%WORKSPACE%\Temp + REM This folder may not be created in the workspace + IF NOT EXIST "!TMP!" ( + MKDIR "!TMP!" + ) + ) ) REM Optionally sign the APK if we are generating an APK From 117cb11505e008f841be1672c5dc72f7e8e31d23 Mon Sep 17 00:00:00 2001 From: Allen Jackson <23512001+jackalbe@users.noreply.github.com> Date: Fri, 28 Jan 2022 15:43:51 -0600 Subject: [PATCH 25/53] {ghi7197} ignore prefab groups with empty JSON DOMS (#7242) * {ghi7197} ignore prefab groups with empty JSON DOMS ignore prefab groups with empty JSON DOMS if the default procedural prefab is being skipped Signed-off-by: Allen Jackson <23512001+jackalbe@users.noreply.github.com> * a cleaner solution is to check if the Editor is attempting to construct, then ignore making a default procedural prefab group Signed-off-by: Allen Jackson <23512001+jackalbe@users.noreply.github.com> --- .../PrefabGroup/PrefabGroupBehavior.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.cpp b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.cpp index 6bd8f0d049..af21e7297c 100644 --- a/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.cpp +++ b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.cpp @@ -166,7 +166,7 @@ namespace AZ::SceneAPI::Behaviors meshNodeFullName.append(meshNodeName.GetName()); auto meshGroup = AZStd::make_shared(); - meshGroup->SetName(meshNodeFullName.c_str()); + meshGroup->SetName(meshNodeFullName); meshGroup->GetSceneNodeSelectionList().AddSelectedNode(AZStd::move(meshNodePath)); for (const auto& meshGoupNamePair : meshTransformMap) { @@ -374,10 +374,18 @@ namespace AZ::SceneAPI::Behaviors Events::ProcessingResult PrefabGroupBehavior::ExportEventHandler::UpdateManifest( Containers::Scene& scene, ManifestAction action, - [[maybe_unused]] RequestingApplication requester) + RequestingApplication requester) { - if (action != Events::AssetImportRequest::ConstructDefault) + if (action == Events::AssetImportRequest::Update) { + // ignore constructing a default procedural prefab if some tool or script is attempting + // to update the scene manifest + return Events::ProcessingResult::Ignored; + } + else if (action == Events::AssetImportRequest::ConstructDefault && requester == RequestingApplication::Editor) + { + // ignore constructing a default procedurla prefab if the Editor's "Edit Settings..." is being used + // the user is trying to assign the source scene asset their own mesh groups return Events::ProcessingResult::Ignored; } From 1c6fbdab2ab1173dbc500b961471a070aa9a69d6 Mon Sep 17 00:00:00 2001 From: Chris Galvan Date: Fri, 28 Jan 2022 15:44:25 -0600 Subject: [PATCH 26/53] Removed legacy/unused IEventLoopHook Signed-off-by: Chris Galvan --- Code/Editor/Core/QtEditorApplication.h | 1 - Code/Editor/CryEdit.cpp | 28 -------------------------- Code/Editor/CryEdit.h | 4 ---- Code/Editor/IEditor.h | 6 ------ Code/Editor/IEditorImpl.cpp | 10 --------- Code/Editor/IEditorImpl.h | 2 -- Code/Editor/Include/IEventLoopHook.h | 24 ---------------------- Code/Editor/Lib/Tests/IEditorMock.h | 2 -- Code/Editor/editor_lib_files.cmake | 1 - 9 files changed, 78 deletions(-) delete mode 100644 Code/Editor/Include/IEventLoopHook.h diff --git a/Code/Editor/Core/QtEditorApplication.h b/Code/Editor/Core/QtEditorApplication.h index 0d702bf647..28ee8ac14b 100644 --- a/Code/Editor/Core/QtEditorApplication.h +++ b/Code/Editor/Core/QtEditorApplication.h @@ -13,7 +13,6 @@ #include #include #include -#include "IEventLoopHook.h" #include #include diff --git a/Code/Editor/CryEdit.cpp b/Code/Editor/CryEdit.cpp index 982f8ba411..480a973282 100644 --- a/Code/Editor/CryEdit.cpp +++ b/Code/Editor/CryEdit.cpp @@ -1828,34 +1828,6 @@ bool CCryEditApp::InitInstance() return true; } -void CCryEditApp::RegisterEventLoopHook(IEventLoopHook* pHook) -{ - pHook->pNextHook = m_pEventLoopHook; - m_pEventLoopHook = pHook; -} - -void CCryEditApp::UnregisterEventLoopHook(IEventLoopHook* pHookToRemove) -{ - IEventLoopHook* pPrevious = nullptr; - for (IEventLoopHook* pHook = m_pEventLoopHook; pHook != nullptr; pHook = pHook->pNextHook) - { - if (pHook == pHookToRemove) - { - if (pPrevious) - { - pPrevious->pNextHook = pHookToRemove->pNextHook; - } - else - { - m_pEventLoopHook = pHookToRemove->pNextHook; - } - - pHookToRemove->pNextHook = nullptr; - return; - } - } -} - ////////////////////////////////////////////////////////////////////////// void CCryEditApp::LoadFile(QString fileName) { diff --git a/Code/Editor/CryEdit.h b/Code/Editor/CryEdit.h index 48f362003c..f472639dcd 100644 --- a/Code/Editor/CryEdit.h +++ b/Code/Editor/CryEdit.h @@ -30,7 +30,6 @@ class CConsoleDialog; struct mg_connection; struct mg_request_info; struct mg_context; -struct IEventLoopHook; class QAction; class MainWindow; class QSharedMemory; @@ -153,8 +152,6 @@ public: int IdleProcessing(bool bBackground); bool IsWindowInForeground(); void RunInitPythonScript(CEditCommandLineInfo& cmdInfo); - void RegisterEventLoopHook(IEventLoopHook* pHook); - void UnregisterEventLoopHook(IEventLoopHook* pHook); void DisableIdleProcessing() override; void EnableIdleProcessing() override; @@ -344,7 +341,6 @@ private: QString m_lastOpenLevelPath; CQuickAccessBar* m_pQuickAccessBar = nullptr; - IEventLoopHook* m_pEventLoopHook = nullptr; QString m_rootEnginePath; int m_disableIdleProcessingCounter = 0; //!< Counts requests to disable idle processing. When non-zero, idle processing will be disabled. diff --git a/Code/Editor/IEditor.h b/Code/Editor/IEditor.h index da29b00f2d..0de08445b4 100644 --- a/Code/Editor/IEditor.h +++ b/Code/Editor/IEditor.h @@ -66,7 +66,6 @@ class IAWSResourceManager; struct ISystem; struct IRenderer; struct AABB; -struct IEventLoopHook; struct IErrorReport; // Vladimir@conffx struct IFileUtil; // Vladimir@conffx struct IEditorLog; // Vladimir@conffx @@ -509,11 +508,6 @@ struct IEditor virtual void SetActiveView(CViewport* viewport) = 0; virtual struct IEditorFileMonitor* GetFileMonitor() = 0; - // These are needed for Qt integration: - virtual void RegisterEventLoopHook(IEventLoopHook* pHook) = 0; - virtual void UnregisterEventLoopHook(IEventLoopHook* pHook) = 0; - // ^^^ - //! QMimeData is used by the Qt clipboard. //! IMPORTANT: Any QMimeData allocated for the clipboard will be deleted //! when the editor exists. If a QMimeData is allocated by a different diff --git a/Code/Editor/IEditorImpl.cpp b/Code/Editor/IEditorImpl.cpp index 05b74f3b05..b6a4209948 100644 --- a/Code/Editor/IEditorImpl.cpp +++ b/Code/Editor/IEditorImpl.cpp @@ -789,16 +789,6 @@ IEditorFileMonitor* CEditorImpl::GetFileMonitor() return m_pEditorFileMonitor.get(); } -void CEditorImpl::RegisterEventLoopHook(IEventLoopHook* pHook) -{ - CCryEditApp::instance()->RegisterEventLoopHook(pHook); -} - -void CEditorImpl::UnregisterEventLoopHook(IEventLoopHook* pHook) -{ - CCryEditApp::instance()->UnregisterEventLoopHook(pHook); -} - float CEditorImpl::GetTerrainElevation(float x, float y) { float terrainElevation = AzFramework::Terrain::TerrainDataRequests::GetDefaultTerrainHeight(); diff --git a/Code/Editor/IEditorImpl.h b/Code/Editor/IEditorImpl.h index 5e47a76802..73fcec917d 100644 --- a/Code/Editor/IEditorImpl.h +++ b/Code/Editor/IEditorImpl.h @@ -155,8 +155,6 @@ public: CMusicManager* GetMusicManager() override { return m_pMusicManager; }; IEditorFileMonitor* GetFileMonitor() override; - void RegisterEventLoopHook(IEventLoopHook* pHook) override; - void UnregisterEventLoopHook(IEventLoopHook* pHook) override; IIconManager* GetIconManager() override; float GetTerrainElevation(float x, float y) override; Editor::EditorQtApplication* GetEditorQtApplication() override { return m_QtApplication; } diff --git a/Code/Editor/Include/IEventLoopHook.h b/Code/Editor/Include/IEventLoopHook.h deleted file mode 100644 index eaf7bf2d62..0000000000 --- a/Code/Editor/Include/IEventLoopHook.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - - -#ifndef CRYINCLUDE_EDITOR_INCLUDE_IEVENTLOOPHOOK_H -#define CRYINCLUDE_EDITOR_INCLUDE_IEVENTLOOPHOOK_H -#pragma once - -struct IEventLoopHook -{ - IEventLoopHook* pNextHook; - - IEventLoopHook() - : pNextHook(0) {} - - virtual bool PrePumpMessage() { return false; } -}; - -#endif // CRYINCLUDE_EDITOR_INCLUDE_IEVENTLOOPHOOK_H diff --git a/Code/Editor/Lib/Tests/IEditorMock.h b/Code/Editor/Lib/Tests/IEditorMock.h index 99c05c7f4d..780362cb30 100644 --- a/Code/Editor/Lib/Tests/IEditorMock.h +++ b/Code/Editor/Lib/Tests/IEditorMock.h @@ -97,8 +97,6 @@ public: MOCK_METHOD0(GetActiveView, class CViewport* ()); MOCK_METHOD1(SetActiveView, void(CViewport*)); MOCK_METHOD0(GetFileMonitor, struct IEditorFileMonitor* ()); - MOCK_METHOD1(RegisterEventLoopHook, void(IEventLoopHook* )); - MOCK_METHOD1(UnregisterEventLoopHook, void(IEventLoopHook* )); MOCK_CONST_METHOD0(CreateQMimeData, QMimeData* ()); MOCK_CONST_METHOD1(DestroyQMimeData, void(QMimeData*)); MOCK_METHOD0(GetLevelIndependentFileMan, class CLevelIndependentFileMan* ()); diff --git a/Code/Editor/editor_lib_files.cmake b/Code/Editor/editor_lib_files.cmake index 75962fe3b3..7908d1e720 100644 --- a/Code/Editor/editor_lib_files.cmake +++ b/Code/Editor/editor_lib_files.cmake @@ -270,7 +270,6 @@ set(FILES Include/ICommandManager.h Include/IDisplayViewport.h Include/IEditorClassFactory.h - Include/IEventLoopHook.h Include/IExportManager.h Include/IGizmoManager.h Include/IIconManager.h From aa093945a6f7c3d43f7f0422000f736edf4a641f Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Thu, 27 Jan 2022 23:04:15 +0100 Subject: [PATCH 27/53] Remove an unused variable and explicitly initialize some bools to false Signed-off-by: Daniel Edwards --- Code/Editor/EditorPreferencesPageAWS.cpp | 2 +- Code/Editor/Objects/EntityObject.cpp | 2 -- Code/Editor/Settings.cpp | 2 +- .../Source/Editor/Attribution/AWSCoreAttributionManager.cpp | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Code/Editor/EditorPreferencesPageAWS.cpp b/Code/Editor/EditorPreferencesPageAWS.cpp index 968969245d..bb57802e47 100644 --- a/Code/Editor/EditorPreferencesPageAWS.cpp +++ b/Code/Editor/EditorPreferencesPageAWS.cpp @@ -101,7 +101,7 @@ void CEditorPreferencesPage_AWS::SaveSettingsRegistryFile() return; } - [[maybe_unused]] bool saved{}; + [[maybe_unused]] bool saved = false; constexpr auto configurationMode = AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY; if (AZ::IO::SystemFile outputFile; outputFile.Open(resolvedPath.data(), configurationMode)) diff --git a/Code/Editor/Objects/EntityObject.cpp b/Code/Editor/Objects/EntityObject.cpp index 450a15766c..0d70d47d7d 100644 --- a/Code/Editor/Objects/EntityObject.cpp +++ b/Code/Editor/Objects/EntityObject.cpp @@ -499,14 +499,12 @@ void CEntityObject::AdjustLightProperties(CVarBlockPtr& properties, const char* pAreaLight->SetHumanName("PlanarLight"); } - bool bCastShadowLegacy = false; // Backward compatibility for existing shadow casting lights if (IVariable* pCastShadowVarLegacy = FindVariableInSubBlock(properties, pSubBlockVar, "bCastShadow")) { pCastShadowVarLegacy->SetFlags(pCastShadowVarLegacy->GetFlags() | IVariable::UI_INVISIBLE); const QString zeroPrefix("0"); if (!pCastShadowVarLegacy->GetDisplayValue().startsWith(zeroPrefix)) { - bCastShadowLegacy = true; pCastShadowVarLegacy->SetDisplayValue(zeroPrefix); } } diff --git a/Code/Editor/Settings.cpp b/Code/Editor/Settings.cpp index 9efe846b9e..a3f08a24b2 100644 --- a/Code/Editor/Settings.cpp +++ b/Code/Editor/Settings.cpp @@ -1084,7 +1084,7 @@ void SEditorSettings::SaveSettingsRegistryFile() return; } - [[maybe_unused]] bool saved{}; + [[maybe_unused]] bool saved = false; constexpr auto configurationMode = AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY; diff --git a/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionManager.cpp b/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionManager.cpp index c52d3be088..8b75a26a43 100644 --- a/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionManager.cpp +++ b/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionManager.cpp @@ -223,7 +223,7 @@ namespace AWSCore return; } - [[maybe_unused]] bool saved {}; + [[maybe_unused]] bool saved = false; constexpr auto configurationMode = AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY; if (AZ::IO::SystemFile outputFile; outputFile.Open(resolvedPathAWSPreference.c_str(), configurationMode)) From 79f4fee96014d7b4c7508fd00ef6ca059f7f8034 Mon Sep 17 00:00:00 2001 From: Roman <69218254+amzn-rhhong@users.noreply.github.com> Date: Sun, 30 Jan 2022 09:55:40 -0800 Subject: [PATCH 28/53] Manipulator for params (#7231) Signed-off-by: rhhong --- .../Code/Tools/EMStudio/AtomRenderPlugin.cpp | 2 - .../EMStudioSDK/Source/EMStudioManager.cpp | 3 ++ .../EMStudioSDK/Source/EMStudioManager.h | 5 +++ .../Vector3GizmoParameterEditor.cpp | 44 +++++++++++++++++++ .../Vector3GizmoParameterEditor.h | 9 +++- 5 files changed, 60 insertions(+), 3 deletions(-) diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.cpp index 660f8e52d7..c39012d303 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.cpp @@ -28,8 +28,6 @@ namespace EMStudio { AZ_CLASS_ALLOCATOR_IMPL(AtomRenderPlugin, EMotionFX::EditorAllocator, 0); - const AzToolsFramework::ManipulatorManagerId g_animManipulatorManagerId = - AzToolsFramework::ManipulatorManagerId(AZ::Crc32("AnimManipulatorManagerId")); AtomRenderPlugin::AtomRenderPlugin() : DockWidgetPlugin() diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.cpp index 6881e49429..efc895c0b5 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.cpp @@ -500,6 +500,9 @@ namespace EMStudio painter.drawPath(path); } + const AzToolsFramework::ManipulatorManagerId g_animManipulatorManagerId = + AzToolsFramework::ManipulatorManagerId(AZ::Crc32("AnimManipulatorManagerId")); + // shortcuts QApplication* GetApp() { diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.h b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.h index 1d60773e1f..753086ca01 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.h +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.h @@ -31,6 +31,8 @@ #include "MainWindow.h" #include +#include + // include Qt #include #include @@ -169,6 +171,9 @@ namespace EMStudio EventProcessingCallback* m_eventProcessingCallback = nullptr; }; + // Define the manipulator id for atom viewport in animation editor. + extern const AzToolsFramework::ManipulatorManagerId g_animManipulatorManagerId; + // Shortcuts QApplication* GetApp(); EMStudioManager* GetManager(); diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/ParameterEditor/Vector3GizmoParameterEditor.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/ParameterEditor/Vector3GizmoParameterEditor.cpp index 7a24ece95a..9993161b70 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/ParameterEditor/Vector3GizmoParameterEditor.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/ParameterEditor/Vector3GizmoParameterEditor.cpp @@ -25,6 +25,8 @@ namespace EMStudio , m_currentValue(0.0f, 0.0f, 0.0f) , m_gizmoButton(nullptr) , m_transformationGizmo(nullptr) + , m_translationManipulators( + AzToolsFramework::TranslationManipulators::Dimensions::Three, AZ::Transform::Identity(), AZ::Vector3::CreateOne()) { UpdateValue(); } @@ -102,6 +104,27 @@ namespace EMStudio m_gizmoButton->setCheckable(true); m_gizmoButton->setEnabled(!IsReadOnly()); m_manipulatorCallback = manipulatorCallback; + + // Setup the translation manipulator + AzToolsFramework::ConfigureTranslationManipulatorAppearance3d(&m_translationManipulators); + m_translationManipulators.InstallLinearManipulatorMouseMoveCallback( + [this](const AzToolsFramework::LinearManipulator::Action& action) + { + OnManipulatorMoved(action.LocalPosition()); + }); + + m_translationManipulators.InstallPlanarManipulatorMouseMoveCallback( + [this](const AzToolsFramework::PlanarManipulator::Action& action) + { + OnManipulatorMoved(action.LocalPosition()); + }); + + m_translationManipulators.InstallSurfaceManipulatorMouseMoveCallback( + [this](const AzToolsFramework::SurfaceManipulator::Action& action) + { + OnManipulatorMoved(action.LocalPosition()); + }); + return m_gizmoButton; } @@ -183,6 +206,17 @@ namespace EMStudio EMStudioManager::MakeTransparentButton(m_gizmoButton, "Images/Icons/Vector3GizmoDisabled.png", "Show/Hide translation gizmo for visual manipulation"); } + // These will enable/disable the translation manipulator for atom render viewport. + if (m_translationManipulators.Registered()) + { + m_translationManipulators.Unregister(); + } + else + { + m_translationManipulators.Register(g_animManipulatorManagerId); + } + + // These will enable/disable the translation manipulator for opengl render viewport. if (!m_transformationGizmo) { m_transformationGizmo = static_cast(GetManager()->AddTransformationManipulator(new MCommon::TranslateManipulator(70.0f, true))); @@ -197,4 +231,14 @@ namespace EMStudio m_transformationGizmo = nullptr; } } + + void Vector3GizmoParameterEditor::OnManipulatorMoved(const AZ::Vector3& position) + { + m_translationManipulators.SetLocalPosition(position); + SetValue(position); + if (m_manipulatorCallback) + { + m_manipulatorCallback(); + } + } } diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/ParameterEditor/Vector3GizmoParameterEditor.h b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/ParameterEditor/Vector3GizmoParameterEditor.h index 92dd40923d..71d6d63cda 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/ParameterEditor/Vector3GizmoParameterEditor.h +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/ParameterEditor/Vector3GizmoParameterEditor.h @@ -8,7 +8,8 @@ #pragma once -#include "ValueParameterEditor.h" +#include +#include #include @@ -50,10 +51,16 @@ namespace EMStudio AZ::Vector3 GetMinValue() const; AZ::Vector3 GetMaxValue() const; + void OnManipulatorMoved(const AZ::Vector3& position); + private: AZ::Vector3 m_currentValue = AZ::Vector3::CreateZero(); QPushButton* m_gizmoButton = nullptr; + + // TODO: Remove this when we remove the opengl widget MCommon::TranslateManipulator* m_transformationGizmo = nullptr; + + AzToolsFramework::TranslationManipulators m_translationManipulators; AZStd::function m_manipulatorCallback; }; } // namespace EMStudio From d7b3d33711e179d835008215b1526b080ca4f08c Mon Sep 17 00:00:00 2001 From: Michael Pollind Date: Mon, 31 Jan 2022 01:57:34 -0800 Subject: [PATCH 29/53] chore: simplify logic for handling expanding entries in Outliner (#5219) (#5394) * chore: simplify logic for handling expanding entries in Outliner Signed-off-by: Michael Pollind * bugfix: fix bottom border for collapsed prefabs. issue: https://github.com/o3de/o3de/issues/5219 Signed-off-by: Michael Pollind * chore: revert changes for expanding entities Signed-off-by: Michael Pollind --- .../UI/Outliner/EntityOutlinerListModel.cpp | 13 +---- .../UI/Outliner/EntityOutlinerListModel.hxx | 2 - .../UI/Outliner/EntityOutlinerTreeView.cpp | 55 +++++++++++++++++++ .../UI/Outliner/EntityOutlinerTreeView.hxx | 6 ++ .../UI/Outliner/EntityOutlinerWidget.cpp | 5 -- .../UI/Outliner/EntityOutlinerWidget.hxx | 1 - 6 files changed, 62 insertions(+), 20 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp index 93e4ffd3da..45363f4bb5 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp @@ -80,7 +80,6 @@ namespace AzToolsFramework EntityOutlinerListModel::EntityOutlinerListModel(QObject* parent) : QAbstractItemModel(parent) , m_entitySelectQueue() - , m_entityExpandQueue() , m_entityChangeQueue() , m_entityChangeQueued(false) , m_entityLayoutQueued(false) @@ -1275,7 +1274,6 @@ namespace AzToolsFramework void EntityOutlinerListModel::QueueEntityToExpand(AZ::EntityId entityId, bool expand) { m_entityExpansionState[entityId] = expand; - m_entityExpandQueue.insert(entityId); QueueEntityUpdate(entityId); } @@ -1300,16 +1298,7 @@ namespace AzToolsFramework { return; } - - { - AZ_PROFILE_SCOPE(Editor, "EntityOutlinerListModel::ProcessEntityUpdates:ExpandQueue"); - for (auto entityId : m_entityExpandQueue) - { - emit ExpandEntity(entityId, IsExpanded(entityId)); - }; - m_entityExpandQueue.clear(); - } - + { AZ_PROFILE_SCOPE(Editor, "EntityOutlinerListModel::ProcessEntityUpdates:SelectQueue"); for (auto entityId : m_entitySelectQueue) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx index f099ed504a..51d047e81a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx @@ -156,7 +156,6 @@ namespace AzToolsFramework void ProcessEntityUpdates(); Q_SIGNALS: - void ExpandEntity(const AZ::EntityId& entityId, bool expand); void SelectEntity(const AZ::EntityId& entityId, bool select); void EnableSelectionUpdates(bool enable); void ResetFilter(); @@ -190,7 +189,6 @@ namespace AzToolsFramework void QueueEntityToExpand(AZ::EntityId entityId, bool expand); void ProcessEntityInfoResetEnd(); AZStd::unordered_set m_entitySelectQueue; - AZStd::unordered_set m_entityExpandQueue; AZStd::unordered_set m_entityChangeQueue; bool m_entityChangeQueued; bool m_entityLayoutQueued; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp index 857fdb5f80..c2bbebb72e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp @@ -81,6 +81,61 @@ namespace AzToolsFramework update(); } + void EntityOutlinerTreeView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles) + { + AzQtComponents::StyledTreeView::dataChanged(topLeft, bottomRight, roles); + + if (topLeft.isValid() && topLeft.parent() == bottomRight.parent() && topLeft.row() <= bottomRight.row() && + topLeft.column() <= bottomRight.column()) + { + for (int i = topLeft.row(); i <= bottomRight.row(); i++) + { + auto modelRow = topLeft.sibling(i, EntityOutlinerListModel::ColumnName); + if (modelRow.isValid()) + { + checkExpandedState(modelRow); + } + } + } + } + + void EntityOutlinerTreeView::rowsInserted(const QModelIndex& parent, int start, int end) + { + if (parent.isValid()) + { + for (int i = start; i <= end; i++) + { + auto modelRow = model()->index(i, EntityOutlinerListModel::ColumnName, parent); + if (modelRow.isValid()) + { + checkExpandedState(modelRow); + recursiveCheckExpandedStates(modelRow); + } + } + } + AzQtComponents::StyledTreeView::rowsInserted(parent, start, end); + } + + void EntityOutlinerTreeView::recursiveCheckExpandedStates(const QModelIndex& current) + { + const int rowCount = model()->rowCount(current); + for (int i = 0; i < rowCount; i++) + { + auto modelRow = model()->index(i, EntityOutlinerListModel::ColumnName, current); + if (modelRow.isValid()) + { + checkExpandedState(modelRow); + recursiveCheckExpandedStates(modelRow); + } + } + } + + void EntityOutlinerTreeView::checkExpandedState(const QModelIndex& current) + { + const bool expandState = current.data(EntityOutlinerListModel::ExpandedRole).template value(); + setExpanded(current, expandState); + } + void EntityOutlinerTreeView::mousePressEvent(QMouseEvent* event) { //postponing normal mouse pressed logic until mouse is released or dragged diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx index 9262da9a73..014bb7bd48 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx @@ -51,6 +51,10 @@ namespace AzToolsFramework Q_SIGNALS: void ItemDropped(); + protected Q_SLOTS: + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector()) override; + void rowsInserted(const QModelIndex &parent, int start, int end) override; + protected: // Qt overrides void mousePressEvent(QMouseEvent* event) override; @@ -75,6 +79,8 @@ namespace AzToolsFramework void ClearQueuedMouseEvent(); void processQueuedMousePressedEvent(QMouseEvent* event); + void recursiveCheckExpandedStates(const QModelIndex& parent); + void checkExpandedState(const QModelIndex& current); void StartCustomDrag(const QModelIndexList& indexList, Qt::DropActions supportedActions) override; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp index 31bb067603..52b688543a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp @@ -224,7 +224,6 @@ namespace AzToolsFramework connect(m_gui->m_objectTree, &QTreeView::expanded, this, &EntityOutlinerWidget::OnTreeItemExpanded); connect(m_gui->m_objectTree, &QTreeView::collapsed, this, &EntityOutlinerWidget::OnTreeItemCollapsed); connect(m_gui->m_objectTree, &EntityOutlinerTreeView::ItemDropped, this, &EntityOutlinerWidget::OnDropEvent); - connect(m_listModel, &EntityOutlinerListModel::ExpandEntity, this, &EntityOutlinerWidget::OnExpandEntity); connect(m_listModel, &EntityOutlinerListModel::SelectEntity, this, &EntityOutlinerWidget::OnSelectEntity); connect(m_listModel, &EntityOutlinerListModel::EnableSelectionUpdates, this, &EntityOutlinerWidget::OnEnableSelectionUpdates); connect(m_listModel, &EntityOutlinerListModel::ResetFilter, this, &EntityOutlinerWidget::ClearFilter); @@ -972,10 +971,6 @@ namespace AzToolsFramework m_listModel->OnEntityCollapsed(entityId); } - void EntityOutlinerWidget::OnExpandEntity(const AZ::EntityId& entityId, bool expand) - { - m_gui->m_objectTree->setExpanded(GetIndexFromEntityId(entityId), expand); - } void EntityOutlinerWidget::OnSelectEntity(const AZ::EntityId& entityId, bool selected) { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.hxx index e6c42fa64e..38d2e16199 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.hxx @@ -155,7 +155,6 @@ namespace AzToolsFramework void OnTreeItemDoubleClicked(const QModelIndex& index); void OnTreeItemExpanded(const QModelIndex& index); void OnTreeItemCollapsed(const QModelIndex& index); - void OnExpandEntity(const AZ::EntityId& entityId, bool expand); void OnSelectEntity(const AZ::EntityId& entityId, bool selected); void OnEnableSelectionUpdates(bool enable); void OnDropEvent(); From 2068c225d1410aa02e3c228acc9e0cecddb4169d Mon Sep 17 00:00:00 2001 From: Benjamin Jillich <43751992+amzn-jillich@users.noreply.github.com> Date: Mon, 31 Jan 2022 11:25:58 +0100 Subject: [PATCH 30/53] Motion Matching (#7232) commit 0c873fc67f0250ec7155e1999c34930b23c7647f Author: Benjamin Jillich Date: Wed Jan 26 10:05:07 2022 +0100 Motion Matching: Automated tests for the feature matrix and feature schema (#38) * Set up base motion matching test fixture * Automated tests for feature matrix * Automated tests for feature schema Signed-off-by: Benjamin Jillich commit cbd4f124481faf4c8bf0b98ae4602140f231e2fb Author: Benjamin Jillich Date: Tue Jan 25 09:59:32 2022 +0100 Motion Matching: User adjustable algorithm via UI and new residual calculation option (#37) * Added the feature properties to the edit context. As all the preparation work has been done, this finalizes the adjustable algorithm UI work. * Added a new residual calculation type for calculating the differences between the input query calues and the features extracted from the motion database. Either the differences are just used as an absolute value or they are squared. "Use 'Squared' in case minimal differences should be ignored and larger differences should overweight others. Use 'Absolute' for linear differences and don't want the mentioned effect." * Added comments and edit context data element descriptions about the feature properties. Signed-off-by: Benjamin Jillich commit ddad62b994be1e89cc394b28863faa44650c8ed1 Author: Benjamin Jillich Date: Fri Jan 21 09:37:55 2022 +0100 Motion Matching: Replaced the MotionMatchEventData with a DiscardFrameEventData and a TagEventData (#36) * Replaced the MotionMatchEventData with a DiscardFrameEventData and a TagEventData * Moved the system components and modules into the EMFX::MotionMatching namespace * Converted all the animation assetinfos to use the new discard frame motion event. * A few other fixes and code cleaning. Signed-off-by: Benjamin Jillich commit f022ab4f3adbe5dc141998bc368a8ca9290698be Author: Benjamin Jillich Date: Thu Jan 20 07:29:01 2022 +0100 Motion Matching: Refactoring [Part 4] Introduced MotionMatchingData (#35) * Renamed MotionMatchingConfig into MotionMatchingData while removing the feature schema from it as that needs to be part of the anim graph node in order to be reflected by the edit context so that users can change the features used in the algorithm. * Default feature schema is applied in the anim graph node in case none got de-serialized along with the node (e.g. when creating a new motion matching node). * Added a few class descriptions. * Fixed the lowest cost search frequency, which was used as the inverse (the time interval) while the UI and variable was named frequency. * Removed the hard-coded weighting for the past and future trajectory and created new members for the cost factors in the trajectory feature. Signed-off-by: Benjamin Jillich commit e05b96b0eb734cd4818e343d5b46dbb54712faf0 Author: Benjamin Jillich Date: Wed Jan 19 10:36:20 2022 +0100 Motion Matching: Architecture and Feature Schema Diagrams and README pass (#34) * Added architecture diagram * Added feature schema diagram * First pass on ReadMe.md Signed-off-by: Benjamin Jillich commit d50deb1f9eea79e9c56b3ded1bda62f755e3e97f Author: Benjamin Jillich Date: Wed Jan 19 10:35:44 2022 +0100 Motion Matching: Refactoring [Part 3] Moved feature schema, matix and the kd-tree to the config and removed the feature database (#33) * Removed feature database which contained the feature schema, feature matrix and the kd-tree. * Feature schema, feature matrix and the kd-tree are now part of the motion matching config. * DebugDraw moved from the config to the instance. * FindLowestCostFrameIndex moved from the config to the instance. * Kd-tree now has a helper to calculate the number of dimensions based on a feature set. * SaveToCsv moved from the feature database to the feature matrix directly. Signed-off-by: Benjamin Jillich commit 09aff543822a2a3e092edc65cfca315a83e25730 Author: Benjamin Jillich Date: Fri Jan 14 17:08:18 2022 +0100 Motion Matching: Refactoring [Part 2] - Added new default feature schema and removed hard-coded locomotion config (#32) * Added a new FeatureSchemaDefault class that creates the default feature schema (left/right foot position and velocity, pelvis velocity and root trajectory). * Removed the LocomotionConfig and moved the functionality like the FindLowestCostFrameIndex() to the motion matching config. * Moved more per-instance data to the motion matching instance where they actually belong like the temporary cost vectors or the cached trajectory feature pointer. * Added cost factor to the feature base class so that users can weight the costs of the features and adjust them in the UI later on. * Removed the hard-coded cost factors from the motion matching node. * Using the new, customizable config rather than the hard-coded locomotion config in the motion matching node. Signed-off-by: Benjamin Jillich commit 8eba891a7b599d81a1ad7cbed841e342034fee9f Author: Benjamin Jillich Date: Thu Jan 13 08:56:26 2022 +0100 Motion Matching: Refactoring [Part 1] (#31) * Storing the joint and relative to joint as strings in the feature so that we can later expose it to the UI. * Joint indices will be cached with initializing the features. * Some more code cleaning here and there. Signed-off-by: Benjamin Jillich commit 31e689015afaf61ededb50d4d3c5dddb61561603 Author: Benjamin Jillich Date: Tue Jan 11 18:34:56 2022 +0100 Motion Matching: Created feature schema class and separated funtionality out from the feature database (#30) * Created new feature schema class which holds the set of features involved in the motion matching search. The schema represents the order of the features as well as their settings while the feature matrix stores the actual feature data. * Untangled feature database with the feature schema functionality and it now just holds a feature schema. * Code cleaning (removed Allocators.cpp, fixed alignment, renamed some variables. Signed-off-by: Benjamin Jillich commit c407959bd5dd1c38a283900815624a8103173d37 Author: Benjamin Jillich Date: Tue Jan 11 13:11:51 2022 +0100 Motion Matching: Eigen SDK is now optional and a simple NxN matrix equivalent is provided (#29) * Eigen SDK is now optional and users can opt-in manually. * Simple NxN matrix class provided for convenience that currently wraps all features needed to run motion matching (default). Signed-off-by: Benjamin Jillich commit dbd27a6ce68674e6fd3fcc9ca59c4d57884a133c Author: Benjamin Jillich Date: Mon Jan 10 08:54:01 2022 +0100 Motion Matching: Added pose data for joint velocities and unified the broad-phase query filling (#28) * Added PoseDataJointVelocities class that extends the pose with relative to a given joint based velocities. * Unified the broad-phase search using the KD-tree by getting rid of the hard-coded fill query feature value calls. We're now just iterating over the features. * Adding the new pose data to the factory from within the MM gem. * Joint velocities are calculated before each motion matching search for the query pose. * Moved the debug draw helper from the locomotion config to the base class. * Some code cleaning. Signed-off-by: Benjamin Jillich commit 7ec4151fede3046379bdbdab86459cc3684c730c Author: Benjamin Jillich Date: Tue Jan 4 09:20:38 2022 +0100 Motion Matching: Yet another round of timing improvements (#26) Motion Matching: Yet another timing improvement * Sampling the input query pose for the motion matching search algorithm for the new motion time as the motion instance has not been updated with the time delta when we do the search. So we pre-sample the future pose to not get little lags as the search was done on the last current pose, which is a time delta old already. * Fixed a bug: We never updated the previous motion instance while we were still blending between the poses. Basically we were blending with a static pose after switching to a new best matching frame. This increased smoothness obviously. * Re-enabled motion extraction delta blending now that we fixed the above bug which gave another smoothness increase. * Renaming MM behavior into config (#27) * Behavior -> MotionMatchingConfig * LocomotionBehavior -> LocomotionConfig * BehaviorInstance -> MotionMatchingInstance * Removed the MotionMatchSystem and cleaned up reflection * Moved to namespace EMotionFX::MotionMatching Signed-off-by: Benjamin Jillich commit e9c6d6fd74641bed90e57efeb5b65ad00ad44562 Author: Benjamin Jillich Date: Fri Dec 10 10:52:06 2021 +0100 Improved smoothness and increased trajectory facing direction influence (#25) * When switching to a new best matching frame we were lagging a frame behind when updating the motion instance time. * Increased the influence of the facing direction as we have two different position difference costs while only one facing direction cost. Signed-off-by: Benjamin Jillich commit f4a5d041a0d3c0bb747dd155a30a1ac993511db7 Author: Benjamin Jillich Date: Wed Dec 8 08:54:16 2021 +0100 Sampling poses for velocity feature directly from the motion data + some refactoring (#24) * New helper to calculate the velocity without a motion instance and sample the poses directly from the motion. * New helper to draw velocities other than the actual feature for a frame. * Now sharing the frame cost context between the position and the trajectory feature. The velocity feature can't use that yet as it needs custom velocity information. Will address that later. * After the changes, we were able to greatly simplify the extract features routine in the feature database as no motion instances are required anymore to extract the features from the source motions! * Some refactoring: Renamed some variables and classes for more sense. Signed-off-by: Benjamin Jillich commit 94e0ce6e6995cd4893eb5dada1119467dc26e360 Author: Benjamin Jillich Date: Fri Dec 3 09:02:09 2021 +0100 Sampling poses for trajectory feature directly from the motion data (#23) * Introduced a new SamplePose() helper to the Frame class which allows sampling a pose of the given frame without a motion instance. We can also apply a time offset to sample a pose before/after the frame which will be needed to sample trajectories or velocities as the Frame objects are sampled at 30 Hz currently. In case the offset reached the animation boundaries, the sample time will be clamped to the animation range. * The trajectory feature now samples the poses using the new helper directly from the motion data and does not need a motion instance object anymore which helps on the path towards multi-threading and also makes the code more readable and less error-prone. Signed-off-by: Benjamin Jillich commit 590ea3ddef575003f29e6d03b6db498a37da23a6 Author: Benjamin Jillich Date: Thu Dec 2 09:20:48 2021 +0100 Motion Matching: Motion extraction sometimes outputs zero movement and results in stuttering (#22) The problem was that after the motion matching algorithm determined a better matching frame and we started a cross-fade, at that given frame the motion extraction delta was outputting a zero vector, resulting in a little stutters at the mm update frequency. This happened because the exrtaction calculation was done before the motion instance time values were updated and also the previous time values weren't set correctly when switching the frame/animation. Signed-off-by: Benjamin Jillich commit 6d0b64fd4ce2e4b3ad696c6cc5f3935447a7782e Author: Benjamin Jillich Date: Wed Dec 1 17:05:49 2021 +0100 Motion Matching: Facing direction support (#21) * Added facing direction to the trajectory feature based on a forward facing axis of the character asset. * The facing direction is calculated in model/animation space relative to the root joint of the character of the current frame. * As the trajectory is looking into the past/future of the current frame the facing direction is relative to the root joint of another frame, either in the future or the past. * The facing direction cost is the normalized difference (normalized dot product) between the current character facing direction and the expected one from the used frame in the database. * The trajectory history as well as the trajectory query got extended by a facing direction. * Added visualization for facing direction. Signed-off-by: Benjamin Jillich commit a552881139debfba2b489b8e23d57cb2556635c1 Author: Benjamin Jillich Date: Wed Nov 24 11:02:03 2021 +0100 Motion Matching: Moved velocity feature from normalized direction and speed to a scaled vector (#20) * More stable representation for joints that remain motionless. * Skipping velocity debug visualizations for zero velocities. Signed-off-by: Benjamin Jillich commit 5a27e1db3de3d4ac753208a3c4af9bc2115f5b40 Author: Benjamin Jillich Date: Thu Nov 18 13:11:15 2021 +0100 Motion Matching: Final tweaks for Phase 1 milestone (#19) * Added pelvis velocity feature to smooth out sudden direction changes of it. * Blending motion extraction delta as it results in smoother motion extraction after all of the fixes from the last weeks Signed-off-by: Benjamin Jillich commit c928dc5502cc9cbb4df3fccdae60039e68571c77 Author: Benjamin Jillich Date: Tue Nov 16 12:45:13 2021 +0100 Motion Matching: Move debug rendering to use DebugDisplayBus & some code cleaning (#18) * Debug drawing for LY Editor as well as Animation Editor in the new Atom viewport. * Ported from Atom aux geom to render backend independent debug display. * Renamed last occurances of frame data to feature. Signed-off-by: Benjamin Jillich commit 57904c55d15694e60713f90f42715c18756f52c4 Author: Benjamin Jillich Date: Thu Nov 11 16:38:22 2021 +0100 Motion Matching: Move control spline into separate trajectory query class (#17) * Introducing new TrajectoryQuery class which represents the trajectory history and desired future trajectory control points input for the motion matching search. * The trajectory history will be used to sample the number of expected control points for the past trajectory based on the trajectory feature. * For the future trajectory, I just ported over the different functions that we had previously. Will revisit that with a later change. * Moved DebugDrawControlSpline() from locomotion behavior to the TrajectoryQuery. * Trajectory query is owned by the behavior instance, same as the trajectory history. Signed-off-by: Benjamin Jillich commit fbed5b1803f820a86ffa562a2be9bbae00c64109 Author: Benjamin Jillich Date: Tue Nov 9 10:36:03 2021 +0100 Motion Matching: Trajectory feature improvements (#16) * Reduced dimensionality of the trajectory feature. Removed the velocity from the sample as the velocity is embedded in the distances between the control points already. * Reduced the position and facing direction features from 3D to 2D as motion extraction is projected to the ground plane anyway and the last component was always zeroed out. * Improved the cost function by removing the angle difference from it. After the latest changes in the trajectory history and the bug fixes in the trajectory feature and history, results are better without it. * Adjusted some target path generation parameters to get more variation in the locomotion results. * Some minor code cleanup. Signed-off-by: Benjamin Jillich commit 796b763d676380e0b9fbe0ae72824a7f29e59eab Author: Benjamin Jillich Date: Thu Nov 4 11:58:39 2021 +0100 Major rewrite of the trajectory history (#15) * Fixed several sampling issues related to indexing issues by elevating the keytrack class that has been proven to work rather than reinventing the wheel here. * Offers two ways to sample the trajectory history, either based on time in seconds or using a normalized value. * Automatically takes care that the trajectory feature is not needing a longer history than the history itself records. * History older than the trajectory feature requres is rendered as semi-transparent spheres that fade out over time. * Trajectory history is now owned by the behavior instance rather than the anim graph node to have everything at a central place. * Trajectory history is pre-filled with the character location at init time so that we simulate a standing still character without confusing the motion matching algorithm or special case handling. Signed-off-by: Benjamin Jillich commit 80c6572380e1b879014aae5aecdaaea1aa3160c1 Author: Benjamin Jillich Date: Wed Nov 3 09:34:16 2021 +0100 Motion Matching: Improve trajectory feature extraction and cost calculation (#14) * Fixed bug in calculating the past frame index which led to retrieving wrong data and skewed costs. * Fixed an off-by-one indexing issue. We were only seeing 5 viual control points in our debug viz while there should have been 6. That is now fixed + the cost is more accurate as we're actually comparing the right control points now. * Changed the cost function from just calculating the spatial differences between the desired and actual trajectory positions to a combination of relative spatial differences and their angles. This reduced the average costs and resulted in better matching frames and smoother synthesized animations. Signed-off-by: Benjamin Jillich commit 7cc180054ede4fab8ba592c45c2feaf746beeb28 Author: Benjamin Jillich Date: Fri Oct 29 08:54:41 2021 +0200 Remove bad animation ranges with discard range events (#12) We got a mechanism in place to discard specific frames/ranges of an animation and exclude it from the motion matching database. We have several sections in our animations where the arms are over the head, where the person suddenly crouches for a bit or other poses that do not match the other locomotion data. I went through the animation database and discarded all of these sections in order to improve the results of the synthesized animations. Signed-off-by: Benjamin Jillich commit 14f36cdc1813aaa9a5ce514272cef01bad3f38b6 Author: Benjamin Jillich Date: Fri Oct 29 08:54:13 2021 +0200 Remove direction feature (facing direction will be part of the trajectory) (#13) Removed the currently unused and half implemented direction feature. The facing direction will be part of the trajectory in the future. Signed-off-by: Benjamin Jillich commit a8e6f1c42f92cb3a207bf04371e884d8f5d0fbb4 Author: Benjamin Jillich Date: Tue Oct 26 13:30:07 2021 +0200 Motion Matching: Improved spatial velocity feature calculation (#11) * Added Motion Matching ImGuiMonitor and Bus Added bus for pushing values to the histogram and a monitor owning and rendering the histograms. * Implemented performance and feature cost histograms * Improved spatial velocity feature calculation Signed-off-by: Benjamin Jillich commit 925e05bdd3c433af767118eff7b3c93bb6e2a94a Author: Benjamin Jillich Date: Mon Oct 25 10:26:37 2021 +0200 Add position feature visualization and other debug viz improvements (#10) * Rendering a sphere for the position feature in order to see the offset between the actual foot position and the best matching frame's extracted position feature. * Improved velocity feature visualization by making rendering an arrow head and a thicker direction. * Added the ability to have different bar colors for different histograms. * Matched histogram bar with feature visualization colors to easily understand which 3D viz belongs to which histogram. * Using better matching color palette. Signed-off-by: Benjamin Jillich commit 6600abcff16df4e49a744bbeb68d00d9fe03f31d Author: Benjamin Jillich Date: Wed Oct 20 17:18:05 2021 +0200 Use feature matrix to generalize FillFrameFloats() and improve feature visualizations (#9) * Generalized the FillFrameFloats() in the KD-tree by utilizing the feature matrix and dimensionality information from the features to replace the custom per-feature functions with a shared version. * Improved the trajectory visualization by consolidating the past and future trajectory rendering into a single function and nicer visuals by replacing the line based markers with spheres and cylinders to fake thicker line until the aux geom is able to render them. * Now using Atom's aux geom for improved rendering of the features. * Included some more feedback from the last PR. Signed-off-by: Benjamin Jillich commit 96a90bc62cf4220d1afd207fce9c6cab9e453c3b Author: Benjamin Jillich Date: Wed Oct 20 09:04:07 2021 +0200 Motion Matching: Store features used for kd-tree in feature database and get rid of the local flags inside the feature class (#8) * The list of features used in the KD-tree is now separated and not part of the actual feature descriptor anymore * Renamed frame floats to query features / feature vales to make it align better with the rewrite from some weeks ago. * Some more code cleanup Signed-off-by: Benjamin Jillich commit e430637f734b20e6813a5c44572645390fe77c92 Author: Benjamin Jillich Date: Fri Oct 15 09:25:49 2021 +0200 Feature cost and performance metrics visualization (#7) * Added Motion Matching ImGuiMonitor and Bus Added bus for pushing values to the histogram and a monitor owning and rendering the histograms. Signed-off-by: Benjamin Jillich * Implemented performance and feature cost histograms Signed-off-by: Benjamin Jillich commit f8ca765fcc168942c361dfea249b08915dac5f77 Author: Benjamin Jillich Date: Mon Oct 11 09:21:17 2021 +0200 Motion matching: Data analysis and Visualization (Part 2) (#6) Added scatterplot using PCA Added feature correlation heatmap Data normalization ground truth using sklearn's scaler (Min-max scaling) Histogram for verifying that the value distributions stayed the same after normalizing PCA scatterplot for the normalized data Signed-off-by: Benjamin Jillich commit 21d3f8b9e4b2545bfe07bf2a08ed1e39337dfc58 Merge: 58abd6c a6587bb Author: Benjamin Jillich Date: Thu Oct 7 09:16:39 2021 +0200 Motion Matching: Feature matrix CSV export Signed-off-by: Benjamin Jillich commit a6587bb9218e2c618ac3a836b6374ab16fcc9bbc Merge: 3f5ac80 aee9039 Author: Benjamin Jillich Date: Thu Oct 7 09:13:32 2021 +0200 Motion Matching: Start of a Jupyter notebook for feature data analysis and visualizations Signed-off-by: Benjamin Jillich commit aee903942ed0b05d9397778d606775482dc1cfc7 Author: Benjamin Jillich Date: Wed Oct 6 15:35:14 2021 +0200 Motion Matching: Feature matrix CSV export Signed-off-by: Benjamin Jillich commit 3f5ac808323a7b6a78cdf5c1f8ceaf8d175e9857 Author: Benjamin Jillich Date: Tue Oct 5 10:04:44 2021 +0200 Motion Matching: Feature matrix CSV export * Added GetDimensionName() function for the feature to output a component's name, which corresponds to a column in the feature matrix. * Added SaveAsCsv() function to feature matrix which exports a Eigen matrix to a .csv file plus column names based on the feature component names. Signed-off-by: Benjamin Jillich commit 58abd6c9600f67121eaec9b80c2264bba73ff198 Merge: 1fb12e4 fd12fda Author: Benjamin Jillich Date: Mon Oct 4 08:44:25 2021 +0200 Motion Matching: Created feature matrix and moved all feature data to it Signed-off-by: Benjamin Jillich commit fd12fda7843ec6d361caa3a26892fcdb0dfc9f7c Author: Benjamin Jillich Date: Fri Oct 1 09:01:24 2021 +0200 Created feature matrix and moved all feature data into it We now have a new FeatureMatrix which is responsible for storing the feature data that we need for the motion matching algorithm in a cache-efficient way. The great thing is that this is also the enabler for any feature analysis and later on machine learning as we're in the right data format already. The FeatureMatrix internally stores the data in a 2D dense matrix from the Eigen library. This can easily be replaced with another linear algebra/vector/matrix library though and the FeatureMatrix acts as a wrapper. We're currently extracting the following motion features from our motion database: Left foot position/velocity, right foot position/velocity and the root joint trajectory with 6 past and 6 future sample positions and directions. Having 41203 keyframes/poses in our motion database, this results in a feature matrix holding 22.63 MB of data. This is only the data size for the extracted features. Signed-off-by: Benjamin Jillich commit 7cdd4d9b0b525b3a7c218ce4a16dfa9d2a90132c Author: Benjamin Jillich Date: Wed Sep 22 14:21:14 2021 +0200 Adding profile instrumentations Signed-off-by: Benjamin Jillich commit 1fb12e4af1fd1d70c8f39842a0207de96bd1550c Author: Benjamin Jillich Date: Fri Sep 17 13:10:55 2021 +0200 Fixing compile issues due to new warning mode Signed-off-by: Benjamin Jillich commit 9ea3a67aa985d872498fcc19bb973311089f9679 Merge: 5e5d69c 99cfb29 Author: Benjamin Jillich Date: Wed Sep 15 17:49:09 2021 +0200 Added velocity feature visualization improved velocity calculation Signed-off-by: Benjamin Jillich commit 99cfb2914cdadcf5616570c30a15b246abb20f97 Author: Benjamin Jillich Date: Wed Sep 8 16:35:12 2021 +0200 Addressed PR feedback Signed-off-by: Benjamin Jillich commit af291c0ca97e17d1bb07075a5546747591812e9e Author: Benjamin Jillich Date: Tue Sep 7 10:36:18 2021 +0200 Added velocity feature visualization improved velocity calculation Signed-off-by: Benjamin Jillich commit 5e5d69c9293c3609950e55d19ea6145a21027187 Merge: f2c1577 902179c Author: Benjamin Jillich Date: Mon Sep 6 18:58:59 2021 +0200 MotionMatching: Separated features from source data and renamed FrameData to Feature #1 Signed-off-by: Benjamin Jillich commit 902179c6a19f91bca64c37a383adc776da15f8e8 Author: Benjamin Jillich Date: Fri Sep 3 15:25:27 2021 +0200 Separated features from source data and renamed FrameData to Feature Signed-off-by: Benjamin Jillich commit f2c1577febb46d8957693826d404f8cc6ad5e240 Author: Benjamin Jillich Date: Mon Aug 30 12:10:30 2021 +0200 Fixed some compile errors for the latest version Signed-off-by: Benjamin Jillich commit 498018553ccf75b0e3983895984401680ffa25b9 Author: Benjamin Jillich Date: Mon Aug 30 11:51:21 2021 +0200 Initial motion matching prototype code Signed-off-by: Benjamin Jillich Signed-off-by: Benjamin Jillich --- .../Assets/Animations/Acceleration1.fbx | 3 + .../Assets/Animations/Circles1.fbx | 3 + .../Assets/Animations/Crouching1.fbx | 3 + .../Assets/Animations/FreeRoaming1.fbx | 3 + .../Assets/Animations/FreeRoaming2.fbx | 3 + .../MotionMatching/Assets/Animations/Jog1.fbx | 3 + .../Assets/Animations/JogPivotTurn1.fbx | 3 + .../Assets/Animations/Jumps1.fbx | 3 + .../Assets/Animations/JumpsFreeRoam1.fbx | 3 + .../Assets/Animations/MixedLocomotion1.fbx | 3 + .../Assets/Animations/OutofRange1.fbx | 3 + .../Assets/Animations/Pushes1.fbx | 3 + .../MotionMatching/Assets/Animations/Run1.fbx | 3 + .../Assets/Animations/RunPivotTurn1.fbx | 3 + .../Assets/Animations/Snake1.fbx | 3 + .../Assets/Animations/TurnOnSpot1.fbx | 3 + .../Assets/Animations/Walk1.fbx | 3 + .../Assets/Animations/Walk2.fbx | 3 + .../Assets/Animations/WalkPivotTurn1.fbx | 3 + .../Assets/Animations/WalkStopTurn1.fbx | 3 + .../Assets/Animations/WalkStopTurnPivot1.fbx | 3 + .../Assets/Animations/WalkTurns1.fbx | 3 + .../Animations/acceleration1.fbx.assetinfo | 50 + .../Assets/Animations/circles1.fbx.assetinfo | 41 + .../Animations/crouching1.fbx.assetinfo | 125 ++ .../Animations/freeroaming1.fbx.assetinfo | 59 + .../Animations/freeroaming2.fbx.assetinfo | 41 + .../Assets/Animations/jog1.fbx.assetinfo | 41 + .../Animations/jogpivotturn1.fbx.assetinfo | 53 + .../Animations/mixedlocomotion1.fbx.assetinfo | 53 + .../Animations/outofrange1.fbx.assetinfo | 41 + .../Assets/Animations/run1.fbx.assetinfo | 41 + .../Animations/runpivotturn1.fbx.assetinfo | 50 + .../Assets/Animations/snake1.fbx.assetinfo | 50 + .../Animations/turnonspot1.fbx.assetinfo | 106 ++ .../Assets/Animations/walk1.fbx.assetinfo | 59 + .../Assets/Animations/walk2.fbx.assetinfo | 50 + .../Animations/walkpivotturn1.fbx.assetinfo | 106 ++ .../Animations/walkstopturn1.fbx.assetinfo | 41 + .../walkstopturnpivot1.fbx.assetinfo | 41 + .../Animations/walkturns1.fbx.assetinfo | 50 + Gems/MotionMatching/Assets/Character/Rin.fbx | 3 + .../Assets/Character/Rin.fbx.assetinfo | 1543 +++++++++++++++++ .../Assets/MotionMatching.animgraph | 3 + .../Assets/MotionMatching.emfxworkspace | 3 + .../Assets/MotionMatching.motionset | 3 + Gems/MotionMatching/CMakeLists.txt | 16 + Gems/MotionMatching/Code/CMakeLists.txt | 155 ++ .../MotionMatching/MotionMatchingBus.h | 38 + Gems/MotionMatching/Code/Source/Allocators.h | 16 + .../Code/Source/BlendTreeMotionMatchNode.cpp | 373 ++++ .../Code/Source/BlendTreeMotionMatchNode.h | 111 ++ Gems/MotionMatching/Code/Source/EventData.cpp | 92 + Gems/MotionMatching/Code/Source/EventData.h | 55 + Gems/MotionMatching/Code/Source/Feature.cpp | 275 +++ Gems/MotionMatching/Code/Source/Feature.h | 174 ++ .../Code/Source/FeatureMatrix.cpp | 102 ++ .../Code/Source/FeatureMatrix.h | 118 ++ .../Code/Source/FeaturePosition.cpp | 127 ++ .../Code/Source/FeaturePosition.h | 55 + .../Code/Source/FeatureSchema.cpp | 123 ++ .../Code/Source/FeatureSchema.h | 47 + .../Code/Source/FeatureSchemaDefault.cpp | 82 + .../Code/Source/FeatureSchemaDefault.h | 23 + .../Code/Source/FeatureTrajectory.cpp | 450 +++++ .../Code/Source/FeatureTrajectory.h | 148 ++ .../Code/Source/FeatureVelocity.cpp | 152 ++ .../Code/Source/FeatureVelocity.h | 64 + Gems/MotionMatching/Code/Source/Frame.cpp | 78 + Gems/MotionMatching/Code/Source/Frame.h | 62 + .../Code/Source/FrameDatabase.cpp | 250 +++ .../Code/Source/FrameDatabase.h | 86 + .../Code/Source/ImGuiMonitor.cpp | 144 ++ .../MotionMatching/Code/Source/ImGuiMonitor.h | 84 + .../Code/Source/ImGuiMonitorBus.h | 37 + Gems/MotionMatching/Code/Source/KdTree.cpp | 454 +++++ Gems/MotionMatching/Code/Source/KdTree.h | 95 + .../Code/Source/MotionMatchingData.cpp | 181 ++ .../Code/Source/MotionMatchingData.h | 74 + .../Source/MotionMatchingEditorModule.cpp | 40 + .../MotionMatchingEditorSystemComponent.cpp | 60 + .../MotionMatchingEditorSystemComponent.h | 40 + .../Code/Source/MotionMatchingInstance.cpp | 571 ++++++ .../Code/Source/MotionMatchingInstance.h | 116 ++ .../Code/Source/MotionMatchingModule.cpp | 23 + .../Source/MotionMatchingModuleInterface.h | 39 + .../Source/MotionMatchingSystemComponent.cpp | 128 ++ .../Source/MotionMatchingSystemComponent.h | 51 + .../Code/Source/PoseDataJointVelocities.cpp | 160 ++ .../Code/Source/PoseDataJointVelocities.h | 60 + .../Code/Source/TrajectoryHistory.cpp | 167 ++ .../Code/Source/TrajectoryHistory.h | 63 + .../Code/Source/TrajectoryQuery.cpp | 163 ++ .../Code/Source/TrajectoryQuery.h | 68 + .../Code/Tests/FeatureMatrixTests.cpp | 62 + .../Code/Tests/FeatureSchemaTests.cpp | 81 + Gems/MotionMatching/Code/Tests/Fixture.h | 23 + .../Code/Tests/MotionMatchingEditorTest.cpp | 11 + .../Code/Tests/MotionMatchingTest.cpp | 11 + .../Code/motionmatching_editor_files.cmake | 12 + .../motionmatching_editor_shared_files.cmake | 11 + .../motionmatching_editor_tests_files.cmake | 11 + .../Code/motionmatching_files.cmake | 52 + .../Code/motionmatching_shared_files.cmake | 11 + .../Code/motionmatching_tests_files.cmake | 14 + .../Docs/Diagrams/ArchitectureDiagram.drawio | 1 + .../Docs/Diagrams/FeatureSchema.drawio | 1 + .../Docs/Images/ArchitectureDiagram.png | 3 + .../Docs/Images/FeatureSchema.png | 3 + .../JupyterNotebooks/FeatureAnalysis.ipynb | 352 ++++ Gems/MotionMatching/README.md | 48 + Gems/MotionMatching/gem.json | 21 + Gems/MotionMatching/preview.png | 3 + engine.json | 1 + 114 files changed, 9541 insertions(+) create mode 100644 Gems/MotionMatching/Assets/Animations/Acceleration1.fbx create mode 100644 Gems/MotionMatching/Assets/Animations/Circles1.fbx create mode 100644 Gems/MotionMatching/Assets/Animations/Crouching1.fbx create mode 100644 Gems/MotionMatching/Assets/Animations/FreeRoaming1.fbx create mode 100644 Gems/MotionMatching/Assets/Animations/FreeRoaming2.fbx create mode 100644 Gems/MotionMatching/Assets/Animations/Jog1.fbx create mode 100644 Gems/MotionMatching/Assets/Animations/JogPivotTurn1.fbx create mode 100644 Gems/MotionMatching/Assets/Animations/Jumps1.fbx create mode 100644 Gems/MotionMatching/Assets/Animations/JumpsFreeRoam1.fbx create mode 100644 Gems/MotionMatching/Assets/Animations/MixedLocomotion1.fbx create mode 100644 Gems/MotionMatching/Assets/Animations/OutofRange1.fbx create mode 100644 Gems/MotionMatching/Assets/Animations/Pushes1.fbx create mode 100644 Gems/MotionMatching/Assets/Animations/Run1.fbx create mode 100644 Gems/MotionMatching/Assets/Animations/RunPivotTurn1.fbx create mode 100644 Gems/MotionMatching/Assets/Animations/Snake1.fbx create mode 100644 Gems/MotionMatching/Assets/Animations/TurnOnSpot1.fbx create mode 100644 Gems/MotionMatching/Assets/Animations/Walk1.fbx create mode 100644 Gems/MotionMatching/Assets/Animations/Walk2.fbx create mode 100644 Gems/MotionMatching/Assets/Animations/WalkPivotTurn1.fbx create mode 100644 Gems/MotionMatching/Assets/Animations/WalkStopTurn1.fbx create mode 100644 Gems/MotionMatching/Assets/Animations/WalkStopTurnPivot1.fbx create mode 100644 Gems/MotionMatching/Assets/Animations/WalkTurns1.fbx create mode 100644 Gems/MotionMatching/Assets/Animations/acceleration1.fbx.assetinfo create mode 100644 Gems/MotionMatching/Assets/Animations/circles1.fbx.assetinfo create mode 100644 Gems/MotionMatching/Assets/Animations/crouching1.fbx.assetinfo create mode 100644 Gems/MotionMatching/Assets/Animations/freeroaming1.fbx.assetinfo create mode 100644 Gems/MotionMatching/Assets/Animations/freeroaming2.fbx.assetinfo create mode 100644 Gems/MotionMatching/Assets/Animations/jog1.fbx.assetinfo create mode 100644 Gems/MotionMatching/Assets/Animations/jogpivotturn1.fbx.assetinfo create mode 100644 Gems/MotionMatching/Assets/Animations/mixedlocomotion1.fbx.assetinfo create mode 100644 Gems/MotionMatching/Assets/Animations/outofrange1.fbx.assetinfo create mode 100644 Gems/MotionMatching/Assets/Animations/run1.fbx.assetinfo create mode 100644 Gems/MotionMatching/Assets/Animations/runpivotturn1.fbx.assetinfo create mode 100644 Gems/MotionMatching/Assets/Animations/snake1.fbx.assetinfo create mode 100644 Gems/MotionMatching/Assets/Animations/turnonspot1.fbx.assetinfo create mode 100644 Gems/MotionMatching/Assets/Animations/walk1.fbx.assetinfo create mode 100644 Gems/MotionMatching/Assets/Animations/walk2.fbx.assetinfo create mode 100644 Gems/MotionMatching/Assets/Animations/walkpivotturn1.fbx.assetinfo create mode 100644 Gems/MotionMatching/Assets/Animations/walkstopturn1.fbx.assetinfo create mode 100644 Gems/MotionMatching/Assets/Animations/walkstopturnpivot1.fbx.assetinfo create mode 100644 Gems/MotionMatching/Assets/Animations/walkturns1.fbx.assetinfo create mode 100644 Gems/MotionMatching/Assets/Character/Rin.fbx create mode 100644 Gems/MotionMatching/Assets/Character/Rin.fbx.assetinfo create mode 100644 Gems/MotionMatching/Assets/MotionMatching.animgraph create mode 100644 Gems/MotionMatching/Assets/MotionMatching.emfxworkspace create mode 100644 Gems/MotionMatching/Assets/MotionMatching.motionset create mode 100644 Gems/MotionMatching/CMakeLists.txt create mode 100644 Gems/MotionMatching/Code/CMakeLists.txt create mode 100644 Gems/MotionMatching/Code/Include/MotionMatching/MotionMatchingBus.h create mode 100644 Gems/MotionMatching/Code/Source/Allocators.h create mode 100644 Gems/MotionMatching/Code/Source/BlendTreeMotionMatchNode.cpp create mode 100644 Gems/MotionMatching/Code/Source/BlendTreeMotionMatchNode.h create mode 100644 Gems/MotionMatching/Code/Source/EventData.cpp create mode 100644 Gems/MotionMatching/Code/Source/EventData.h create mode 100644 Gems/MotionMatching/Code/Source/Feature.cpp create mode 100644 Gems/MotionMatching/Code/Source/Feature.h create mode 100644 Gems/MotionMatching/Code/Source/FeatureMatrix.cpp create mode 100644 Gems/MotionMatching/Code/Source/FeatureMatrix.h create mode 100644 Gems/MotionMatching/Code/Source/FeaturePosition.cpp create mode 100644 Gems/MotionMatching/Code/Source/FeaturePosition.h create mode 100644 Gems/MotionMatching/Code/Source/FeatureSchema.cpp create mode 100644 Gems/MotionMatching/Code/Source/FeatureSchema.h create mode 100644 Gems/MotionMatching/Code/Source/FeatureSchemaDefault.cpp create mode 100644 Gems/MotionMatching/Code/Source/FeatureSchemaDefault.h create mode 100644 Gems/MotionMatching/Code/Source/FeatureTrajectory.cpp create mode 100644 Gems/MotionMatching/Code/Source/FeatureTrajectory.h create mode 100644 Gems/MotionMatching/Code/Source/FeatureVelocity.cpp create mode 100644 Gems/MotionMatching/Code/Source/FeatureVelocity.h create mode 100644 Gems/MotionMatching/Code/Source/Frame.cpp create mode 100644 Gems/MotionMatching/Code/Source/Frame.h create mode 100644 Gems/MotionMatching/Code/Source/FrameDatabase.cpp create mode 100644 Gems/MotionMatching/Code/Source/FrameDatabase.h create mode 100644 Gems/MotionMatching/Code/Source/ImGuiMonitor.cpp create mode 100644 Gems/MotionMatching/Code/Source/ImGuiMonitor.h create mode 100644 Gems/MotionMatching/Code/Source/ImGuiMonitorBus.h create mode 100644 Gems/MotionMatching/Code/Source/KdTree.cpp create mode 100644 Gems/MotionMatching/Code/Source/KdTree.h create mode 100644 Gems/MotionMatching/Code/Source/MotionMatchingData.cpp create mode 100644 Gems/MotionMatching/Code/Source/MotionMatchingData.h create mode 100644 Gems/MotionMatching/Code/Source/MotionMatchingEditorModule.cpp create mode 100644 Gems/MotionMatching/Code/Source/MotionMatchingEditorSystemComponent.cpp create mode 100644 Gems/MotionMatching/Code/Source/MotionMatchingEditorSystemComponent.h create mode 100644 Gems/MotionMatching/Code/Source/MotionMatchingInstance.cpp create mode 100644 Gems/MotionMatching/Code/Source/MotionMatchingInstance.h create mode 100644 Gems/MotionMatching/Code/Source/MotionMatchingModule.cpp create mode 100644 Gems/MotionMatching/Code/Source/MotionMatchingModuleInterface.h create mode 100644 Gems/MotionMatching/Code/Source/MotionMatchingSystemComponent.cpp create mode 100644 Gems/MotionMatching/Code/Source/MotionMatchingSystemComponent.h create mode 100644 Gems/MotionMatching/Code/Source/PoseDataJointVelocities.cpp create mode 100644 Gems/MotionMatching/Code/Source/PoseDataJointVelocities.h create mode 100644 Gems/MotionMatching/Code/Source/TrajectoryHistory.cpp create mode 100644 Gems/MotionMatching/Code/Source/TrajectoryHistory.h create mode 100644 Gems/MotionMatching/Code/Source/TrajectoryQuery.cpp create mode 100644 Gems/MotionMatching/Code/Source/TrajectoryQuery.h create mode 100644 Gems/MotionMatching/Code/Tests/FeatureMatrixTests.cpp create mode 100644 Gems/MotionMatching/Code/Tests/FeatureSchemaTests.cpp create mode 100644 Gems/MotionMatching/Code/Tests/Fixture.h create mode 100644 Gems/MotionMatching/Code/Tests/MotionMatchingEditorTest.cpp create mode 100644 Gems/MotionMatching/Code/Tests/MotionMatchingTest.cpp create mode 100644 Gems/MotionMatching/Code/motionmatching_editor_files.cmake create mode 100644 Gems/MotionMatching/Code/motionmatching_editor_shared_files.cmake create mode 100644 Gems/MotionMatching/Code/motionmatching_editor_tests_files.cmake create mode 100644 Gems/MotionMatching/Code/motionmatching_files.cmake create mode 100644 Gems/MotionMatching/Code/motionmatching_shared_files.cmake create mode 100644 Gems/MotionMatching/Code/motionmatching_tests_files.cmake create mode 100644 Gems/MotionMatching/Docs/Diagrams/ArchitectureDiagram.drawio create mode 100644 Gems/MotionMatching/Docs/Diagrams/FeatureSchema.drawio create mode 100644 Gems/MotionMatching/Docs/Images/ArchitectureDiagram.png create mode 100644 Gems/MotionMatching/Docs/Images/FeatureSchema.png create mode 100644 Gems/MotionMatching/JupyterNotebooks/FeatureAnalysis.ipynb create mode 100644 Gems/MotionMatching/README.md create mode 100644 Gems/MotionMatching/gem.json create mode 100644 Gems/MotionMatching/preview.png diff --git a/Gems/MotionMatching/Assets/Animations/Acceleration1.fbx b/Gems/MotionMatching/Assets/Animations/Acceleration1.fbx new file mode 100644 index 0000000000..c2c167cdd1 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/Acceleration1.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33d5b3035966ac54bbb97a207ca14868a6daef9ad9e596281f7930764b54c819 +size 2527664 diff --git a/Gems/MotionMatching/Assets/Animations/Circles1.fbx b/Gems/MotionMatching/Assets/Animations/Circles1.fbx new file mode 100644 index 0000000000..fcdecc0ab5 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/Circles1.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91d6a8c16bae554339d1849285804b798e136113c6aecc3dfed1be635c484600 +size 4750720 diff --git a/Gems/MotionMatching/Assets/Animations/Crouching1.fbx b/Gems/MotionMatching/Assets/Animations/Crouching1.fbx new file mode 100644 index 0000000000..bf5c171f8b --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/Crouching1.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e18101baa7b33af4ac26181b7c3c20d5c12e9d9d2f57bd24271c6e4665e1a65a +size 3001296 diff --git a/Gems/MotionMatching/Assets/Animations/FreeRoaming1.fbx b/Gems/MotionMatching/Assets/Animations/FreeRoaming1.fbx new file mode 100644 index 0000000000..f44eba537a --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/FreeRoaming1.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8eda027438797e48e6b00745e17e3ec4da6d507735c754056a8fea19c465a528 +size 5926096 diff --git a/Gems/MotionMatching/Assets/Animations/FreeRoaming2.fbx b/Gems/MotionMatching/Assets/Animations/FreeRoaming2.fbx new file mode 100644 index 0000000000..837f8df648 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/FreeRoaming2.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c2af3ea28464b4d7269f54c1ff1ded691b19b826d5de85139d20ae825aae992 +size 5085168 diff --git a/Gems/MotionMatching/Assets/Animations/Jog1.fbx b/Gems/MotionMatching/Assets/Animations/Jog1.fbx new file mode 100644 index 0000000000..02e6c73ac5 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/Jog1.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25593a1e4670dbb9cb1c5a0fc375ba2bf7862784c45847d76eefd44d39df86f9 +size 2974896 diff --git a/Gems/MotionMatching/Assets/Animations/JogPivotTurn1.fbx b/Gems/MotionMatching/Assets/Animations/JogPivotTurn1.fbx new file mode 100644 index 0000000000..51fac75ca6 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/JogPivotTurn1.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e21e3f9b7db6572f740a99159bba72eca3697d3d3e1c07562579be9599bcebb7 +size 2511488 diff --git a/Gems/MotionMatching/Assets/Animations/Jumps1.fbx b/Gems/MotionMatching/Assets/Animations/Jumps1.fbx new file mode 100644 index 0000000000..d075da8991 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/Jumps1.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3802b0e44b3e0aa6610a4157510c3038bfe4038211202cebd6eb116ffd53cee9 +size 2587408 diff --git a/Gems/MotionMatching/Assets/Animations/JumpsFreeRoam1.fbx b/Gems/MotionMatching/Assets/Animations/JumpsFreeRoam1.fbx new file mode 100644 index 0000000000..d384c9f156 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/JumpsFreeRoam1.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f07e278fcee81d719ec2e7c02417e2ee710b9d224a29798cfb97b55a9ffe351 +size 3515488 diff --git a/Gems/MotionMatching/Assets/Animations/MixedLocomotion1.fbx b/Gems/MotionMatching/Assets/Animations/MixedLocomotion1.fbx new file mode 100644 index 0000000000..317fa22e76 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/MixedLocomotion1.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc4d0b653f910cea3c7c3b8d08963af0cc331ad91e467c7becb2c7f9b9c5b402 +size 4068144 diff --git a/Gems/MotionMatching/Assets/Animations/OutofRange1.fbx b/Gems/MotionMatching/Assets/Animations/OutofRange1.fbx new file mode 100644 index 0000000000..ecde12dee1 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/OutofRange1.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58cd43568cdd3f9b9585b65208cfff6d6c5f5bea9ad67878a1efbeebb8ef8a6d +size 1812176 diff --git a/Gems/MotionMatching/Assets/Animations/Pushes1.fbx b/Gems/MotionMatching/Assets/Animations/Pushes1.fbx new file mode 100644 index 0000000000..d89c45a611 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/Pushes1.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18a831cfe702120d163e44a27d5cd33faf64f5f892da3789aea1bbfecd528e72 +size 3288400 diff --git a/Gems/MotionMatching/Assets/Animations/Run1.fbx b/Gems/MotionMatching/Assets/Animations/Run1.fbx new file mode 100644 index 0000000000..150ae00f4f --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/Run1.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3649e2d0b239f9da45af2b04e937ce6113e8dee74a70a3cef748995ea15f6120 +size 2354544 diff --git a/Gems/MotionMatching/Assets/Animations/RunPivotTurn1.fbx b/Gems/MotionMatching/Assets/Animations/RunPivotTurn1.fbx new file mode 100644 index 0000000000..8e67a72016 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/RunPivotTurn1.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:207378e42ebdad1c3dfe8cefc84ed50e1d322895f018e2efca14fdcbf2e6600f +size 1890352 diff --git a/Gems/MotionMatching/Assets/Animations/Snake1.fbx b/Gems/MotionMatching/Assets/Animations/Snake1.fbx new file mode 100644 index 0000000000..ff597b968d --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/Snake1.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78c68566aba845544ec96a92f2cf4e36350c74c16e931c3aa9e14151bcaed8e8 +size 3881680 diff --git a/Gems/MotionMatching/Assets/Animations/TurnOnSpot1.fbx b/Gems/MotionMatching/Assets/Animations/TurnOnSpot1.fbx new file mode 100644 index 0000000000..ac71a0f42b --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/TurnOnSpot1.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73560b662d3819647985c3f7b0544ba3ad71365e8393a3915630b254f86c6286 +size 3669552 diff --git a/Gems/MotionMatching/Assets/Animations/Walk1.fbx b/Gems/MotionMatching/Assets/Animations/Walk1.fbx new file mode 100644 index 0000000000..423a59b31f --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/Walk1.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e2fad264af33252d49c6e3a0b6c6ef83697b951a54c7ab0cd4259f145024d915 +size 3962336 diff --git a/Gems/MotionMatching/Assets/Animations/Walk2.fbx b/Gems/MotionMatching/Assets/Animations/Walk2.fbx new file mode 100644 index 0000000000..0aaf74b496 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/Walk2.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:149ae8bfc0dc4ad8471e79f5cd8ce5420c02bc5c8a5cf1971ebbf997cae21096 +size 3834704 diff --git a/Gems/MotionMatching/Assets/Animations/WalkPivotTurn1.fbx b/Gems/MotionMatching/Assets/Animations/WalkPivotTurn1.fbx new file mode 100644 index 0000000000..1dac028839 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/WalkPivotTurn1.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6468a660cfd88c350443e92b31e41362909d705c344cda6640c65a5e1fd2a29 +size 2288048 diff --git a/Gems/MotionMatching/Assets/Animations/WalkStopTurn1.fbx b/Gems/MotionMatching/Assets/Animations/WalkStopTurn1.fbx new file mode 100644 index 0000000000..20f967fece --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/WalkStopTurn1.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b824d6fd5bb3e7f6b4f29ff90b406179539e1c155ba9f8870ef54f1d1d0e22e7 +size 3128032 diff --git a/Gems/MotionMatching/Assets/Animations/WalkStopTurnPivot1.fbx b/Gems/MotionMatching/Assets/Animations/WalkStopTurnPivot1.fbx new file mode 100644 index 0000000000..50f69af25c --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/WalkStopTurnPivot1.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38f0f9e280ff686032aa223cc8ce754442edc616d28a16d39a87c90bd221c91a +size 2807280 diff --git a/Gems/MotionMatching/Assets/Animations/WalkTurns1.fbx b/Gems/MotionMatching/Assets/Animations/WalkTurns1.fbx new file mode 100644 index 0000000000..acdb27e1d9 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/WalkTurns1.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:862ddf8893923169938db017f573f63228bb5aec3d4892d21a525200dd6c4680 +size 4063280 diff --git a/Gems/MotionMatching/Assets/Animations/acceleration1.fbx.assetinfo b/Gems/MotionMatching/Assets/Animations/acceleration1.fbx.assetinfo new file mode 100644 index 0000000000..c9749b8bf3 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/acceleration1.fbx.assetinfo @@ -0,0 +1,50 @@ +{ + "values": [ + { + "$type": "MotionGroup", + "name": "acceleration1", + "selectedRootBone": "RootNode.root", + "id": "{ADB2CDC1-8EA3-5B21-90D6-43EBE9991709}", + "rules": { + "rules": [ + { + "$type": "EMotionFX::Pipeline::Rule::MotionMetaDataRule", + "data": { + "motionEventTable": { + "tracks": [ + { + "name": "Sync", + "deletable": false, + "events": [ + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 19.600000381469727, + "endTime": 20.666667938232422 + }, + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 50.599998474121094, + "endTime": 53.19999694824219 + } + ] + } + ] + } + } + }, + { + "$type": "MotionSamplingRule" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/Gems/MotionMatching/Assets/Animations/circles1.fbx.assetinfo b/Gems/MotionMatching/Assets/Animations/circles1.fbx.assetinfo new file mode 100644 index 0000000000..4276767f05 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/circles1.fbx.assetinfo @@ -0,0 +1,41 @@ +{ + "values": [ + { + "$type": "MotionGroup", + "name": "circles1", + "selectedRootBone": "RootNode.root", + "id": "{BF21E0D5-87F6-5A3F-B100-507F217D4C7E}", + "rules": { + "rules": [ + { + "$type": "EMotionFX::Pipeline::Rule::MotionMetaDataRule", + "data": { + "motionEventTable": { + "tracks": [ + { + "name": "Sync", + "deletable": false, + "events": [ + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 112.73334503173828, + "endTime": 114.00001525878906 + } + ] + } + ] + } + } + }, + { + "$type": "MotionSamplingRule" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/Gems/MotionMatching/Assets/Animations/crouching1.fbx.assetinfo b/Gems/MotionMatching/Assets/Animations/crouching1.fbx.assetinfo new file mode 100644 index 0000000000..2f2f8c52e5 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/crouching1.fbx.assetinfo @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gems/MotionMatching/Assets/Animations/freeroaming1.fbx.assetinfo b/Gems/MotionMatching/Assets/Animations/freeroaming1.fbx.assetinfo new file mode 100644 index 0000000000..0a6534208d --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/freeroaming1.fbx.assetinfo @@ -0,0 +1,59 @@ +{ + "values": [ + { + "$type": "MotionGroup", + "name": "freeroaming1", + "selectedRootBone": "RootNode.root", + "id": "{A07E54E7-BB49-5DB3-BCA1-5EC8B4FA74A3}", + "rules": { + "rules": [ + { + "$type": "EMotionFX::Pipeline::Rule::MotionMetaDataRule", + "data": { + "motionEventTable": { + "tracks": [ + { + "name": "Sync", + "deletable": false, + "events": [ + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 73.33333587646484, + "endTime": 111.13333129882813 + }, + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 134.3333282470703, + "endTime": 136.13333129882813 + }, + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 145.1999969482422, + "endTime": 146.13333129882813 + } + ] + } + ] + } + } + }, + { + "$type": "MotionSamplingRule" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/Gems/MotionMatching/Assets/Animations/freeroaming2.fbx.assetinfo b/Gems/MotionMatching/Assets/Animations/freeroaming2.fbx.assetinfo new file mode 100644 index 0000000000..bdb6c10668 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/freeroaming2.fbx.assetinfo @@ -0,0 +1,41 @@ +{ + "values": [ + { + "$type": "MotionGroup", + "name": "freeroaming2", + "selectedRootBone": "RootNode.root", + "id": "{96DC1ABD-1F72-5546-8B7F-7092C3AC0E5D}", + "rules": { + "rules": [ + { + "$type": "EMotionFX::Pipeline::Rule::MotionMetaDataRule", + "data": { + "motionEventTable": { + "tracks": [ + { + "name": "Sync", + "deletable": false, + "events": [ + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 119.26667022705078, + "endTime": 123.4000015258789 + } + ] + } + ] + } + } + }, + { + "$type": "MotionSamplingRule" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/Gems/MotionMatching/Assets/Animations/jog1.fbx.assetinfo b/Gems/MotionMatching/Assets/Animations/jog1.fbx.assetinfo new file mode 100644 index 0000000000..dbb83cb4d3 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/jog1.fbx.assetinfo @@ -0,0 +1,41 @@ +{ + "values": [ + { + "$type": "MotionGroup", + "name": "jog1", + "selectedRootBone": "RootNode.root", + "id": "{9200D325-808C-5B2D-B323-1FF9790C07B7}", + "rules": { + "rules": [ + { + "$type": "EMotionFX::Pipeline::Rule::MotionMetaDataRule", + "data": { + "motionEventTable": { + "tracks": [ + { + "name": "Sync", + "deletable": false, + "events": [ + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 64.0, + "endTime": 65.53333282470703 + } + ] + } + ] + } + } + }, + { + "$type": "MotionSamplingRule" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/Gems/MotionMatching/Assets/Animations/jogpivotturn1.fbx.assetinfo b/Gems/MotionMatching/Assets/Animations/jogpivotturn1.fbx.assetinfo new file mode 100644 index 0000000000..10b3e2e59a --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/jogpivotturn1.fbx.assetinfo @@ -0,0 +1,53 @@ +{ + "values": [ + { + "$type": "MotionGroup", + "name": "jogpivotturn1", + "selectedRootBone": "RootNode.root", + "id": "{596210E7-A7F4-511D-886B-AA4FED4AC92B}", + "rules": { + "rules": [ + { + "$type": "EMotionFX::Pipeline::Rule::MotionMetaDataRule", + "data": { + "motionEventTable": { + "tracks": [ + { + "name": "Sync", + "deletable": false + }, + { + "name": "Event Track 2", + "events": [ + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 36.66666793823242, + "endTime": 40.733333587646484 + }, + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 41.733333587646484, + "endTime": 53.06666564941406 + } + ] + } + ] + } + } + }, + { + "$type": "MotionSamplingRule" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/Gems/MotionMatching/Assets/Animations/mixedlocomotion1.fbx.assetinfo b/Gems/MotionMatching/Assets/Animations/mixedlocomotion1.fbx.assetinfo new file mode 100644 index 0000000000..d8d1a4d4e6 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/mixedlocomotion1.fbx.assetinfo @@ -0,0 +1,53 @@ +{ + "values": [ + { + "$type": "MotionGroup", + "name": "mixedlocomotion1", + "selectedRootBone": "RootNode.root", + "id": "{2F7682E4-235E-5A31-B450-266D7DC00E39}", + "rules": { + "rules": [ + { + "$type": "EMotionFX::Pipeline::Rule::MotionMetaDataRule", + "data": { + "motionEventTable": { + "tracks": [ + { + "name": "Sync", + "deletable": false + }, + { + "name": "Event Track 2", + "events": [ + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 27.53333282470703, + "endTime": 29.999998092651367 + }, + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 56.53333282470703, + "endTime": 60.666664123535156 + } + ] + } + ] + } + } + }, + { + "$type": "MotionSamplingRule" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/Gems/MotionMatching/Assets/Animations/outofrange1.fbx.assetinfo b/Gems/MotionMatching/Assets/Animations/outofrange1.fbx.assetinfo new file mode 100644 index 0000000000..9e206c09ae --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/outofrange1.fbx.assetinfo @@ -0,0 +1,41 @@ +{ + "values": [ + { + "$type": "MotionGroup", + "name": "outofrange1", + "selectedRootBone": "RootNode.root", + "id": "{6B28C886-471C-5506-AD03-DF19025F6DA1}", + "rules": { + "rules": [ + { + "$type": "EMotionFX::Pipeline::Rule::MotionMetaDataRule", + "data": { + "motionEventTable": { + "tracks": [ + { + "name": "Sync", + "deletable": false, + "events": [ + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 29.299814224243164, + "endTime": 33.83555221557617 + } + ] + } + ] + } + } + }, + { + "$type": "MotionSamplingRule" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/Gems/MotionMatching/Assets/Animations/run1.fbx.assetinfo b/Gems/MotionMatching/Assets/Animations/run1.fbx.assetinfo new file mode 100644 index 0000000000..fe0bd2f197 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/run1.fbx.assetinfo @@ -0,0 +1,41 @@ +{ + "values": [ + { + "$type": "MotionGroup", + "name": "run1", + "selectedRootBone": "RootNode.root", + "id": "{12953346-AF3A-5481-A54F-9119523C4538}", + "rules": { + "rules": [ + { + "$type": "EMotionFX::Pipeline::Rule::MotionMetaDataRule", + "data": { + "motionEventTable": { + "tracks": [ + { + "name": "Sync", + "deletable": false, + "events": [ + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 23.600000381469727, + "endTime": 27.933334350585938 + } + ] + } + ] + } + } + }, + { + "$type": "MotionSamplingRule" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/Gems/MotionMatching/Assets/Animations/runpivotturn1.fbx.assetinfo b/Gems/MotionMatching/Assets/Animations/runpivotturn1.fbx.assetinfo new file mode 100644 index 0000000000..353f4bd81c --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/runpivotturn1.fbx.assetinfo @@ -0,0 +1,50 @@ +{ + "values": [ + { + "$type": "MotionGroup", + "name": "runpivotturn1", + "selectedRootBone": "RootNode.root", + "id": "{DEF0D469-00AB-57D0-AF05-6AF1D6563D4A}", + "rules": { + "rules": [ + { + "$type": "EMotionFX::Pipeline::Rule::MotionMetaDataRule", + "data": { + "motionEventTable": { + "tracks": [ + { + "name": "Sync", + "deletable": false, + "events": [ + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 25.080034255981445, + "endTime": 28.964109420776367 + }, + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 29.574464797973633, + "endTime": 36.288368225097656 + } + ] + } + ] + } + } + }, + { + "$type": "MotionSamplingRule" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/Gems/MotionMatching/Assets/Animations/snake1.fbx.assetinfo b/Gems/MotionMatching/Assets/Animations/snake1.fbx.assetinfo new file mode 100644 index 0000000000..5c8961c700 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/snake1.fbx.assetinfo @@ -0,0 +1,50 @@ +{ + "values": [ + { + "$type": "MotionGroup", + "name": "snake1", + "selectedRootBone": "RootNode.root", + "id": "{4A29F10E-0083-559F-A78B-282A9EF87E00}", + "rules": { + "rules": [ + { + "$type": "EMotionFX::Pipeline::Rule::MotionMetaDataRule", + "data": { + "motionEventTable": { + "tracks": [ + { + "name": "Sync", + "deletable": false, + "events": [ + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 34.46666717529297, + "endTime": 38.733333587646484 + }, + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 89.4000015258789, + "endTime": 90.86666870117188 + } + ] + } + ] + } + } + }, + { + "$type": "MotionSamplingRule" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/Gems/MotionMatching/Assets/Animations/turnonspot1.fbx.assetinfo b/Gems/MotionMatching/Assets/Animations/turnonspot1.fbx.assetinfo new file mode 100644 index 0000000000..76c0729bb2 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/turnonspot1.fbx.assetinfo @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gems/MotionMatching/Assets/Animations/walk1.fbx.assetinfo b/Gems/MotionMatching/Assets/Animations/walk1.fbx.assetinfo new file mode 100644 index 0000000000..f21f187899 --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/walk1.fbx.assetinfo @@ -0,0 +1,59 @@ +{ + "values": [ + { + "$type": "MotionGroup", + "name": "walk1", + "selectedRootBone": "RootNode.root", + "id": "{B161FB42-0EC0-51DA-BB0E-F04B73C0DE0C}", + "rules": { + "rules": [ + { + "$type": "EMotionFX::Pipeline::Rule::MotionMetaDataRule", + "data": { + "motionEventTable": { + "tracks": [ + { + "name": "Sync", + "deletable": false, + "events": [ + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 11.533333778381348, + "endTime": 12.533333778381348 + }, + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 35.20000076293945, + "endTime": 37.53333282470703 + }, + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 76.33333587646484, + "endTime": 93.66667175292969 + } + ] + } + ] + } + } + }, + { + "$type": "MotionSamplingRule" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/Gems/MotionMatching/Assets/Animations/walk2.fbx.assetinfo b/Gems/MotionMatching/Assets/Animations/walk2.fbx.assetinfo new file mode 100644 index 0000000000..c945ba6a6d --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/walk2.fbx.assetinfo @@ -0,0 +1,50 @@ +{ + "values": [ + { + "$type": "MotionGroup", + "name": "walk2", + "selectedRootBone": "RootNode.root", + "id": "{D4D1809B-5085-59E4-B98C-D29AE1A90277}", + "rules": { + "rules": [ + { + "$type": "EMotionFX::Pipeline::Rule::MotionMetaDataRule", + "data": { + "motionEventTable": { + "tracks": [ + { + "name": "Sync", + "deletable": false, + "events": [ + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 37.599998474121094, + "endTime": 40.266666412353516 + }, + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 81.26667022705078, + "endTime": 84.0666732788086 + } + ] + } + ] + } + } + }, + { + "$type": "MotionSamplingRule" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/Gems/MotionMatching/Assets/Animations/walkpivotturn1.fbx.assetinfo b/Gems/MotionMatching/Assets/Animations/walkpivotturn1.fbx.assetinfo new file mode 100644 index 0000000000..f603a5858e --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/walkpivotturn1.fbx.assetinfo @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gems/MotionMatching/Assets/Animations/walkstopturn1.fbx.assetinfo b/Gems/MotionMatching/Assets/Animations/walkstopturn1.fbx.assetinfo new file mode 100644 index 0000000000..ffd5dbbd0e --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/walkstopturn1.fbx.assetinfo @@ -0,0 +1,41 @@ +{ + "values": [ + { + "$type": "MotionGroup", + "name": "walkstopturn1", + "selectedRootBone": "RootNode.root", + "id": "{D38F0C22-1841-5EBB-A198-9D9441CD7C80}", + "rules": { + "rules": [ + { + "$type": "EMotionFX::Pipeline::Rule::MotionMetaDataRule", + "data": { + "motionEventTable": { + "tracks": [ + { + "name": "Sync", + "deletable": false, + "events": [ + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 63.53333282470703, + "endTime": 70.53333282470703 + } + ] + } + ] + } + } + }, + { + "$type": "MotionSamplingRule" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/Gems/MotionMatching/Assets/Animations/walkstopturnpivot1.fbx.assetinfo b/Gems/MotionMatching/Assets/Animations/walkstopturnpivot1.fbx.assetinfo new file mode 100644 index 0000000000..121124029b --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/walkstopturnpivot1.fbx.assetinfo @@ -0,0 +1,41 @@ +{ + "values": [ + { + "$type": "MotionGroup", + "name": "walkstopturnpivot1", + "selectedRootBone": "RootNode.root", + "id": "{FCD5DF16-A875-552E-9333-3C7BF8554CBB}", + "rules": { + "rules": [ + { + "$type": "EMotionFX::Pipeline::Rule::MotionMetaDataRule", + "data": { + "motionEventTable": { + "tracks": [ + { + "name": "Sync", + "deletable": false, + "events": [ + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 53.06666564941406, + "endTime": 55.86666488647461 + } + ] + } + ] + } + } + }, + { + "$type": "MotionSamplingRule" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/Gems/MotionMatching/Assets/Animations/walkturns1.fbx.assetinfo b/Gems/MotionMatching/Assets/Animations/walkturns1.fbx.assetinfo new file mode 100644 index 0000000000..5f200f546e --- /dev/null +++ b/Gems/MotionMatching/Assets/Animations/walkturns1.fbx.assetinfo @@ -0,0 +1,50 @@ +{ + "values": [ + { + "$type": "MotionGroup", + "name": "walkturns1", + "selectedRootBone": "RootNode.root", + "id": "{FD1981AB-0270-56F7-9062-ABA4D73686F9}", + "rules": { + "rules": [ + { + "$type": "EMotionFX::Pipeline::Rule::MotionMetaDataRule", + "data": { + "motionEventTable": { + "tracks": [ + { + "name": "Sync", + "deletable": false, + "events": [ + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 32.266666412353516, + "endTime": 51.33333206176758 + }, + { + "eventDatas": [ + { + "$type": "DiscardFrameEventData" + } + ], + "startTime": 86.86666870117188, + "endTime": 89.46666717529297 + } + ] + } + ] + } + } + }, + { + "$type": "MotionSamplingRule" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/Gems/MotionMatching/Assets/Character/Rin.fbx b/Gems/MotionMatching/Assets/Character/Rin.fbx new file mode 100644 index 0000000000..3041a9efe1 --- /dev/null +++ b/Gems/MotionMatching/Assets/Character/Rin.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d38ee57dcf86d1209982ffa29cf5a2b42f5e0e0b86f5045a8485e3e431d74b03 +size 12267120 diff --git a/Gems/MotionMatching/Assets/Character/Rin.fbx.assetinfo b/Gems/MotionMatching/Assets/Character/Rin.fbx.assetinfo new file mode 100644 index 0000000000..1a3d8be596 --- /dev/null +++ b/Gems/MotionMatching/Assets/Character/Rin.fbx.assetinfo @@ -0,0 +1,1543 @@ +{ + "values": [ + { + "$type": "ActorGroup", + "name": "RinMM", + "id": "{3B0C7D44-39A9-5B89-B361-4789175AE832}", + "rules": { + "rules": [ + { + "$type": "MetaDataRule", + "metaData": "AdjustActor -actorID $(ACTORID) -name \"RinMM\"\nActorSetCollisionMeshes -actorID $(ACTORID) -lod 0 -nodeList \"\"\nAdjustActor -actorID $(ACTORID) -nodesExcludedFromBounds \"\" -nodeAction \"select\"\nAdjustActor -actorID $(ACTORID) -nodeAction \"replace\" -attachmentNodes \"\"\nAdjustActor -actorID $(ACTORID) -motionExtractionNodeName \"root\"\nAdjustActor -actorID $(ACTORID) -mirrorSetup \"L_leg_JNT,R_leg_JNT;R_leg_JNT,L_leg_JNT;L_ribbon_01_JNT,R_ribbon_01_JNT;R_ribbon_01_JNT,L_ribbon_01_JNT;L_knee_JNT,R_knee_JNT;L_leg_twist_JNT,R_leg_twist_JNT;R_knee_JNT,L_knee_JNT;R_leg_twist_JNT,L_leg_twist_JNT;L_ribbon_02_JNT,R_ribbon_02_JNT;R_ribbon_02_JNT,L_ribbon_02_JNT;L_foot_JNT,R_foot_JNT;L_knee_twist_JNT,R_knee_twist_JNT;R_foot_JNT,L_foot_JNT;R_knee_twist_JNT,L_knee_twist_JNT;L_toe_JNT,R_toe_JNT;R_toe_JNT,L_toe_JNT;L_clavicle_JNT,R_clavicle_JNT;R_clavicle_JNT,L_clavicle_JNT;L_neckCollar_01_JNT,R_neckCollar_01_JNT;L_neckCollar_02_JNT,R_neckCollar_02_JNT;R_neckCollar_01_JNT,L_neckCollar_01_JNT;R_neckCollar_02_JNT,L_neckCollar_02_JNT;L_arm_JNT,R_arm_JNT;L_tassle_01_JNT,R_tassle_01_JNT;L_tassleLoop_01_JNT,R_tassleLoop_01_JNT;L_armPit_corr_JNT,R_armPit_corr_JNT;R_arm_JNT,L_arm_JNT;R_tassle_01_JNT,L_tassle_01_JNT;R_tassleLoop_01_JNT,L_tassleLoop_01_JNT;R_armPit_corr_JNT,L_armPit_corr_JNT;L_elbow_JNT,R_elbow_JNT;L_arm_twist_JNT,R_arm_twist_JNT;L_armBulge_corr_JNT,R_armBulge_corr_JNT;L_tassle_02_JNT,R_tassle_02_JNT;L_tassleLoop_02_JNT,R_tassleLoop_02_JNT;R_elbow_JNT,L_elbow_JNT;R_arm_twist_JNT,L_arm_twist_JNT;R_armBulge_corr_JNT,L_armBulge_corr_JNT;R_tassle_02_JNT,L_tassle_02_JNT;R_tassleLoop_02_JNT,L_tassleLoop_02_JNT;L_wrist_JNT,R_wrist_JNT;L_elbow_twist_JNT,R_elbow_twist_JNT;L_tassle_03_JNT,R_tassle_03_JNT;L_brow_inner_JNT,R_brow_inner_JNT;L_brow_mid_JNT,R_brow_mid_JNT;L_brow_outer_JNT,R_brow_outer_JNT;L_nostril_inner_JNT,R_nostril_inner_JNT;L_nostril_outer_JNT,R_nostril_outer_JNT;L_cheekUpper_inner_JNT,R_cheekUpper_inner_JNT;L_squint_mid_JNT,R_squint_mid_JNT;L_squint_outer_JNT,R_squint_outer_JNT;L_squint_inner_JNT,R_squint_inner_JNT;L_zygomatic_outer_JNT,R_zygomatic_outer_JNT;L_cheekBone_JNT,R_cheekBone_JNT;L_ear_JNT,R_ear_JNT;L_chin_below_JNT,R_chin_below_JNT;L_eyelid_lower_JNT,R_eyelid_lower_JNT;L_eyelid_upper_JNT,R_eyelid_upper_JNT;L_eye_JNT,R_eye_JNT;L_lipUpper_JNT,R_lipUpper_JNT;L_lipLevator_inner_JNT,R_lipLevator_inner_JNT;L_lipLevator_corner_JNT,R_lipLevator_corner_JNT;L_cheekUpper_outer_JNT,R_cheekUpper_outer_JNT;L_cheekUpper_mid_JNT,R_cheekUpper_mid_JNT;R_eyelid_upper_JNT,L_eyelid_upper_JNT;R_eyelid_lower_JNT,L_eyelid_lower_JNT;R_nostril_inner_JNT,L_nostril_inner_JNT;R_lipLevator_inner_JNT,L_lipLevator_inner_JNT;R_cheekBone_JNT,L_cheekBone_JNT;R_zygomatic_outer_JNT,L_zygomatic_outer_JNT;R_lipUpper_JNT,L_lipUpper_JNT;R_chin_below_JNT,L_chin_below_JNT;R_ear_JNT,L_ear_JNT;R_eye_JNT,L_eye_JNT;R_brow_inner_JNT,L_brow_inner_JNT;R_brow_mid_JNT,L_brow_mid_JNT;R_brow_outer_JNT,L_brow_outer_JNT;R_lipLevator_corner_JNT,L_lipLevator_corner_JNT;R_cheekUpper_outer_JNT,L_cheekUpper_outer_JNT;R_cheekUpper_mid_JNT,L_cheekUpper_mid_JNT;R_nostril_outer_JNT,L_nostril_outer_JNT;R_cheekUpper_inner_JNT,L_cheekUpper_inner_JNT;R_squint_inner_JNT,L_squint_inner_JNT;R_squint_mid_JNT,L_squint_mid_JNT;R_squint_outer_JNT,L_squint_outer_JNT;L_lipUpper_corner_JNT,R_lipUpper_corner_JNT;R_lipUpper_corner_JNT,L_lipUpper_corner_JNT;R_frontalis_inner_JNT,L_frontalis_inner_JNT;R_frontalis_outer_JNT,L_frontalis_outer_JNT;L_frontalis_outer_JNT,R_frontalis_outer_JNT;L_frontalis_inner_JNT,R_frontalis_inner_JNT;R_eyelid_fold_JNT,L_eyelid_fold_JNT;L_eyelid_fold_JNT,R_eyelid_fold_JNT;L_eye_bulge_JNT,R_eye_bulge_JNT;R_eye_bulge_JNT,L_eye_bulge_JNT;R_wrist_JNT,L_wrist_JNT;R_elbow_twist_JNT,L_elbow_twist_JNT;R_tassle_03_JNT,L_tassle_03_JNT;L_thumb_01_JNT,R_thumb_01_JNT;L_index_root_JNT,R_index_root_JNT;L_middle_root_JNT,R_middle_root_JNT;L_ring_root_JNT,R_ring_root_JNT;L_pinky_root_JNT,R_pinky_root_JNT;L_depressor_JNT,R_depressor_JNT;R_depressor_JNT,L_depressor_JNT;L_lip_nasolabial_JNT,R_lip_nasolabial_JNT;R_mouth_corner_JNT,L_mouth_corner_JNT;R_lip_nasolabial_JNT,L_lip_nasolabial_JNT;R_lipLower_corner_JNT,L_lipLower_corner_JNT;R_lipLower_JNT,L_lipLower_JNT;L_lipLower_JNT,R_lipLower_JNT;L_mouth_corner_JNT,R_mouth_corner_JNT;L_lipLower_corner_JNT,R_lipLower_corner_JNT;R_jaw_clench_JNT,L_jaw_clench_JNT;L_jaw_clench_JNT,R_jaw_clench_JNT;L_zygomatic_inner_JNT,R_zygomatic_inner_JNT;R_zygomatic_inner_JNT,L_zygomatic_inner_JNT;R_thumb_01_JNT,L_thumb_01_JNT;R_index_root_JNT,L_index_root_JNT;R_middle_root_JNT,L_middle_root_JNT;R_ring_root_JNT,L_ring_root_JNT;R_pinky_root_JNT,L_pinky_root_JNT;L_thumb_02_JNT,R_thumb_02_JNT;L_index_01_JNT,R_index_01_JNT;L_middle_01_JNT,R_middle_01_JNT;L_ring_01_JNT,R_ring_01_JNT;L_pinky_01_JNT,R_pinky_01_JNT;R_thumb_02_JNT,L_thumb_02_JNT;R_index_01_JNT,L_index_01_JNT;R_middle_01_JNT,L_middle_01_JNT;R_ring_01_JNT,L_ring_01_JNT;R_pinky_01_JNT,L_pinky_01_JNT;L_thumb_03_JNT,R_thumb_03_JNT;L_index_02_JNT,R_index_02_JNT;L_middle_02_JNT,R_middle_02_JNT;L_ring_02_JNT,R_ring_02_JNT;L_pinky_02_JNT,R_pinky_02_JNT;R_thumb_03_JNT,L_thumb_03_JNT;R_index_02_JNT,L_index_02_JNT;R_middle_02_JNT,L_middle_02_JNT;R_ring_02_JNT,L_ring_02_JNT;R_pinky_02_JNT,L_pinky_02_JNT;L_index_03_JNT,R_index_03_JNT;L_middle_03_JNT,R_middle_03_JNT;L_ring_03_JNT,R_ring_03_JNT;L_pinky_03_JNT,R_pinky_03_JNT;R_index_03_JNT,L_index_03_JNT;R_middle_03_JNT,L_middle_03_JNT;R_ring_03_JNT,L_ring_03_JNT;R_pinky_03_JNT,L_pinky_03_JNT;\"\n" + } + ] + } + }, + { + "$type": "{5B03C8E6-8CEE-4DA0-A7FA-CD88689DD45B} MeshGroup", + "id": "{3C9D4C02-8F36-5F94-8B47-CEC412E736F3}", + "name": "anigmarinactor", + "NodeSelectionList": { + "unselectedNodes": [ + "RootNode", + "RootNode.rin_eyeballs", + "RootNode.rin_haircap", + "RootNode.rin_cloth", + "RootNode.rin_leather", + "RootNode.rin_armor", + "RootNode.rin_hands", + "RootNode.rin_props", + "RootNode.rin_teeth_low", + "RootNode.rin_teeth_up", + "RootNode.rin_tongue", + "RootNode.rin_face", + "RootNode.rin_armorstraps", + "RootNode.rin_hairplanes", + "RootNode.rin_eyebrows_top", + "RootNode.rin_eyelashes_top", + "RootNode.rin_eyelashes_lower", + "RootNode.rin_eyecover", + "RootNode.rin_facefuzz", + "RootNode.rin_haircards", + "RootNode.rin_lash_01", + "RootNode.rin_lash_02", + "RootNode.rin_lash_03", + "RootNode.rin_eyewetness", + "RootNode.rin_eyebrows_lower", + "RootNode.rin_tearduct", + "RootNode.root", + "RootNode.rin_eyeballs.rin_eyeballs_1", + "RootNode.rin_eyeballs.rin_eyeballs_2", + "RootNode.rin_haircap.rin_haircap_1", + "RootNode.rin_haircap.rin_haircap_2", + "RootNode.rin_cloth.rin_cloth_1", + "RootNode.rin_cloth.rin_cloth_2", + "RootNode.rin_leather.rin_leather_1", + "RootNode.rin_leather.rin_leather_2", + "RootNode.rin_armor.rin_armor_1", + "RootNode.rin_armor.rin_armor_2", + "RootNode.rin_hands.rin_hands_1", + "RootNode.rin_hands.rin_hands_2", + "RootNode.rin_props.rin_props_1", + "RootNode.rin_props.rin_props_2", + "RootNode.rin_teeth_low.rin_teeth_low_1", + "RootNode.rin_teeth_low.rin_teeth_low_2", + "RootNode.rin_teeth_up.rin_teeth_up_1", + "RootNode.rin_teeth_up.rin_teeth_up_2", + "RootNode.rin_tongue.rin_tongue_1", + "RootNode.rin_tongue.rin_tongue_2", + "RootNode.rin_face.rin_face_1", + "RootNode.rin_face.rin_face_2", + "RootNode.rin_armorstraps.rin_armorstraps_1", + "RootNode.rin_armorstraps.rin_armorstraps_2", + "RootNode.rin_hairplanes.rin_hairplanes_1", + "RootNode.rin_hairplanes.rin_hairplanes_2", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_1", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_2", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_1", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_2", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_1", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_2", + "RootNode.rin_eyecover.rin_eyecover_1", + "RootNode.rin_eyecover.rin_eyecover_2", + "RootNode.rin_facefuzz.rin_facefuzz_1", + "RootNode.rin_facefuzz.rin_facefuzz_2", + "RootNode.rin_haircards.rin_haircards_1", + "RootNode.rin_haircards.rin_haircards_2", + "RootNode.rin_lash_01.rin_lash_01_1", + "RootNode.rin_lash_01.rin_lash_01_2", + "RootNode.rin_lash_02.rin_lash_02_1", + "RootNode.rin_lash_02.rin_lash_02_2", + "RootNode.rin_lash_03.rin_lash_03_1", + "RootNode.rin_lash_03.rin_lash_03_2", + "RootNode.rin_eyewetness.rin_eyewetness_1", + "RootNode.rin_eyewetness.rin_eyewetness_2", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_1", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_2", + "RootNode.rin_tearduct.rin_tearduct_1", + "RootNode.rin_tearduct.rin_tearduct_2", + "RootNode.root.C_pelvis_JNT", + "RootNode.rin_eyeballs.rin_eyeballs_1.Bitangent", + "RootNode.rin_eyeballs.rin_eyeballs_1.SkinWeight_", + "RootNode.rin_eyeballs.rin_eyeballs_1.transform", + "RootNode.rin_eyeballs.rin_eyeballs_1.Tangent", + "RootNode.rin_eyeballs.rin_eyeballs_1.map1", + "RootNode.rin_eyeballs.rin_eyeballs_1.rin_m_eyeballs", + "RootNode.rin_eyeballs.rin_eyeballs_2.Bitangent", + "RootNode.rin_eyeballs.rin_eyeballs_2.SkinWeight_", + "RootNode.rin_eyeballs.rin_eyeballs_2.transform", + "RootNode.rin_eyeballs.rin_eyeballs_2.Tangent", + "RootNode.rin_eyeballs.rin_eyeballs_2.map1", + "RootNode.rin_eyeballs.rin_eyeballs_2.rin_m_eyeballs", + "RootNode.rin_haircap.rin_haircap_1.Bitangent", + "RootNode.rin_haircap.rin_haircap_1.SkinWeight_", + "RootNode.rin_haircap.rin_haircap_1.transform", + "RootNode.rin_haircap.rin_haircap_1.Tangent", + "RootNode.rin_haircap.rin_haircap_1.map1", + "RootNode.rin_haircap.rin_haircap_1.rin_m_haircap", + "RootNode.rin_haircap.rin_haircap_2.Bitangent", + "RootNode.rin_haircap.rin_haircap_2.SkinWeight_", + "RootNode.rin_haircap.rin_haircap_2.transform", + "RootNode.rin_haircap.rin_haircap_2.Tangent", + "RootNode.rin_haircap.rin_haircap_2.map1", + "RootNode.rin_haircap.rin_haircap_2.rin_m_haircap", + "RootNode.rin_cloth.rin_cloth_1.Col0", + "RootNode.rin_cloth.rin_cloth_1.Bitangent", + "RootNode.rin_cloth.rin_cloth_1.SkinWeight_", + "RootNode.rin_cloth.rin_cloth_1.transform", + "RootNode.rin_cloth.rin_cloth_1.Tangent", + "RootNode.rin_cloth.rin_cloth_1.UVMap", + "RootNode.rin_cloth.rin_cloth_1.rin_m_cloth", + "RootNode.rin_cloth.rin_cloth_2.Col0", + "RootNode.rin_cloth.rin_cloth_2.Bitangent", + "RootNode.rin_cloth.rin_cloth_2.SkinWeight_", + "RootNode.rin_cloth.rin_cloth_2.transform", + "RootNode.rin_cloth.rin_cloth_2.Tangent", + "RootNode.rin_cloth.rin_cloth_2.UVMap", + "RootNode.rin_cloth.rin_cloth_2.rin_m_cloth", + "RootNode.rin_leather.rin_leather_1.Col0", + "RootNode.rin_leather.rin_leather_1.Bitangent", + "RootNode.rin_leather.rin_leather_1.SkinWeight_", + "RootNode.rin_leather.rin_leather_1.transform", + "RootNode.rin_leather.rin_leather_1.Tangent", + "RootNode.rin_leather.rin_leather_1.UVMap", + "RootNode.rin_leather.rin_leather_1.rin_m_leather", + "RootNode.rin_leather.rin_leather_2.Col0", + "RootNode.rin_leather.rin_leather_2.Bitangent", + "RootNode.rin_leather.rin_leather_2.SkinWeight_", + "RootNode.rin_leather.rin_leather_2.transform", + "RootNode.rin_leather.rin_leather_2.Tangent", + "RootNode.rin_leather.rin_leather_2.UVMap", + "RootNode.rin_leather.rin_leather_2.rin_m_leather", + "RootNode.rin_armor.rin_armor_1.Col0", + "RootNode.rin_armor.rin_armor_1.Bitangent", + "RootNode.rin_armor.rin_armor_1.SkinWeight_", + "RootNode.rin_armor.rin_armor_1.transform", + "RootNode.rin_armor.rin_armor_1.Tangent", + "RootNode.rin_armor.rin_armor_1.UVMap", + "RootNode.rin_armor.rin_armor_1.rin_m_armor", + "RootNode.rin_armor.rin_armor_2.Col0", + "RootNode.rin_armor.rin_armor_2.Bitangent", + "RootNode.rin_armor.rin_armor_2.SkinWeight_", + "RootNode.rin_armor.rin_armor_2.transform", + "RootNode.rin_armor.rin_armor_2.Tangent", + "RootNode.rin_armor.rin_armor_2.UVMap", + "RootNode.rin_armor.rin_armor_2.rin_m_armor", + "RootNode.rin_hands.rin_hands_1.Col0", + "RootNode.rin_hands.rin_hands_1.Bitangent", + "RootNode.rin_hands.rin_hands_1.SkinWeight_", + "RootNode.rin_hands.rin_hands_1.transform", + "RootNode.rin_hands.rin_hands_1.Tangent", + "RootNode.rin_hands.rin_hands_1.UVMap", + "RootNode.rin_hands.rin_hands_1.rin_m_hands", + "RootNode.rin_hands.rin_hands_2.Col0", + "RootNode.rin_hands.rin_hands_2.Bitangent", + "RootNode.rin_hands.rin_hands_2.SkinWeight_", + "RootNode.rin_hands.rin_hands_2.transform", + "RootNode.rin_hands.rin_hands_2.Tangent", + "RootNode.rin_hands.rin_hands_2.UVMap", + "RootNode.rin_hands.rin_hands_2.rin_m_hands", + "RootNode.rin_props.rin_props_1.Bitangent", + "RootNode.rin_props.rin_props_1.SkinWeight_", + "RootNode.rin_props.rin_props_1.transform", + "RootNode.rin_props.rin_props_1.Tangent", + "RootNode.rin_props.rin_props_1.UVMap", + "RootNode.rin_props.rin_props_1.map1", + "RootNode.rin_props.rin_props_1.rin_m_props", + "RootNode.rin_props.rin_props_2.Bitangent", + "RootNode.rin_props.rin_props_2.SkinWeight_", + "RootNode.rin_props.rin_props_2.transform", + "RootNode.rin_props.rin_props_2.Tangent", + "RootNode.rin_props.rin_props_2.UVMap", + "RootNode.rin_props.rin_props_2.map1", + "RootNode.rin_props.rin_props_2.rin_m_props", + "RootNode.rin_teeth_low.rin_teeth_low_1.Bitangent", + "RootNode.rin_teeth_low.rin_teeth_low_1.SkinWeight_", + "RootNode.rin_teeth_low.rin_teeth_low_1.transform", + "RootNode.rin_teeth_low.rin_teeth_low_1.Tangent", + "RootNode.rin_teeth_low.rin_teeth_low_1.map1", + "RootNode.rin_teeth_low.rin_teeth_low_1.rin_m_mouth", + "RootNode.rin_teeth_low.rin_teeth_low_2.Bitangent", + "RootNode.rin_teeth_low.rin_teeth_low_2.SkinWeight_", + "RootNode.rin_teeth_low.rin_teeth_low_2.transform", + "RootNode.rin_teeth_low.rin_teeth_low_2.Tangent", + "RootNode.rin_teeth_low.rin_teeth_low_2.map1", + "RootNode.rin_teeth_low.rin_teeth_low_2.rin_m_mouth", + "RootNode.rin_teeth_up.rin_teeth_up_1.Bitangent", + "RootNode.rin_teeth_up.rin_teeth_up_1.SkinWeight_", + "RootNode.rin_teeth_up.rin_teeth_up_1.transform", + "RootNode.rin_teeth_up.rin_teeth_up_1.Tangent", + "RootNode.rin_teeth_up.rin_teeth_up_1.map1", + "RootNode.rin_teeth_up.rin_teeth_up_1.rin_m_mouth", + "RootNode.rin_teeth_up.rin_teeth_up_2.Bitangent", + "RootNode.rin_teeth_up.rin_teeth_up_2.SkinWeight_", + "RootNode.rin_teeth_up.rin_teeth_up_2.transform", + "RootNode.rin_teeth_up.rin_teeth_up_2.Tangent", + "RootNode.rin_teeth_up.rin_teeth_up_2.map1", + "RootNode.rin_teeth_up.rin_teeth_up_2.rin_m_mouth", + "RootNode.rin_tongue.rin_tongue_1.Bitangent", + "RootNode.rin_tongue.rin_tongue_1.SkinWeight_", + "RootNode.rin_tongue.rin_tongue_1.transform", + "RootNode.rin_tongue.rin_tongue_1.Tangent", + "RootNode.rin_tongue.rin_tongue_1.map1", + "RootNode.rin_tongue.rin_tongue_1.rin_m_mouth", + "RootNode.rin_tongue.rin_tongue_2.Bitangent", + "RootNode.rin_tongue.rin_tongue_2.SkinWeight_", + "RootNode.rin_tongue.rin_tongue_2.transform", + "RootNode.rin_tongue.rin_tongue_2.Tangent", + "RootNode.rin_tongue.rin_tongue_2.map1", + "RootNode.rin_tongue.rin_tongue_2.rin_m_mouth", + "RootNode.rin_face.rin_face_1.Bitangent", + "RootNode.rin_face.rin_face_1.SkinWeight_", + "RootNode.rin_face.rin_face_1.transform", + "RootNode.rin_face.rin_face_1.Tangent", + "RootNode.rin_face.rin_face_1.map1", + "RootNode.rin_face.rin_face_1.rin_m_face", + "RootNode.rin_face.rin_face_2.Bitangent", + "RootNode.rin_face.rin_face_2.SkinWeight_", + "RootNode.rin_face.rin_face_2.transform", + "RootNode.rin_face.rin_face_2.Tangent", + "RootNode.rin_face.rin_face_2.map1", + "RootNode.rin_face.rin_face_2.rin_m_face", + "RootNode.rin_armorstraps.rin_armorstraps_1.Bitangent", + "RootNode.rin_armorstraps.rin_armorstraps_1.SkinWeight_", + "RootNode.rin_armorstraps.rin_armorstraps_1.transform", + "RootNode.rin_armorstraps.rin_armorstraps_1.Tangent", + "RootNode.rin_armorstraps.rin_armorstraps_1.map1", + "RootNode.rin_armorstraps.rin_armorstraps_1.rin_m_armor", + "RootNode.rin_armorstraps.rin_armorstraps_2.Bitangent", + "RootNode.rin_armorstraps.rin_armorstraps_2.SkinWeight_", + "RootNode.rin_armorstraps.rin_armorstraps_2.transform", + "RootNode.rin_armorstraps.rin_armorstraps_2.Tangent", + "RootNode.rin_armorstraps.rin_armorstraps_2.map1", + "RootNode.rin_armorstraps.rin_armorstraps_2.rin_m_armor", + "RootNode.rin_hairplanes.rin_hairplanes_1.Bitangent", + "RootNode.rin_hairplanes.rin_hairplanes_1.SkinWeight_", + "RootNode.rin_hairplanes.rin_hairplanes_1.transform", + "RootNode.rin_hairplanes.rin_hairplanes_1.Tangent", + "RootNode.rin_hairplanes.rin_hairplanes_1.map1", + "RootNode.rin_hairplanes.rin_hairplanes_1.rin_m_hairplanes", + "RootNode.rin_hairplanes.rin_hairplanes_2.Bitangent", + "RootNode.rin_hairplanes.rin_hairplanes_2.SkinWeight_", + "RootNode.rin_hairplanes.rin_hairplanes_2.transform", + "RootNode.rin_hairplanes.rin_hairplanes_2.Tangent", + "RootNode.rin_hairplanes.rin_hairplanes_2.map1", + "RootNode.rin_hairplanes.rin_hairplanes_2.rin_m_hairplanes", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_1.Bitangent", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_1.SkinWeight_", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_1.transform", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_1.Tangent", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_1.map1", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_1.rin_m_eyebrow", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_2.Bitangent", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_2.SkinWeight_", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_2.transform", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_2.Tangent", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_2.map1", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_2.rin_m_eyebrow", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_1.Bitangent", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_1.SkinWeight_", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_1.transform", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_1.Tangent", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_1.map1", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_1.rin_m_lashes", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_2.Bitangent", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_2.SkinWeight_", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_2.transform", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_2.Tangent", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_2.map1", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_2.rin_m_lashes", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_1.Bitangent", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_1.SkinWeight_", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_1.transform", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_1.Tangent", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_1.map1", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_1.rin_m_eyebrow", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_2.Bitangent", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_2.SkinWeight_", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_2.transform", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_2.Tangent", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_2.map1", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_2.rin_m_eyebrow", + "RootNode.rin_eyecover.rin_eyecover_1.Bitangent", + "RootNode.rin_eyecover.rin_eyecover_1.SkinWeight_", + "RootNode.rin_eyecover.rin_eyecover_1.transform", + "RootNode.rin_eyecover.rin_eyecover_1.Tangent", + "RootNode.rin_eyecover.rin_eyecover_1.map1", + "RootNode.rin_eyecover.rin_eyecover_1.rin_m_eyecover", + "RootNode.rin_eyecover.rin_eyecover_2.Bitangent", + "RootNode.rin_eyecover.rin_eyecover_2.SkinWeight_", + "RootNode.rin_eyecover.rin_eyecover_2.transform", + "RootNode.rin_eyecover.rin_eyecover_2.Tangent", + "RootNode.rin_eyecover.rin_eyecover_2.map1", + "RootNode.rin_eyecover.rin_eyecover_2.rin_m_eyecover", + "RootNode.rin_facefuzz.rin_facefuzz_1.Bitangent", + "RootNode.rin_facefuzz.rin_facefuzz_1.SkinWeight_", + "RootNode.rin_facefuzz.rin_facefuzz_1.transform", + "RootNode.rin_facefuzz.rin_facefuzz_1.Tangent", + "RootNode.rin_facefuzz.rin_facefuzz_1.map1", + "RootNode.rin_facefuzz.rin_facefuzz_1.rin_m_fuzz", + "RootNode.rin_facefuzz.rin_facefuzz_2.Bitangent", + "RootNode.rin_facefuzz.rin_facefuzz_2.SkinWeight_", + "RootNode.rin_facefuzz.rin_facefuzz_2.transform", + "RootNode.rin_facefuzz.rin_facefuzz_2.Tangent", + "RootNode.rin_facefuzz.rin_facefuzz_2.map1", + "RootNode.rin_facefuzz.rin_facefuzz_2.rin_m_fuzz", + "RootNode.rin_haircards.rin_haircards_1.Bitangent", + "RootNode.rin_haircards.rin_haircards_1.SkinWeight_", + "RootNode.rin_haircards.rin_haircards_1.transform", + "RootNode.rin_haircards.rin_haircards_1.Tangent", + "RootNode.rin_haircards.rin_haircards_1.map1", + "RootNode.rin_haircards.rin_haircards_1.rin_m_haircards", + "RootNode.rin_haircards.rin_haircards_2.Bitangent", + "RootNode.rin_haircards.rin_haircards_2.SkinWeight_", + "RootNode.rin_haircards.rin_haircards_2.transform", + "RootNode.rin_haircards.rin_haircards_2.Tangent", + "RootNode.rin_haircards.rin_haircards_2.map1", + "RootNode.rin_haircards.rin_haircards_2.rin_m_haircards", + "RootNode.rin_lash_01.rin_lash_01_1.Bitangent", + "RootNode.rin_lash_01.rin_lash_01_1.SkinWeight_", + "RootNode.rin_lash_01.rin_lash_01_1.transform", + "RootNode.rin_lash_01.rin_lash_01_1.Tangent", + "RootNode.rin_lash_01.rin_lash_01_1.map1", + "RootNode.rin_lash_01.rin_lash_01_1.rin_m_lashes", + "RootNode.rin_lash_01.rin_lash_01_2.Bitangent", + "RootNode.rin_lash_01.rin_lash_01_2.SkinWeight_", + "RootNode.rin_lash_01.rin_lash_01_2.transform", + "RootNode.rin_lash_01.rin_lash_01_2.Tangent", + "RootNode.rin_lash_01.rin_lash_01_2.map1", + "RootNode.rin_lash_01.rin_lash_01_2.rin_m_lashes", + "RootNode.rin_lash_02.rin_lash_02_1.Bitangent", + "RootNode.rin_lash_02.rin_lash_02_1.SkinWeight_", + "RootNode.rin_lash_02.rin_lash_02_1.transform", + "RootNode.rin_lash_02.rin_lash_02_1.Tangent", + "RootNode.rin_lash_02.rin_lash_02_1.map1", + "RootNode.rin_lash_02.rin_lash_02_1.rin_m_lashes", + "RootNode.rin_lash_02.rin_lash_02_2.Bitangent", + "RootNode.rin_lash_02.rin_lash_02_2.SkinWeight_", + "RootNode.rin_lash_02.rin_lash_02_2.transform", + "RootNode.rin_lash_02.rin_lash_02_2.Tangent", + "RootNode.rin_lash_02.rin_lash_02_2.map1", + "RootNode.rin_lash_02.rin_lash_02_2.rin_m_lashes", + "RootNode.rin_lash_03.rin_lash_03_1.Bitangent", + "RootNode.rin_lash_03.rin_lash_03_1.SkinWeight_", + "RootNode.rin_lash_03.rin_lash_03_1.transform", + "RootNode.rin_lash_03.rin_lash_03_1.Tangent", + "RootNode.rin_lash_03.rin_lash_03_1.map1", + "RootNode.rin_lash_03.rin_lash_03_1.rin_m_lashes", + "RootNode.rin_lash_03.rin_lash_03_2.Bitangent", + "RootNode.rin_lash_03.rin_lash_03_2.SkinWeight_", + "RootNode.rin_lash_03.rin_lash_03_2.transform", + "RootNode.rin_lash_03.rin_lash_03_2.Tangent", + "RootNode.rin_lash_03.rin_lash_03_2.map1", + "RootNode.rin_lash_03.rin_lash_03_2.rin_m_lashes", + "RootNode.rin_eyewetness.rin_eyewetness_1.Bitangent", + "RootNode.rin_eyewetness.rin_eyewetness_1.SkinWeight_", + "RootNode.rin_eyewetness.rin_eyewetness_1.transform", + "RootNode.rin_eyewetness.rin_eyewetness_1.Tangent", + "RootNode.rin_eyewetness.rin_eyewetness_1.map1", + "RootNode.rin_eyewetness.rin_eyewetness_1.rin_m_eyewetness", + "RootNode.rin_eyewetness.rin_eyewetness_2.Bitangent", + "RootNode.rin_eyewetness.rin_eyewetness_2.SkinWeight_", + "RootNode.rin_eyewetness.rin_eyewetness_2.transform", + "RootNode.rin_eyewetness.rin_eyewetness_2.Tangent", + "RootNode.rin_eyewetness.rin_eyewetness_2.map1", + "RootNode.rin_eyewetness.rin_eyewetness_2.rin_m_eyewetness", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_1.Bitangent", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_1.SkinWeight_", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_1.transform", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_1.Tangent", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_1.map1", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_1.rin_m_eyebrow", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_2.Bitangent", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_2.SkinWeight_", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_2.transform", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_2.Tangent", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_2.map1", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_2.rin_m_eyebrow", + "RootNode.rin_tearduct.rin_tearduct_1.Bitangent", + "RootNode.rin_tearduct.rin_tearduct_1.SkinWeight_", + "RootNode.rin_tearduct.rin_tearduct_1.transform", + "RootNode.rin_tearduct.rin_tearduct_1.Tangent", + "RootNode.rin_tearduct.rin_tearduct_1.map1", + "RootNode.rin_tearduct.rin_tearduct_1.rin_m_tearduct", + "RootNode.rin_tearduct.rin_tearduct_2.Bitangent", + "RootNode.rin_tearduct.rin_tearduct_2.SkinWeight_", + "RootNode.rin_tearduct.rin_tearduct_2.transform", + "RootNode.rin_tearduct.rin_tearduct_2.Tangent", + "RootNode.rin_tearduct.rin_tearduct_2.map1", + "RootNode.rin_tearduct.rin_tearduct_2.rin_m_tearduct", + "RootNode.root.C_pelvis_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT", + "RootNode.root.C_pelvis_JNT.L_leg_JNT", + "RootNode.root.C_pelvis_JNT.R_leg_JNT", + "RootNode.root.C_pelvis_JNT.L_ribbon_01_JNT", + "RootNode.root.C_pelvis_JNT.R_ribbon_01_JNT", + "RootNode.root.C_pelvis_JNT.C_legArmor_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_sword_JNT", + "RootNode.root.C_pelvis_JNT.L_leg_JNT.transform", + "RootNode.root.C_pelvis_JNT.L_leg_JNT.L_knee_JNT", + "RootNode.root.C_pelvis_JNT.L_leg_JNT.L_leg_twist_JNT", + "RootNode.root.C_pelvis_JNT.R_leg_JNT.transform", + "RootNode.root.C_pelvis_JNT.R_leg_JNT.R_knee_JNT", + "RootNode.root.C_pelvis_JNT.R_leg_JNT.R_leg_twist_JNT", + "RootNode.root.C_pelvis_JNT.L_ribbon_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.L_ribbon_01_JNT.L_ribbon_02_JNT", + "RootNode.root.C_pelvis_JNT.R_ribbon_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.R_ribbon_01_JNT.R_ribbon_02_JNT", + "RootNode.root.C_pelvis_JNT.C_legArmor_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_sword_JNT.transform", + "RootNode.root.C_pelvis_JNT.L_leg_JNT.L_knee_JNT.transform", + "RootNode.root.C_pelvis_JNT.L_leg_JNT.L_knee_JNT.L_foot_JNT", + "RootNode.root.C_pelvis_JNT.L_leg_JNT.L_knee_JNT.L_knee_twist_JNT", + "RootNode.root.C_pelvis_JNT.L_leg_JNT.L_leg_twist_JNT.transform", + "RootNode.root.C_pelvis_JNT.R_leg_JNT.R_knee_JNT.transform", + "RootNode.root.C_pelvis_JNT.R_leg_JNT.R_knee_JNT.R_foot_JNT", + "RootNode.root.C_pelvis_JNT.R_leg_JNT.R_knee_JNT.R_knee_twist_JNT", + "RootNode.root.C_pelvis_JNT.R_leg_JNT.R_leg_twist_JNT.transform", + "RootNode.root.C_pelvis_JNT.L_ribbon_01_JNT.L_ribbon_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.R_ribbon_01_JNT.R_ribbon_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT", + "RootNode.root.C_pelvis_JNT.L_leg_JNT.L_knee_JNT.L_foot_JNT.transform", + "RootNode.root.C_pelvis_JNT.L_leg_JNT.L_knee_JNT.L_foot_JNT.L_toe_JNT", + "RootNode.root.C_pelvis_JNT.L_leg_JNT.L_knee_JNT.L_knee_twist_JNT.transform", + "RootNode.root.C_pelvis_JNT.R_leg_JNT.R_knee_JNT.R_foot_JNT.transform", + "RootNode.root.C_pelvis_JNT.R_leg_JNT.R_knee_JNT.R_foot_JNT.R_toe_JNT", + "RootNode.root.C_pelvis_JNT.R_leg_JNT.R_knee_JNT.R_knee_twist_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_hood_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neckCollar_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_neckCollar_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_neckCollar_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_neckCollar_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_neckCollar_02_JNT", + "RootNode.root.C_pelvis_JNT.L_leg_JNT.L_knee_JNT.L_foot_JNT.L_toe_JNT.transform", + "RootNode.root.C_pelvis_JNT.R_leg_JNT.R_knee_JNT.R_foot_JNT.R_toe_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_tassle_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_tassleLoop_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_armPit_corr_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_tassle_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_tassleLoop_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_armPit_corr_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_hood_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_hood_01_JNT.C_hood_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neckCollar_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_neckCollar_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_neckCollar_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_neckCollar_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_neckCollar_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_arm_twist_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_armBulge_corr_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_tassle_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_tassle_01_JNT.L_tassle_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_tassleLoop_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_tassleLoop_01_JNT.L_tassleLoop_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_armPit_corr_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_throat_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_arm_twist_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_armBulge_corr_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_tassle_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_tassle_01_JNT.R_tassle_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_tassleLoop_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_tassleLoop_01_JNT.R_tassleLoop_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_armPit_corr_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_hood_01_JNT.C_hood_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_hood_01_JNT.C_hood_02_JNT.C_hood_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_elbow_twist_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_arm_twist_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_armBulge_corr_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_tassle_01_JNT.L_tassle_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_tassle_01_JNT.L_tassle_02_JNT.L_tassle_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_tassleLoop_01_JNT.L_tassleLoop_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_brow_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_brow_mid_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_brow_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_corrugator_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_noseBridge_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_nostril_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_nostril_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_cheekUpper_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_squint_mid_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_squint_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_squint_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_zygomatic_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_cheekBone_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_ear_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_nose_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_chin_below_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_chin_below_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eyelid_lower_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eyelid_upper_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eyelid_levator_mid_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eye_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_lipLevator_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_lipUpper_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_lipUpper_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_lipLevator_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_lipLevator_corner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_cheekUpper_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_cheekUpper_mid_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_eyelid_upper_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_eyelid_lower_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_nostril_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_lipLevator_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_cheekBone_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_zygomatic_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_lipUpper_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_chin_below_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_ear_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_nose_slide_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_eye_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_brow_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_brow_mid_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_brow_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_lipLevator_corner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_cheekUpper_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_cheekUpper_mid_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_nostril_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_cheekUpper_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_squint_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_squint_mid_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_squint_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_lipUpper_corner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_lipUpper_corner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_frontalis_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_frontalis_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_frontalis_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_frontalis_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_eyelid_fold_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eyelid_fold_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eye_bulge_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_eye_bulge_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_throat_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_elbow_twist_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_arm_twist_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_armBulge_corr_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_tassle_01_JNT.R_tassle_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_tassle_01_JNT.R_tassle_02_JNT.R_tassle_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_tassleLoop_01_JNT.R_tassleLoop_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_hood_01_JNT.C_hood_02_JNT.C_hood_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_thumb_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_index_root_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_middle_root_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_ring_root_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_pinky_root_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_elbow_twist_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_tassle_01_JNT.L_tassle_02_JNT.L_tassle_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_brow_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_brow_mid_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_brow_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_corrugator_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_noseBridge_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_nostril_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_nostril_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_cheekUpper_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_squint_mid_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_squint_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_squint_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_zygomatic_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_cheekBone_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_ear_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_nose_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_chin_below_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_chin_below_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eyelid_lower_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eyelid_upper_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eyelid_levator_mid_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eye_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_lipLevator_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_lipUpper_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_lipUpper_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_lipLevator_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_lipLevator_corner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_mentalis_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_depressor_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_depressor_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_lip_nasolabial_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_mouth_corner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_lip_below_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_lip_nasolabial_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_lipLower_corner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_lipLower_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_lipLower_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_lipLower_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_mouth_corner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_lipLower_corner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_jaw_clench_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_jaw_clench_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_zygomatic_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_zygomatic_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_tongue_root_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_cheekUpper_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_cheekUpper_mid_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_eyelid_upper_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_eyelid_lower_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_nostril_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_lipLevator_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_cheekBone_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_zygomatic_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_lipUpper_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_chin_below_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_ear_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_nose_slide_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_eye_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_brow_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_brow_mid_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_brow_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_lipLevator_corner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_cheekUpper_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_cheekUpper_mid_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_nostril_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_cheekUpper_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_squint_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_squint_mid_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_squint_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_lipUpper_corner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_lipUpper_corner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_frontalis_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_frontalis_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_frontalis_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_frontalis_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_eyelid_fold_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eyelid_fold_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eye_bulge_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_eye_bulge_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_thumb_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_index_root_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_middle_root_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_ring_root_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_pinky_root_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_elbow_twist_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_tassle_01_JNT.R_tassle_02_JNT.R_tassle_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_thumb_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_thumb_01_JNT.L_thumb_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_index_root_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_index_root_JNT.L_index_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_middle_root_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_middle_root_JNT.L_middle_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_ring_root_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_ring_root_JNT.L_ring_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_pinky_root_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_pinky_root_JNT.L_pinky_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_mentalis_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_depressor_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_depressor_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_lip_nasolabial_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_mouth_corner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_lip_below_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_lip_nasolabial_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_lipLower_corner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_lipLower_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_lipLower_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_lipLower_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_mouth_corner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_lipLower_corner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_jaw_clench_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_jaw_clench_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_zygomatic_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_zygomatic_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_tongue_root_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_tongue_root_JNT.C_tongue_mid_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_thumb_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_thumb_01_JNT.R_thumb_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_index_root_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_index_root_JNT.R_index_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_middle_root_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_middle_root_JNT.R_middle_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_ring_root_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_ring_root_JNT.R_ring_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_pinky_root_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_pinky_root_JNT.R_pinky_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_thumb_01_JNT.L_thumb_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_thumb_01_JNT.L_thumb_02_JNT.L_thumb_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_index_root_JNT.L_index_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_index_root_JNT.L_index_01_JNT.L_index_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_middle_root_JNT.L_middle_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_middle_root_JNT.L_middle_01_JNT.L_middle_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_ring_root_JNT.L_ring_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_ring_root_JNT.L_ring_01_JNT.L_ring_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_pinky_root_JNT.L_pinky_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_pinky_root_JNT.L_pinky_01_JNT.L_pinky_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_tongue_root_JNT.C_tongue_mid_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_tongue_root_JNT.C_tongue_mid_JNT.C_tongue_tip_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_thumb_01_JNT.R_thumb_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_thumb_01_JNT.R_thumb_02_JNT.R_thumb_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_index_root_JNT.R_index_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_index_root_JNT.R_index_01_JNT.R_index_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_middle_root_JNT.R_middle_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_middle_root_JNT.R_middle_01_JNT.R_middle_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_ring_root_JNT.R_ring_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_ring_root_JNT.R_ring_01_JNT.R_ring_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_pinky_root_JNT.R_pinky_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_pinky_root_JNT.R_pinky_01_JNT.R_pinky_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_thumb_01_JNT.L_thumb_02_JNT.L_thumb_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_index_root_JNT.L_index_01_JNT.L_index_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_index_root_JNT.L_index_01_JNT.L_index_02_JNT.L_index_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_middle_root_JNT.L_middle_01_JNT.L_middle_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_middle_root_JNT.L_middle_01_JNT.L_middle_02_JNT.L_middle_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_ring_root_JNT.L_ring_01_JNT.L_ring_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_ring_root_JNT.L_ring_01_JNT.L_ring_02_JNT.L_ring_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_pinky_root_JNT.L_pinky_01_JNT.L_pinky_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_pinky_root_JNT.L_pinky_01_JNT.L_pinky_02_JNT.L_pinky_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_tongue_root_JNT.C_tongue_mid_JNT.C_tongue_tip_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_thumb_01_JNT.R_thumb_02_JNT.R_thumb_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_index_root_JNT.R_index_01_JNT.R_index_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_index_root_JNT.R_index_01_JNT.R_index_02_JNT.R_index_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_middle_root_JNT.R_middle_01_JNT.R_middle_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_middle_root_JNT.R_middle_01_JNT.R_middle_02_JNT.R_middle_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_ring_root_JNT.R_ring_01_JNT.R_ring_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_ring_root_JNT.R_ring_01_JNT.R_ring_02_JNT.R_ring_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_pinky_root_JNT.R_pinky_01_JNT.R_pinky_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_pinky_root_JNT.R_pinky_01_JNT.R_pinky_02_JNT.R_pinky_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_index_root_JNT.L_index_01_JNT.L_index_02_JNT.L_index_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_middle_root_JNT.L_middle_01_JNT.L_middle_02_JNT.L_middle_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_ring_root_JNT.L_ring_01_JNT.L_ring_02_JNT.L_ring_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_pinky_root_JNT.L_pinky_01_JNT.L_pinky_02_JNT.L_pinky_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_index_root_JNT.R_index_01_JNT.R_index_02_JNT.R_index_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_middle_root_JNT.R_middle_01_JNT.R_middle_02_JNT.R_middle_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_ring_root_JNT.R_ring_01_JNT.R_ring_02_JNT.R_ring_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_pinky_root_JNT.R_pinky_01_JNT.R_pinky_02_JNT.R_pinky_03_JNT.transform" + ] + } + }, + { + "$type": "{07B356B7-3635-40B5-878A-FAC4EFD5AD86} MeshGroup", + "name": "RinMM", + "nodeSelectionList": { + "selectedNodes": [ + "RootNode", + "RootNode.rin_eyeballs", + "RootNode.rin_haircap", + "RootNode.rin_cloth", + "RootNode.rin_leather", + "RootNode.rin_armor", + "RootNode.rin_hands", + "RootNode.rin_props", + "RootNode.rin_teeth_low", + "RootNode.rin_teeth_up", + "RootNode.rin_tongue", + "RootNode.rin_face", + "RootNode.rin_armorstraps", + "RootNode.rin_hairplanes", + "RootNode.rin_eyebrows_top", + "RootNode.rin_eyelashes_top", + "RootNode.rin_eyelashes_lower", + "RootNode.rin_eyecover", + "RootNode.rin_facefuzz", + "RootNode.rin_haircards", + "RootNode.rin_lash_01", + "RootNode.rin_lash_02", + "RootNode.rin_lash_03", + "RootNode.rin_eyewetness", + "RootNode.rin_eyebrows_lower", + "RootNode.rin_tearduct", + "RootNode.root", + "RootNode.rin_eyeballs.rin_eyeballs_1", + "RootNode.rin_eyeballs.rin_eyeballs_2", + "RootNode.rin_haircap.rin_haircap_1", + "RootNode.rin_haircap.rin_haircap_2", + "RootNode.rin_cloth.rin_cloth_1", + "RootNode.rin_cloth.rin_cloth_2", + "RootNode.rin_leather.rin_leather_1", + "RootNode.rin_leather.rin_leather_2", + "RootNode.rin_armor.rin_armor_1", + "RootNode.rin_armor.rin_armor_2", + "RootNode.rin_hands.rin_hands_1", + "RootNode.rin_hands.rin_hands_2", + "RootNode.rin_props.rin_props_1", + "RootNode.rin_props.rin_props_2", + "RootNode.rin_teeth_low.rin_teeth_low_1", + "RootNode.rin_teeth_low.rin_teeth_low_2", + "RootNode.rin_teeth_up.rin_teeth_up_1", + "RootNode.rin_teeth_up.rin_teeth_up_2", + "RootNode.rin_tongue.rin_tongue_1", + "RootNode.rin_tongue.rin_tongue_2", + "RootNode.rin_face.rin_face_1", + "RootNode.rin_face.rin_face_2", + "RootNode.rin_armorstraps.rin_armorstraps_1", + "RootNode.rin_armorstraps.rin_armorstraps_2", + "RootNode.rin_hairplanes.rin_hairplanes_1", + "RootNode.rin_hairplanes.rin_hairplanes_2", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_1", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_2", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_1", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_2", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_1", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_2", + "RootNode.rin_eyecover.rin_eyecover_1", + "RootNode.rin_eyecover.rin_eyecover_2", + "RootNode.rin_facefuzz.rin_facefuzz_1", + "RootNode.rin_facefuzz.rin_facefuzz_2", + "RootNode.rin_haircards.rin_haircards_1", + "RootNode.rin_haircards.rin_haircards_2", + "RootNode.rin_lash_01.rin_lash_01_1", + "RootNode.rin_lash_01.rin_lash_01_2", + "RootNode.rin_lash_02.rin_lash_02_1", + "RootNode.rin_lash_02.rin_lash_02_2", + "RootNode.rin_lash_03.rin_lash_03_1", + "RootNode.rin_lash_03.rin_lash_03_2", + "RootNode.rin_eyewetness.rin_eyewetness_1", + "RootNode.rin_eyewetness.rin_eyewetness_2", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_1", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_2", + "RootNode.rin_tearduct.rin_tearduct_1", + "RootNode.rin_tearduct.rin_tearduct_2", + "RootNode.root.C_pelvis_JNT", + "RootNode.rin_eyeballs.rin_eyeballs_1.Bitangent", + "RootNode.rin_eyeballs.rin_eyeballs_1.SkinWeight_", + "RootNode.rin_eyeballs.rin_eyeballs_1.transform", + "RootNode.rin_eyeballs.rin_eyeballs_1.Tangent", + "RootNode.rin_eyeballs.rin_eyeballs_1.map1", + "RootNode.rin_eyeballs.rin_eyeballs_1.rin_m_eyeballs", + "RootNode.rin_eyeballs.rin_eyeballs_2.Bitangent", + "RootNode.rin_eyeballs.rin_eyeballs_2.SkinWeight_", + "RootNode.rin_eyeballs.rin_eyeballs_2.transform", + "RootNode.rin_eyeballs.rin_eyeballs_2.Tangent", + "RootNode.rin_eyeballs.rin_eyeballs_2.map1", + "RootNode.rin_eyeballs.rin_eyeballs_2.rin_m_eyeballs", + "RootNode.rin_haircap.rin_haircap_1.Bitangent", + "RootNode.rin_haircap.rin_haircap_1.SkinWeight_", + "RootNode.rin_haircap.rin_haircap_1.transform", + "RootNode.rin_haircap.rin_haircap_1.Tangent", + "RootNode.rin_haircap.rin_haircap_1.map1", + "RootNode.rin_haircap.rin_haircap_1.rin_m_haircap", + "RootNode.rin_haircap.rin_haircap_2.Bitangent", + "RootNode.rin_haircap.rin_haircap_2.SkinWeight_", + "RootNode.rin_haircap.rin_haircap_2.transform", + "RootNode.rin_haircap.rin_haircap_2.Tangent", + "RootNode.rin_haircap.rin_haircap_2.map1", + "RootNode.rin_haircap.rin_haircap_2.rin_m_haircap", + "RootNode.rin_cloth.rin_cloth_1.Col0", + "RootNode.rin_cloth.rin_cloth_1.Bitangent", + "RootNode.rin_cloth.rin_cloth_1.SkinWeight_", + "RootNode.rin_cloth.rin_cloth_1.transform", + "RootNode.rin_cloth.rin_cloth_1.Tangent", + "RootNode.rin_cloth.rin_cloth_1.UVMap", + "RootNode.rin_cloth.rin_cloth_1.rin_m_cloth", + "RootNode.rin_cloth.rin_cloth_2.Col0", + "RootNode.rin_cloth.rin_cloth_2.Bitangent", + "RootNode.rin_cloth.rin_cloth_2.SkinWeight_", + "RootNode.rin_cloth.rin_cloth_2.transform", + "RootNode.rin_cloth.rin_cloth_2.Tangent", + "RootNode.rin_cloth.rin_cloth_2.UVMap", + "RootNode.rin_cloth.rin_cloth_2.rin_m_cloth", + "RootNode.rin_leather.rin_leather_1.Col0", + "RootNode.rin_leather.rin_leather_1.Bitangent", + "RootNode.rin_leather.rin_leather_1.SkinWeight_", + "RootNode.rin_leather.rin_leather_1.transform", + "RootNode.rin_leather.rin_leather_1.Tangent", + "RootNode.rin_leather.rin_leather_1.UVMap", + "RootNode.rin_leather.rin_leather_1.rin_m_leather", + "RootNode.rin_leather.rin_leather_2.Col0", + "RootNode.rin_leather.rin_leather_2.Bitangent", + "RootNode.rin_leather.rin_leather_2.SkinWeight_", + "RootNode.rin_leather.rin_leather_2.transform", + "RootNode.rin_leather.rin_leather_2.Tangent", + "RootNode.rin_leather.rin_leather_2.UVMap", + "RootNode.rin_leather.rin_leather_2.rin_m_leather", + "RootNode.rin_armor.rin_armor_1.Col0", + "RootNode.rin_armor.rin_armor_1.Bitangent", + "RootNode.rin_armor.rin_armor_1.SkinWeight_", + "RootNode.rin_armor.rin_armor_1.transform", + "RootNode.rin_armor.rin_armor_1.Tangent", + "RootNode.rin_armor.rin_armor_1.UVMap", + "RootNode.rin_armor.rin_armor_1.rin_m_armor", + "RootNode.rin_armor.rin_armor_2.Col0", + "RootNode.rin_armor.rin_armor_2.Bitangent", + "RootNode.rin_armor.rin_armor_2.SkinWeight_", + "RootNode.rin_armor.rin_armor_2.transform", + "RootNode.rin_armor.rin_armor_2.Tangent", + "RootNode.rin_armor.rin_armor_2.UVMap", + "RootNode.rin_armor.rin_armor_2.rin_m_armor", + "RootNode.rin_hands.rin_hands_1.Col0", + "RootNode.rin_hands.rin_hands_1.Bitangent", + "RootNode.rin_hands.rin_hands_1.SkinWeight_", + "RootNode.rin_hands.rin_hands_1.transform", + "RootNode.rin_hands.rin_hands_1.Tangent", + "RootNode.rin_hands.rin_hands_1.UVMap", + "RootNode.rin_hands.rin_hands_1.rin_m_hands", + "RootNode.rin_hands.rin_hands_2.Col0", + "RootNode.rin_hands.rin_hands_2.Bitangent", + "RootNode.rin_hands.rin_hands_2.SkinWeight_", + "RootNode.rin_hands.rin_hands_2.transform", + "RootNode.rin_hands.rin_hands_2.Tangent", + "RootNode.rin_hands.rin_hands_2.UVMap", + "RootNode.rin_hands.rin_hands_2.rin_m_hands", + "RootNode.rin_props.rin_props_1.Bitangent", + "RootNode.rin_props.rin_props_1.SkinWeight_", + "RootNode.rin_props.rin_props_1.transform", + "RootNode.rin_props.rin_props_1.Tangent", + "RootNode.rin_props.rin_props_1.UVMap", + "RootNode.rin_props.rin_props_1.map1", + "RootNode.rin_props.rin_props_1.rin_m_props", + "RootNode.rin_props.rin_props_2.Bitangent", + "RootNode.rin_props.rin_props_2.SkinWeight_", + "RootNode.rin_props.rin_props_2.transform", + "RootNode.rin_props.rin_props_2.Tangent", + "RootNode.rin_props.rin_props_2.UVMap", + "RootNode.rin_props.rin_props_2.map1", + "RootNode.rin_props.rin_props_2.rin_m_props", + "RootNode.rin_teeth_low.rin_teeth_low_1.Bitangent", + "RootNode.rin_teeth_low.rin_teeth_low_1.SkinWeight_", + "RootNode.rin_teeth_low.rin_teeth_low_1.transform", + "RootNode.rin_teeth_low.rin_teeth_low_1.Tangent", + "RootNode.rin_teeth_low.rin_teeth_low_1.map1", + "RootNode.rin_teeth_low.rin_teeth_low_1.rin_m_mouth", + "RootNode.rin_teeth_low.rin_teeth_low_2.Bitangent", + "RootNode.rin_teeth_low.rin_teeth_low_2.SkinWeight_", + "RootNode.rin_teeth_low.rin_teeth_low_2.transform", + "RootNode.rin_teeth_low.rin_teeth_low_2.Tangent", + "RootNode.rin_teeth_low.rin_teeth_low_2.map1", + "RootNode.rin_teeth_low.rin_teeth_low_2.rin_m_mouth", + "RootNode.rin_teeth_up.rin_teeth_up_1.Bitangent", + "RootNode.rin_teeth_up.rin_teeth_up_1.SkinWeight_", + "RootNode.rin_teeth_up.rin_teeth_up_1.transform", + "RootNode.rin_teeth_up.rin_teeth_up_1.Tangent", + "RootNode.rin_teeth_up.rin_teeth_up_1.map1", + "RootNode.rin_teeth_up.rin_teeth_up_1.rin_m_mouth", + "RootNode.rin_teeth_up.rin_teeth_up_2.Bitangent", + "RootNode.rin_teeth_up.rin_teeth_up_2.SkinWeight_", + "RootNode.rin_teeth_up.rin_teeth_up_2.transform", + "RootNode.rin_teeth_up.rin_teeth_up_2.Tangent", + "RootNode.rin_teeth_up.rin_teeth_up_2.map1", + "RootNode.rin_teeth_up.rin_teeth_up_2.rin_m_mouth", + "RootNode.rin_tongue.rin_tongue_1.Bitangent", + "RootNode.rin_tongue.rin_tongue_1.SkinWeight_", + "RootNode.rin_tongue.rin_tongue_1.transform", + "RootNode.rin_tongue.rin_tongue_1.Tangent", + "RootNode.rin_tongue.rin_tongue_1.map1", + "RootNode.rin_tongue.rin_tongue_1.rin_m_mouth", + "RootNode.rin_tongue.rin_tongue_2.Bitangent", + "RootNode.rin_tongue.rin_tongue_2.SkinWeight_", + "RootNode.rin_tongue.rin_tongue_2.transform", + "RootNode.rin_tongue.rin_tongue_2.Tangent", + "RootNode.rin_tongue.rin_tongue_2.map1", + "RootNode.rin_tongue.rin_tongue_2.rin_m_mouth", + "RootNode.rin_face.rin_face_1.Bitangent", + "RootNode.rin_face.rin_face_1.SkinWeight_", + "RootNode.rin_face.rin_face_1.transform", + "RootNode.rin_face.rin_face_1.Tangent", + "RootNode.rin_face.rin_face_1.map1", + "RootNode.rin_face.rin_face_1.rin_m_face", + "RootNode.rin_face.rin_face_2.Bitangent", + "RootNode.rin_face.rin_face_2.SkinWeight_", + "RootNode.rin_face.rin_face_2.transform", + "RootNode.rin_face.rin_face_2.Tangent", + "RootNode.rin_face.rin_face_2.map1", + "RootNode.rin_face.rin_face_2.rin_m_face", + "RootNode.rin_armorstraps.rin_armorstraps_1.Bitangent", + "RootNode.rin_armorstraps.rin_armorstraps_1.SkinWeight_", + "RootNode.rin_armorstraps.rin_armorstraps_1.transform", + "RootNode.rin_armorstraps.rin_armorstraps_1.Tangent", + "RootNode.rin_armorstraps.rin_armorstraps_1.map1", + "RootNode.rin_armorstraps.rin_armorstraps_1.rin_m_armor", + "RootNode.rin_armorstraps.rin_armorstraps_2.Bitangent", + "RootNode.rin_armorstraps.rin_armorstraps_2.SkinWeight_", + "RootNode.rin_armorstraps.rin_armorstraps_2.transform", + "RootNode.rin_armorstraps.rin_armorstraps_2.Tangent", + "RootNode.rin_armorstraps.rin_armorstraps_2.map1", + "RootNode.rin_armorstraps.rin_armorstraps_2.rin_m_armor", + "RootNode.rin_hairplanes.rin_hairplanes_1.Bitangent", + "RootNode.rin_hairplanes.rin_hairplanes_1.SkinWeight_", + "RootNode.rin_hairplanes.rin_hairplanes_1.transform", + "RootNode.rin_hairplanes.rin_hairplanes_1.Tangent", + "RootNode.rin_hairplanes.rin_hairplanes_1.map1", + "RootNode.rin_hairplanes.rin_hairplanes_1.rin_m_hairplanes", + "RootNode.rin_hairplanes.rin_hairplanes_2.Bitangent", + "RootNode.rin_hairplanes.rin_hairplanes_2.SkinWeight_", + "RootNode.rin_hairplanes.rin_hairplanes_2.transform", + "RootNode.rin_hairplanes.rin_hairplanes_2.Tangent", + "RootNode.rin_hairplanes.rin_hairplanes_2.map1", + "RootNode.rin_hairplanes.rin_hairplanes_2.rin_m_hairplanes", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_1.Bitangent", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_1.SkinWeight_", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_1.transform", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_1.Tangent", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_1.map1", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_1.rin_m_eyebrow", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_2.Bitangent", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_2.SkinWeight_", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_2.transform", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_2.Tangent", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_2.map1", + "RootNode.rin_eyebrows_top.rin_eyebrows_top_2.rin_m_eyebrow", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_1.Bitangent", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_1.SkinWeight_", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_1.transform", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_1.Tangent", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_1.map1", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_1.rin_m_lashes", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_2.Bitangent", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_2.SkinWeight_", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_2.transform", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_2.Tangent", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_2.map1", + "RootNode.rin_eyelashes_top.rin_eyelashes_top_2.rin_m_lashes", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_1.Bitangent", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_1.SkinWeight_", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_1.transform", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_1.Tangent", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_1.map1", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_1.rin_m_eyebrow", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_2.Bitangent", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_2.SkinWeight_", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_2.transform", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_2.Tangent", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_2.map1", + "RootNode.rin_eyelashes_lower.rin_eyelashes_lower_2.rin_m_eyebrow", + "RootNode.rin_eyecover.rin_eyecover_1.Bitangent", + "RootNode.rin_eyecover.rin_eyecover_1.SkinWeight_", + "RootNode.rin_eyecover.rin_eyecover_1.transform", + "RootNode.rin_eyecover.rin_eyecover_1.Tangent", + "RootNode.rin_eyecover.rin_eyecover_1.map1", + "RootNode.rin_eyecover.rin_eyecover_1.rin_m_eyecover", + "RootNode.rin_eyecover.rin_eyecover_2.Bitangent", + "RootNode.rin_eyecover.rin_eyecover_2.SkinWeight_", + "RootNode.rin_eyecover.rin_eyecover_2.transform", + "RootNode.rin_eyecover.rin_eyecover_2.Tangent", + "RootNode.rin_eyecover.rin_eyecover_2.map1", + "RootNode.rin_eyecover.rin_eyecover_2.rin_m_eyecover", + "RootNode.rin_facefuzz.rin_facefuzz_1.Bitangent", + "RootNode.rin_facefuzz.rin_facefuzz_1.SkinWeight_", + "RootNode.rin_facefuzz.rin_facefuzz_1.transform", + "RootNode.rin_facefuzz.rin_facefuzz_1.Tangent", + "RootNode.rin_facefuzz.rin_facefuzz_1.map1", + "RootNode.rin_facefuzz.rin_facefuzz_1.rin_m_fuzz", + "RootNode.rin_facefuzz.rin_facefuzz_2.Bitangent", + "RootNode.rin_facefuzz.rin_facefuzz_2.SkinWeight_", + "RootNode.rin_facefuzz.rin_facefuzz_2.transform", + "RootNode.rin_facefuzz.rin_facefuzz_2.Tangent", + "RootNode.rin_facefuzz.rin_facefuzz_2.map1", + "RootNode.rin_facefuzz.rin_facefuzz_2.rin_m_fuzz", + "RootNode.rin_haircards.rin_haircards_1.Bitangent", + "RootNode.rin_haircards.rin_haircards_1.SkinWeight_", + "RootNode.rin_haircards.rin_haircards_1.transform", + "RootNode.rin_haircards.rin_haircards_1.Tangent", + "RootNode.rin_haircards.rin_haircards_1.map1", + "RootNode.rin_haircards.rin_haircards_1.rin_m_haircards", + "RootNode.rin_haircards.rin_haircards_2.Bitangent", + "RootNode.rin_haircards.rin_haircards_2.SkinWeight_", + "RootNode.rin_haircards.rin_haircards_2.transform", + "RootNode.rin_haircards.rin_haircards_2.Tangent", + "RootNode.rin_haircards.rin_haircards_2.map1", + "RootNode.rin_haircards.rin_haircards_2.rin_m_haircards", + "RootNode.rin_lash_01.rin_lash_01_1.Bitangent", + "RootNode.rin_lash_01.rin_lash_01_1.SkinWeight_", + "RootNode.rin_lash_01.rin_lash_01_1.transform", + "RootNode.rin_lash_01.rin_lash_01_1.Tangent", + "RootNode.rin_lash_01.rin_lash_01_1.map1", + "RootNode.rin_lash_01.rin_lash_01_1.rin_m_lashes", + "RootNode.rin_lash_01.rin_lash_01_2.Bitangent", + "RootNode.rin_lash_01.rin_lash_01_2.SkinWeight_", + "RootNode.rin_lash_01.rin_lash_01_2.transform", + "RootNode.rin_lash_01.rin_lash_01_2.Tangent", + "RootNode.rin_lash_01.rin_lash_01_2.map1", + "RootNode.rin_lash_01.rin_lash_01_2.rin_m_lashes", + "RootNode.rin_lash_02.rin_lash_02_1.Bitangent", + "RootNode.rin_lash_02.rin_lash_02_1.SkinWeight_", + "RootNode.rin_lash_02.rin_lash_02_1.transform", + "RootNode.rin_lash_02.rin_lash_02_1.Tangent", + "RootNode.rin_lash_02.rin_lash_02_1.map1", + "RootNode.rin_lash_02.rin_lash_02_1.rin_m_lashes", + "RootNode.rin_lash_02.rin_lash_02_2.Bitangent", + "RootNode.rin_lash_02.rin_lash_02_2.SkinWeight_", + "RootNode.rin_lash_02.rin_lash_02_2.transform", + "RootNode.rin_lash_02.rin_lash_02_2.Tangent", + "RootNode.rin_lash_02.rin_lash_02_2.map1", + "RootNode.rin_lash_02.rin_lash_02_2.rin_m_lashes", + "RootNode.rin_lash_03.rin_lash_03_1.Bitangent", + "RootNode.rin_lash_03.rin_lash_03_1.SkinWeight_", + "RootNode.rin_lash_03.rin_lash_03_1.transform", + "RootNode.rin_lash_03.rin_lash_03_1.Tangent", + "RootNode.rin_lash_03.rin_lash_03_1.map1", + "RootNode.rin_lash_03.rin_lash_03_1.rin_m_lashes", + "RootNode.rin_lash_03.rin_lash_03_2.Bitangent", + "RootNode.rin_lash_03.rin_lash_03_2.SkinWeight_", + "RootNode.rin_lash_03.rin_lash_03_2.transform", + "RootNode.rin_lash_03.rin_lash_03_2.Tangent", + "RootNode.rin_lash_03.rin_lash_03_2.map1", + "RootNode.rin_lash_03.rin_lash_03_2.rin_m_lashes", + "RootNode.rin_eyewetness.rin_eyewetness_1.Bitangent", + "RootNode.rin_eyewetness.rin_eyewetness_1.SkinWeight_", + "RootNode.rin_eyewetness.rin_eyewetness_1.transform", + "RootNode.rin_eyewetness.rin_eyewetness_1.Tangent", + "RootNode.rin_eyewetness.rin_eyewetness_1.map1", + "RootNode.rin_eyewetness.rin_eyewetness_1.rin_m_eyewetness", + "RootNode.rin_eyewetness.rin_eyewetness_2.Bitangent", + "RootNode.rin_eyewetness.rin_eyewetness_2.SkinWeight_", + "RootNode.rin_eyewetness.rin_eyewetness_2.transform", + "RootNode.rin_eyewetness.rin_eyewetness_2.Tangent", + "RootNode.rin_eyewetness.rin_eyewetness_2.map1", + "RootNode.rin_eyewetness.rin_eyewetness_2.rin_m_eyewetness", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_1.Bitangent", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_1.SkinWeight_", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_1.transform", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_1.Tangent", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_1.map1", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_1.rin_m_eyebrow", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_2.Bitangent", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_2.SkinWeight_", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_2.transform", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_2.Tangent", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_2.map1", + "RootNode.rin_eyebrows_lower.rin_eyebrows_lower_2.rin_m_eyebrow", + "RootNode.rin_tearduct.rin_tearduct_1.Bitangent", + "RootNode.rin_tearduct.rin_tearduct_1.SkinWeight_", + "RootNode.rin_tearduct.rin_tearduct_1.transform", + "RootNode.rin_tearduct.rin_tearduct_1.Tangent", + "RootNode.rin_tearduct.rin_tearduct_1.map1", + "RootNode.rin_tearduct.rin_tearduct_1.rin_m_tearduct", + "RootNode.rin_tearduct.rin_tearduct_2.Bitangent", + "RootNode.rin_tearduct.rin_tearduct_2.SkinWeight_", + "RootNode.rin_tearduct.rin_tearduct_2.transform", + "RootNode.rin_tearduct.rin_tearduct_2.Tangent", + "RootNode.rin_tearduct.rin_tearduct_2.map1", + "RootNode.rin_tearduct.rin_tearduct_2.rin_m_tearduct", + "RootNode.root.C_pelvis_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT", + "RootNode.root.C_pelvis_JNT.L_leg_JNT", + "RootNode.root.C_pelvis_JNT.R_leg_JNT", + "RootNode.root.C_pelvis_JNT.L_ribbon_01_JNT", + "RootNode.root.C_pelvis_JNT.R_ribbon_01_JNT", + "RootNode.root.C_pelvis_JNT.C_legArmor_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_sword_JNT", + "RootNode.root.C_pelvis_JNT.L_leg_JNT.transform", + "RootNode.root.C_pelvis_JNT.L_leg_JNT.L_knee_JNT", + "RootNode.root.C_pelvis_JNT.L_leg_JNT.L_leg_twist_JNT", + "RootNode.root.C_pelvis_JNT.R_leg_JNT.transform", + "RootNode.root.C_pelvis_JNT.R_leg_JNT.R_knee_JNT", + "RootNode.root.C_pelvis_JNT.R_leg_JNT.R_leg_twist_JNT", + "RootNode.root.C_pelvis_JNT.L_ribbon_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.L_ribbon_01_JNT.L_ribbon_02_JNT", + "RootNode.root.C_pelvis_JNT.R_ribbon_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.R_ribbon_01_JNT.R_ribbon_02_JNT", + "RootNode.root.C_pelvis_JNT.C_legArmor_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_sword_JNT.transform", + "RootNode.root.C_pelvis_JNT.L_leg_JNT.L_knee_JNT.transform", + "RootNode.root.C_pelvis_JNT.L_leg_JNT.L_knee_JNT.L_foot_JNT", + "RootNode.root.C_pelvis_JNT.L_leg_JNT.L_knee_JNT.L_knee_twist_JNT", + "RootNode.root.C_pelvis_JNT.L_leg_JNT.L_leg_twist_JNT.transform", + "RootNode.root.C_pelvis_JNT.R_leg_JNT.R_knee_JNT.transform", + "RootNode.root.C_pelvis_JNT.R_leg_JNT.R_knee_JNT.R_foot_JNT", + "RootNode.root.C_pelvis_JNT.R_leg_JNT.R_knee_JNT.R_knee_twist_JNT", + "RootNode.root.C_pelvis_JNT.R_leg_JNT.R_leg_twist_JNT.transform", + "RootNode.root.C_pelvis_JNT.L_ribbon_01_JNT.L_ribbon_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.R_ribbon_01_JNT.R_ribbon_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT", + "RootNode.root.C_pelvis_JNT.L_leg_JNT.L_knee_JNT.L_foot_JNT.transform", + "RootNode.root.C_pelvis_JNT.L_leg_JNT.L_knee_JNT.L_foot_JNT.L_toe_JNT", + "RootNode.root.C_pelvis_JNT.L_leg_JNT.L_knee_JNT.L_knee_twist_JNT.transform", + "RootNode.root.C_pelvis_JNT.R_leg_JNT.R_knee_JNT.R_foot_JNT.transform", + "RootNode.root.C_pelvis_JNT.R_leg_JNT.R_knee_JNT.R_foot_JNT.R_toe_JNT", + "RootNode.root.C_pelvis_JNT.R_leg_JNT.R_knee_JNT.R_knee_twist_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_hood_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neckCollar_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_neckCollar_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_neckCollar_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_neckCollar_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_neckCollar_02_JNT", + "RootNode.root.C_pelvis_JNT.L_leg_JNT.L_knee_JNT.L_foot_JNT.L_toe_JNT.transform", + "RootNode.root.C_pelvis_JNT.R_leg_JNT.R_knee_JNT.R_foot_JNT.R_toe_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_tassle_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_tassleLoop_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_armPit_corr_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_tassle_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_tassleLoop_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_armPit_corr_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_hood_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_hood_01_JNT.C_hood_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neckCollar_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_neckCollar_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_neckCollar_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_neckCollar_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_neckCollar_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_arm_twist_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_armBulge_corr_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_tassle_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_tassle_01_JNT.L_tassle_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_tassleLoop_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_tassleLoop_01_JNT.L_tassleLoop_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_armPit_corr_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_throat_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_arm_twist_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_armBulge_corr_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_tassle_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_tassle_01_JNT.R_tassle_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_tassleLoop_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_tassleLoop_01_JNT.R_tassleLoop_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_armPit_corr_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_hood_01_JNT.C_hood_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_hood_01_JNT.C_hood_02_JNT.C_hood_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_elbow_twist_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_arm_twist_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_armBulge_corr_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_tassle_01_JNT.L_tassle_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_tassle_01_JNT.L_tassle_02_JNT.L_tassle_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_tassleLoop_01_JNT.L_tassleLoop_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_brow_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_brow_mid_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_brow_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_corrugator_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_noseBridge_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_nostril_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_nostril_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_cheekUpper_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_squint_mid_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_squint_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_squint_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_zygomatic_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_cheekBone_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_ear_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_nose_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_chin_below_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_chin_below_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eyelid_lower_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eyelid_upper_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eyelid_levator_mid_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eye_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_lipLevator_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_lipUpper_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_lipUpper_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_lipLevator_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_lipLevator_corner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_cheekUpper_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_cheekUpper_mid_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_eyelid_upper_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_eyelid_lower_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_nostril_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_lipLevator_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_cheekBone_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_zygomatic_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_lipUpper_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_chin_below_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_ear_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_nose_slide_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_eye_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_brow_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_brow_mid_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_brow_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_lipLevator_corner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_cheekUpper_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_cheekUpper_mid_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_nostril_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_cheekUpper_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_squint_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_squint_mid_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_squint_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_lipUpper_corner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_lipUpper_corner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_frontalis_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_frontalis_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_frontalis_outer_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_frontalis_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_eyelid_fold_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eyelid_fold_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eye_bulge_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_eye_bulge_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_throat_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_elbow_twist_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_arm_twist_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_armBulge_corr_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_tassle_01_JNT.R_tassle_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_tassle_01_JNT.R_tassle_02_JNT.R_tassle_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_tassleLoop_01_JNT.R_tassleLoop_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_hood_01_JNT.C_hood_02_JNT.C_hood_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_thumb_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_index_root_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_middle_root_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_ring_root_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_pinky_root_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_elbow_twist_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_tassle_01_JNT.L_tassle_02_JNT.L_tassle_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_brow_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_brow_mid_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_brow_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_corrugator_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_noseBridge_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_nostril_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_nostril_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_cheekUpper_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_squint_mid_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_squint_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_squint_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_zygomatic_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_cheekBone_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_ear_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_nose_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_chin_below_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_chin_below_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eyelid_lower_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eyelid_upper_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eyelid_levator_mid_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eye_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_lipLevator_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_lipUpper_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_lipUpper_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_lipLevator_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_lipLevator_corner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_mentalis_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_depressor_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_depressor_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_lip_nasolabial_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_mouth_corner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_lip_below_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_lip_nasolabial_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_lipLower_corner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_lipLower_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_lipLower_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_lipLower_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_mouth_corner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_lipLower_corner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_jaw_clench_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_jaw_clench_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_zygomatic_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_zygomatic_inner_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_tongue_root_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_cheekUpper_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_cheekUpper_mid_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_eyelid_upper_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_eyelid_lower_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_nostril_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_lipLevator_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_cheekBone_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_zygomatic_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_lipUpper_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_chin_below_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_ear_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_nose_slide_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_eye_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_brow_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_brow_mid_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_brow_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_lipLevator_corner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_cheekUpper_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_cheekUpper_mid_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_nostril_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_cheekUpper_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_squint_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_squint_mid_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_squint_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_lipUpper_corner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_lipUpper_corner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_frontalis_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_frontalis_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_frontalis_outer_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_frontalis_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_eyelid_fold_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eyelid_fold_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.L_eye_bulge_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.R_eye_bulge_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_thumb_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_index_root_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_middle_root_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_ring_root_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_pinky_root_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_elbow_twist_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_tassle_01_JNT.R_tassle_02_JNT.R_tassle_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_thumb_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_thumb_01_JNT.L_thumb_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_index_root_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_index_root_JNT.L_index_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_middle_root_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_middle_root_JNT.L_middle_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_ring_root_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_ring_root_JNT.L_ring_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_pinky_root_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_pinky_root_JNT.L_pinky_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_mentalis_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_depressor_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_depressor_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_lip_nasolabial_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_mouth_corner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_lip_below_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_lip_nasolabial_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_lipLower_corner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_lipLower_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_lipLower_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_lipLower_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_mouth_corner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_lipLower_corner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_jaw_clench_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_jaw_clench_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.L_zygomatic_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.R_zygomatic_inner_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_tongue_root_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_tongue_root_JNT.C_tongue_mid_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_thumb_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_thumb_01_JNT.R_thumb_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_index_root_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_index_root_JNT.R_index_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_middle_root_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_middle_root_JNT.R_middle_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_ring_root_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_ring_root_JNT.R_ring_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_pinky_root_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_pinky_root_JNT.R_pinky_01_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_thumb_01_JNT.L_thumb_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_thumb_01_JNT.L_thumb_02_JNT.L_thumb_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_index_root_JNT.L_index_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_index_root_JNT.L_index_01_JNT.L_index_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_middle_root_JNT.L_middle_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_middle_root_JNT.L_middle_01_JNT.L_middle_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_ring_root_JNT.L_ring_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_ring_root_JNT.L_ring_01_JNT.L_ring_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_pinky_root_JNT.L_pinky_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_pinky_root_JNT.L_pinky_01_JNT.L_pinky_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_tongue_root_JNT.C_tongue_mid_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_tongue_root_JNT.C_tongue_mid_JNT.C_tongue_tip_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_thumb_01_JNT.R_thumb_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_thumb_01_JNT.R_thumb_02_JNT.R_thumb_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_index_root_JNT.R_index_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_index_root_JNT.R_index_01_JNT.R_index_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_middle_root_JNT.R_middle_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_middle_root_JNT.R_middle_01_JNT.R_middle_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_ring_root_JNT.R_ring_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_ring_root_JNT.R_ring_01_JNT.R_ring_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_pinky_root_JNT.R_pinky_01_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_pinky_root_JNT.R_pinky_01_JNT.R_pinky_02_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_thumb_01_JNT.L_thumb_02_JNT.L_thumb_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_index_root_JNT.L_index_01_JNT.L_index_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_index_root_JNT.L_index_01_JNT.L_index_02_JNT.L_index_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_middle_root_JNT.L_middle_01_JNT.L_middle_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_middle_root_JNT.L_middle_01_JNT.L_middle_02_JNT.L_middle_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_ring_root_JNT.L_ring_01_JNT.L_ring_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_ring_root_JNT.L_ring_01_JNT.L_ring_02_JNT.L_ring_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_pinky_root_JNT.L_pinky_01_JNT.L_pinky_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_pinky_root_JNT.L_pinky_01_JNT.L_pinky_02_JNT.L_pinky_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.C_neck_01_JNT.C_neck_02_JNT.C_head_JNT.C_jaw_JNT.C_tongue_root_JNT.C_tongue_mid_JNT.C_tongue_tip_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_thumb_01_JNT.R_thumb_02_JNT.R_thumb_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_index_root_JNT.R_index_01_JNT.R_index_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_index_root_JNT.R_index_01_JNT.R_index_02_JNT.R_index_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_middle_root_JNT.R_middle_01_JNT.R_middle_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_middle_root_JNT.R_middle_01_JNT.R_middle_02_JNT.R_middle_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_ring_root_JNT.R_ring_01_JNT.R_ring_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_ring_root_JNT.R_ring_01_JNT.R_ring_02_JNT.R_ring_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_pinky_root_JNT.R_pinky_01_JNT.R_pinky_02_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_pinky_root_JNT.R_pinky_01_JNT.R_pinky_02_JNT.R_pinky_03_JNT", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_index_root_JNT.L_index_01_JNT.L_index_02_JNT.L_index_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_middle_root_JNT.L_middle_01_JNT.L_middle_02_JNT.L_middle_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_ring_root_JNT.L_ring_01_JNT.L_ring_02_JNT.L_ring_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.L_clavicle_JNT.L_arm_JNT.L_elbow_JNT.L_wrist_JNT.L_pinky_root_JNT.L_pinky_01_JNT.L_pinky_02_JNT.L_pinky_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_index_root_JNT.R_index_01_JNT.R_index_02_JNT.R_index_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_middle_root_JNT.R_middle_01_JNT.R_middle_02_JNT.R_middle_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_ring_root_JNT.R_ring_01_JNT.R_ring_02_JNT.R_ring_03_JNT.transform", + "RootNode.root.C_pelvis_JNT.C_spine_01_JNT.C_spine_02_JNT.C_spine_03_JNT.C_spine_04_JNT.R_clavicle_JNT.R_arm_JNT.R_elbow_JNT.R_wrist_JNT.R_pinky_root_JNT.R_pinky_01_JNT.R_pinky_02_JNT.R_pinky_03_JNT.transform" + ] + }, + "rules": { + "rules": [ + { + "$type": "SkinRule" + }, + { + "$type": "StaticMeshAdvancedRule", + "vertexColorStreamName": "Col0" + }, + { + "$type": "MaterialRule" + } + ] + }, + "id": "{CA754822-3673-46C8-9EE7-3453CF782C5A}" + } + ] +} \ No newline at end of file diff --git a/Gems/MotionMatching/Assets/MotionMatching.animgraph b/Gems/MotionMatching/Assets/MotionMatching.animgraph new file mode 100644 index 0000000000..10707eb6c4 --- /dev/null +++ b/Gems/MotionMatching/Assets/MotionMatching.animgraph @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09cc2374b99c219421812ec39b68a350912d44777a50e3078c8cd67e91cc74cf +size 30927 diff --git a/Gems/MotionMatching/Assets/MotionMatching.emfxworkspace b/Gems/MotionMatching/Assets/MotionMatching.emfxworkspace new file mode 100644 index 0000000000..6d59fa310d --- /dev/null +++ b/Gems/MotionMatching/Assets/MotionMatching.emfxworkspace @@ -0,0 +1,3 @@ +[General] +version=1 +startScript="ImportActor -filename \"Character/RinMM.actor\"\nCreateActorInstance -actorID %LASTRESULT% -xPos 4.585605 -yPos -7.166286 -zPos 0.000000 -xScale 1.000000 -yScale 1.000000 -zScale 1.000000 -rot 0.00000000,0.00000000,0.98711985,-0.15998250\nLoadMotionSet -filename \"@products@/MotionMatching.motionset\"\nLoadAnimGraph -filename \"@products@/MotionMatching.animgraph\"\nActivateAnimGraph -actorInstanceID %LASTRESULT3% -animGraphID %LASTRESULT1% -motionSetID %LASTRESULT2% -visualizeScale 1.000000\n" diff --git a/Gems/MotionMatching/Assets/MotionMatching.motionset b/Gems/MotionMatching/Assets/MotionMatching.motionset new file mode 100644 index 0000000000..276895e3a0 --- /dev/null +++ b/Gems/MotionMatching/Assets/MotionMatching.motionset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c895a1696f5f37492089ceaef11210d704c1fb7e3dd31305cf4732d63489507 +size 13645 diff --git a/Gems/MotionMatching/CMakeLists.txt b/Gems/MotionMatching/CMakeLists.txt new file mode 100644 index 0000000000..341df6e33d --- /dev/null +++ b/Gems/MotionMatching/CMakeLists.txt @@ -0,0 +1,16 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(o3de_gem_path ${CMAKE_CURRENT_LIST_DIR}) +set(o3de_gem_json ${o3de_gem_path}/gem.json) +o3de_read_json_key(o3de_gem_name ${o3de_gem_json} "gem_name") +o3de_restricted_path(${o3de_gem_json} o3de_gem_restricted_path) + +ly_add_external_target_path(${CMAKE_CURRENT_LIST_DIR}/3rdParty) + +add_subdirectory(Code) diff --git a/Gems/MotionMatching/Code/CMakeLists.txt b/Gems/MotionMatching/Code/CMakeLists.txt new file mode 100644 index 0000000000..275f8f2530 --- /dev/null +++ b/Gems/MotionMatching/Code/CMakeLists.txt @@ -0,0 +1,155 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +# Add the MotionMatching.Static target +ly_add_target( + NAME MotionMatching.Static STATIC + NAMESPACE Gem + FILES_CMAKE + motionmatching_files.cmake + INCLUDE_DIRECTORIES + PUBLIC + Include + PRIVATE + Source + BUILD_DEPENDENCIES + PUBLIC + AZ::AzCore + AZ::AzFramework + Gem::EMotionFXStaticLib + Gem::ImguiAtom.Static +) + +# Here add MotionMatching target, it depends on the MotionMatching.Static +ly_add_target( + NAME MotionMatching ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE} + NAMESPACE Gem + FILES_CMAKE + motionmatching_shared_files.cmake + INCLUDE_DIRECTORIES + PUBLIC + Include + PRIVATE + Source + BUILD_DEPENDENCIES + PRIVATE + Gem::MotionMatching.Static + Gem::ImGui.Static + Gem::ImGui.ImGuiLYUtils +) + +# By default, we will specify that the above target MotionMatching would be used by +# Client and Server type targets when this gem is enabled. If you don't want it +# active in Clients or Servers by default, delete one of both of the following lines: +ly_create_alias(NAME MotionMatching.Clients NAMESPACE Gem TARGETS Gem::MotionMatching) +ly_create_alias(NAME MotionMatching.Servers NAMESPACE Gem TARGETS Gem::MotionMatching) + +# If we are on a host platform, we want to add the host tools targets like the MotionMatching.Editor target which +# will also depend on MotionMatching.Static +if(PAL_TRAIT_BUILD_HOST_TOOLS) + ly_add_target( + NAME MotionMatching.Editor.Static STATIC + NAMESPACE Gem + FILES_CMAKE + motionmatching_editor_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Source + PUBLIC + Include + BUILD_DEPENDENCIES + PUBLIC + AZ::AzToolsFramework + Gem::MotionMatching.Static + ) + + ly_add_target( + NAME MotionMatching.Editor GEM_MODULE + NAMESPACE Gem + AUTOMOC + OUTPUT_NAME Gem.MotionMatching.Editor + FILES_CMAKE + motionmatching_editor_shared_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Source + PUBLIC + Include + BUILD_DEPENDENCIES + PUBLIC + Gem::MotionMatching.Editor.Static + ) + + # By default, we will specify that the above target MotionMatching would be used by + # Tool and Builder type targets when this gem is enabled. If you don't want it + # active in Tools or Builders by default, delete one of both of the following lines: + ly_create_alias(NAME MotionMatching.Tools NAMESPACE Gem TARGETS Gem::MotionMatching.Editor) + ly_create_alias(NAME MotionMatching.Builders NAMESPACE Gem TARGETS Gem::MotionMatching.Editor) + + +endif() + +################################################################################ +# Tests +################################################################################ +# See if globally, tests are supported +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) + # We globally support tests, see if we support tests on this platform for MotionMatching.Static + if(PAL_TRAIT_MOTIONMATCHING_TEST_SUPPORTED) + # We support MotionMatching.Tests on this platform, add MotionMatching.Tests target which depends on MotionMatching.Static + ly_add_target( + NAME MotionMatching.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} + NAMESPACE Gem + FILES_CMAKE + motionmatching_files.cmake + motionmatching_tests_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Tests + Source + BUILD_DEPENDENCIES + PRIVATE + AZ::AzTest + AZ::AzFramework + Gem::EMotionFX.Tests.Static + Gem::MotionMatching.Static + ) + + # Add MotionMatching.Tests to googletest + ly_add_googletest( + NAME Gem::MotionMatching.Tests + ) + endif() + + # If we are a host platform we want to add tools test like editor tests here + if(PAL_TRAIT_BUILD_HOST_TOOLS) + # We are a host platform, see if Editor tests are supported on this platform + if(PAL_TRAIT_MOTIONMATCHING_EDITOR_TEST_SUPPORTED) + # We support MotionMatching.Editor.Tests on this platform, add MotionMatching.Editor.Tests target which depends on MotionMatching.Editor + ly_add_target( + NAME MotionMatching.Editor.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} + NAMESPACE Gem + FILES_CMAKE + motionmatching_editor_tests_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Tests + Source + BUILD_DEPENDENCIES + PRIVATE + AZ::AzTest + Gem::MotionMatching.Editor + ) + + # Add MotionMatching.Editor.Tests to googletest + ly_add_googletest( + NAME Gem::MotionMatching.Editor.Tests + ) + endif() + endif() +endif() diff --git a/Gems/MotionMatching/Code/Include/MotionMatching/MotionMatchingBus.h b/Gems/MotionMatching/Code/Include/MotionMatching/MotionMatchingBus.h new file mode 100644 index 0000000000..5b2bc1847a --- /dev/null +++ b/Gems/MotionMatching/Code/Include/MotionMatching/MotionMatchingBus.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +namespace EMotionFX::MotionMatching +{ + class MotionMatchingRequests + { + public: + AZ_RTTI(MotionMatchingRequests, "{b08f73cc-a922-49ef-8c0e-07166b43ea65}"); + virtual ~MotionMatchingRequests() = default; + // Put your public methods here + }; + + class MotionMatchingBusTraits + : public AZ::EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + ////////////////////////////////////////////////////////////////////////// + }; + + using MotionMatchingRequestBus = AZ::EBus; + using MotionMatchingInterface = AZ::Interface; + +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/Allocators.h b/Gems/MotionMatching/Code/Source/Allocators.h new file mode 100644 index 0000000000..af6fa27cc8 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/Allocators.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +namespace EMotionFX::MotionMatching +{ + using MotionMatchAllocator = AZ::SystemAllocator; +} // namespace MotionMatching diff --git a/Gems/MotionMatching/Code/Source/BlendTreeMotionMatchNode.cpp b/Gems/MotionMatching/Code/Source/BlendTreeMotionMatchNode.cpp new file mode 100644 index 0000000000..58cd3903e1 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/BlendTreeMotionMatchNode.cpp @@ -0,0 +1,373 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace EMotionFX::MotionMatching +{ + AZ_CLASS_ALLOCATOR_IMPL(BlendTreeMotionMatchNode, AnimGraphAllocator, 0) + AZ_CLASS_ALLOCATOR_IMPL(BlendTreeMotionMatchNode::UniqueData, AnimGraphObjectUniqueDataAllocator, 0) + + BlendTreeMotionMatchNode::BlendTreeMotionMatchNode() + : AnimGraphNode() + { + // Setup the input ports. + InitInputPorts(2); + SetupInputPort("Goal Pos", INPUTPORT_TARGETPOS, MCore::AttributeVector3::TYPE_ID, PORTID_INPUT_TARGETPOS); + SetupInputPort("Goal Facing Dir", INPUTPORT_TARGETFACINGDIR, MCore::AttributeVector3::TYPE_ID, PORTID_INPUT_TARGETFACINGDIR); + + // Setup the output ports. + InitOutputPorts(1); + SetupOutputPortAsPose("Output Pose", OUTPUTPORT_POSE, PORTID_OUTPUT_POSE); + } + + BlendTreeMotionMatchNode::~BlendTreeMotionMatchNode() + { + } + + bool BlendTreeMotionMatchNode::InitAfterLoading(AnimGraph* animGraph) + { + if (!AnimGraphNode::InitAfterLoading(animGraph)) + { + return false; + } + + // Automatically register the default feature schema in case the schema is empty after loading the node. + if (m_featureSchema.GetNumFeatures() == 0) + { + AZStd::string rootJointName; + if (m_animGraph->GetNumAnimGraphInstances() > 0) + { + const Actor* actor = m_animGraph->GetAnimGraphInstance(0)->GetActorInstance()->GetActor(); + const Node* rootJoint = actor->GetMotionExtractionNode(); + if (rootJoint) + { + rootJointName = rootJoint->GetNameString(); + } + } + + DefaultFeatureSchemaInitSettings defaultSettings; + defaultSettings.m_rootJointName = rootJointName.c_str(); + defaultSettings.m_leftFootJointName = "L_foot_JNT"; + defaultSettings.m_rightFootJointName = "R_foot_JNT"; + defaultSettings.m_pelvisJointName = "C_pelvis_JNT"; + DefaultFeatureSchema(m_featureSchema, defaultSettings); + } + + InitInternalAttributesForAllInstances(); + + Reinit(); + return true; + } + + const char* BlendTreeMotionMatchNode::GetPaletteName() const + { + return "Motion Matching"; + } + + AnimGraphObject::ECategory BlendTreeMotionMatchNode::GetPaletteCategory() const + { + return AnimGraphObject::CATEGORY_SOURCES; + } + + void BlendTreeMotionMatchNode::UniqueData::Update() + { + AZ_PROFILE_SCOPE(Animation, "BlendTreeMotionMatchNode::UniqueData::Update"); + + auto animGraphNode = azdynamic_cast(m_object); + AZ_Assert(animGraphNode, "Unique data linked to incorrect node type."); + + ActorInstance* actorInstance = m_animGraphInstance->GetActorInstance(); + + // Clear existing data. + delete m_instance; + delete m_data; + + m_data = aznew MotionMatching::MotionMatchingData(animGraphNode->m_featureSchema); + m_instance = aznew MotionMatching::MotionMatchingInstance(); + + MotionSet* motionSet = m_animGraphInstance->GetMotionSet(); + if (!motionSet) + { + SetHasError(true); + return; + } + + //--------------------------------- + AZ::Debug::Timer timer; + timer.Stamp(); + + // Build a list of motions we want to import the frames from. + AZ_Printf("Motion Matching", "Importing motion database..."); + MotionMatching::MotionMatchingData::InitSettings settings; + settings.m_actorInstance = actorInstance; + settings.m_frameImportSettings.m_sampleRate = animGraphNode->m_sampleRate; + settings.m_importMirrored = animGraphNode->m_mirror; + settings.m_maxKdTreeDepth = animGraphNode->m_maxKdTreeDepth; + settings.m_minFramesPerKdTreeNode = animGraphNode->m_minFramesPerKdTreeNode; + settings.m_motionList.reserve(animGraphNode->m_motionIds.size()); + for (const AZStd::string& id : animGraphNode->m_motionIds) + { + Motion* motion = motionSet->RecursiveFindMotionById(id); + if (motion) + { + settings.m_motionList.emplace_back(motion); + } + else + { + AZ_Warning("Motion Matching", false, "Failed to get motion for motionset entry id '%s'", id.c_str()); + } + } + + // Initialize the motion matching data (slow). + AZ_Printf("Motion Matching", "Initializing motion matching..."); + if (!m_data->Init(settings)) + { + AZ_Warning("Motion Matching", false, "Failed to initialize motion matching for anim graph node '%s'!", animGraphNode->GetName()); + SetHasError(true); + return; + } + + // Initialize the instance. + AZ_Printf("Motion Matching", "Initializing instance..."); + MotionMatching::MotionMatchingInstance::InitSettings initSettings; + initSettings.m_actorInstance = actorInstance; + initSettings.m_data = m_data; + m_instance->Init(initSettings); + + const float initTime = timer.GetDeltaTimeInSeconds(); + const size_t memUsage = m_data->GetFrameDatabase().CalcMemoryUsageInBytes(); + AZ_Printf("Motion Matching", "Finished in %.2f seconds (mem usage=%d bytes or %.2f mb)", initTime, memUsage, memUsage / (float)(1024 * 1024)); + //--------------------------------- + + SetHasError(false); + } + + void BlendTreeMotionMatchNode::Update(AnimGraphInstance* animGraphInstance, float timePassedInSeconds) + { + AZ_PROFILE_SCOPE(Animation, "BlendTreeMotionMatchNode::Update"); + + m_timer.Stamp(); + + UniqueData* uniqueData = static_cast(FindOrCreateUniqueNodeData(animGraphInstance)); + UpdateAllIncomingNodes(animGraphInstance, timePassedInSeconds); + uniqueData->Clear(); + if (uniqueData->GetHasError()) + { + m_updateTimeInMs = 0.0f; + m_postUpdateTimeInMs = 0.0f; + m_outputTimeInMs = 0.0f; + return; + } + + AZ::Vector3 targetPos = AZ::Vector3::CreateZero(); + TryGetInputVector3(animGraphInstance, INPUTPORT_TARGETPOS, targetPos); + + AZ::Vector3 targetFacingDir = AZ::Vector3::CreateAxisY(); + TryGetInputVector3(animGraphInstance, INPUTPORT_TARGETFACINGDIR, targetFacingDir); + + MotionMatching::MotionMatchingInstance* instance = uniqueData->m_instance; + instance->Update(timePassedInSeconds, targetPos, targetFacingDir, m_trajectoryQueryMode, m_pathRadius, m_pathSpeed); + + // set the current time to the new calculated time + uniqueData->ClearInheritFlags(); + uniqueData->SetPreSyncTime(instance->GetMotionInstance()->GetCurrentTime()); + uniqueData->SetCurrentPlayTime(instance->GetNewMotionTime()); + + if (uniqueData->GetPreSyncTime() > uniqueData->GetCurrentPlayTime()) + { + uniqueData->SetPreSyncTime(uniqueData->GetCurrentPlayTime()); + } + + m_updateTimeInMs = m_timer.GetDeltaTimeInSeconds() * 1000.0f; + } + + void BlendTreeMotionMatchNode::PostUpdate(AnimGraphInstance* animGraphInstance, float timePassedInSeconds) + { + AZ_PROFILE_SCOPE(Animation, "BlendTreeMotionMatchNode::PostUpdate"); + + AZ_UNUSED(animGraphInstance); + AZ_UNUSED(timePassedInSeconds); + m_timer.Stamp(); + + for (AZ::u32 i = 0; i < GetNumConnections(); ++i) + { + AnimGraphNode* node = GetConnection(i)->GetSourceNode(); + node->PerformPostUpdate(animGraphInstance, timePassedInSeconds); + } + + UniqueData* uniqueData = static_cast(FindOrCreateUniqueNodeData(animGraphInstance)); + MotionMatching::MotionMatchingInstance* instance = uniqueData->m_instance; + + RequestRefDatas(animGraphInstance); + AnimGraphRefCountedData* data = uniqueData->GetRefCountedData(); + data->ClearEventBuffer(); + data->ZeroTrajectoryDelta(); + + if (uniqueData->GetHasError()) + { + return; + } + + MotionInstance* motionInstance = instance->GetMotionInstance(); + motionInstance->UpdateByTimeValues(uniqueData->GetPreSyncTime(), uniqueData->GetCurrentPlayTime(), &data->GetEventBuffer()); + + uniqueData->SetCurrentPlayTime(motionInstance->GetCurrentTime()); + data->GetEventBuffer().UpdateEmitters(this); + + instance->PostUpdate(timePassedInSeconds); + + const Transform& trajectoryDelta = instance->GetMotionExtractionDelta(); + data->SetTrajectoryDelta(trajectoryDelta); + data->SetTrajectoryDeltaMirrored(trajectoryDelta); // TODO: use a real mirrored version here. + + m_postUpdateTimeInMs = m_timer.GetDeltaTimeInSeconds() * 1000.0f; + } + + void BlendTreeMotionMatchNode::Output(AnimGraphInstance* animGraphInstance) + { + AZ_PROFILE_SCOPE(Animation, "BlendTreeMotionMatchNode::Output"); + + AZ_UNUSED(animGraphInstance); + m_timer.Stamp(); + + AnimGraphPose* outputPose; + + // Initialize to bind pose. + ActorInstance* actorInstance = animGraphInstance->GetActorInstance(); + RequestPoses(animGraphInstance); + outputPose = GetOutputPose(animGraphInstance, OUTPUTPORT_POSE)->GetValue(); + outputPose->InitFromBindPose(actorInstance); + + if (m_disabled) + { + return; + } + + UniqueData* uniqueData = static_cast(FindOrCreateUniqueNodeData(animGraphInstance)); + if (GetEMotionFX().GetIsInEditorMode()) + { + SetHasError(uniqueData, uniqueData->GetHasError()); + } + + if (uniqueData->GetHasError()) + { + return; + } + + OutputIncomingNode(animGraphInstance, GetInputNode(INPUTPORT_TARGETPOS)); + OutputIncomingNode(animGraphInstance, GetInputNode(INPUTPORT_TARGETFACINGDIR)); + + MotionMatching::MotionMatchingInstance* instance = uniqueData->m_instance; + instance->SetLowestCostSearchFrequency(m_lowestCostSearchFrequency); + + Pose& outTransformPose = outputPose->GetPose(); + instance->Output(outTransformPose); + + // Performance metrics + m_outputTimeInMs = m_timer.GetDeltaTimeInSeconds() * 1000.0f; + { + //AZ_Printf("MotionMatch", "Update = %.2f, PostUpdate = %.2f, Output = %.2f", m_updateTime, m_postUpdateTime, m_outputTime); +#ifdef IMGUI_ENABLED + ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::PushPerformanceHistogramValue, "Update", m_updateTimeInMs); + ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::PushPerformanceHistogramValue, "Post Update", m_postUpdateTimeInMs); + ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::PushPerformanceHistogramValue, "Output", m_outputTimeInMs); +#endif + } + + instance->DebugDraw(); + } + + void BlendTreeMotionMatchNode::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (!serializeContext) + { + return; + } + + serializeContext->Class() + ->Version(9) + ->Field("sampleRate", &BlendTreeMotionMatchNode::m_sampleRate) + ->Field("lowestCostSearchFrequency", &BlendTreeMotionMatchNode::m_lowestCostSearchFrequency) + ->Field("maxKdTreeDepth", &BlendTreeMotionMatchNode::m_maxKdTreeDepth) + ->Field("minFramesPerKdTreeNode", &BlendTreeMotionMatchNode::m_minFramesPerKdTreeNode) + ->Field("mirror", &BlendTreeMotionMatchNode::m_mirror) + ->Field("controlSplineMode", &BlendTreeMotionMatchNode::m_trajectoryQueryMode) + ->Field("pathRadius", &BlendTreeMotionMatchNode::m_pathRadius) + ->Field("pathSpeed", &BlendTreeMotionMatchNode::m_pathSpeed) + ->Field("featureSchema", &BlendTreeMotionMatchNode::m_featureSchema) + ->Field("motionIds", &BlendTreeMotionMatchNode::m_motionIds) + ; + + AZ::EditContext* editContext = serializeContext->GetEditContext(); + if (!editContext) + { + return; + } + + editContext->Class("Motion Matching Node", "Motion Matching Attributes") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::AutoExpand, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->DataElement(AZ::Edit::UIHandlers::Default, &BlendTreeMotionMatchNode::m_sampleRate, "Feature sample rate", "The sample rate (in Hz) used for extracting the features from the animations. The higher the sample rate, the more data will be used and the more options the motion matching search has available for the best matching frame.") + ->Attribute(AZ::Edit::Attributes::Min, 1) + ->Attribute(AZ::Edit::Attributes::Max, 240) + ->Attribute(AZ::Edit::Attributes::ChangeNotify, &BlendTreeMotionMatchNode::Reinit) + ->DataElement(AZ::Edit::UIHandlers::Default, &BlendTreeMotionMatchNode::m_lowestCostSearchFrequency, "Search frequency", "How often per second we apply the motion matching search and find the lowest cost / best matching frame, and start to blend towards it.") + ->Attribute(AZ::Edit::Attributes::Min, 0.001f) + ->Attribute(AZ::Edit::Attributes::Max, std::numeric_limits::max()) + ->Attribute(AZ::Edit::Attributes::Step, 0.05f) + ->DataElement(AZ::Edit::UIHandlers::Default, &BlendTreeMotionMatchNode::m_maxKdTreeDepth, "Max kdTree depth", "The maximum number of hierarchy levels in the kdTree.") + ->Attribute(AZ::Edit::Attributes::Min, 1) + ->Attribute(AZ::Edit::Attributes::Max, 20) + ->Attribute(AZ::Edit::Attributes::ChangeNotify, &BlendTreeMotionMatchNode::Reinit) + ->DataElement(AZ::Edit::UIHandlers::Default, &BlendTreeMotionMatchNode::m_minFramesPerKdTreeNode, "Min kdTree node size", "The minimum number of frames to store per kdTree node.") + ->Attribute(AZ::Edit::Attributes::Min, 1) + ->Attribute(AZ::Edit::Attributes::Max, 100000) + ->Attribute(AZ::Edit::Attributes::ChangeNotify, &BlendTreeMotionMatchNode::Reinit) + ->DataElement(AZ::Edit::UIHandlers::Default, &BlendTreeMotionMatchNode::m_pathRadius, "Path radius", "") + ->Attribute(AZ::Edit::Attributes::Min, 0.0001f) + ->Attribute(AZ::Edit::Attributes::Max, std::numeric_limits::max()) + ->Attribute(AZ::Edit::Attributes::Step, 0.01f) + ->DataElement(AZ::Edit::UIHandlers::Default, &BlendTreeMotionMatchNode::m_pathSpeed, "Path speed", "") + ->Attribute(AZ::Edit::Attributes::Min, 0.0001f) + ->Attribute(AZ::Edit::Attributes::Max, std::numeric_limits::max()) + ->Attribute(AZ::Edit::Attributes::Step, 0.01f) + ->DataElement(AZ::Edit::UIHandlers::ComboBox, &BlendTreeMotionMatchNode::m_trajectoryQueryMode, "Trajectory mode", "Desired future trajectory generation mode.") + ->EnumAttribute(TrajectoryQuery::MODE_TARGETDRIVEN, "Target driven") + ->EnumAttribute(TrajectoryQuery::MODE_ONE, "Mode one") + ->EnumAttribute(TrajectoryQuery::MODE_TWO, "Mode two") + ->EnumAttribute(TrajectoryQuery::MODE_THREE, "Mode three") + ->EnumAttribute(TrajectoryQuery::MODE_FOUR, "Mode four") + ->DataElement(AZ::Edit::UIHandlers::Default, &BlendTreeMotionMatchNode::m_featureSchema, "FeatureSchema", "") + ->Attribute(AZ::Edit::Attributes::AutoExpand, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->Attribute(AZ::Edit::Attributes::ChangeNotify, &BlendTreeMotionMatchNode::Reinit) + ->DataElement(AZ_CRC("MotionSetMotionIds", 0x8695c0fa), &BlendTreeMotionMatchNode::m_motionIds, "Motions", "") + ->Attribute(AZ::Edit::Attributes::ChangeNotify, &BlendTreeMotionMatchNode::Reinit) + ->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, false) + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::HideChildren) + ; + } +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/BlendTreeMotionMatchNode.h b/Gems/MotionMatching/Code/Source/BlendTreeMotionMatchNode.h new file mode 100644 index 0000000000..3acae96d2c --- /dev/null +++ b/Gems/MotionMatching/Code/Source/BlendTreeMotionMatchNode.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace EMotionFX::MotionMatching +{ + class EMFX_API BlendTreeMotionMatchNode + : public AnimGraphNode + { + public: + AZ_RTTI(BlendTreeMotionMatchNode, "{1DC80DCD-6536-4950-9260-A4615C03E3C5}", AnimGraphNode) + AZ_CLASS_ALLOCATOR_DECL + + enum + { + INPUTPORT_TARGETPOS = 0, + INPUTPORT_TARGETFACINGDIR = 1, + OUTPUTPORT_POSE = 0 + }; + + enum + { + PORTID_INPUT_TARGETPOS = 0, + PORTID_INPUT_TARGETFACINGDIR = 1, + PORTID_OUTPUT_POSE = 0 + }; + + class EMFX_API UniqueData + : public AnimGraphNodeData + { + EMFX_ANIMGRAPHOBJECTDATA_IMPLEMENT_LOADSAVE + public: + AZ_CLASS_ALLOCATOR_DECL + + UniqueData(AnimGraphNode* node, AnimGraphInstance* animGraphInstance) + : AnimGraphNodeData(node, animGraphInstance) + { + } + + ~UniqueData() + { + delete m_data; + delete m_instance; + } + + void Update() override; + + public: + MotionMatching::MotionMatchingInstance* m_instance = nullptr; + MotionMatching::MotionMatchingData* m_data = nullptr; + }; + + BlendTreeMotionMatchNode(); + ~BlendTreeMotionMatchNode(); + + bool InitAfterLoading(AnimGraph* animGraph) override; + + bool GetSupportsVisualization() const override { return true; } + bool GetHasOutputPose() const override { return true; } + bool GetSupportsDisable() const override { return true; } + AZ::Color GetVisualColor() const override { return AZ::Colors::Green; } + AnimGraphPose* GetMainOutputPose(AnimGraphInstance* animGraphInstance) const override { return GetOutputPose(animGraphInstance, OUTPUTPORT_POSE)->GetValue(); } + + const char* GetPaletteName() const override; + AnimGraphObject::ECategory GetPaletteCategory() const override; + + AnimGraphObjectData* CreateUniqueData(AnimGraphInstance* animGraphInstance) override { return aznew UniqueData(this, animGraphInstance); } + + static void Reflect(AZ::ReflectContext* context); + + private: + void Output(AnimGraphInstance* animGraphInstance) override; + void Update(AnimGraphInstance* animGraphInstance, float timePassedInSeconds) override; + void PostUpdate(AnimGraphInstance* animGraphInstance, float timePassedInSeconds) override; + + FeatureSchema m_featureSchema; + AZStd::vector m_motionIds; + + float m_pathRadius = 1.0f; + float m_pathSpeed = 1.0f; + float m_lowestCostSearchFrequency = 5.0f; + AZ::u32 m_sampleRate = 30; + AZ::u32 m_maxKdTreeDepth = 15; + AZ::u32 m_minFramesPerKdTreeNode = 1000; + TrajectoryQuery::EMode m_trajectoryQueryMode = TrajectoryQuery::MODE_TARGETDRIVEN; + bool m_mirror = false; + + AZ::Debug::Timer m_timer; + float m_updateTimeInMs = 0.0f; + float m_postUpdateTimeInMs = 0.0f; + float m_outputTimeInMs = 0.0f; + +#ifdef IMGUI_ENABLED + ImGuiMonitor m_imguiMonitor; +#endif + }; +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/EventData.cpp b/Gems/MotionMatching/Code/Source/EventData.cpp new file mode 100644 index 0000000000..32c05ee58b --- /dev/null +++ b/Gems/MotionMatching/Code/Source/EventData.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +#include +#include + +namespace EMotionFX::MotionMatching +{ + AZ_CLASS_ALLOCATOR_IMPL(DiscardFrameEventData, MotionEventAllocator, 0) + + bool DiscardFrameEventData::Equal([[maybe_unused]]const EventData& rhs, [[maybe_unused]] bool ignoreEmptyFields) const + { + return true; + } + + void DiscardFrameEventData::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (!serializeContext) + { + return; + } + + serializeContext->Class() + ->Version(1) + ; + + AZ::EditContext* editContext = serializeContext->GetEditContext(); + if (!editContext) + { + return; + } + + editContext->Class("[Motion Matching] Discard Frame", "Event used for discarding ranges of the animation..") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->Attribute(AZ_CRC_CE("Creatable"), true) + ; + } + + /////////////////////////////////////////////////////////////////////////// + + AZ_CLASS_ALLOCATOR_IMPL(TagEventData, MotionEventAllocator, 0) + + bool TagEventData::Equal(const EventData& rhs, [[maybe_unused]] bool ignoreEmptyFields) const + { + const TagEventData* other = azdynamic_cast(&rhs); + if (other) + { + return AZ::StringFunc::Equal(m_tag.c_str(), other->m_tag.c_str(), /*caseSensitive=*/false); + } + return false; + } + + void TagEventData::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (!serializeContext) + { + return; + } + + serializeContext->Class() + ->Version(1) + ->Field("tag", &TagEventData::m_tag) + ; + + AZ::EditContext* editContext = serializeContext->GetEditContext(); + if (!editContext) + { + return; + } + + editContext->Class("[Motion Matching] Tag", "") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->Attribute(AZ_CRC_CE("Creatable"), true) + ->DataElement(AZ::Edit::UIHandlers::Default, &TagEventData::m_tag, "Tag", "The tag that should be active.") + ; + } +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/EventData.h b/Gems/MotionMatching/Code/Source/EventData.h new file mode 100644 index 0000000000..8b80499b78 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/EventData.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +namespace AZ +{ + class ReflectContext; +} + +namespace EMotionFX::MotionMatching +{ + class EMFX_API DiscardFrameEventData + : public EventData + { + public: + AZ_RTTI(DiscardFrameEventData, "{25499823-E611-4958-85B7-476BC1918744}", EventData); + AZ_CLASS_ALLOCATOR_DECL + + DiscardFrameEventData() = default; + ~DiscardFrameEventData() override = default; + + static void Reflect(AZ::ReflectContext* context); + + bool Equal(const EventData& rhs, bool ignoreEmptyFields = false) const override; + + private: + AZStd::string m_tag; + }; + + class EMFX_API TagEventData + : public EventData + { + public: + AZ_RTTI(TagEventData, "{FEFEA2C7-CD68-43B2-94D6-85559E29EABF}", EventData); + AZ_CLASS_ALLOCATOR_DECL + + TagEventData() = default; + ~TagEventData() override = default; + + static void Reflect(AZ::ReflectContext* context); + + bool Equal(const EventData& rhs, bool ignoreEmptyFields = false) const override; + + private: + AZStd::string m_tag; + }; +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/Feature.cpp b/Gems/MotionMatching/Code/Source/Feature.cpp new file mode 100644 index 0000000000..0d135d6ed8 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/Feature.cpp @@ -0,0 +1,275 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace EMotionFX::MotionMatching +{ + AZ_CLASS_ALLOCATOR_IMPL(Feature, MotionMatchAllocator, 0) + + bool Feature::Init(const InitSettings& settings) + { + const Actor* actor = settings.m_actorInstance->GetActor(); + const Skeleton* skeleton = actor->GetSkeleton(); + + const Node* joint = skeleton->FindNodeByNameNoCase(m_jointName.c_str()); + m_jointIndex = joint ? joint->GetNodeIndex() : InvalidIndex; + if (m_jointIndex == InvalidIndex) + { + AZ_Error("MotionMatching", false, "Feature::Init(): Cannot find index for joint named '%s'.", m_jointName.c_str()); + return false; + } + + const Node* relativeToJoint = skeleton->FindNodeByNameNoCase(m_relativeToJointName.c_str()); + m_relativeToNodeIndex = relativeToJoint ? relativeToJoint->GetNodeIndex() : InvalidIndex; + if (m_relativeToNodeIndex == InvalidIndex) + { + AZ_Error("MotionMatching", false, "Feature::Init(): Cannot find index for joint named '%s'.", m_relativeToJointName.c_str()); + return false; + } + + // Set a default feature name in case it did not get set manually. + if (m_name.empty()) + { + AZStd::string featureTypeName = this->RTTI_GetTypeName(); + AzFramework::StringFunc::Replace(featureTypeName, "Feature", ""); + m_name = AZStd::string::format("%s (%s)", featureTypeName.c_str(), m_jointName.c_str()); + } + return true; + } + + void Feature::SetDebugDrawColor(const AZ::Color& color) + { + m_debugColor = color; + } + + const AZ::Color& Feature::GetDebugDrawColor() const + { + return m_debugColor; + } + + void Feature::SetDebugDrawEnabled(bool enabled) + { + m_debugDrawEnabled = enabled; + } + + bool Feature::GetDebugDrawEnabled() const + { + return m_debugDrawEnabled; + } + + float Feature::CalculateFrameCost([[maybe_unused]] size_t frameIndex, [[maybe_unused]] const FrameCostContext& context) const + { + AZ_Assert(false, "Feature::CalculateFrameCost(): Not implemented for the given feature."); + return 0.0f; + } + + void Feature::SetRelativeToNodeIndex(size_t nodeIndex) + { + m_relativeToNodeIndex = nodeIndex; + } + + void Feature::CalculateVelocity(size_t jointIndex, size_t relativeToJointIndex, MotionInstance* motionInstance, AZ::Vector3& outVelocity) + { + const float originalTime = motionInstance->GetCurrentTime(); + + // Prepare for sampling. + ActorInstance* actorInstance = motionInstance->GetActorInstance(); + AnimGraphPosePool& posePool = GetEMotionFX().GetThreadData(actorInstance->GetThreadIndex())->GetPosePool(); + AnimGraphPose* prevPose = posePool.RequestPose(actorInstance); + AnimGraphPose* currentPose = posePool.RequestPose(actorInstance); + Pose* bindPose = actorInstance->GetTransformData()->GetBindPose(); + + const size_t numSamples = 3; + const float timeRange = 0.05f; // secs + const float halfTimeRange = timeRange * 0.5f; + const float startTime = originalTime - halfTimeRange; + const float frameDelta = timeRange / numSamples; + + AZ::Vector3 accumulatedVelocity = AZ::Vector3::CreateZero(); + + for (size_t sampleIndex = 0; sampleIndex < numSamples + 1; ++sampleIndex) + { + float sampleTime = startTime + sampleIndex * frameDelta; + if (sampleTime < 0.0f) + { + sampleTime = 0.0f; + } + if (sampleTime >= motionInstance->GetMotion()->GetDuration()) + { + sampleTime = motionInstance->GetMotion()->GetDuration(); + } + + if (sampleIndex == 0) + { + motionInstance->SetCurrentTime(sampleTime); + motionInstance->GetMotion()->Update(bindPose, &prevPose->GetPose(), motionInstance); + continue; + } + + motionInstance->SetCurrentTime(sampleTime); + motionInstance->GetMotion()->Update(bindPose, ¤tPose->GetPose(), motionInstance); + + const Transform inverseJointWorldTransform = currentPose->GetPose().GetWorldSpaceTransform(relativeToJointIndex).Inversed(); + + // Calculate the velocity. + const AZ::Vector3 prevPosition = prevPose->GetPose().GetWorldSpaceTransform(jointIndex).m_position; + const AZ::Vector3 currentPosition = currentPose->GetPose().GetWorldSpaceTransform(jointIndex).m_position; + const AZ::Vector3 velocity = CalculateLinearVelocity(prevPosition, currentPosition, frameDelta); + + accumulatedVelocity += inverseJointWorldTransform.TransformVector(velocity); + + *prevPose = *currentPose; + } + + outVelocity = accumulatedVelocity / aznumeric_cast(numSamples); + + motionInstance->SetCurrentTime(originalTime); // set back to what it was + + posePool.FreePose(prevPose); + posePool.FreePose(currentPose); + } + + void Feature::CalculateVelocity(const ActorInstance* actorInstance, size_t jointIndex, size_t relativeToJointIndex, const Frame& frame, AZ::Vector3& outVelocity) + { + AnimGraphPosePool& posePool = GetEMotionFX().GetThreadData(actorInstance->GetThreadIndex())->GetPosePool(); + AnimGraphPose* prevPose = posePool.RequestPose(actorInstance); + AnimGraphPose* currentPose = posePool.RequestPose(actorInstance); + + const size_t numSamples = 3; + const float timeRange = 0.05f; // secs + const float halfTimeRange = timeRange * 0.5f; + const float frameDelta = timeRange / numSamples; + + AZ::Vector3 accumulatedVelocity = AZ::Vector3::CreateZero(); + + for (size_t sampleIndex = 0; sampleIndex < numSamples + 1; ++sampleIndex) + { + const float sampleTimeOffset = (-halfTimeRange) + sampleIndex * frameDelta; + + if (sampleIndex == 0) + { + frame.SamplePose(&prevPose->GetPose(), sampleTimeOffset); + continue; + } + + frame.SamplePose(¤tPose->GetPose(), sampleTimeOffset); + const Transform inverseJointWorldTransform = currentPose->GetPose().GetWorldSpaceTransform(relativeToJointIndex).Inversed(); + + // Calculate the velocity. + const AZ::Vector3 prevPosition = prevPose->GetPose().GetWorldSpaceTransform(jointIndex).m_position; + const AZ::Vector3 currentPosition = currentPose->GetPose().GetWorldSpaceTransform(jointIndex).m_position; + const AZ::Vector3 velocity = CalculateLinearVelocity(prevPosition, currentPosition, frameDelta); + + accumulatedVelocity += inverseJointWorldTransform.TransformVector(velocity); + + *prevPose = *currentPose; + } + + outVelocity = accumulatedVelocity / aznumeric_cast(numSamples); + + posePool.FreePose(prevPose); + posePool.FreePose(currentPose); + } + + float Feature::GetNormalizedDirectionDifference(const AZ::Vector2& directionA, const AZ::Vector2& directionB) const + { + const float dotProduct = directionA.GetNormalized().Dot(directionB.GetNormalized()); + const float normalizedDirectionDifference = (2.0f - (1.0f + dotProduct)) * 0.5f; + return AZ::GetAbs(normalizedDirectionDifference); + } + + float Feature::GetNormalizedDirectionDifference(const AZ::Vector3& directionA, const AZ::Vector3& directionB) const + { + const float dotProduct = directionA.GetNormalized().Dot(directionB.GetNormalized()); + const float normalizedDirectionDifference = (2.0f - (1.0f + dotProduct)) * 0.5f; + return AZ::GetAbs(normalizedDirectionDifference); + } + + float Feature::CalcResidual(float value) const + { + if (m_residualType == ResidualType::Squared) + { + return value * value; + } + + return AZ::Abs(value); + } + + float Feature::CalcResidual(const AZ::Vector3& a, const AZ::Vector3& b) const + { + const float euclideanDistance = (b - a).GetLength(); + return CalcResidual(euclideanDistance); + } + + AZ::Crc32 Feature::GetCostFactorVisibility() const + { + return AZ::Edit::PropertyVisibility::Show; + } + + void Feature::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (!serializeContext) + { + return; + } + + serializeContext->Class() + ->Version(2) + ->Field("id", &Feature::m_id) + ->Field("name", &Feature::m_name) + ->Field("jointName", &Feature::m_jointName) + ->Field("relativeToJointName", &Feature::m_relativeToJointName) + ->Field("debugDraw", &Feature::m_debugDrawEnabled) + ->Field("debugColor", &Feature::m_debugColor) + ->Field("costFactor", &Feature::m_costFactor) + ->Field("residualType", &Feature::m_residualType) + ; + + AZ::EditContext* editContext = serializeContext->GetEditContext(); + if (!editContext) + { + return; + } + + editContext->Class("Feature", "Base class for a feature") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::AutoExpand, "") + ->DataElement(AZ::Edit::UIHandlers::Default, &Feature::m_name, "Name", "Custom name of the feature used for identification and debug visualizations.") + ->DataElement(AZ_CRC_CE("ActorNode"), &Feature::m_jointName, "Joint", "The joint to extract the data from.") + ->DataElement(AZ_CRC_CE("ActorNode"), &Feature::m_relativeToJointName, "Relative To Joint", "When extracting feature data, convert it to relative-space to the given joint.") + ->DataElement(AZ::Edit::UIHandlers::Default, &Feature::m_debugDrawEnabled, "Debug Draw", "Are debug visualizations enabled for this feature?") + ->DataElement(AZ::Edit::UIHandlers::Default, &Feature::m_debugColor, "Debug Draw Color", "Color used for debug visualizations to identify the feature.") + ->DataElement(AZ::Edit::UIHandlers::SpinBox, &Feature::m_costFactor, "Cost Factor", "The cost factor for the feature is multiplied with the actual and can be used to change a feature's influence in the motion matching search.") + ->Attribute(AZ::Edit::Attributes::Min, 0.0f) + ->Attribute(AZ::Edit::Attributes::Max, 100.0f) + ->Attribute(AZ::Edit::Attributes::Step, 0.1f) + ->Attribute(AZ::Edit::Attributes::Visibility, &Feature::GetCostFactorVisibility) + ->DataElement(AZ::Edit::UIHandlers::ComboBox, &Feature::m_residualType, "Residual", "Use 'Squared' in case minimal differences should be ignored and larger differences should overweight others. Use 'Absolute' for linear differences and don't want the mentioned effect.") + ->EnumAttribute(ResidualType::Absolute, "Absolute") + ->EnumAttribute(ResidualType::Squared, "Squared") + ; + } +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/Feature.h b/Gems/MotionMatching/Code/Source/Feature.h new file mode 100644 index 0000000000..9a0fe9fa8c --- /dev/null +++ b/Gems/MotionMatching/Code/Source/Feature.h @@ -0,0 +1,174 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include + +#include + +#include + +namespace EMotionFX +{ + class ActorInstance; + class MotionInstance; + class Pose; + class Motion; +}; + +namespace EMotionFX::MotionMatching +{ + class Frame; + class FrameDatabase; + class MotionMatchingInstance; + class TrajectoryQuery; + + class EMFX_API Feature + { + public: + AZ_RTTI(Feature, "{DE9CBC48-9176-4DF1-8306-4B1E621F0E76}") + AZ_CLASS_ALLOCATOR_DECL + + Feature() = default; + virtual ~Feature() = default; + + //////////////////////////////////////////////////////////////////////// + // Initialization + struct EMFX_API InitSettings + { + ActorInstance* m_actorInstance = nullptr; + FeatureMatrix::Index m_featureColumnStartOffset = 0; + }; + virtual bool Init(const InitSettings& settings); + + //////////////////////////////////////////////////////////////////////// + // Feature extraction + struct EMFX_API ExtractFeatureContext + { + ExtractFeatureContext(FeatureMatrix& featureMatrix) + : m_featureMatrix(featureMatrix) + { + } + + FrameDatabase* m_frameDatabase = nullptr; + FeatureMatrix& m_featureMatrix; + + size_t m_frameIndex = InvalidIndex; + const Pose* m_framePose = nullptr; //! Pre-sampled pose for the given frame. + + ActorInstance* m_actorInstance = nullptr; + }; + virtual void ExtractFeatureValues(const ExtractFeatureContext& context) = 0; + + //////////////////////////////////////////////////////////////////////// + // Feature cost + struct EMFX_API FrameCostContext + { + FrameCostContext(const FeatureMatrix& featureMatrix, const Pose& currentPose) + : m_featureMatrix(featureMatrix) + , m_currentPose(currentPose) + { + } + + const FeatureMatrix& m_featureMatrix; + const ActorInstance* m_actorInstance = nullptr; + const Pose& m_currentPose; //! Current actor instance pose. + const TrajectoryQuery* m_trajectoryQuery; + }; + virtual float CalculateFrameCost(size_t frameIndex, const FrameCostContext& context) const; + + //! Specifies how the feature value differences (residuals), between the input query values + //! and the frames in the motion database that sum up the feature cost, are calculated. + enum ResidualType + { + Absolute, + Squared + }; + + void SetCostFactor(float costFactor) { m_costFactor = costFactor; } + float GetCostFactor() const { return m_costFactor; } + + virtual void FillQueryFeatureValues([[maybe_unused]] size_t startIndex, + [[maybe_unused]] AZStd::vector& queryFeatureValues, + [[maybe_unused]] const FrameCostContext& context) {} + + virtual void DebugDraw([[maybe_unused]] AzFramework::DebugDisplayRequests& debugDisplay, + [[maybe_unused]] MotionMatchingInstance* instance, + [[maybe_unused]] size_t frameIndex) {} + + void SetDebugDrawColor(const AZ::Color& color); + const AZ::Color& GetDebugDrawColor() const; + + void SetDebugDrawEnabled(bool enabled); + bool GetDebugDrawEnabled() const; + + void SetJointName(const AZStd::string& jointName) { m_jointName = jointName; } + const AZStd::string& GetJointName() const { return m_jointName; } + + void SetRelativeToJointName(const AZStd::string& jointName) { m_relativeToJointName = jointName; } + const AZStd::string& GetRelativeToJointName() const { return m_relativeToJointName; } + + void SetName(const AZStd::string& name) { m_name = name; } + const AZStd::string& GetName() const { return m_name; } + + // Column offset for the first value for the given feature inside the feature matrix. + virtual size_t GetNumDimensions() const = 0; + virtual AZStd::string GetDimensionName([[maybe_unused]] size_t index) const { return "Unknown"; } + FeatureMatrix::Index GetColumnOffset() const { return m_featureColumnOffset; } + void SetColumnOffset(FeatureMatrix::Index offset) { m_featureColumnOffset = offset; } + + const AZ::TypeId& GetId() const { return m_id; } + size_t GetRelativeToNodeIndex() const { return m_relativeToNodeIndex; } + void SetRelativeToNodeIndex(size_t nodeIndex); + + static void Reflect(AZ::ReflectContext* context); + static void CalculateVelocity(size_t jointIndex, size_t relativeToJointIndex, MotionInstance* motionInstance, AZ::Vector3& outVelocity); + static void CalculateVelocity(const ActorInstance* actorInstance, size_t jointIndex, size_t relativeToJointIndex, const Frame& frame, AZ::Vector3& outVelocity); + + protected: + /** + * Calculate a normalized direction vector difference between the two given vectors. + * A dot product of the two vectors is taken and the result in range [-1, 1] is scaled to [0, 1]. + * @result Normalized, absolute difference between the vectors. + * Angle difference dot result cost + * 0.0 degrees 1.0 0.0 + * 90.0 degrees 0.0 0.5 + * 180.0 degrees -1.0 1.0 + * 270.0 degrees 0.0 0.5 + **/ + float GetNormalizedDirectionDifference(const AZ::Vector2& directionA, const AZ::Vector2& directionB) const; + float GetNormalizedDirectionDifference(const AZ::Vector3& directionA, const AZ::Vector3& directionB) const; + + float CalcResidual(float value) const; + float CalcResidual(const AZ::Vector3& a, const AZ::Vector3& b) const; + + virtual AZ::Crc32 GetCostFactorVisibility() const; + + // Shared and reflected data. + AZ::TypeId m_id = AZ::TypeId::CreateRandom(); //< The feature identification number. Use this instead of the RTTI class ID so that we can have multiple of the same type. + AZStd::string m_name; //< Display name used for feature identification and debug visualizations. + AZStd::string m_jointName; //< Joint name to extract the data from. + AZStd::string m_relativeToJointName; //< When extracting feature data, convert it to relative-space to the given joint. + AZ::Color m_debugColor = AZ::Colors::Green; //< Color used for debug visualizations to identify the feature. + bool m_debugDrawEnabled = false; //< Are debug visualizations enabled for this feature? + float m_costFactor = 1.0f; //< The cost factor for the feature is multiplied with the actual and can be used to change a feature's influence in the motion matching search. + ResidualType m_residualType = ResidualType::Squared; //< How do we calculate the differences (residuals) between the input query values and the frames in the motion database that sum up the feature cost. + + // Instance data (depends on the feature schema or actor instance). + FeatureMatrix::Index m_featureColumnOffset; //< Float/Value offset, starting column for where the feature should be places at. + size_t m_relativeToNodeIndex = InvalidIndex; + size_t m_jointIndex = InvalidIndex; + }; +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/FeatureMatrix.cpp b/Gems/MotionMatching/Code/Source/FeatureMatrix.cpp new file mode 100644 index 0000000000..7da9e146d9 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/FeatureMatrix.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include + +#include +#include +#include + +namespace EMotionFX::MotionMatching +{ + AZ_CLASS_ALLOCATOR_IMPL(FeatureMatrix, MotionMatchAllocator, 0) + + void FeatureMatrix::Clear() + { + resize(0, 0); + } + + void FeatureMatrix::SaveAsCsv(const AZStd::string& filename, const AZStd::vector& columnNames) + { + std::ofstream file(filename.c_str()); + + // Save column names in the first row + if (!columnNames.empty()) + { + for (size_t i = 0; i < columnNames.size(); ++i) + { + if (i != 0) + { + file << ","; + } + + file << columnNames[i].c_str(); + } + file << "\n"; + } + + // Save coefficients +#ifdef O3DE_USE_EIGEN + // Force specify precision, else wise values close to 0.0 get rounded to 0.0. + const static Eigen::IOFormat csvFormat(/*Eigen::StreamPrecision|FullPrecision*/8, Eigen::DontAlignCols, ", ", "\n"); + file << format(csvFormat); +#endif + } + + void FeatureMatrix::SaveAsCsv(const AZStd::string& filename, const FeatureSchema* featureSchema) + { + AZStd::vector columnNames; + + for (Feature* feature: featureSchema->GetFeatures()) + { + const size_t numDimensions = feature->GetNumDimensions(); + for (size_t dimension = 0; dimension < numDimensions; ++dimension) + { + columnNames.push_back(feature->GetDimensionName(dimension)); + } + } + + SaveAsCsv(filename, columnNames); + } + + AZ::Vector2 FeatureMatrix::GetVector2(Index row, Index startColumn) const + { + return AZ::Vector2( + coeff(row, startColumn + 0), + coeff(row, startColumn + 1)); + } + + void FeatureMatrix::SetVector2(Index row, Index startColumn, const AZ::Vector2& value) + { + operator()(row, startColumn + 0) = value.GetX(); + operator()(row, startColumn + 1) = value.GetY(); + } + + AZ::Vector3 FeatureMatrix::GetVector3(Index row, Index startColumn) const + { + return AZ::Vector3( + coeff(row, startColumn + 0), + coeff(row, startColumn + 1), + coeff(row, startColumn + 2)); + } + + void FeatureMatrix::SetVector3(Index row, Index startColumn, const AZ::Vector3& value) + { + operator()(row, startColumn + 0) = value.GetX(); + operator()(row, startColumn + 1) = value.GetY(); + operator()(row, startColumn + 2) = value.GetZ(); + } + + size_t FeatureMatrix::CalcMemoryUsageInBytes() const + { + const size_t bytesPerValue = sizeof(O3DE_MM_FLOATTYPE); + const size_t numValues = size(); + return numValues * bytesPerValue; + } +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/FeatureMatrix.h b/Gems/MotionMatching/Code/Source/FeatureMatrix.h new file mode 100644 index 0000000000..1cf42933ce --- /dev/null +++ b/Gems/MotionMatching/Code/Source/FeatureMatrix.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +//#define O3DE_USE_EIGEN +#define O3DE_MM_FLOATTYPE float + +#ifdef O3DE_USE_EIGEN +#pragma warning (push, 1) +#pragma warning (disable:4834) // C4834: discarding return value of function with 'nodiscard' attribute +#pragma warning (disable:5031) // #pragma warning(pop): likely mismatch, popping warning state pushed in different file +#pragma warning (disable:4702) // warning C4702: unreachable code +#pragma warning (disable:4723) // warning C4723: potential divide by 0 +#include "../../3rdParty/eigen-3.3.9/Eigen/Dense" +#pragma warning (pop) +#endif + +namespace EMotionFX::MotionMatching +{ + class FeatureSchema; + +#ifdef O3DE_USE_EIGEN + // Features are stored in columns, each row represents a frame + // RowMajor: Store row components next to each other in memory for cache-optimized feature access for a given frame. + using FeatureMatrixType = Eigen::Matrix; +#else + /** + * Small wrapper for a 2D matrix similar to the Eigen::Matrix. + */ + class FeatureMatrixType + { + public: + size_t size() const + { + return m_data.size(); + } + + size_t rows() const + { + return m_rowCount; + } + + size_t cols() const + { + return m_columnCount; + } + + void resize(size_t rowCount, size_t columnCount) + { + m_rowCount = rowCount; + m_columnCount = columnCount; + m_data.resize(m_rowCount * m_columnCount); + } + + float& operator()(size_t row, size_t column) + { + return m_data[row * m_columnCount + column]; + } + + const float& operator()(size_t row, size_t column) const + { + return m_data[row * m_columnCount + column]; + } + + float coeff(size_t row, size_t column) const + { + return m_data[row * m_columnCount + column]; + } + + private: + AZStd::vector m_data; + size_t m_rowCount = 0; + size_t m_columnCount = 0; + }; +#endif + + class FeatureMatrix + : public FeatureMatrixType + { + public: + AZ_RTTI(FeatureMatrix, "{E063C9CB-7147-4776-A6E0-98584DD93FEF}"); + AZ_CLASS_ALLOCATOR_DECL + +#ifdef O3DE_USE_EIGEN + using Index = Eigen::Index; +#else + using Index = size_t; +#endif + + virtual ~FeatureMatrix() = default; + + void Clear(); + + void SaveAsCsv(const AZStd::string& filename, const AZStd::vector& columnNames = {}); + void SaveAsCsv(const AZStd::string& filename, const FeatureSchema* featureSchema); + + size_t CalcMemoryUsageInBytes() const; + + AZ::Vector2 GetVector2(Index row, Index startColumn) const; + void SetVector2(Index row, Index startColumn, const AZ::Vector2& value); + + AZ::Vector3 GetVector3(Index row, Index startColumn) const; + void SetVector3(Index row, Index startColumn, const AZ::Vector3& value); + }; +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/FeaturePosition.cpp b/Gems/MotionMatching/Code/Source/FeaturePosition.cpp new file mode 100644 index 0000000000..b81ec081f7 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/FeaturePosition.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace EMotionFX::MotionMatching +{ + AZ_CLASS_ALLOCATOR_IMPL(FeaturePosition, MotionMatchAllocator, 0) + + void FeaturePosition::FillQueryFeatureValues(size_t startIndex, AZStd::vector& queryFeatureValues, const FrameCostContext& context) + { + const Transform invRootTransform = context.m_currentPose.GetWorldSpaceTransform(m_relativeToNodeIndex).Inversed(); + const AZ::Vector3 worldInputPosition = context.m_currentPose.GetWorldSpaceTransform(m_jointIndex).m_position; + const AZ::Vector3 relativeInputPosition = invRootTransform.TransformPoint(worldInputPosition); + queryFeatureValues[startIndex + 0] = relativeInputPosition.GetX(); + queryFeatureValues[startIndex + 1] = relativeInputPosition.GetY(); + queryFeatureValues[startIndex + 2] = relativeInputPosition.GetZ(); + } + + void FeaturePosition::ExtractFeatureValues(const ExtractFeatureContext& context) + { + const Transform invRootTransform = context.m_framePose->GetWorldSpaceTransform(m_relativeToNodeIndex).Inversed(); + const AZ::Vector3 nodeWorldPosition = context.m_framePose->GetWorldSpaceTransform(m_jointIndex).m_position; + const AZ::Vector3 position = invRootTransform.TransformPoint(nodeWorldPosition); + SetFeatureData(context.m_featureMatrix, context.m_frameIndex, position); + } + + void FeaturePosition::DebugDraw(AzFramework::DebugDisplayRequests& debugDisplay, + MotionMatchingInstance* instance, + size_t frameIndex) + { + const MotionMatchingData* data = instance->GetData(); + const ActorInstance* actorInstance = instance->GetActorInstance(); + const Pose* pose = actorInstance->GetTransformData()->GetCurrentPose(); + const Transform jointModelTM = pose->GetModelSpaceTransform(m_jointIndex); + const Transform relativeToWorldTM = pose->GetWorldSpaceTransform(m_relativeToNodeIndex); + + const AZ::Vector3 position = GetFeatureData(data->GetFeatureMatrix(), frameIndex); + const AZ::Vector3 transformedPos = relativeToWorldTM.TransformPoint(position); + + constexpr float markerSize = 0.03f; + debugDisplay.DepthTestOff(); + debugDisplay.SetColor(m_debugColor); + debugDisplay.DrawBall(transformedPos, markerSize, /*drawShaded=*/false); + } + + float FeaturePosition::CalculateFrameCost(size_t frameIndex, const FrameCostContext& context) const + { + const Transform invRootTransform = context.m_currentPose.GetWorldSpaceTransform(m_relativeToNodeIndex).Inversed(); + const AZ::Vector3 worldInputPosition = context.m_currentPose.GetWorldSpaceTransform(m_jointIndex).m_position; + const AZ::Vector3 relativeInputPosition = invRootTransform.TransformPoint(worldInputPosition); + const AZ::Vector3 framePosition = GetFeatureData(context.m_featureMatrix, frameIndex); // This is already relative to the root node + return CalcResidual(relativeInputPosition, framePosition); + } + + void FeaturePosition::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (!serializeContext) + { + return; + } + + serializeContext->Class() + ->Version(1); + + AZ::EditContext* editContext = serializeContext->GetEditContext(); + if (!editContext) + { + return; + } + + editContext->Class("FeaturePosition", "Matches joint positions.") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::AutoExpand, "") + ; + } + + size_t FeaturePosition::GetNumDimensions() const + { + return 3; + } + + AZStd::string FeaturePosition::GetDimensionName(size_t index) const + { + AZStd::string result = m_jointName; + result += '.'; + + switch (index) + { + case 0: { result += "PosX"; break; } + case 1: { result += "PosY"; break; } + case 2: { result += "PosZ"; break; } + default: { result += Feature::GetDimensionName(index); } + } + + return result; + } + + AZ::Vector3 FeaturePosition::GetFeatureData(const FeatureMatrix& featureMatrix, size_t frameIndex) const + { + return featureMatrix.GetVector3(frameIndex, m_featureColumnOffset); + } + + void FeaturePosition::SetFeatureData(FeatureMatrix& featureMatrix, size_t frameIndex, const AZ::Vector3& position) + { + featureMatrix.SetVector3(frameIndex, m_featureColumnOffset, position); + } +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/FeaturePosition.h b/Gems/MotionMatching/Code/Source/FeaturePosition.h new file mode 100644 index 0000000000..d62dce9a18 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/FeaturePosition.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +namespace AZ +{ + class ReflectContext; +} + +namespace EMotionFX::MotionMatching +{ + class FrameDatabase; + + class EMFX_API FeaturePosition + : public Feature + { + public: + AZ_RTTI(FeaturePosition, "{3EAA6459-DB59-4EA1-B8B3-C933A83AA77D}", Feature) + AZ_CLASS_ALLOCATOR_DECL + + FeaturePosition() = default; + ~FeaturePosition() override = default; + + void ExtractFeatureValues(const ExtractFeatureContext& context) override; + void DebugDraw(AzFramework::DebugDisplayRequests& debugDisplay, + MotionMatchingInstance* instance, + size_t frameIndex) override; + + float CalculateFrameCost(size_t frameIndex, const FrameCostContext& context) const override; + + void FillQueryFeatureValues(size_t startIndex, AZStd::vector& queryFeatureValues, const FrameCostContext& context) override; + + static void Reflect(AZ::ReflectContext* context); + + size_t GetNumDimensions() const override; + AZStd::string GetDimensionName(size_t index) const override; + AZ::Vector3 GetFeatureData(const FeatureMatrix& featureMatrix, size_t frameIndex) const; + void SetFeatureData(FeatureMatrix& featureMatrix, size_t frameIndex, const AZ::Vector3& position); + }; +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/FeatureSchema.cpp b/Gems/MotionMatching/Code/Source/FeatureSchema.cpp new file mode 100644 index 0000000000..1e36a755ee --- /dev/null +++ b/Gems/MotionMatching/Code/Source/FeatureSchema.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +#include +#include + +namespace EMotionFX::MotionMatching +{ + AZ_CLASS_ALLOCATOR_IMPL(FeatureSchema, MotionMatchAllocator, 0) + + FeatureSchema::~FeatureSchema() + { + Clear(); + } + + Feature* FeatureSchema::GetFeature(size_t index) const + { + return m_features[index]; + } + + const AZStd::vector& FeatureSchema::GetFeatures() const + { + return m_features; + } + + void FeatureSchema::AddFeature(Feature* feature) + { + // Try to see if there is a feature with the same id already. + auto iterator = AZStd::find_if(m_featuresById.begin(), m_featuresById.end(), [&feature](const auto& curEntry) -> bool { + return (feature->GetId() == curEntry.second->GetId()); + }); + + if (iterator != m_featuresById.end()) + { + AZ_Assert(false, "Cannot add feature. Feature with id '%s' has already been registered.", feature->GetId().data); + return; + } + + m_featuresById.emplace(feature->GetId(), feature); + m_features.emplace_back(feature); + } + + void FeatureSchema::Clear() + { + for (Feature* feature : m_features) + { + delete feature; + } + m_featuresById.clear(); + m_features.clear(); + } + + size_t FeatureSchema::GetNumFeatures() const + { + return m_features.size(); + } + + Feature* FeatureSchema::FindFeatureById(const AZ::TypeId& featureId) const + { + const auto result = m_featuresById.find(featureId); + if (result == m_featuresById.end()) + { + return nullptr; + } + + return result->second; + } + + Feature* FeatureSchema::CreateFeatureByType(const AZ::TypeId& typeId) + { + AZ::SerializeContext* context = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(context, &AZ::ComponentApplicationBus::Events::GetSerializeContext); + if (!context) + { + AZ_Error("Motion Matching", false, "Can't get serialize context from component application."); + return nullptr; + } + + const AZ::SerializeContext::ClassData* classData = context->FindClassData(typeId); + if (!classData) + { + AZ_Warning("Motion Matching", false, "Can't find class data for this type."); + return nullptr; + } + + Feature* featureObject = reinterpret_cast(classData->m_factory->Create(classData->m_name)); + return featureObject; + } + + void FeatureSchema::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (!serializeContext) + { + return; + } + + serializeContext->Class() + ->Version(1) + ->Field("features", &FeatureSchema::m_features); + + AZ::EditContext* editContext = serializeContext->GetEditContext(); + if (!editContext) + { + return; + } + + editContext->Class("FeatureSchema", "") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->DataElement(AZ::Edit::UIHandlers::Default, &FeatureSchema::m_features, "Features", "") + ->Attribute(AZ::Edit::Attributes::AutoExpand, "") + ; + } +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/FeatureSchema.h b/Gems/MotionMatching/Code/Source/FeatureSchema.h new file mode 100644 index 0000000000..d1005ef6dc --- /dev/null +++ b/Gems/MotionMatching/Code/Source/FeatureSchema.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include + +#include + +namespace EMotionFX::MotionMatching +{ + //! The set of features involved in the motion matching search. + //! The schema represents the order of the features as well as their settings while the feature matrix stores the actual feature data. + class EMFX_API FeatureSchema + { + public: + AZ_RTTI(FrameDatabase, "{E34F6BFE-73DB-4DED-AAB9-09FBC5113236}") + AZ_CLASS_ALLOCATOR_DECL + + virtual ~FeatureSchema(); + + void AddFeature(Feature* feature); + void Clear(); + + size_t GetNumFeatures() const; + Feature* GetFeature(size_t index) const; + const AZStd::vector& GetFeatures() const; + + Feature* FindFeatureById(const AZ::TypeId& featureId) const; + + static void Reflect(AZ::ReflectContext* context); + + protected: + static Feature* CreateFeatureByType(const AZ::TypeId& typeId); + + AZStd::vector m_features; //< Ordered set of features (Owns the feature objects). + AZStd::unordered_map m_featuresById; //< Hash-map for fast access to the features by ID. (Weak ownership) + }; +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/FeatureSchemaDefault.cpp b/Gems/MotionMatching/Code/Source/FeatureSchemaDefault.cpp new file mode 100644 index 0000000000..9201c525b6 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/FeatureSchemaDefault.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include + +namespace EMotionFX::MotionMatching +{ + void DefaultFeatureSchema(FeatureSchema& featureSchema, DefaultFeatureSchemaInitSettings settings) + { + featureSchema.Clear(); + const AZStd::string rootJointName = settings.m_rootJointName; + + //---------------------------------------------------------------------------------------------------------- + // Past and future root trajectory + FeatureTrajectory* rootTrajectory = aznew FeatureTrajectory(); + rootTrajectory->SetJointName(rootJointName); + rootTrajectory->SetRelativeToJointName(rootJointName); + rootTrajectory->SetDebugDrawColor(AZ::Color::CreateFromRgba(157,78,221,255)); + rootTrajectory->SetDebugDrawEnabled(true); + featureSchema.AddFeature(rootTrajectory); + + //---------------------------------------------------------------------------------------------------------- + // Left foot position + FeaturePosition* leftFootPosition = aznew FeaturePosition(); + leftFootPosition->SetName("Left Foot Position"); + leftFootPosition->SetJointName(settings.m_leftFootJointName); + leftFootPosition->SetRelativeToJointName(rootJointName); + leftFootPosition->SetDebugDrawColor(AZ::Color::CreateFromRgba(255,173,173,255)); + leftFootPosition->SetDebugDrawEnabled(true); + featureSchema.AddFeature(leftFootPosition); + + //---------------------------------------------------------------------------------------------------------- + // Right foot position + FeaturePosition* rightFootPosition = aznew FeaturePosition(); + rightFootPosition->SetName("Right Foot Position"); + rightFootPosition->SetJointName(settings.m_rightFootJointName); + rightFootPosition->SetRelativeToJointName(rootJointName); + rightFootPosition->SetDebugDrawColor(AZ::Color::CreateFromRgba(253,255,182,255)); + rightFootPosition->SetDebugDrawEnabled(true); + featureSchema.AddFeature(rightFootPosition); + + //---------------------------------------------------------------------------------------------------------- + // Left foot velocity + FeatureVelocity* leftFootVelocity = aznew FeatureVelocity(); + leftFootVelocity->SetName("Left Foot Velocity"); + leftFootVelocity->SetJointName(settings.m_leftFootJointName); + leftFootVelocity->SetRelativeToJointName(rootJointName); + leftFootVelocity->SetDebugDrawColor(AZ::Color::CreateFromRgba(155,246,255,255)); + leftFootVelocity->SetDebugDrawEnabled(true); + leftFootVelocity->SetCostFactor(0.75f); + featureSchema.AddFeature(leftFootVelocity); + + //---------------------------------------------------------------------------------------------------------- + // Right foot velocity + FeatureVelocity* rightFootVelocity = aznew FeatureVelocity(); + rightFootVelocity->SetName("Right Foot Velocity"); + rightFootVelocity->SetJointName(settings.m_rightFootJointName); + rightFootVelocity->SetRelativeToJointName(rootJointName); + rightFootVelocity->SetDebugDrawColor(AZ::Color::CreateFromRgba(189,178,255,255)); + rightFootVelocity->SetDebugDrawEnabled(true); + rightFootVelocity->SetCostFactor(0.75f); + featureSchema.AddFeature(rightFootVelocity); + + //---------------------------------------------------------------------------------------------------------- + // Pelvis velocity + FeatureVelocity* pelvisVelocity = aznew FeatureVelocity(); + pelvisVelocity->SetName("Pelvis Velocity"); + pelvisVelocity->SetJointName(settings.m_pelvisJointName); + pelvisVelocity->SetRelativeToJointName(rootJointName); + pelvisVelocity->SetDebugDrawColor(AZ::Color::CreateFromRgba(185,255,175,255)); + pelvisVelocity->SetDebugDrawEnabled(true); + featureSchema.AddFeature(pelvisVelocity); + } +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/FeatureSchemaDefault.h b/Gems/MotionMatching/Code/Source/FeatureSchemaDefault.h new file mode 100644 index 0000000000..0c9cda228f --- /dev/null +++ b/Gems/MotionMatching/Code/Source/FeatureSchemaDefault.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +namespace EMotionFX::MotionMatching +{ + struct DefaultFeatureSchemaInitSettings + { + AZStd::string m_rootJointName; + AZStd::string m_leftFootJointName; + AZStd::string m_rightFootJointName; + AZStd::string m_pelvisJointName; + }; + void DefaultFeatureSchema(FeatureSchema& featureSchema, DefaultFeatureSchemaInitSettings settings); +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/FeatureTrajectory.cpp b/Gems/MotionMatching/Code/Source/FeatureTrajectory.cpp new file mode 100644 index 0000000000..3de6053b06 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/FeatureTrajectory.cpp @@ -0,0 +1,450 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace EMotionFX::MotionMatching +{ + AZ_CLASS_ALLOCATOR_IMPL(FeatureTrajectory, MotionMatchAllocator, 0) + + bool FeatureTrajectory::Init(const InitSettings& settings) + { + const bool result = Feature::Init(settings); + UpdateFacingAxis(); + return result; + } + + size_t FeatureTrajectory::CalcNumSamplesPerFrame() const + { + return m_numPastSamples + 1 + m_numFutureSamples; + } + + void FeatureTrajectory::SetFacingAxis(const Axis axis) + { + m_facingAxis = axis; + UpdateFacingAxis(); + } + + void FeatureTrajectory::UpdateFacingAxis() + { + switch (m_facingAxis) + { + case Axis::X: + { + m_facingAxisDir = AZ::Vector3::CreateAxisX(); + break; + } + case Axis::Y: + { + m_facingAxisDir = AZ::Vector3::CreateAxisY(); + break; + } + case Axis::X_NEGATIVE: + { + m_facingAxisDir = -AZ::Vector3::CreateAxisX(); + break; + } + case Axis::Y_NEGATIVE: + { + m_facingAxisDir = -AZ::Vector3::CreateAxisY(); + break; + } + default: + { + AZ_Assert(false, "Facing direction axis unknown."); + } + } + } + + AZ::Vector2 FeatureTrajectory::CalculateFacingDirection(const Pose& pose, const Transform& invRootTransform) const + { + // Get the facing direction of the given joint for the given pose in animation world space. + // The given pose is either sampled into the relative past or future based on the frame we want to extract the feature for. + const AZ::Vector3 facingDirAnimationWorldSpace = pose.GetWorldSpaceTransform(m_jointIndex).TransformVector(m_facingAxisDir); + + // The invRootTransform is the inverse of the world space transform for the given joint at the frame we want to extract the feature for. + // The result after this will be the facing direction relative to the frame we want to extract the feature for. + const AZ::Vector3 facingDirection = invRootTransform.TransformVector(facingDirAnimationWorldSpace); + + // Project to the ground plane and make sure the direction is normalized. + return AZ::Vector2(facingDirection).GetNormalizedSafe(); + } + + FeatureTrajectory::Sample FeatureTrajectory::GetSampleFromPose(const Pose& pose, const Transform& invRootTransform) const + { + // Position of the root joint in the model space relative to frame to extract. + const AZ::Vector2 position = AZ::Vector2(invRootTransform.TransformPoint(pose.GetWorldSpaceTransform(m_jointIndex).m_position)); + + // Calculate the facing direction. + const AZ::Vector2 facingDirection = CalculateFacingDirection(pose, invRootTransform); + + return { position, facingDirection }; + } + + void FeatureTrajectory::ExtractFeatureValues(const ExtractFeatureContext& context) + { + const ActorInstance* actorInstance = context.m_actorInstance; + AnimGraphPosePool& posePool = GetEMotionFX().GetThreadData(actorInstance->GetThreadIndex())->GetPosePool(); + AnimGraphPose* samplePose = posePool.RequestPose(actorInstance); + AnimGraphPose* nextSamplePose = posePool.RequestPose(actorInstance); + + const size_t frameIndex = context.m_frameIndex; + const Frame& currentFrame = context.m_frameDatabase->GetFrame(context.m_frameIndex); + + // Inverse of the root transform for the frame that we want to extract data from. + const Transform invRootTransform = context.m_framePose->GetWorldSpaceTransform(m_relativeToNodeIndex).Inversed(); + + const size_t midSampleIndex = CalcMidFrameIndex(); + const Sample midSample = GetSampleFromPose(*context.m_framePose, invRootTransform); + SetFeatureData(context.m_featureMatrix, frameIndex, midSampleIndex, midSample); + + // Sample the past. + const float pastFrameTimeDelta = m_pastTimeRange / static_cast(m_numPastSamples - 1); + currentFrame.SamplePose(&samplePose->GetPose()); + for (size_t i = 0; i < m_numPastSamples; ++i) + { + // Increase the sample index by one as the zeroth past/future sample actually needs one time delta time difference to the current frame. + const float sampleTimeOffset = (i+1) * pastFrameTimeDelta * (-1.0f); + currentFrame.SamplePose(&nextSamplePose->GetPose(), sampleTimeOffset); + + const Sample sample = GetSampleFromPose(samplePose->GetPose(), invRootTransform); + const size_t sampleIndex = CalcPastFrameIndex(i); + SetFeatureData(context.m_featureMatrix, frameIndex, sampleIndex, sample); + + *samplePose = *nextSamplePose; + } + + // Sample into the future. + const float futureFrameTimeDelta = m_futureTimeRange / (float)(m_numFutureSamples - 1); + currentFrame.SamplePose(&samplePose->GetPose()); + for (size_t i = 0; i < m_numFutureSamples; ++i) + { + // Sample the value at the future sample point. + const float sampleTimeOffset = (i+1) * futureFrameTimeDelta; + currentFrame.SamplePose(&nextSamplePose->GetPose(), sampleTimeOffset); + + const Sample sample = GetSampleFromPose(samplePose->GetPose(), invRootTransform); + const size_t sampleIndex = CalcFutureFrameIndex(i); + SetFeatureData(context.m_featureMatrix, frameIndex, sampleIndex, sample); + + *samplePose = *nextSamplePose; + } + + posePool.FreePose(samplePose); + posePool.FreePose(nextSamplePose); + } + + void FeatureTrajectory::SetPastTimeRange(float timeInSeconds) + { + m_pastTimeRange = timeInSeconds; + } + + void FeatureTrajectory::SetFutureTimeRange(float timeInSeconds) + { + m_futureTimeRange = timeInSeconds; + } + + void FeatureTrajectory::SetNumPastSamplesPerFrame(size_t numHistorySamples) + { + m_numPastSamples = numHistorySamples; + } + + void FeatureTrajectory::SetNumFutureSamplesPerFrame(size_t numFutureSamples) + { + m_numFutureSamples = numFutureSamples; + } + + void FeatureTrajectory::DebugDrawFacingDirection(AzFramework::DebugDisplayRequests& debugDisplay, + const AZ::Vector3& positionWorldSpace, + const AZ::Vector3& facingDirectionWorldSpace) + { + const float length = 0.2f; + const float radius = 0.01f; + + const AZ::Vector3 facingDirectionTarget = positionWorldSpace + facingDirectionWorldSpace * length; + debugDisplay.DrawSolidCylinder(/*center=*/(facingDirectionTarget + positionWorldSpace) * 0.5f, + /*direction=*/facingDirectionWorldSpace, + radius, + /*height=*/length, + /*drawShaded=*/false); + } + + void FeatureTrajectory::DebugDrawFacingDirection(AzFramework::DebugDisplayRequests& debugDisplay, + const Transform& worldSpaceTransform, + const Sample& sample, + const AZ::Vector3& samplePosWorldSpace) const + { + const AZ::Vector3 facingDirectionWorldSpace = worldSpaceTransform.TransformVector(AZ::Vector3(sample.m_facingDirection)).GetNormalizedSafe(); + DebugDrawFacingDirection(debugDisplay, samplePosWorldSpace, facingDirectionWorldSpace); + } + + void FeatureTrajectory::DebugDrawTrajectory(AzFramework::DebugDisplayRequests& debugDisplay, + MotionMatchingInstance* instance, + size_t frameIndex, + const Transform& worldSpaceTransform, + const AZ::Color& color, + size_t numSamples, + const SplineToFeatureMatrixIndex& splineToFeatureMatrixIndex) const + { + if (frameIndex == InvalidIndex) + { + return; + } + + constexpr float markerSize = 0.02f; + const FeatureMatrix& featureMatrix = instance->GetData()->GetFeatureMatrix(); + + debugDisplay.DepthTestOff(); + debugDisplay.SetColor(color); + + Sample nextSample; + AZ::Vector3 nextSamplePos; + for (size_t i = 0; i < numSamples - 1; ++i) + { + const Sample currentSample = GetFeatureData(featureMatrix, frameIndex, splineToFeatureMatrixIndex(i)); + nextSample = GetFeatureData(featureMatrix, frameIndex, splineToFeatureMatrixIndex(i + 1)); + + const AZ::Vector3 currentSamplePos = worldSpaceTransform.TransformPoint(AZ::Vector3(currentSample.m_position)); + nextSamplePos = worldSpaceTransform.TransformPoint(AZ::Vector3(nextSample.m_position)); + + // Line between current and next sample. + debugDisplay.DrawSolidCylinder(/*center=*/(nextSamplePos + currentSamplePos) * 0.5f, + /*direction=*/(nextSamplePos - currentSamplePos).GetNormalizedSafe(), + /*radius=*/0.0025f, + /*height=*/(nextSamplePos - currentSamplePos).GetLength(), + /*drawShaded=*/false); + + // Sphere at the sample position and a cylinder to indicate the facing direction. + debugDisplay.DrawBall(currentSamplePos, markerSize, /*drawShaded=*/false); + DebugDrawFacingDirection(debugDisplay, worldSpaceTransform, currentSample, currentSamplePos); + } + + debugDisplay.DrawBall(nextSamplePos, markerSize, /*drawShaded=*/false); + DebugDrawFacingDirection(debugDisplay, worldSpaceTransform, nextSample, nextSamplePos); + } + + void FeatureTrajectory::DebugDraw(AzFramework::DebugDisplayRequests& debugDisplay, + MotionMatchingInstance* instance, + size_t frameIndex) + { + const ActorInstance* actorInstance = instance->GetActorInstance(); + const Transform transform = actorInstance->GetTransformData()->GetCurrentPose()->GetWorldSpaceTransform(m_jointIndex); + + DebugDrawTrajectory(debugDisplay, instance, frameIndex, transform, + m_debugColor, m_numPastSamples, AZStd::bind(&FeatureTrajectory::CalcPastFrameIndex, this, AZStd::placeholders::_1)); + + DebugDrawTrajectory(debugDisplay, instance, frameIndex, transform, + m_debugColor, m_numFutureSamples, AZStd::bind(&FeatureTrajectory::CalcFutureFrameIndex, this, AZStd::placeholders::_1)); + } + + size_t FeatureTrajectory::CalcMidFrameIndex() const + { + return m_numPastSamples; + } + + size_t FeatureTrajectory::CalcPastFrameIndex(size_t historyFrameIndex) const + { + AZ_Assert(historyFrameIndex < m_numPastSamples, "The history frame index is out of range"); + return m_numPastSamples - historyFrameIndex - 1; + } + + size_t FeatureTrajectory::CalcFutureFrameIndex(size_t futureFrameIndex) const + { + AZ_Assert(futureFrameIndex < m_numFutureSamples, "The future frame index is out of range"); + return CalcMidFrameIndex() + 1 + futureFrameIndex; + } + + float FeatureTrajectory::CalculateCost(const FeatureMatrix& featureMatrix, + size_t frameIndex, + const Transform& invRootTransform, + const AZStd::vector& controlPoints, + const SplineToFeatureMatrixIndex& splineToFeatureMatrixIndex) const + { + float cost = 0.0f; + AZ::Vector2 lastControlPoint, lastSamplePos; + + for (size_t i = 0; i < controlPoints.size(); ++i) + { + const TrajectoryQuery::ControlPoint& controlPoint = controlPoints[i]; + const Sample sample = GetFeatureData(featureMatrix, frameIndex, splineToFeatureMatrixIndex(i)); + const AZ::Vector2& samplePos = sample.m_position; + const AZ::Vector2 controlPointPos = AZ::Vector2(invRootTransform.TransformPoint(controlPoint.m_position)); // Convert so it is relative to where we are and pointing to. + + if (i != 0) + { + const AZ::Vector2 controlPointDelta = controlPointPos - lastControlPoint; + const AZ::Vector2 sampleDelta = samplePos - lastSamplePos; + + const float posDistance = (samplePos - controlPointPos).GetLength(); + const float posDeltaDistance = (controlPointDelta - sampleDelta).GetLength(); + + // The facing direction from the control point (trajectory query) is in world space while the facing direction from the + // sample of this trajectory feature is in relative-to-frame-root-joint space. + const AZ::Vector2 controlPointFacingDirRelativeSpace = AZ::Vector2(invRootTransform.TransformVector(controlPoint.m_facingDirection)); + const float facingDirectionCost = GetNormalizedDirectionDifference(sample.m_facingDirection, + controlPointFacingDirRelativeSpace); + + // As we got two different costs for the position, double the cost of the facing direction to equal out the influence. + cost += CalcResidual(posDistance) + CalcResidual(posDeltaDistance) + CalcResidual(facingDirectionCost) * 2.0f; + } + + lastControlPoint = controlPointPos; + lastSamplePos = samplePos; + } + + return cost; + } + + float FeatureTrajectory::CalculateFutureFrameCost(size_t frameIndex, const FrameCostContext& context) const + { + AZ_Assert(context.m_trajectoryQuery->GetFutureControlPoints().size() == m_numFutureSamples, "Number of future control points from the trajectory query does not match the one from the trajectory feature."); + const Transform invRootTransform = context.m_currentPose.GetWorldSpaceTransform(m_relativeToNodeIndex).Inversed(); + return CalculateCost(context.m_featureMatrix, frameIndex, invRootTransform, context.m_trajectoryQuery->GetFutureControlPoints(), AZStd::bind(&FeatureTrajectory::CalcFutureFrameIndex, this, AZStd::placeholders::_1)); + } + + float FeatureTrajectory::CalculatePastFrameCost(size_t frameIndex, const FrameCostContext& context) const + { + AZ_Assert(context.m_trajectoryQuery->GetPastControlPoints().size() == m_numPastSamples, "Number of past control points from the trajectory query does not match the one from the trajectory feature"); + const Transform invRootTransform = context.m_currentPose.GetWorldSpaceTransform(m_relativeToNodeIndex).Inversed(); + return CalculateCost(context.m_featureMatrix, frameIndex, invRootTransform, context.m_trajectoryQuery->GetPastControlPoints(), AZStd::bind(&FeatureTrajectory::CalcPastFrameIndex, this, AZStd::placeholders::_1)); + } + + AZ::Crc32 FeatureTrajectory::GetCostFactorVisibility() const + { + return AZ::Edit::PropertyVisibility::Hide; + } + + void FeatureTrajectory::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (!serializeContext) + { + return; + } + + serializeContext->Class() + ->Version(2) + ->Field("pastTimeRange", &FeatureTrajectory::m_pastTimeRange) + ->Field("numPastSamples", &FeatureTrajectory::m_numPastSamples) + ->Field("pastCostFactor", &FeatureTrajectory::m_pastCostFactor) + ->Field("futureTimeRange", &FeatureTrajectory::m_futureTimeRange) + ->Field("numFutureSamples", &FeatureTrajectory::m_numFutureSamples) + ->Field("futureCostFactor", &FeatureTrajectory::m_futureCostFactor) + ->Field("facingAxis", &FeatureTrajectory::m_facingAxis) + ; + + AZ::EditContext* editContext = serializeContext->GetEditContext(); + if (!editContext) + { + return; + } + + editContext->Class("FeatureTrajectory", "Matches the joint past and future trajectory.") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::AutoExpand, "") + ->DataElement(AZ::Edit::UIHandlers::Default, &FeatureTrajectory::m_numPastSamples, "Past Samples", "The number of samples stored per frame for the past trajectory. [Default = 4 samples to represent the trajectory history]") + ->Attribute(AZ::Edit::Attributes::Min, 1) + ->Attribute(AZ::Edit::Attributes::Max, 100) + ->Attribute(AZ::Edit::Attributes::Step, 1) + ->DataElement(AZ::Edit::UIHandlers::Default, &FeatureTrajectory::m_pastTimeRange, "Past Time Range", "The time window the samples are distributed along for the trajectory history. [Default = 0.7 seconds]") + ->Attribute(AZ::Edit::Attributes::Min, 0.01f) + ->Attribute(AZ::Edit::Attributes::Max, 10.0f) + ->Attribute(AZ::Edit::Attributes::Step, 0.1f) + ->DataElement(AZ::Edit::UIHandlers::Default, &FeatureTrajectory::m_pastCostFactor, "Past Cost Factor", "The cost factor is multiplied with the cost from the trajectory history and can be used to change the influence of the trajectory history match in the motion matching search.") + ->Attribute(AZ::Edit::Attributes::Min, 0.0f) + ->Attribute(AZ::Edit::Attributes::Max, 100.0f) + ->Attribute(AZ::Edit::Attributes::Step, 0.1f) + ->DataElement(AZ::Edit::UIHandlers::Default, &FeatureTrajectory::m_numFutureSamples, "Future Samples", "The number of samples stored per frame for the future trajectory. [Default = 6 samples to represent the future trajectory]") + ->Attribute(AZ::Edit::Attributes::Min, 1) + ->Attribute(AZ::Edit::Attributes::Max, 100) + ->Attribute(AZ::Edit::Attributes::Step, 1) + ->DataElement(AZ::Edit::UIHandlers::Default, &FeatureTrajectory::m_futureTimeRange, "Future Time Range", "The time window the samples are distributed along for the future trajectory. [Default = 1.2 seconds]") + ->Attribute(AZ::Edit::Attributes::Min, 0.01f) + ->Attribute(AZ::Edit::Attributes::Max, 10.0f) + ->Attribute(AZ::Edit::Attributes::Step, 0.1f) + ->DataElement(AZ::Edit::UIHandlers::Default, &FeatureTrajectory::m_futureCostFactor, "Future Cost Factor", "The cost factor is multiplied with the cost from the future trajectory and can be used to change the influence of the future trajectory match in the motion matching search.") + ->Attribute(AZ::Edit::Attributes::Min, 0.0f) + ->Attribute(AZ::Edit::Attributes::Max, 100.0f) + ->Attribute(AZ::Edit::Attributes::Step, 0.1f) + ->DataElement(AZ::Edit::UIHandlers::ComboBox, &FeatureTrajectory::m_facingAxis, "Facing Axis", "The facing direction of the character. Which axis of the joint transform is facing forward? [Default = Looking into Y-axis direction]") + ->Attribute(AZ::Edit::Attributes::ChangeNotify, &FeatureTrajectory::UpdateFacingAxis) + ->EnumAttribute(Axis::X, "X") + ->EnumAttribute(Axis::X_NEGATIVE, "-X") + ->EnumAttribute(Axis::Y, "Y") + ->EnumAttribute(Axis::Y_NEGATIVE, "-Y") + ; + } + + size_t FeatureTrajectory::GetNumDimensions() const + { + return CalcNumSamplesPerFrame() * Sample::s_componentsPerSample; + } + + AZStd::string FeatureTrajectory::GetDimensionName(size_t index) const + { + AZStd::string result = "Trajectory"; + + const int sampleIndex = aznumeric_cast(index) / aznumeric_cast(Sample::s_componentsPerSample); + const int componentIndex = index % Sample::s_componentsPerSample; + const int midSampleIndex = aznumeric_cast(CalcMidFrameIndex()); + + if (sampleIndex == midSampleIndex) + { + result += ".Current."; + } + else if (sampleIndex < midSampleIndex) + { + result += AZStd::string::format(".Past%i.", sampleIndex - static_cast(m_numPastSamples)); + } + else + { + result += AZStd::string::format(".Future%i.", sampleIndex - static_cast(m_numPastSamples)); + } + + switch (componentIndex) + { + case 0: { result += "PosX"; break; } + case 1: { result += "PosY"; break; } + case 2: { result += "FacingDirX"; break; } + case 3: { result += "FacingDirY"; break; } + default: { result += Feature::GetDimensionName(index); } + } + + return result; + } + + FeatureTrajectory::Sample FeatureTrajectory::GetFeatureData(const FeatureMatrix& featureMatrix, size_t frameIndex, size_t sampleIndex) const + { + const size_t columnOffset = m_featureColumnOffset + sampleIndex * Sample::s_componentsPerSample; + return { + /*.m_position =*/ featureMatrix.GetVector2(frameIndex, columnOffset + 0), + /*.m_facingDirection =*/ featureMatrix.GetVector2(frameIndex, columnOffset + 2), + }; + } + + void FeatureTrajectory::SetFeatureData(FeatureMatrix& featureMatrix, size_t frameIndex, size_t sampleIndex, const Sample& sample) + { + const size_t columnOffset = m_featureColumnOffset + sampleIndex * Sample::s_componentsPerSample; + featureMatrix.SetVector2(frameIndex, columnOffset + 0, sample.m_position); + featureMatrix.SetVector2(frameIndex, columnOffset + 2, sample.m_facingDirection); + } +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/FeatureTrajectory.h b/Gems/MotionMatching/Code/Source/FeatureTrajectory.h new file mode 100644 index 0000000000..7eacbf684c --- /dev/null +++ b/Gems/MotionMatching/Code/Source/FeatureTrajectory.h @@ -0,0 +1,148 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace AZ +{ + class ReflectContext; +} + +namespace EMotionFX::MotionMatching +{ + class FrameDatabase; + + /** + * Matches the root joint past and future trajectory. + * For each frame in the motion database, the position and facing direction relative to the current frame of the joint will be evaluated for a past and future time window. + * The past and future samples together form the trajectory of the current frame within the time window. This basically describes where the character came from to reach the + * current frame and where it will go when continuing to play the animation. + **/ + class EMFX_API FeatureTrajectory + : public Feature + { + public: + AZ_RTTI(FeatureTrajectory, "{0451E95B-A452-439A-81ED-3962A06A3992}", Feature) + AZ_CLASS_ALLOCATOR_DECL + + enum class Axis + { + X = 0, + Y = 1, + X_NEGATIVE = 2, + Y_NEGATIVE = 3, + }; + + struct EMFX_API Sample + { + AZ::Vector2 m_position; //! Position in the space relative to the extracted frame. + AZ::Vector2 m_facingDirection; //! Facing direction in the space relative to the extracted frame. + + static constexpr size_t s_componentsPerSample = 4; + }; + + FeatureTrajectory() = default; + ~FeatureTrajectory() override = default; + + bool Init(const InitSettings& settings) override; + void ExtractFeatureValues(const ExtractFeatureContext& context) override; + void DebugDraw(AzFramework::DebugDisplayRequests& debugDisplay, + MotionMatchingInstance* instance, + size_t frameIndex) override; + + float CalculateFutureFrameCost(size_t frameIndex, const FrameCostContext& context) const; + float CalculatePastFrameCost(size_t frameIndex, const FrameCostContext& context) const; + + void SetNumPastSamplesPerFrame(size_t numHistorySamples); + void SetNumFutureSamplesPerFrame(size_t numFutureSamples); + void SetPastTimeRange(float timeInSeconds); + void SetFutureTimeRange(float timeInSeconds); + void SetFacingAxis(const Axis axis); + void UpdateFacingAxis(); + + float GetPastTimeRange() const { return m_pastTimeRange; } + size_t GetNumPastSamples() const { return m_numPastSamples; } + float GetPastCostFactor() const { return m_pastCostFactor; } + + float GetFutureTimeRange() const { return m_futureTimeRange; } + size_t GetNumFutureSamples() const { return m_numFutureSamples; } + float GetFutureCostFactor() const { return m_futureCostFactor; } + + AZ::Vector2 CalculateFacingDirection(const Pose& pose, const Transform& invRootTransform) const; + AZ::Vector3 GetFacingAxisDir() const { return m_facingAxisDir; } + + static void Reflect(AZ::ReflectContext* context); + + size_t GetNumDimensions() const override; + AZStd::string GetDimensionName(size_t index) const override; + + // Shared helper function to draw a facing direction. + static void DebugDrawFacingDirection(AzFramework::DebugDisplayRequests& debugDisplay, + const AZ::Vector3& positionWorldSpace, + const AZ::Vector3& facingDirectionWorldSpace); + + private: + size_t CalcMidFrameIndex() const; + size_t CalcPastFrameIndex(size_t historyFrameIndex) const; + size_t CalcFutureFrameIndex(size_t futureFrameIndex) const; + size_t CalcNumSamplesPerFrame() const; + + using SplineToFeatureMatrixIndex = AZStd::function; + float CalculateCost(const FeatureMatrix& featureMatrix, + size_t frameIndex, + const Transform& invRootTransform, + const AZStd::vector& controlPoints, + const SplineToFeatureMatrixIndex& splineToFeatureMatrixIndex) const; + + //! Called for every sample in the past or future range to extract its information. + //! @param[in] pose The sampled pose within the trajectory range [m_pastTimeRange, m_futureTimeRange]. + //! @param[in] invRootTransform The inverse of the world space transform of the joint at frame time that the feature is extracted for. + Sample GetSampleFromPose(const Pose& pose, const Transform& invRootTransform) const; + + Sample GetFeatureData(const FeatureMatrix& featureMatrix, size_t frameIndex, size_t sampleIndex) const; + void SetFeatureData(FeatureMatrix& featureMatrix, size_t frameIndex, size_t sampleIndex, const Sample& sample); + + void DebugDrawTrajectory(AzFramework::DebugDisplayRequests& debugDisplay, + MotionMatchingInstance* instance, + size_t frameIndex, + const Transform& transform, + const AZ::Color& color, + size_t numSamples, + const SplineToFeatureMatrixIndex& splineToFeatureMatrixIndex) const; + + void DebugDrawFacingDirection(AzFramework::DebugDisplayRequests& debugDisplay, + const Transform& worldSpaceTransform, + const Sample& sample, + const AZ::Vector3& samplePosWorldSpace) const; + + AZ::Crc32 GetCostFactorVisibility() const override; + + float m_pastTimeRange = 0.7f; //< The time window the samples are distributed along for the past trajectory. + size_t m_numPastSamples = 4; //< The number of samples stored per frame for the past (history) trajectory. + float m_pastCostFactor = 0.5f; //< Normalized value to weight or scale the future trajectory cost. + + float m_futureTimeRange = 1.2f; //< The time window the samples are distributed along for the future trajectory. + size_t m_numFutureSamples = 6; //< The number of samples stored per frame for the future trajectory. + float m_futureCostFactor = 0.75f; //< Normalized value to weight or scale the future trajectory cost. + + Axis m_facingAxis = Axis::Y; //< Which axis of the joint transform is facing forward? + AZ::Vector3 m_facingAxisDir = AZ::Vector3::CreateAxisY(); + }; +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/FeatureVelocity.cpp b/Gems/MotionMatching/Code/Source/FeatureVelocity.cpp new file mode 100644 index 0000000000..e210ca1a61 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/FeatureVelocity.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace EMotionFX::MotionMatching +{ + AZ_CLASS_ALLOCATOR_IMPL(FeatureVelocity, MotionMatchAllocator, 0) + + void FeatureVelocity::FillQueryFeatureValues(size_t startIndex, AZStd::vector& queryFeatureValues, const FrameCostContext& context) + { + PoseDataJointVelocities* velocityPoseData = static_cast(context.m_currentPose.GetPoseDataByType(azrtti_typeid())); + AZ_Assert(velocityPoseData, "Cannot calculate velocity feature cost without joint velocity pose data."); + const AZ::Vector3 currentVelocity = velocityPoseData->GetVelocity(m_jointIndex); + + queryFeatureValues[startIndex + 0] = currentVelocity.GetX(); + queryFeatureValues[startIndex + 1] = currentVelocity.GetY(); + queryFeatureValues[startIndex + 2] = currentVelocity.GetZ(); + } + + void FeatureVelocity::ExtractFeatureValues(const ExtractFeatureContext& context) + { + AZ::Vector3 velocity; + CalculateVelocity(context.m_actorInstance, m_jointIndex, m_relativeToNodeIndex, context.m_frameDatabase->GetFrame(context.m_frameIndex), velocity); + + SetFeatureData(context.m_featureMatrix, context.m_frameIndex, velocity); + } + + void FeatureVelocity::DebugDraw(AzFramework::DebugDisplayRequests& debugDisplay, + MotionMatchingInstance* instance, + const AZ::Vector3& velocity, + size_t jointIndex, + size_t relativeToJointIndex, + const AZ::Color& color) + { + const ActorInstance* actorInstance = instance->GetActorInstance(); + const Pose* pose = actorInstance->GetTransformData()->GetCurrentPose(); + const Transform jointModelTM = pose->GetModelSpaceTransform(jointIndex); + const Transform relativeToWorldTM = pose->GetWorldSpaceTransform(relativeToJointIndex); + + const AZ::Vector3 jointPosition = relativeToWorldTM.TransformPoint(jointModelTM.m_position); + const float scale = 0.15f; + const AZ::Vector3 velocityWorldSpace = relativeToWorldTM.TransformVector(velocity * scale); + + DebugDrawVelocity(debugDisplay, jointPosition, velocityWorldSpace, color); + } + + void FeatureVelocity::DebugDraw(AzFramework::DebugDisplayRequests& debugDisplay, + MotionMatchingInstance* instance, + size_t frameIndex) + { + if (m_jointIndex == InvalidIndex) + { + return; + } + + const MotionMatchingData* data = instance->GetData(); + const AZ::Vector3 velocity = GetFeatureData(data->GetFeatureMatrix(), frameIndex); + DebugDraw(debugDisplay, instance, velocity, m_jointIndex, m_relativeToNodeIndex, m_debugColor); + } + + float FeatureVelocity::CalculateFrameCost(size_t frameIndex, const FrameCostContext& context) const + { + PoseDataJointVelocities* velocityPoseData = static_cast(context.m_currentPose.GetPoseDataByType(azrtti_typeid())); + AZ_Assert(velocityPoseData, "Cannot calculate velocity feature cost without joint velocity pose data."); + const AZ::Vector3 currentVelocity = velocityPoseData->GetVelocity(m_jointIndex); + + const AZ::Vector3 frameVelocity = GetFeatureData(context.m_featureMatrix, frameIndex); + + // Direction difference + const float directionDifferenceCost = GetNormalizedDirectionDifference(frameVelocity.GetNormalized(), currentVelocity.GetNormalized()); + + // Speed difference + // TODO: This needs to be normalized later on, else wise it could be that the direction difference is weights + // too heavily or too less compared to what the speed values are + const float speedDifferenceCost = frameVelocity.GetLength() - currentVelocity.GetLength(); + + return CalcResidual(directionDifferenceCost) + CalcResidual(speedDifferenceCost); + } + + void FeatureVelocity::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (!serializeContext) + { + return; + } + + serializeContext->Class() + ->Version(1) + ; + + AZ::EditContext* editContext = serializeContext->GetEditContext(); + if (!editContext) + { + return; + } + + editContext->Class("FeatureVelocity", "Matches joint velocities.") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::AutoExpand, "") + ; + } + + size_t FeatureVelocity::GetNumDimensions() const + { + return 3; + } + + AZStd::string FeatureVelocity::GetDimensionName(size_t index) const + { + AZStd::string result = m_jointName; + result += '.'; + + switch (index) + { + case 0: { result += "VelocityX"; break; } + case 1: { result += "VelocityY"; break; } + case 2: { result += "VelocityZ"; break; } + default: { result += Feature::GetDimensionName(index); } + } + + return result; + } + + AZ::Vector3 FeatureVelocity::GetFeatureData(const FeatureMatrix& featureMatrix, size_t frameIndex) const + { + return featureMatrix.GetVector3(frameIndex, m_featureColumnOffset); + } + + void FeatureVelocity::SetFeatureData(FeatureMatrix& featureMatrix, size_t frameIndex, const AZ::Vector3& velocity) + { + featureMatrix.SetVector3(frameIndex, m_featureColumnOffset, velocity); + } +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/FeatureVelocity.h b/Gems/MotionMatching/Code/Source/FeatureVelocity.h new file mode 100644 index 0000000000..37cd8f3d7e --- /dev/null +++ b/Gems/MotionMatching/Code/Source/FeatureVelocity.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace AZ +{ + class ReflectContext; +} + +namespace EMotionFX::MotionMatching +{ + class FrameDatabase; + + class EMFX_API FeatureVelocity + : public Feature + { + public: + AZ_RTTI(FeatureVelocity, "{DEEA4F0F-CE70-4F16-9136-C2BFDDA29336}", Feature) + AZ_CLASS_ALLOCATOR_DECL + + FeatureVelocity() = default; + ~FeatureVelocity() override = default; + + void ExtractFeatureValues(const ExtractFeatureContext& context) override; + + static void DebugDraw(AzFramework::DebugDisplayRequests& debugDisplay, + MotionMatchingInstance* instance, + const AZ::Vector3& velocity, // in world space + size_t jointIndex, + size_t relativeToJointIndex, + const AZ::Color& color); + + void DebugDraw(AzFramework::DebugDisplayRequests& debugDisplay, + MotionMatchingInstance* instance, + size_t frameIndex) override; + + float CalculateFrameCost(size_t frameIndex, const FrameCostContext& context) const override; + + void FillQueryFeatureValues(size_t startIndex, AZStd::vector& queryFeatureValues, const FrameCostContext& context) override; + + static void Reflect(AZ::ReflectContext* context); + + size_t GetNumDimensions() const override; + AZStd::string GetDimensionName(size_t index) const override; + AZ::Vector3 GetFeatureData(const FeatureMatrix& featureMatrix, size_t frameIndex) const; + void SetFeatureData(FeatureMatrix& featureMatrix, size_t frameIndex, const AZ::Vector3& velocity); + }; +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/Frame.cpp b/Gems/MotionMatching/Code/Source/Frame.cpp new file mode 100644 index 0000000000..170d3050f7 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/Frame.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include + +namespace EMotionFX::MotionMatching +{ + AZ_CLASS_ALLOCATOR_IMPL(Frame, MotionMatchAllocator, 0) + + Frame::Frame() + : m_frameIndex(InvalidIndex) + , m_sampleTime(0.0f) + , m_sourceMotion(nullptr) + , m_mirrored(false) + { + } + + Frame::Frame(size_t frameIndex, Motion* sourceMotion, float sampleTime, bool mirrored) + : m_frameIndex(frameIndex) + , m_sourceMotion(sourceMotion) + , m_sampleTime(sampleTime) + , m_mirrored(mirrored) + { + } + + void Frame::SamplePose(Pose* outputPose, float timeOffset) const + { + MotionDataSampleSettings sampleSettings; + sampleSettings.m_actorInstance = outputPose->GetActorInstance(); + sampleSettings.m_inPlace = false; + sampleSettings.m_mirror = m_mirrored; + sampleSettings.m_retarget = false; + sampleSettings.m_inputPose = sampleSettings.m_actorInstance->GetTransformData()->GetBindPose(); + + sampleSettings.m_sampleTime = m_sampleTime + timeOffset; + sampleSettings.m_sampleTime = AZ::GetClamp(m_sampleTime + timeOffset, 0.0f, m_sourceMotion->GetDuration()); + + m_sourceMotion->SamplePose(outputPose, sampleSettings); + } + + void Frame::SetFrameIndex(size_t frameIndex) + { + m_frameIndex = frameIndex; + } + + Motion* Frame::GetSourceMotion() const + { + return m_sourceMotion; + } + + float Frame::GetSampleTime() const + { + return m_sampleTime; + } + + void Frame::SetSourceMotion(Motion* sourceMotion) + { + m_sourceMotion = sourceMotion; + } + + void Frame::SetMirrored(bool enabled) + { + m_mirrored = enabled; + } + + void Frame::SetSampleTime(float sampleTime) + { + m_sampleTime = sampleTime; + } +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/Frame.h b/Gems/MotionMatching/Code/Source/Frame.h new file mode 100644 index 0000000000..02150cfa5a --- /dev/null +++ b/Gems/MotionMatching/Code/Source/Frame.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +#include +#include + +namespace EMotionFX +{ + class Motion; + + namespace MotionMatching + { + /** + * A motion matching frame. + * This holds information required in order to extract a given pose in a given motion. + */ + class EMFX_API Frame + { + public: + AZ_RTTI(Frame, "{985BD732-D80E-4898-AB6C-CAB22D88AACD}") + AZ_CLASS_ALLOCATOR_DECL + + Frame(); + Frame(size_t frameIndex, Motion* sourceMotion, float sampleTime, bool mirrored); + ~Frame() = default; + + //! Sample the pose for the given frame. + //! @param[in] outputPose The pose used to store the sampled result. + //! @param[in] timeOffset Frames in the frame database are samples with a given sample rate (default = 30 fps). + //! For calculating velocities for example, it is needed to sample a pose close to a frame but not exactly at the frame position. + //! The timeOffset parameter can be used for that and represents the offset in time from the frame sample time in seconds. + //! In case the time offset is 0.0, the pose exactly at the frame position will be sampled. + void SamplePose(Pose* outputPose, float timeOffset = 0.0f) const; + + Motion* GetSourceMotion() const; + float GetSampleTime() const; + size_t GetFrameIndex() const { return m_frameIndex; } + bool GetMirrored() const { return m_mirrored; } + + void SetSourceMotion(Motion* sourceMotion); + void SetSampleTime(float sampleTime); + void SetFrameIndex(size_t frameIndex); + void SetMirrored(bool enabled); + + private: + size_t m_frameIndex = 0; /**< The motion frame index inside the data object. */ + float m_sampleTime = 0.0f; /**< The time offset in the original motion. */ + Motion* m_sourceMotion = nullptr; /**< The original motion that we sample from to restore the pose. */ + bool m_mirrored = false; /**< Is this frame mirrored? */ + }; + } // namespace MotionMatching +} // namespace EMotionFX diff --git a/Gems/MotionMatching/Code/Source/FrameDatabase.cpp b/Gems/MotionMatching/Code/Source/FrameDatabase.cpp new file mode 100644 index 0000000000..7d38060dcc --- /dev/null +++ b/Gems/MotionMatching/Code/Source/FrameDatabase.cpp @@ -0,0 +1,250 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace EMotionFX::MotionMatching +{ + AZ_CLASS_ALLOCATOR_IMPL(FrameDatabase, MotionMatchAllocator, 0) + + FrameDatabase::FrameDatabase() + { + } + + FrameDatabase::~FrameDatabase() + { + Clear(); + } + + void FrameDatabase::Clear() + { + // Clear the frames. + m_frames.clear(); + m_frames.shrink_to_fit(); + + m_frameIndexByMotion.clear(); + + // Clear other things. + m_usedMotions.clear(); + m_usedMotions.shrink_to_fit(); + } + + void FrameDatabase::ExtractActiveMotionEventDatas(const Motion* motion, float time, AZStd::vector& activeEventDatas) + { + activeEventDatas.clear(); + + // Iterate over all motion event tracks and all events inside them. + const MotionEventTable* eventTable = motion->GetEventTable(); + const size_t numTracks = eventTable->GetNumTracks(); + for (size_t t = 0; t < numTracks; ++t) + { + const MotionEventTrack* track = eventTable->GetTrack(t); + const size_t numEvents = track->GetNumEvents(); + for (size_t e = 0; e < numEvents; ++e) + { + const MotionEvent& motionEvent = track->GetEvent(e); + + // Only handle range based events and events that include our time value. + if (motionEvent.GetIsTickEvent() || + motionEvent.GetStartTime() > time || + motionEvent.GetEndTime() < time) + { + continue; + } + + for (auto eventData : motionEvent.GetEventDatas()) + { + activeEventDatas.emplace_back(const_cast(eventData.get())); + } + } + } + } + + bool FrameDatabase::IsFrameDiscarded(const AZStd::vector& activeEventDatas) const + { + for (const EventData* eventData : activeEventDatas) + { + if (eventData->RTTI_GetType() == azrtti_typeid()) + { + return true; + } + } + + return false; + } + + AZStd::tuple FrameDatabase::ImportFrames(Motion* motion, const FrameImportSettings& settings, bool mirrored) + { + AZ_PROFILE_SCOPE(Animation, "FrameDatabase::ImportFrames"); + + AZ_Assert(motion, "The motion cannot be a nullptr"); + AZ_Assert(settings.m_sampleRate > 0, "The sample rate must be bigger than zero frames per second"); + AZ_Assert(settings.m_sampleRate <= 120, "The sample rate must be smaller than 120 frames per second"); + + size_t numFramesImported = 0; + size_t numFramesDiscarded = 0; + + // Calculate the number of frames we might need to import, in worst case. + m_sampleRate = settings.m_sampleRate; + const double timeStep = 1.0 / aznumeric_cast(settings.m_sampleRate); + const size_t worstCaseNumFrames = aznumeric_cast(ceil(motion->GetDuration() / timeStep)) + 1; + + // Try to pre-allocate memory for the worst case scenario. + if (m_frames.capacity() < m_frames.size() + worstCaseNumFrames) + { + m_frames.reserve(m_frames.size() + worstCaseNumFrames); + } + + AZStd::vector activeEvents; + + // Iterate over all sample positions in the motion. + const double totalTime = aznumeric_cast(motion->GetDuration()); + double curTime = 0.0; + while (curTime <= totalTime) + { + const float floatTime = aznumeric_cast(curTime); + ExtractActiveMotionEventDatas(motion, floatTime, activeEvents); + if (!IsFrameDiscarded(activeEvents)) + { + ImportFrame(motion, floatTime, mirrored); + numFramesImported++; + } + else + { + numFramesDiscarded++; + } + curTime += timeStep; + } + + // Make sure we include the last frame, if we stepped over it. + if (curTime - timeStep < totalTime - 0.000001) + { + const float floatTime = aznumeric_cast(totalTime); + ExtractActiveMotionEventDatas(motion, floatTime, activeEvents); + if (!IsFrameDiscarded(activeEvents)) + { + ImportFrame(motion, floatTime, mirrored); + numFramesImported++; + } + else + { + numFramesDiscarded++; + } + } + + // Automatically shrink the frame storage to their minimum size. + if (settings.m_autoShrink) + { + m_frames.shrink_to_fit(); + } + + // Register the motion. + if (AZStd::find(m_usedMotions.begin(), m_usedMotions.end(), motion) == m_usedMotions.end()) + { + m_usedMotions.emplace_back(motion); + } + + return { numFramesImported, numFramesDiscarded }; + } + + void FrameDatabase::ImportFrame(Motion* motion, float timeValue, bool mirrored) + { + m_frames.emplace_back(Frame(m_frames.size(), motion, timeValue, mirrored)); + m_frameIndexByMotion[motion].emplace_back(m_frames.back().GetFrameIndex()); + } + + size_t FrameDatabase::CalcMemoryUsageInBytes() const + { + size_t total = 0; + + total += m_frames.capacity() * sizeof(Frame); + total += sizeof(m_frames); + total += m_usedMotions.capacity() * sizeof(const Motion*); + total += sizeof(m_usedMotions); + + return total; + } + + size_t FrameDatabase::GetNumFrames() const + { + return m_frames.size(); + } + + size_t FrameDatabase::GetNumUsedMotions() const + { + return m_usedMotions.size(); + } + + const Motion* FrameDatabase::GetUsedMotion(size_t index) const + { + return m_usedMotions[index]; + } + + const Frame& FrameDatabase::GetFrame(size_t index) const + { + AZ_Assert(index < m_frames.size(), "Frame index is out of range!"); + return m_frames[index]; + } + + AZStd::vector& FrameDatabase::GetFrames() + { + return m_frames; + } + + const AZStd::vector& FrameDatabase::GetFrames() const + { + return m_frames; + } + + const AZStd::vector& FrameDatabase::GetUsedMotions() const + { + return m_usedMotions; + } + + size_t FrameDatabase::FindFrameIndex(Motion* motion, float playtime) const + { + auto iterator = m_frameIndexByMotion.find(motion); + if (iterator == m_frameIndexByMotion.end()) + { + return InvalidIndex; + } + + const AZStd::vector& frameIndices = iterator->second; + for (const size_t frameIndex : frameIndices) + { + const Frame& frame = m_frames[frameIndex]; + if (playtime >= frame.GetSampleTime() && + frameIndex + 1 < m_frames.size() && + playtime <= m_frames[frameIndex + 1].GetSampleTime()) + { + return frameIndex; + } + } + + return InvalidIndex; + } +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/FrameDatabase.h b/Gems/MotionMatching/Code/Source/FrameDatabase.h new file mode 100644 index 0000000000..c5258e1b39 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/FrameDatabase.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace EMotionFX +{ + class Motion; + class ActorInstance; +} + +namespace EMotionFX::MotionMatching +{ + class MotionMatchingInstance; + class MotionMatchEventData; + + // The motion matching data. + // This is basically a database of frames (which point to motion objects), together with meta data per frame. + // No actual pose data is stored directly inside this class, just references to the right sample times inside specific motions. + class EMFX_API FrameDatabase + { + public: + AZ_RTTI(FrameDatabase, "{3E5ED4F9-8975-41F2-B665-0086368F0DDA}") + AZ_CLASS_ALLOCATOR_DECL + + // The settings used when importing motions into the frame database. + // Used in combination with ImportFrames(). + struct EMFX_API FrameImportSettings + { + size_t m_sampleRate = 30; /**< Sample at 30 frames per second on default. */ + bool m_autoShrink = true; /**< Automatically shrink the internal frame arrays to their minimum size afterwards. */ + }; + + FrameDatabase(); + virtual ~FrameDatabase(); + + // Main functions. + AZStd::tuple ImportFrames(Motion* motion, const FrameImportSettings& settings, bool mirrored); // Returns the number of imported frames and the number of discarded frames as second element. + void Clear(); // Clear the data, so you can re-initialize it with new data. + + // Statistics. + size_t GetNumFrames() const; + size_t GetNumUsedMotions() const; + size_t CalcMemoryUsageInBytes() const; + + // Misc. + const Motion* GetUsedMotion(size_t index) const; + const Frame& GetFrame(size_t index) const; + const AZStd::vector& GetFrames() const; + AZStd::vector& GetFrames(); + const AZStd::vector& GetUsedMotions() const; + size_t GetSampleRate() const { return m_sampleRate; } + + /** + * Find the frame index for the given playtime and motion. + * NOTE: This is a slow operation and should not be used by the runtime without visual debugging. + */ + size_t FindFrameIndex(Motion* motion, float playtime) const; + + private: + void ImportFrame(Motion* motion, float timeValue, bool mirrored); + bool IsFrameDiscarded(const AZStd::vector& activeEventDatas) const; + void ExtractActiveMotionEventDatas(const Motion* motion, float time, AZStd::vector& activeEventDatas); // Vector will be cleared internally. + + private: + AZStd::vector m_frames; /**< The collection of frames. Keep in mind these don't hold a pose, but reference to a given frame/time value inside a given motion. */ + AZStd::unordered_map> m_frameIndexByMotion; + AZStd::vector m_usedMotions; /**< The list of used motions. */ + size_t m_sampleRate = 0; + }; +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/ImGuiMonitor.cpp b/Gems/MotionMatching/Code/Source/ImGuiMonitor.cpp new file mode 100644 index 0000000000..97b666c693 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/ImGuiMonitor.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#ifdef IMGUI_ENABLED +#include +#include + +namespace EMotionFX::MotionMatching +{ + AZ_CLASS_ALLOCATOR_IMPL(ImGuiMonitor, MotionMatchAllocator, 0) + + ImGuiMonitor::ImGuiMonitor() + { + m_performanceStats.m_name = "Performance Statistics"; + + m_featureCosts.m_name = "Feature Costs"; + m_featureCosts.m_histogramContainerCount = 100; + + ImGui::ImGuiUpdateListenerBus::Handler::BusConnect(); + ImGuiMonitorRequestBus::Handler::BusConnect(); + } + + ImGuiMonitor::~ImGuiMonitor() + { + ImGui::ImGuiUpdateListenerBus::Handler::BusDisconnect(); + ImGuiMonitorRequestBus::Handler::BusDisconnect(); + } + + void ImGuiMonitor::OnImGuiUpdate() + { + if (!m_performanceStats.m_show && !m_featureCosts.m_show) + { + return; + } + + if (ImGui::Begin("Motion Matching")) + { + if (ImGui::CollapsingHeader("Feature Matrix", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed)) + { + ImGui::Text("Memory Usage: %.2f MB", m_featureMatrixMemoryUsageInBytes / 1024.0f / 1024.0f); + ImGui::Text("Num Frames: %zu", m_featureMatrixNumFrames); + ImGui::Text("Num Feature Components: %zu", m_featureMatrixNumComponents); + } + + if (ImGui::CollapsingHeader("Kd-Tree", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed)) + { + ImGui::Text("Memory Usage: %.2f MB", m_kdTreeMemoryUsageInBytes / 1024.0f / 1024.0f); + ImGui::Text("Num Nodes: %zu", m_kdTreeNumNodes); + ImGui::Text("Num Dimensions: %zu", m_kdTreeNumDimensions); + } + + m_performanceStats.OnImGuiUpdate(); + m_featureCosts.OnImGuiUpdate(); + } + } + + void ImGuiMonitor::OnImGuiMainMenuUpdate() + { + if (ImGui::BeginMenu("Motion Matching")) + { + ImGui::MenuItem(m_performanceStats.m_name.c_str(), "", &m_performanceStats.m_show); + ImGui::MenuItem(m_featureCosts.m_name.c_str(), "", &m_featureCosts.m_show); + ImGui::EndMenu(); + } + } + + void ImGuiMonitor::PushPerformanceHistogramValue(const char* performanceMetricName, float value) + { + m_performanceStats.PushHistogramValue(performanceMetricName, value, AZ::Color::CreateFromRgba(229,56,59,255)); + } + + void ImGuiMonitor::PushCostHistogramValue(const char* costName, float value, const AZ::Color& color) + { + m_featureCosts.PushHistogramValue(costName, value, color); + } + + void ImGuiMonitor::HistogramGroup::PushHistogramValue(const char* valueName, float value, const AZ::Color& color) + { + auto iterator = m_histogramIndexByName.find(valueName); + if (iterator != m_histogramIndexByName.end()) + { + ImGui::LYImGuiUtils::HistogramContainer& histogramContiner = m_histograms[iterator->second]; + histogramContiner.PushValue(value); + histogramContiner.SetBarLineColor(ImColor(color.GetR(), color.GetG(), color.GetB(), color.GetA())); + } + else + { + ImGui::LYImGuiUtils::HistogramContainer newHistogram; + newHistogram.Init(/*histogramName=*/valueName, + /*containerCount=*/m_histogramContainerCount, + /*viewType=*/ImGui::LYImGuiUtils::HistogramContainer::ViewType::Histogram, + /*displayOverlays=*/true, + /*min=*/0.0f, + /*max=*/0.0f); + + newHistogram.SetMoveDirection(ImGui::LYImGuiUtils::HistogramContainer::PushRightMoveLeft); + newHistogram.PushValue(value); + + m_histogramIndexByName[valueName] = m_histograms.size(); + m_histograms.push_back(newHistogram); + } + } + + void ImGuiMonitor::HistogramGroup::OnImGuiUpdate() + { + if (!m_show) + { + return; + } + + if (ImGui::CollapsingHeader(m_name.c_str(), ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed)) + { + for (auto& histogram : m_histograms) + { + ImGui::BeginGroup(); + { + histogram.Draw(ImGui::GetColumnWidth() - 70, s_histogramHeight); + + ImGui::SameLine(); + + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0,0,0,255)); + { + const ImColor color = histogram.GetBarLineColor(); + ImGui::PushStyleColor(ImGuiCol_Button, color.Value); + { + const AZStd::string valueString = AZStd::string::format("%.2f", histogram.GetLastValue()); + ImGui::Button(valueString.c_str()); + } + ImGui::PopStyleColor(); + } + ImGui::PopStyleColor(); + } + ImGui::EndGroup(); + } + } + } +} // namespace EMotionFX::MotionMatching + +#endif // IMGUI_ENABLED diff --git a/Gems/MotionMatching/Code/Source/ImGuiMonitor.h b/Gems/MotionMatching/Code/Source/ImGuiMonitor.h new file mode 100644 index 0000000000..0583d0ba41 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/ImGuiMonitor.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once +#ifdef IMGUI_ENABLED + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace EMotionFX::MotionMatching +{ + class EMFX_API ImGuiMonitor + : public ImGui::ImGuiUpdateListenerBus::Handler + , public ImGuiMonitorRequestBus::Handler + { + public: + AZ_RTTI(ImGuiMonitor, "{BF1B85A4-215C-4E3A-8FD8-CE3233E5C779}") + AZ_CLASS_ALLOCATOR_DECL + + ImGuiMonitor(); + ~ImGuiMonitor(); + + // ImGui::ImGuiUpdateListenerBus::Handler + void OnImGuiUpdate() override; + void OnImGuiMainMenuUpdate() override; + + // ImGuiMonitorRequestBus::Handler + void PushPerformanceHistogramValue(const char* performanceMetricName, float value) override; + void PushCostHistogramValue(const char* costName, float value, const AZ::Color& color) override; + + void SetFeatureMatrixMemoryUsage(size_t sizeInBytes) override { m_featureMatrixMemoryUsageInBytes = sizeInBytes; } + void SetFeatureMatrixNumFrames(size_t numFrames) override { m_featureMatrixNumFrames = numFrames; } + void SetFeatureMatrixNumComponents(size_t numFeatureComponents) override { m_featureMatrixNumComponents = numFeatureComponents; } + + void SetKdTreeMemoryUsage(size_t sizeInBytes) override { m_kdTreeMemoryUsageInBytes = sizeInBytes; } + void SetKdTreeNumNodes(size_t numNodes) override { m_kdTreeNumNodes = numNodes; } + void SetKdTreeNumDimensions(size_t numDimensions) override { m_kdTreeNumDimensions = numDimensions; } + + private: + //! Named and sub-divided group containing several histograms. + struct HistogramGroup + { + void OnImGuiUpdate(); + void PushHistogramValue(const char* valueName, float value, const AZ::Color& color); + + bool m_show = true; + AZStd::string m_name; + using HistogramIndexByNames = AZStd::unordered_map; + HistogramIndexByNames m_histogramIndexByName; + AZStd::vector m_histograms; + int m_histogramContainerCount = 500; + + static constexpr float s_histogramHeight = 95.0f; + }; + + HistogramGroup m_performanceStats; + HistogramGroup m_featureCosts; + + size_t m_featureMatrixMemoryUsageInBytes = 0; + size_t m_featureMatrixNumFrames = 0; + size_t m_featureMatrixNumComponents = 0; + + size_t m_kdTreeMemoryUsageInBytes = 0; + size_t m_kdTreeNumNodes = 0; + size_t m_kdTreeNumDimensions = 0; + }; +} // namespace EMotionFX::MotionMatching + +#endif // IMGUI_ENABLED diff --git a/Gems/MotionMatching/Code/Source/ImGuiMonitorBus.h b/Gems/MotionMatching/Code/Source/ImGuiMonitorBus.h new file mode 100644 index 0000000000..7c50b317c5 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/ImGuiMonitorBus.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include + +namespace EMotionFX::MotionMatching +{ + class ImGuiMonitorRequests + : public AZ::EBusTraits + { + public: + // Enable multi-threaded access by locking primitive using a mutex when connecting handlers to the EBus or executing events. + using MutexType = AZStd::recursive_mutex; + + virtual void PushPerformanceHistogramValue(const char* performanceMetricName, float value) = 0; + virtual void PushCostHistogramValue(const char* costName, float value, const AZ::Color& color) = 0; + + virtual void SetFeatureMatrixMemoryUsage(size_t sizeInBytes) = 0; + virtual void SetFeatureMatrixNumFrames(size_t numFrames) = 0; + virtual void SetFeatureMatrixNumComponents(size_t numFeatureComponents) = 0; + + virtual void SetKdTreeMemoryUsage(size_t sizeInBytes) = 0; + virtual void SetKdTreeNumNodes(size_t numNodes) = 0; + virtual void SetKdTreeNumDimensions(size_t numDimensions) = 0; + }; + using ImGuiMonitorRequestBus = AZ::EBus; +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/KdTree.cpp b/Gems/MotionMatching/Code/Source/KdTree.cpp new file mode 100644 index 0000000000..e496f0b63e --- /dev/null +++ b/Gems/MotionMatching/Code/Source/KdTree.cpp @@ -0,0 +1,454 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +#include + +namespace EMotionFX::MotionMatching +{ + AZ_CLASS_ALLOCATOR_IMPL(KdTree, MotionMatchAllocator, 0) + + KdTree::~KdTree() + { + Clear(); + } + + size_t KdTree::CalcNumDimensions(const AZStd::vector& features) + { + size_t result = 0; + for (Feature* feature : features) + { + if (feature->GetId().IsNull()) + { + continue; + } + + result += feature->GetNumDimensions(); + } + return result; + } + + bool KdTree::Init(const FrameDatabase& frameDatabase, + const FeatureMatrix& featureMatrix, + const AZStd::vector& features, + size_t maxDepth, + size_t minFramesPerLeaf) + { + AZ::Debug::Timer timer; + timer.Stamp(); + + Clear(); + + // Verify the dimensions. + // Going above a 20 dimensional tree would start eating up too much memory. + m_numDimensions = CalcNumDimensions(features); + if (m_numDimensions == 0 || m_numDimensions > 20) + { + AZ_Error("Motion Matching", false, "Cannot initialize KD-tree. KD-tree dimension (%d) has to be between 1 and 20. Please use Feature::SetIncludeInKdTree(false) on some features.", m_numDimensions); + return false; + } + + if (minFramesPerLeaf > 100000) + { + AZ_Error("Motion Matching", false, "KdTree minFramesPerLeaf (%d) cannot be smaller than 100000.", minFramesPerLeaf); + return false; + } + + if (maxDepth == 0) + { + AZ_Error("Motion Matching", false, "KdTree max depth (%d) cannot be zero", maxDepth); + return false; + } + + m_maxDepth = maxDepth; + m_minFramesPerLeaf = minFramesPerLeaf; + + // Build the tree. + m_featureValues.resize(m_numDimensions); + BuildTreeNodes(frameDatabase, featureMatrix, features, new Node(), nullptr, 0); + MergeSmallLeafNodesToParents(); + ClearFramesForNonEssentialNodes(); + RemoveZeroFrameLeafNodes(); + + const float initTime = timer.GetDeltaTimeInSeconds(); + AZ_TracePrintf("EMotionFX", "KdTree initialized in %f seconds (numNodes = %d numDims = %d Memory used = %.2f MB).", + initTime, m_nodes.size(), + m_numDimensions, + static_cast(CalcMemoryUsageInBytes()) / 1024.0f / 1024.0f); + + PrintStats(); + return true; + } + + void KdTree::Clear() + { + // delete all nodes + for (Node* node : m_nodes) + { + delete node; + } + + m_nodes.clear(); + m_featureValues.clear(); + m_numDimensions = 0; + } + + size_t KdTree::CalcMemoryUsageInBytes() const + { + size_t totalBytes = 0; + + for (const Node* node : m_nodes) + { + totalBytes += sizeof(Node); + totalBytes += node->m_frames.capacity() * sizeof(size_t); + } + + totalBytes += m_featureValues.capacity() * sizeof(float); + totalBytes += sizeof(KdTree); + return totalBytes; + } + + bool KdTree::IsInitialized() const + { + return (m_numDimensions != 0); + } + + size_t KdTree::GetNumNodes() const + { + return m_nodes.size(); + } + + size_t KdTree::GetNumDimensions() const + { + return m_numDimensions; + } + + void KdTree::BuildTreeNodes(const FrameDatabase& frameDatabase, + const FeatureMatrix& featureMatrix, + const AZStd::vector& features, + Node* node, + Node* parent, + size_t dimension, + bool leftSide) + { + node->m_parent = parent; + node->m_dimension = dimension; + m_nodes.emplace_back(node); + + // Fill the frames array and calculate the median. + FillFramesForNode(node, frameDatabase, featureMatrix, features, parent, leftSide); + + // Prevent splitting further when we don't want to. + const size_t maxDimensions = AZ::GetMin(m_numDimensions, m_maxDepth); + if (node->m_frames.size() < m_minFramesPerLeaf * 2 || + dimension >= maxDimensions) + { + return; + } + + // Create the left node. + Node* leftNode = new Node(); + AZ_Assert(!node->m_leftNode, "Expected the parent left node to be a nullptr"); + node->m_leftNode = leftNode; + BuildTreeNodes(frameDatabase, featureMatrix, features, leftNode, node, dimension + 1, true); + + // Create the right node. + Node* rightNode = new Node(); + AZ_Assert(!node->m_rightNode, "Expected the parent right node to be a nullptr"); + node->m_rightNode = rightNode; + BuildTreeNodes(frameDatabase, featureMatrix, features, rightNode, node, dimension + 1, false); + } + + void KdTree::ClearFramesForNonEssentialNodes() + { + for (Node* node : m_nodes) + { + if (node->m_leftNode && node->m_rightNode) + { + node->m_frames.clear(); + node->m_frames.shrink_to_fit(); + } + } + } + + void KdTree::RemoveLeafNode(Node* node) + { + Node* parent = node->m_parent; + + if (parent->m_leftNode == node) + { + parent->m_leftNode = nullptr; + } + + if (parent->m_rightNode == node) + { + parent->m_rightNode = nullptr; + } + + // Remove it from the node vector. + const auto location = AZStd::find(m_nodes.begin(), m_nodes.end(), node); + AZ_Assert(location != m_nodes.end(), "Expected to find the item to remove."); + m_nodes.erase(location); + + delete node; + } + + void KdTree::MergeSmallLeafNodesToParents() + { + AZStd::vector nodesToRemove; + for (Node* node : m_nodes) + { + // If we are a leaf node and we don't have enough frames. + if ((!node->m_leftNode && !node->m_rightNode) && + node->m_frames.size() < m_minFramesPerLeaf) + { + nodesToRemove.emplace_back(node); + } + } + + // Remove the actual nodes. + for (Node* node : nodesToRemove) + { + RemoveLeafNode(node); + } + } + + void KdTree::RemoveZeroFrameLeafNodes() + { + AZStd::vector nodesToRemove; + + // Build a list of leaf nodes to remove. + // These are ones that have no feature inside them. + for (Node* node : m_nodes) + { + if ((!node->m_leftNode && !node->m_rightNode) && + node->m_frames.empty()) + { + nodesToRemove.emplace_back(node); + } + } + + // Remove the actual nodes. + for (Node* node : nodesToRemove) + { + RemoveLeafNode(node); + } + } + + void KdTree::FillFramesForNode(Node* node, + const FrameDatabase& frameDatabase, + const FeatureMatrix& featureMatrix, + const AZStd::vector& features, + Node* parent, + bool leftSide) + { + float median = 0.0f; + if (parent) + { + // Assume half of the parent frames are in this node. + node->m_frames.reserve((parent->m_frames.size() / 2) + 1); + + // Add parent frames to this node, but only ones that should be on this side. + for (const size_t frameIndex : parent->m_frames) + { + FillFeatureValues(featureMatrix, features, frameIndex); + + const float value = m_featureValues[parent->m_dimension]; + if (leftSide) + { + if (value <= parent->m_median) + { + node->m_frames.emplace_back(frameIndex); + } + } + else + { + if (value > parent->m_median) + { + node->m_frames.emplace_back(frameIndex); + } + } + + median += value; + } + } + else // We're the root node. + { + node->m_frames.reserve(frameDatabase.GetNumFrames()); + for (const Frame& frame : frameDatabase.GetFrames()) + { + const size_t frameIndex = frame.GetFrameIndex(); + node->m_frames.emplace_back(frameIndex); + FillFeatureValues(featureMatrix, features, frameIndex); + median += m_featureValues[node->m_dimension]; + } + } + + if (!node->m_frames.empty()) + { + median /= static_cast(node->m_frames.size()); + } + node->m_median = median; + } + + void KdTree::FillFeatureValues(const FeatureMatrix& featureMatrix, const Feature* feature, size_t frameIndex, size_t startIndex) + { + const size_t numDimensions = feature->GetNumDimensions(); + const size_t featureColumnOffset = feature->GetColumnOffset(); + for (size_t i = 0; i < numDimensions; ++i) + { + m_featureValues[startIndex + i] = featureMatrix(frameIndex, featureColumnOffset + i); + } + } + + void KdTree::FillFeatureValues(const FeatureMatrix& featureMatrix, const AZStd::vector& features, size_t frameIndex) + { + size_t startDimension = 0; + for (const Feature* feature : features) + { + FillFeatureValues(featureMatrix, feature, frameIndex, startDimension); + startDimension += feature->GetNumDimensions(); + } + } + + void KdTree::RecursiveCalcNumFrames(Node* node, size_t& outNumFrames) const + { + if (node->m_leftNode && node->m_rightNode) + { + RecursiveCalcNumFrames(node->m_leftNode, outNumFrames); + RecursiveCalcNumFrames(node->m_rightNode, outNumFrames); + } + else + { + outNumFrames += node->m_frames.size(); + } + } + + void KdTree::PrintStats() + { + size_t leftNumFrames = 0; + size_t rightNumFrames = 0; + if (m_nodes[0]->m_leftNode) + { + RecursiveCalcNumFrames(m_nodes[0]->m_leftNode, leftNumFrames); + } + + if (m_nodes[0]->m_rightNode) + { + RecursiveCalcNumFrames(m_nodes[0]->m_rightNode, rightNumFrames); + } + + const float numFrames = static_cast(leftNumFrames + rightNumFrames); + const float halfFrames = numFrames / 2.0f; + const float balanceScore = 100.0f - (AZ::GetAbs(halfFrames - static_cast(leftNumFrames)) / numFrames) * 100.0f; + + // Get the maximum depth. + size_t maxDepth = 0; + for (const Node* node : m_nodes) + { + maxDepth = AZ::GetMax(maxDepth, node->m_dimension); + } + + AZ_TracePrintf("EMotionFX", "KdTree Balance Info: leftSide=%d rightSide=%d score=%.2f totalFrames=%d maxDepth=%d", leftNumFrames, rightNumFrames, balanceScore, leftNumFrames + rightNumFrames, maxDepth); + + size_t numLeafNodes = 0; + size_t numZeroNodes = 0; + size_t minFrames = 1000000000; + size_t maxFrames = 0; + for (const Node* node : m_nodes) + { + if (node->m_leftNode || node->m_rightNode) + { + continue; + } + + numLeafNodes++; + + if (node->m_frames.empty()) + { + numZeroNodes++; + } + + AZ_TracePrintf("EMotionFX", "Frames = %d", node->m_frames.size()); + + minFrames = AZ::GetMin(minFrames, node->m_frames.size()); + maxFrames = AZ::GetMax(maxFrames, node->m_frames.size()); + } + + const size_t avgFrames = (leftNumFrames + rightNumFrames) / numLeafNodes; + AZ_TracePrintf("EMotionFX", "KdTree Node Info: leafs=%d avgFrames=%d zeroFrames=%d minFrames=%d maxFrames=%d", numLeafNodes, avgFrames, numZeroNodes, minFrames, maxFrames); + } + + void KdTree::FindNearestNeighbors(const AZStd::vector& frameFloats, AZStd::vector& resultFrameIndices) const + { + AZ_Assert(IsInitialized() && !m_nodes.empty(), "Expecting a valid and initialized kdTree. Did you forget to call KdTree::Init()?"); + Node* curNode = m_nodes[0]; + + // Step as far as we need to through the kdTree. + Node* nodeToSearch = nullptr; + const size_t numDimensions = frameFloats.size(); + for (size_t d = 0; d < numDimensions; ++d) + { + AZ_Assert(curNode->m_dimension == d, "Dimension mismatch"); + + // We have children in both directions. + if (curNode->m_leftNode && curNode->m_rightNode) + { + curNode = (frameFloats[d] <= curNode->m_median) ? curNode->m_leftNode : curNode->m_rightNode; + } + else if (!curNode->m_leftNode && !curNode->m_rightNode) // we have a leaf node + { + nodeToSearch = curNode; + } + else + { + // We have both a left and right node, so we're not at a leaf yet. + if (curNode->m_leftNode) + { + if (frameFloats[d] <= curNode->m_median) + { + curNode = curNode->m_leftNode; + } + else + { + nodeToSearch = curNode; + } + } + else // We have a right node. + { + if (frameFloats[d] > curNode->m_median) + { + curNode = curNode->m_rightNode; + } + else + { + nodeToSearch = curNode; + } + } + } + + // If we found our search node, perform a linear search through the frames inside this node. + if (nodeToSearch) + { + //AZ_Assert(d == nodeToSearch->m_dimension, "Dimension mismatch inside kdTree nearest neighbor search."); + FindNearestNeighbors(nodeToSearch, frameFloats, resultFrameIndices); + return; + } + } + + FindNearestNeighbors(curNode, frameFloats, resultFrameIndices); + } + + void KdTree::FindNearestNeighbors([[maybe_unused]] Node* node, [[maybe_unused]] const AZStd::vector& frameFloats, AZStd::vector& resultFrameIndices) const + { + resultFrameIndices = node->m_frames; + } +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/KdTree.h b/Gems/MotionMatching/Code/Source/KdTree.h new file mode 100644 index 0000000000..8b62788c32 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/KdTree.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +#include + +#include +#include +#include + +namespace EMotionFX::MotionMatching +{ + class KdTree + { + public: + AZ_RTTI(KdTree, "{CDA707EC-4150-463B-8157-90D98351ACED}") + AZ_CLASS_ALLOCATOR_DECL + + KdTree() = default; + virtual ~KdTree(); + + bool Init(const FrameDatabase& frameDatabase, + const FeatureMatrix& featureMatrix, + const AZStd::vector& features, + size_t maxDepth=10, + size_t minFramesPerLeaf=1000); + + /** + * Calculate the number of dimensions or values for the given feature set. + * Each feature might store one or multiple values inside the feature matrix and the number of + * values each feature holds varies with the feature type. This calculates the sum of the number of + * values of the given feature set. + */ + static size_t CalcNumDimensions(const AZStd::vector& features); + + void Clear(); + void PrintStats(); + + size_t GetNumNodes() const; + size_t GetNumDimensions() const; + size_t CalcMemoryUsageInBytes() const; + bool IsInitialized() const; + + void FindNearestNeighbors(const AZStd::vector& frameFloats, AZStd::vector& resultFrameIndices) const; + + private: + struct Node + { + Node* m_leftNode = nullptr; + Node* m_rightNode = nullptr; + Node* m_parent = nullptr; + float m_median = 0.0f; + size_t m_dimension = 0; + AZStd::vector m_frames; + }; + + void BuildTreeNodes(const FrameDatabase& frameDatabase, + const FeatureMatrix& featureMatrix, + const AZStd::vector& features, + Node* node, + Node* parent, + size_t dimension = 0, + bool leftSide = true); + void FillFeatureValues(const FeatureMatrix& featureMatrix, const Feature* feature, size_t frameIndex, size_t startIndex); + void FillFeatureValues(const FeatureMatrix& featureMatrix, const AZStd::vector& features, size_t frameIndex); + void FillFramesForNode(Node* node, + const FrameDatabase& frameDatabase, + const FeatureMatrix& featureMatrix, + const AZStd::vector& features, + Node* parent, + bool leftSide); + void RecursiveCalcNumFrames(Node* node, size_t& outNumFrames) const; + void ClearFramesForNonEssentialNodes(); + void MergeSmallLeafNodesToParents(); + void RemoveZeroFrameLeafNodes(); + void RemoveLeafNode(Node* node); + void FindNearestNeighbors(Node* node, const AZStd::vector& frameFloats, AZStd::vector& resultFrameIndices) const; + + private: + AZStd::vector m_nodes; + AZStd::vector m_featureValues; + size_t m_numDimensions = 0; + size_t m_maxDepth = 20; + size_t m_minFramesPerLeaf = 1000; + }; +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/MotionMatchingData.cpp b/Gems/MotionMatching/Code/Source/MotionMatchingData.cpp new file mode 100644 index 0000000000..1ce891c98b --- /dev/null +++ b/Gems/MotionMatching/Code/Source/MotionMatchingData.cpp @@ -0,0 +1,181 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace EMotionFX::MotionMatching +{ + AZ_CLASS_ALLOCATOR_IMPL(MotionMatchingData, MotionMatchAllocator, 0) + + MotionMatchingData::MotionMatchingData(const FeatureSchema& featureSchema) + : m_featureSchema(featureSchema) + { + m_kdTree = AZStd::make_unique(); + } + + MotionMatchingData::~MotionMatchingData() + { + Clear(); + } + + bool MotionMatchingData::ExtractFeatures(ActorInstance* actorInstance, FrameDatabase* frameDatabase, size_t maxKdTreeDepth, size_t minFramesPerKdTreeNode) + { + AZ_PROFILE_SCOPE(Animation, "MotionMatchingData::ExtractFeatures"); + AZ::Debug::Timer timer; + timer.Stamp(); + + const size_t numFrames = frameDatabase->GetNumFrames(); + if (numFrames == 0) + { + return true; + } + + // Initialize all features before we process each frame. + FeatureMatrix::Index featureComponentCount = 0; + for (Feature* feature : m_featureSchema.GetFeatures()) + { + Feature::InitSettings frameSettings; + frameSettings.m_actorInstance = actorInstance; + if (!feature->Init(frameSettings)) + { + return false; + } + + feature->SetColumnOffset(featureComponentCount); + featureComponentCount += feature->GetNumDimensions(); + } + + const auto& frames = frameDatabase->GetFrames(); + + // Allocate memory for the feature matrix + m_featureMatrix.resize(/*rows=*/numFrames, /*columns=*/featureComponentCount); + + // Iterate over all frames and extract the data for this frame. + AnimGraphPosePool& posePool = GetEMotionFX().GetThreadData(actorInstance->GetThreadIndex())->GetPosePool(); + AnimGraphPose* pose = posePool.RequestPose(actorInstance); + + Feature::ExtractFeatureContext context(m_featureMatrix); + context.m_frameDatabase = frameDatabase; + context.m_framePose = &pose->GetPose(); + context.m_actorInstance = actorInstance; + + for (const Frame& frame : frames) + { + context.m_frameIndex = frame.GetFrameIndex(); + + // Pre-sample the frame pose as that will be needed by many of the feature extraction calculations. + frame.SamplePose(const_cast(context.m_framePose)); + + // Extract all features for the given frame. + { + for (Feature* feature : m_featureSchema.GetFeatures()) + { + feature->ExtractFeatureValues(context); + } + } + } + + posePool.FreePose(pose); + + const float extractFeaturesTime = timer.GetDeltaTimeInSeconds(); + timer.Stamp(); + + // Initialize the kd-tree used to accelerate the searches. + if (!m_kdTree->Init(*frameDatabase, m_featureMatrix, m_featuresInKdTree, maxKdTreeDepth, minFramesPerKdTreeNode)) // Internally automatically clears any existing contents. + { + AZ_Error("EMotionFX", false, "Failed to initialize KdTree acceleration structure."); + return false; + } + + const float initKdTreeTimer = timer.GetDeltaTimeInSeconds(); + + AZ_Printf("MotionMatching", "Feature matrix (%zu, %zu) uses %.2f MB and took %.2f ms to initialize (KD-Tree %.2f ms).", + m_featureMatrix.rows(), + m_featureMatrix.cols(), + static_cast(m_featureMatrix.CalcMemoryUsageInBytes()) / 1024.0f / 1024.0f, + extractFeaturesTime * 1000.0f, + initKdTreeTimer * 1000.0f); + + return true; + } + + bool MotionMatchingData::Init(const InitSettings& settings) + { + AZ_PROFILE_SCOPE(Animation, "MotionMatchingData::Init"); + + // Import all motion frames. + size_t totalNumFramesImported = 0; + size_t totalNumFramesDiscarded = 0; + for (Motion* motion : settings.m_motionList) + { + size_t numFrames = 0; + size_t numDiscarded = 0; + std::tie(numFrames, numDiscarded) = m_frameDatabase.ImportFrames(motion, settings.m_frameImportSettings, false); + totalNumFramesImported += numFrames; + totalNumFramesDiscarded += numDiscarded; + + if (settings.m_importMirrored) + { + std::tie(numFrames, numDiscarded) = m_frameDatabase.ImportFrames(motion, settings.m_frameImportSettings, true); + totalNumFramesImported += numFrames; + totalNumFramesDiscarded += numDiscarded; + } + } + + if (totalNumFramesImported > 0 || totalNumFramesDiscarded > 0) + { + AZ_TracePrintf("Motion Matching", "Imported a total of %d frames (%d frames discarded) across %d motions. This is %.2f seconds (%.2f minutes) of motion data.", + totalNumFramesImported, + totalNumFramesDiscarded, + settings.m_motionList.size(), + totalNumFramesImported / (float)settings.m_frameImportSettings.m_sampleRate, + (totalNumFramesImported / (float)settings.m_frameImportSettings.m_sampleRate) / 60.0f); + } + + // Use all features other than the trajectory for the broad-phase search using the KD-Tree. + for (Feature* feature : m_featureSchema.GetFeatures()) + { + if (feature->RTTI_GetType() != azrtti_typeid()) + { + m_featuresInKdTree.push_back(feature); + } + } + + // Extract feature data and place the values into the feature matrix. + if (!ExtractFeatures(settings.m_actorInstance, &m_frameDatabase, settings.m_maxKdTreeDepth, settings.m_minFramesPerKdTreeNode)) + { + AZ_Error("Motion Matching", false, "Failed to extract features from motion database."); + return false; + } + + return true; + } + + void MotionMatchingData::Clear() + { + m_frameDatabase.Clear(); + m_featureMatrix.Clear(); + m_kdTree->Clear(); + m_featuresInKdTree.clear(); + } +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/MotionMatchingData.h b/Gems/MotionMatching/Code/Source/MotionMatchingData.h new file mode 100644 index 0000000000..15748bb849 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/MotionMatchingData.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace AZ +{ + class ReflectContext; +} + +namespace EMotionFX +{ + class ActorInstance; +} + +namespace EMotionFX::MotionMatching +{ + class EMFX_API MotionMatchingData + { + public: + AZ_RTTI(MotionMatchingData, "{7BC3DFF5-8864-4518-B6F0-0553ADFAB5C1}") + AZ_CLASS_ALLOCATOR_DECL + + MotionMatchingData(const FeatureSchema& featureSchema); + virtual ~MotionMatchingData(); + + struct EMFX_API InitSettings + { + ActorInstance* m_actorInstance = nullptr; + AZStd::vector m_motionList; + FrameDatabase::FrameImportSettings m_frameImportSettings; + size_t m_maxKdTreeDepth = 20; + size_t m_minFramesPerKdTreeNode = 1000; + bool m_importMirrored = false; + }; + bool Init(const InitSettings& settings); + + void Clear(); + + const FrameDatabase& GetFrameDatabase() const { return m_frameDatabase; } + FrameDatabase& GetFrameDatabase() { return m_frameDatabase; } + const FeatureSchema& GetFeatureSchema() const { return m_featureSchema; } + const FeatureMatrix& GetFeatureMatrix() const { return m_featureMatrix; } + const KdTree& GetKdTree() const { return *m_kdTree.get(); } + const AZStd::vector& GetFeaturesInKdTree() const { return m_featuresInKdTree; } + + protected: + bool ExtractFeatures(ActorInstance* actorInstance, FrameDatabase* frameDatabase, size_t maxKdTreeDepth=20, size_t minFramesPerKdTreeNode=2000); + + FrameDatabase m_frameDatabase; /**< The animation database with all the keyframes and joint transform data. */ + + const FeatureSchema& m_featureSchema; + FeatureMatrix m_featureMatrix; + + AZStd::unique_ptr m_kdTree; /**< The acceleration structure to speed up the search for lowest cost frames. */ + AZStd::vector m_featuresInKdTree; + }; +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/MotionMatchingEditorModule.cpp b/Gems/MotionMatching/Code/Source/MotionMatchingEditorModule.cpp new file mode 100644 index 0000000000..fce6957901 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/MotionMatchingEditorModule.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include + +namespace EMotionFX::MotionMatching +{ + class MotionMatchingEditorModule + : public MotionMatchingModuleInterface + { + public: + AZ_RTTI(MotionMatchingEditorModule, "{cf4381d1-0207-4ef8-85f0-6c88ec28a7b6}", MotionMatchingModuleInterface); + AZ_CLASS_ALLOCATOR(MotionMatchingEditorModule, AZ::SystemAllocator, 0); + + MotionMatchingEditorModule() + { + m_descriptors.insert(m_descriptors.end(), + { + MotionMatchingEditorSystemComponent::CreateDescriptor(), + }); + } + + /// Add required SystemComponents to the SystemEntity. Non-SystemComponents should not be added here. + AZ::ComponentTypeList GetRequiredSystemComponents() const override + { + return AZ::ComponentTypeList + { + azrtti_typeid(), + }; + } + }; +}// namespace EMotionFX::MotionMatching + +AZ_DECLARE_MODULE_CLASS(Gem_MotionMatching, EMotionFX::MotionMatching::MotionMatchingEditorModule) diff --git a/Gems/MotionMatching/Code/Source/MotionMatchingEditorSystemComponent.cpp b/Gems/MotionMatching/Code/Source/MotionMatchingEditorSystemComponent.cpp new file mode 100644 index 0000000000..d8f0079a59 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/MotionMatchingEditorSystemComponent.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include + +namespace EMotionFX::MotionMatching +{ + void MotionMatchingEditorSystemComponent::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0); + } + } + + MotionMatchingEditorSystemComponent::MotionMatchingEditorSystemComponent() = default; + + MotionMatchingEditorSystemComponent::~MotionMatchingEditorSystemComponent() = default; + + void MotionMatchingEditorSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + BaseSystemComponent::GetProvidedServices(provided); + provided.push_back(AZ_CRC_CE("MotionMatchingEditorService")); + } + + void MotionMatchingEditorSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + BaseSystemComponent::GetIncompatibleServices(incompatible); + incompatible.push_back(AZ_CRC_CE("MotionMatchingEditorService")); + } + + void MotionMatchingEditorSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required) + { + BaseSystemComponent::GetRequiredServices(required); + } + + void MotionMatchingEditorSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent) + { + BaseSystemComponent::GetDependentServices(dependent); + } + + void MotionMatchingEditorSystemComponent::Activate() + { + MotionMatchingSystemComponent::Activate(); + AzToolsFramework::EditorEvents::Bus::Handler::BusConnect(); + } + + void MotionMatchingEditorSystemComponent::Deactivate() + { + AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect(); + MotionMatchingSystemComponent::Deactivate(); + } +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/MotionMatchingEditorSystemComponent.h b/Gems/MotionMatching/Code/Source/MotionMatchingEditorSystemComponent.h new file mode 100644 index 0000000000..a9d3bb528f --- /dev/null +++ b/Gems/MotionMatching/Code/Source/MotionMatchingEditorSystemComponent.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +#include + +namespace EMotionFX::MotionMatching +{ + /// System component for MotionMatching editor + class MotionMatchingEditorSystemComponent + : public MotionMatchingSystemComponent + , private AzToolsFramework::EditorEvents::Bus::Handler + { + using BaseSystemComponent = MotionMatchingSystemComponent; + public: + AZ_COMPONENT(MotionMatchingEditorSystemComponent, "{a43957d3-5a2d-4c29-873d-7daacc357722}", BaseSystemComponent); + static void Reflect(AZ::ReflectContext* context); + + MotionMatchingEditorSystemComponent(); + ~MotionMatchingEditorSystemComponent(); + + private: + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); + + // AZ::Component + void Activate() override; + void Deactivate() override; + }; +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/MotionMatchingInstance.cpp b/Gems/MotionMatching/Code/Source/MotionMatchingInstance.cpp new file mode 100644 index 0000000000..54fc2918e6 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/MotionMatchingInstance.cpp @@ -0,0 +1,571 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace EMotionFX::MotionMatching +{ + AZ_CLASS_ALLOCATOR_IMPL(MotionMatchingInstance, MotionMatchAllocator, 0) + + MotionMatchingInstance::~MotionMatchingInstance() + { + if (m_motionInstance) + { + GetMotionInstancePool().Free(m_motionInstance); + } + + if (m_prevMotionInstance) + { + GetMotionInstancePool().Free(m_prevMotionInstance); + } + } + + MotionInstance* MotionMatchingInstance::CreateMotionInstance() const + { + MotionInstance* result = GetMotionInstancePool().RequestNew(m_data->GetFrameDatabase().GetFrame(0).GetSourceMotion(), m_actorInstance); + return result; + } + + void MotionMatchingInstance::Init(const InitSettings& settings) + { + AZ_Assert(settings.m_actorInstance, "The actor instance cannot be a nullptr."); + AZ_Assert(settings.m_data, "The motion match data cannot be nullptr."); + + // Update the cached pointer to the trajectory feature. + const FeatureSchema& featureSchema = settings.m_data->GetFeatureSchema(); + for (Feature* feature : featureSchema.GetFeatures()) + { + if (feature->RTTI_GetType() == azrtti_typeid()) + { + m_cachedTrajectoryFeature = static_cast(feature); + break; + } + } + + // Debug display initialization. + const auto AddDebugDisplay = [=](AZ::s32 debugDisplayId) + { + if (debugDisplayId == -1) + { + return; + } + + AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus; + AzFramework::DebugDisplayRequestBus::Bind(debugDisplayBus, debugDisplayId); + + AzFramework::DebugDisplayRequests* debugDisplay = AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus); + if (debugDisplay) + { + m_debugDisplays.emplace_back(debugDisplay); + } + }; + // Draw the debug visualizations to the Animation Editor as well as the LY Editor viewport. + AZ::s32 animationEditorViewportId = -1; + EMStudio::ViewportPluginRequestBus::BroadcastResult(animationEditorViewportId, &EMStudio::ViewportPluginRequestBus::Events::GetViewportId); + AddDebugDisplay(animationEditorViewportId); + AddDebugDisplay(AzFramework::g_defaultSceneEntityDebugDisplayId); + + m_actorInstance = settings.m_actorInstance; + m_data = settings.m_data; + if (settings.m_data->GetFrameDatabase().GetNumFrames() == 0) + { + return; + } + + if (!m_motionInstance) + { + m_motionInstance = CreateMotionInstance(); + } + + if (!m_prevMotionInstance) + { + m_prevMotionInstance = CreateMotionInstance(); + } + + m_blendSourcePose.LinkToActorInstance(m_actorInstance); + m_blendSourcePose.InitFromBindPose(m_actorInstance); + + m_blendTargetPose.LinkToActorInstance(m_actorInstance); + m_blendTargetPose.InitFromBindPose(m_actorInstance); + + m_queryPose.LinkToActorInstance(m_actorInstance); + m_queryPose.InitFromBindPose(m_actorInstance); + + // Make sure we have enough space inside the frame floats array, which is used to search the kdTree. + const size_t numValuesInKdTree = m_data->GetKdTree().GetNumDimensions(); + m_queryFeatureValues.resize(numValuesInKdTree); + + // Initialize the trajectory history. + size_t rootJointIndex = m_actorInstance->GetActor()->GetMotionExtractionNodeIndex(); + if (rootJointIndex == InvalidIndex32) + { + rootJointIndex = 0; + } + m_trajectoryHistory.Init(*m_actorInstance->GetTransformData()->GetCurrentPose(), + rootJointIndex, + m_cachedTrajectoryFeature->GetFacingAxisDir(), + m_trajectorySecsToTrack); + } + + void MotionMatchingInstance::DebugDraw() + { + if (m_data && !m_debugDisplays.empty()) + { + for (AzFramework::DebugDisplayRequests* debugDisplay : m_debugDisplays) + { + if (debugDisplay) + { + const AZ::u32 prevState = debugDisplay->GetState(); + DebugDraw(*debugDisplay); + debugDisplay->SetState(prevState); + } + } + } + } + + void MotionMatchingInstance::DebugDraw(AzFramework::DebugDisplayRequests& debugDisplay) + { + AZ_PROFILE_SCOPE(Animation, "MotionMatchingInstance::DebugDraw"); + + // Get the lowest cost frame index from the last search. As we're searching the feature database with a much lower + // frequency and sample the animation onwards from this, the resulting frame index does not represent the current + // feature values from the shown pose. + const size_t curFrameIndex = GetLowestCostFrameIndex(); + if (curFrameIndex == InvalidIndex) + { + return; + } + + const FrameDatabase& frameDatabase = m_data->GetFrameDatabase(); + const FeatureSchema& featureSchema = m_data->GetFeatureSchema(); + + // Find the frame index in the frame database that belongs to the currently used pose. + const size_t currentFrame = frameDatabase.FindFrameIndex(m_motionInstance->GetMotion(), m_motionInstance->GetCurrentTime()); + + // Render the feature debug visualizations for the current frame. + if (currentFrame != InvalidIndex) + { + for (Feature* feature: featureSchema.GetFeatures()) + { + if (feature->GetDebugDrawEnabled()) + { + feature->DebugDraw(debugDisplay, this, currentFrame); + } + } + } + + // Draw the desired future trajectory and the sampled version of the past trajectory. + const AZ::Color trajectoryQueryColor = AZ::Color::CreateFromRgba(90,219,64,255); + m_trajectoryQuery.DebugDraw(debugDisplay, trajectoryQueryColor); + + // Draw the trajectory history starting after the sampled version of the past trajectory. + m_trajectoryHistory.DebugDraw(debugDisplay, trajectoryQueryColor, m_cachedTrajectoryFeature->GetPastTimeRange()); + } + + void MotionMatchingInstance::SamplePose(MotionInstance* motionInstance, Pose& outputPose) + { + const Pose* bindPose = m_actorInstance->GetTransformData()->GetBindPose(); + motionInstance->GetMotion()->Update(bindPose, &outputPose, motionInstance); + if (m_actorInstance->GetActor()->GetMotionExtractionNode() && m_actorInstance->GetMotionExtractionEnabled()) + { + outputPose.CompensateForMotionExtraction(); + } + } + + void MotionMatchingInstance::SamplePose(Motion* motion, Pose& outputPose, float sampleTime) const + { + MotionDataSampleSettings sampleSettings; + sampleSettings.m_actorInstance = outputPose.GetActorInstance(); + sampleSettings.m_inPlace = false; + sampleSettings.m_mirror = false; + sampleSettings.m_retarget = false; + sampleSettings.m_inputPose = sampleSettings.m_actorInstance->GetTransformData()->GetBindPose(); + + sampleSettings.m_sampleTime = sampleTime; + sampleSettings.m_sampleTime = AZ::GetClamp(sampleTime, 0.0f, motion->GetDuration()); + + motion->SamplePose(&outputPose, sampleSettings); + } + + void MotionMatchingInstance::PostUpdate([[maybe_unused]] float timeDelta) + { + if (!m_data) + { + m_motionExtractionDelta.Identity(); + return; + } + + const size_t lowestCostFrame = GetLowestCostFrameIndex(); + if (m_data->GetFrameDatabase().GetNumFrames() == 0 || lowestCostFrame == InvalidIndex) + { + m_motionExtractionDelta.Identity(); + return; + } + + // Blend the motion extraction deltas. + // Note: Make sure to update the previous as well as the current/target motion instances. + if (m_blendWeight >= 1.0f - AZ::Constants::FloatEpsilon) + { + m_motionInstance->ExtractMotion(m_motionExtractionDelta); + } + else if (m_blendWeight > AZ::Constants::FloatEpsilon && m_blendWeight < 1.0f - AZ::Constants::FloatEpsilon) + { + Transform targetMotionExtractionDelta; + m_motionInstance->ExtractMotion(m_motionExtractionDelta); + m_prevMotionInstance->ExtractMotion(targetMotionExtractionDelta); + m_motionExtractionDelta.Blend(targetMotionExtractionDelta, m_blendWeight); + } + else + { + m_prevMotionInstance->ExtractMotion(m_motionExtractionDelta); + } + } + + void MotionMatchingInstance::Output(Pose& outputPose) + { + AZ_PROFILE_SCOPE(Animation, "MotionMatchingInstance::Output"); + + if (!m_data) + { + outputPose.InitFromBindPose(m_actorInstance); + return; + } + + const size_t lowestCostFrame = GetLowestCostFrameIndex(); + if (m_data->GetFrameDatabase().GetNumFrames() == 0 || lowestCostFrame == InvalidIndex) + { + outputPose.InitFromBindPose(m_actorInstance); + return; + } + + // Sample the motions and blend the results when needed. + if (m_blendWeight >= 1.0f - AZ::Constants::FloatEpsilon) + { + m_blendTargetPose.InitFromBindPose(m_actorInstance); + if (m_motionInstance) + { + SamplePose(m_motionInstance, m_blendTargetPose); + } + outputPose = m_blendTargetPose; + } + else if (m_blendWeight > AZ::Constants::FloatEpsilon && m_blendWeight < 1.0f - AZ::Constants::FloatEpsilon) + { + m_blendSourcePose.InitFromBindPose(m_actorInstance); + m_blendTargetPose.InitFromBindPose(m_actorInstance); + if (m_motionInstance) + { + SamplePose(m_motionInstance, m_blendTargetPose); + } + if (m_prevMotionInstance) + { + SamplePose(m_prevMotionInstance, m_blendSourcePose); + } + + outputPose = m_blendSourcePose; + outputPose.Blend(&m_blendTargetPose, m_blendWeight); + } + else + { + m_blendSourcePose.InitFromBindPose(m_actorInstance); + if (m_prevMotionInstance) + { + SamplePose(m_prevMotionInstance, m_blendSourcePose); + } + outputPose = m_blendSourcePose; + } + } + + void MotionMatchingInstance::Update(float timePassedInSeconds, const AZ::Vector3& targetPos, const AZ::Vector3& targetFacingDir, TrajectoryQuery::EMode mode, float pathRadius, float pathSpeed) + { + AZ_PROFILE_SCOPE(Animation, "MotionMatchingInstance::Update"); + + if (!m_data) + { + return; + } + + size_t currentFrameIndex = GetLowestCostFrameIndex(); + if (currentFrameIndex == InvalidIndex) + { + currentFrameIndex = 0; + } + + // Add the sample from the last frame (post-motion extraction) + m_trajectoryHistory.AddSample(*m_actorInstance->GetTransformData()->GetCurrentPose()); + // Update the time. After this there is no sample for the updated time in the history as we're about to prepare this with the current update. + m_trajectoryHistory.Update(timePassedInSeconds); + + // Register the current actor instance position to the history data of the spline. + m_trajectoryQuery.Update(m_actorInstance, + m_cachedTrajectoryFeature, + m_trajectoryHistory, + mode, + targetPos, + targetFacingDir, + timePassedInSeconds, + pathRadius, + pathSpeed); + + // Calculate the new time value of the motion, but don't set it yet (the syncing might adjust this again) + m_motionInstance->SetFreezeAtLastFrame(true); + m_motionInstance->SetMaxLoops(1); + const float newMotionTime = m_motionInstance->CalcPlayStateAfterUpdate(timePassedInSeconds).m_currentTime; + m_newMotionTime = newMotionTime; + + // Keep on playing the previous instance as we're blending the poses and motion extraction deltas. + m_prevMotionInstance->Update(timePassedInSeconds); + + m_timeSinceLastFrameSwitch += timePassedInSeconds; + + const float lowestCostSearchTimeInterval = 1.0f / m_lowestCostSearchFrequency; + + if (m_blending) + { + const float maxBlendTime = lowestCostSearchTimeInterval; + m_blendProgressTime += timePassedInSeconds; + if (m_blendProgressTime > maxBlendTime) + { + m_blendWeight = 1.0f; + m_blendProgressTime = maxBlendTime; + m_blending = false; + } + else + { + m_blendWeight = AZ::GetClamp(m_blendProgressTime / maxBlendTime, 0.0f, 1.0f); + } + } + + const bool searchLowestCostFrame = m_timeSinceLastFrameSwitch >= lowestCostSearchTimeInterval; + if (searchLowestCostFrame) + { + // Calculate the input query pose for the motion matching search algorithm. + { + // Sample the pose for the new motion time as the motion instance has not been updated with the timeDelta from this frame yet. + SamplePose(m_motionInstance->GetMotion(), m_queryPose, newMotionTime); + + // Copy over the motion extraction joint transform from the current pose to the newly sampled pose. + // When sampling a motion, the motion extraction joint is in animation space, while we need the query pose to be in + // world space. + // Note: This does not yet take the extraction delta from the current tick into account. + if (m_actorInstance->GetActor()->GetMotionExtractionNode()) + { + const Pose* currentPose = m_actorInstance->GetTransformData()->GetCurrentPose(); + const size_t motionExtractionJointIndex = m_actorInstance->GetActor()->GetMotionExtractionNodeIndex(); + m_queryPose.SetWorldSpaceTransform(motionExtractionJointIndex, + currentPose->GetWorldSpaceTransform(motionExtractionJointIndex)); + } + + // Calculate the joint velocities for the sampled pose using the same method as we do for the frame database. + PoseDataJointVelocities* velocityPoseData = m_queryPose.GetAndPreparePoseData(m_actorInstance); + velocityPoseData->CalculateVelocity(m_motionInstance, m_cachedTrajectoryFeature->GetRelativeToNodeIndex()); + } + + const FeatureMatrix& featureMatrix = m_data->GetFeatureMatrix(); + const FrameDatabase& frameDatabase = m_data->GetFrameDatabase(); + + Feature::FrameCostContext frameCostContext(featureMatrix, m_queryPose); + frameCostContext.m_trajectoryQuery = &m_trajectoryQuery; + frameCostContext.m_actorInstance = m_actorInstance; + const size_t lowestCostFrameIndex = FindLowestCostFrameIndex(frameCostContext); + + const Frame& currentFrame = frameDatabase.GetFrame(currentFrameIndex); + const Frame& lowestCostFrame = frameDatabase.GetFrame(lowestCostFrameIndex); + const bool sameMotion = (currentFrame.GetSourceMotion() == lowestCostFrame.GetSourceMotion()); + const float timeBetweenFrames = newMotionTime - lowestCostFrame.GetSampleTime(); + const bool sameLocation = sameMotion && (AZ::GetAbs(timeBetweenFrames) < 0.1f); + + if (lowestCostFrameIndex != currentFrameIndex && !sameLocation) + { + // Start a blend. + m_blending = true; + m_blendWeight = 0.0f; + m_blendProgressTime = 0.0f; + + // Store the current motion instance state, so we can sample this as source pose. + m_prevMotionInstance->SetMotion(m_motionInstance->GetMotion()); + m_prevMotionInstance->SetMirrorMotion(m_motionInstance->GetMirrorMotion()); + m_prevMotionInstance->SetCurrentTime(newMotionTime, true); + m_prevMotionInstance->SetLastCurrentTime(m_prevMotionInstance->GetCurrentTime() - timePassedInSeconds); + + m_lowestCostFrameIndex = lowestCostFrameIndex; + + m_motionInstance->SetMotion(lowestCostFrame.GetSourceMotion()); + m_motionInstance->SetMirrorMotion(lowestCostFrame.GetMirrored()); + + // The new motion time will become the current time after this frame while the current time + // becomes the last current time. As we just start playing at the search frame, calculate + // the last time based on the time delta. + m_motionInstance->SetCurrentTime(lowestCostFrame.GetSampleTime() - timePassedInSeconds, true); + m_newMotionTime = lowestCostFrame.GetSampleTime(); + } + + // Do this always, else wise we search for the lowest cost frame index too many times. + m_timeSinceLastFrameSwitch = 0.0f; + } + + // ImGui monitor + { +#ifdef IMGUI_ENABLED + const KdTree& kdTree = m_data->GetKdTree(); + ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetKdTreeMemoryUsage, kdTree.CalcMemoryUsageInBytes()); + ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetKdTreeNumNodes, kdTree.GetNumNodes()); + ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetKdTreeNumDimensions, kdTree.GetNumDimensions()); + // TODO: add memory usage for frame database + + const FeatureMatrix& featureMatrix = m_data->GetFeatureMatrix(); + ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetFeatureMatrixMemoryUsage, featureMatrix.CalcMemoryUsageInBytes()); + ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetFeatureMatrixNumFrames, featureMatrix.rows()); + ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::SetFeatureMatrixNumComponents, featureMatrix.cols()); +#endif + } + } + + size_t MotionMatchingInstance::FindLowestCostFrameIndex(const Feature::FrameCostContext& context) + { + AZ::Debug::Timer timer; + timer.Stamp(); + + AZ_PROFILE_SCOPE(Animation, "MotionMatchingInstance::FindLowestCostFrameIndex"); + + const FrameDatabase& frameDatabase = m_data->GetFrameDatabase(); + const FeatureSchema& featureSchema = m_data->GetFeatureSchema(); + const FeatureTrajectory* trajectoryFeature = m_cachedTrajectoryFeature; + + // 1. Broad-phase search using KD-tree + { + // Build the input query features that will be compared to every entry in the feature database in the motion matching search. + size_t startOffset = 0; + for (Feature* feature : m_data->GetFeaturesInKdTree()) + { + feature->FillQueryFeatureValues(startOffset, m_queryFeatureValues, context); + startOffset += feature->GetNumDimensions(); + } + AZ_Assert(startOffset == m_queryFeatureValues.size(), "Frame float vector is not the expected size."); + + // Find our nearest frames. + m_data->GetKdTree().FindNearestNeighbors(m_queryFeatureValues, m_nearestFrames); + } + + // 2. Narrow-phase, brute force find the actual best matching frame (frame with the minimal cost). + float minCost = FLT_MAX; + size_t minCostFrameIndex = 0; + m_tempCosts.resize(featureSchema.GetNumFeatures()); + m_minCosts.resize(featureSchema.GetNumFeatures()); + float minTrajectoryPastCost = 0.0f; + float minTrajectoryFutureCost = 0.0f; + + // Iterate through the frames filtered by the broad-phase search. + for (const size_t frameIndex : m_nearestFrames) + { + const Frame& frame = frameDatabase.GetFrame(frameIndex); + + // TODO: This shouldn't be there, we should be discarding the frames when extracting the features and not at runtime when checking the cost. + if (frame.GetSampleTime() >= frame.GetSourceMotion()->GetDuration() - 1.0f) + { + continue; + } + + float frameCost = 0.0f; + + // Calculate the frame cost by accumulating the weighted feature costs. + for (size_t featureIndex = 0; featureIndex < featureSchema.GetNumFeatures(); ++featureIndex) + { + Feature* feature = featureSchema.GetFeature(featureIndex); + if (feature->RTTI_GetType() != azrtti_typeid()) + { + const float featureCost = feature->CalculateFrameCost(frameIndex, context); + const float featureCostFactor = feature->GetCostFactor(); + const float featureFinalCost = featureCost * featureCostFactor; + + frameCost += featureFinalCost; + m_tempCosts[featureIndex] = featureFinalCost; + } + } + + // Manually add the trajectory cost. + float trajectoryPastCost = 0.0f; + float trajectoryFutureCost = 0.0f; + if (trajectoryFeature) + { + trajectoryPastCost = trajectoryFeature->CalculatePastFrameCost(frameIndex, context) * trajectoryFeature->GetPastCostFactor(); + trajectoryFutureCost = trajectoryFeature->CalculateFutureFrameCost(frameIndex, context) * trajectoryFeature->GetFutureCostFactor(); + frameCost += trajectoryPastCost; + frameCost += trajectoryFutureCost; + } + + // Track the minimum feature and frame costs. + if (frameCost < minCost) + { + minCost = frameCost; + minCostFrameIndex = frameIndex; + + for (size_t featureIndex = 0; featureIndex < featureSchema.GetNumFeatures(); ++featureIndex) + { + Feature* feature = featureSchema.GetFeature(featureIndex); + if (feature->RTTI_GetType() != azrtti_typeid()) + { + m_minCosts[featureIndex] = m_tempCosts[featureIndex]; + } + } + + minTrajectoryPastCost = trajectoryPastCost; + minTrajectoryFutureCost = trajectoryFutureCost; + } + } + + // 3. ImGui debug visualization + { + const float time = timer.GetDeltaTimeInSeconds(); + ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::PushPerformanceHistogramValue, "FindLowestCostFrameIndex", time * 1000.0f); + + for (size_t featureIndex = 0; featureIndex < featureSchema.GetNumFeatures(); ++featureIndex) + { + Feature* feature = featureSchema.GetFeature(featureIndex); + if (feature->RTTI_GetType() != azrtti_typeid()) + { + ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::PushCostHistogramValue, + feature->GetName().c_str(), + m_minCosts[featureIndex], + feature->GetDebugDrawColor()); + } + } + + if (trajectoryFeature) + { + ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::PushCostHistogramValue, "Future Trajectory", minTrajectoryFutureCost, trajectoryFeature->GetDebugDrawColor()); + ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::PushCostHistogramValue, "Past Trajectory", minTrajectoryPastCost, trajectoryFeature->GetDebugDrawColor()); + } + + ImGuiMonitorRequestBus::Broadcast(&ImGuiMonitorRequests::PushCostHistogramValue, "Total Cost", minCost, AZ::Color::CreateFromRgba(202,255,191,255)); + } + + return minCostFrameIndex; + } +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/MotionMatchingInstance.h b/Gems/MotionMatching/Code/Source/MotionMatchingInstance.h new file mode 100644 index 0000000000..49c781162d --- /dev/null +++ b/Gems/MotionMatching/Code/Source/MotionMatchingInstance.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace AZ +{ + class ReflectContext; +} + +namespace EMotionFX +{ + class ActorInstance; + class Motion; +} + +namespace EMotionFX::MotionMatching +{ + class MotionMatchingData; + + class EMFX_API MotionMatchingInstance + { + public: + AZ_RTTI(MotionMatchingInstance, "{1ED03AD8-0FB2-431B-AF01-02F7E930EB73}") + AZ_CLASS_ALLOCATOR_DECL + + virtual ~MotionMatchingInstance(); + + struct EMFX_API InitSettings + { + ActorInstance* m_actorInstance = nullptr; + MotionMatchingData* m_data = nullptr; + }; + void Init(const InitSettings& settings); + + void DebugDraw(); + void DebugDraw(AzFramework::DebugDisplayRequests& debugDisplay); + + void Update(float timePassedInSeconds, const AZ::Vector3& targetPos, const AZ::Vector3& targetFacingDir, TrajectoryQuery::EMode mode, float pathRadius, float pathSpeed); + void PostUpdate(float timeDelta); + void Output(Pose& outputPose); + + MotionInstance* GetMotionInstance() const { return m_motionInstance; } + ActorInstance* GetActorInstance() const { return m_actorInstance; } + MotionMatchingData* GetData() const { return m_data; } + + size_t GetLowestCostFrameIndex() const { return m_lowestCostFrameIndex; } + void SetLowestCostSearchFrequency(float frequency) { m_lowestCostSearchFrequency = frequency; } + float GetNewMotionTime() const { return m_newMotionTime; } + + /** + * Get the cached trajectory feature. + * The trajectory feature is searched in the feature schema used in the current instance at init time. + */ + FeatureTrajectory* GetTrajectoryFeature() const { return m_cachedTrajectoryFeature; } + const TrajectoryQuery& GetTrajectoryQuery() const { return m_trajectoryQuery; } + const TrajectoryHistory& GetTrajectoryHistory() const { return m_trajectoryHistory; } + const Transform& GetMotionExtractionDelta() const { return m_motionExtractionDelta; } + + private: + MotionInstance* CreateMotionInstance() const; + void SamplePose(MotionInstance* motionInstance, Pose& outputPose); + void SamplePose(Motion* motion, Pose& outputPose, float sampleTime) const; + + size_t FindLowestCostFrameIndex(const Feature::FrameCostContext& context); + + MotionMatchingData* m_data = nullptr; + ActorInstance* m_actorInstance = nullptr; + Pose m_blendSourcePose; + Pose m_blendTargetPose; + Pose m_queryPose; //! Input query pose for the motion matching search. + MotionInstance* m_motionInstance = nullptr; + MotionInstance* m_prevMotionInstance = nullptr; + Transform m_motionExtractionDelta = Transform::CreateIdentity(); + + /// Buffers used for the broad-phase KD-tree search. + AZStd::vector m_queryFeatureValues; /** The input query features to be compared to every entry/row in the feature matrix with the motion matching search. */ + AZStd::vector m_nearestFrames; /** Stores the nearest matching frames / search result from the KD-tree. */ + + FeatureTrajectory* m_cachedTrajectoryFeature = nullptr; /** Cached pointer to the trajectory feature in the feature schema. */ + TrajectoryQuery m_trajectoryQuery; + TrajectoryHistory m_trajectoryHistory; + static constexpr float m_trajectorySecsToTrack = 5.0f; + + float m_timeSinceLastFrameSwitch = 0.0f; + float m_newMotionTime = 0.0f; + size_t m_lowestCostFrameIndex = InvalidIndex; + float m_lowestCostSearchFrequency = 5.0f; //< How often the lowest cost frame shall be searched per second. + + bool m_blending = false; + float m_blendWeight = 1.0f; + float m_blendProgressTime = 0.0f; // How long are we already blending? In seconds. + + /// Buffers used for FindLowestCostFrameIndex(). + AZStd::vector m_tempCosts; + AZStd::vector m_minCosts; + + AZStd::vector m_debugDisplays; + }; +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/MotionMatchingModule.cpp b/Gems/MotionMatching/Code/Source/MotionMatchingModule.cpp new file mode 100644 index 0000000000..bc29172bb5 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/MotionMatchingModule.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include + +namespace EMotionFX::MotionMatching +{ + class MotionMatchingModule + : public MotionMatchingModuleInterface + { + public: + AZ_RTTI(MotionMatchingModule, "{cf4381d1-0207-4ef8-85f0-6c88ec28a7b6}", MotionMatchingModuleInterface); + AZ_CLASS_ALLOCATOR(MotionMatchingModule, AZ::SystemAllocator, 0); + }; +}// namespace EMotionFX::MotionMatching + +AZ_DECLARE_MODULE_CLASS(Gem_MotionMatching, EMotionFX::MotionMatching::MotionMatchingModule) diff --git a/Gems/MotionMatching/Code/Source/MotionMatchingModuleInterface.h b/Gems/MotionMatching/Code/Source/MotionMatchingModuleInterface.h new file mode 100644 index 0000000000..e2110263f5 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/MotionMatchingModuleInterface.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +namespace EMotionFX::MotionMatching +{ + class MotionMatchingModuleInterface + : public AZ::Module + { + public: + AZ_RTTI(MotionMatchingModuleInterface, "{33e8e826-b143-4008-89f3-9a46ad3de4fe}", AZ::Module); + AZ_CLASS_ALLOCATOR(MotionMatchingModuleInterface, AZ::SystemAllocator, 0); + + MotionMatchingModuleInterface() + { + m_descriptors.insert(m_descriptors.end(), + { + MotionMatchingSystemComponent::CreateDescriptor(), + }); + } + + /// Add required SystemComponents to the SystemEntity. + AZ::ComponentTypeList GetRequiredSystemComponents() const override + { + return AZ::ComponentTypeList + { + azrtti_typeid(), + }; + } + }; +}// namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/MotionMatchingSystemComponent.cpp b/Gems/MotionMatching/Code/Source/MotionMatchingSystemComponent.cpp new file mode 100644 index 0000000000..073362d741 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/MotionMatchingSystemComponent.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace EMotionFX::MotionMatching +{ + void MotionMatchingSystemComponent::Reflect(AZ::ReflectContext* context) + { + if (AZ::SerializeContext* serialize = azrtti_cast(context)) + { + serialize->Class() + ->Version(0) + ; + + if (AZ::EditContext* ec = serialize->GetEditContext()) + { + ec->Class("MotionMatching", "[Description of functionality provided by this System Component]") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System")) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ; + } + } + + EMotionFX::MotionMatching::DiscardFrameEventData::Reflect(context); + EMotionFX::MotionMatching::TagEventData::Reflect(context); + + EMotionFX::MotionMatching::FeatureSchema::Reflect(context); + EMotionFX::MotionMatching::Feature::Reflect(context); + EMotionFX::MotionMatching::FeaturePosition::Reflect(context); + EMotionFX::MotionMatching::FeatureTrajectory::Reflect(context); + EMotionFX::MotionMatching::FeatureVelocity::Reflect(context); + + EMotionFX::MotionMatching::PoseDataJointVelocities::Reflect(context); + + EMotionFX::MotionMatching::BlendTreeMotionMatchNode::Reflect(context); + } + + void MotionMatchingSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("MotionMatchingService")); + } + + void MotionMatchingSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + incompatible.push_back(AZ_CRC_CE("MotionMatchingService")); + } + + void MotionMatchingSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required) + { + required.push_back(AZ_CRC("EMotionFXAnimationService", 0x3f8a6369)); + } + + void MotionMatchingSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent) + { + } + + MotionMatchingSystemComponent::MotionMatchingSystemComponent() + { + if (MotionMatchingInterface::Get() == nullptr) + { + MotionMatchingInterface::Register(this); + } + } + + MotionMatchingSystemComponent::~MotionMatchingSystemComponent() + { + if (MotionMatchingInterface::Get() == this) + { + MotionMatchingInterface::Unregister(this); + } + } + + void MotionMatchingSystemComponent::Init() + { + } + + void MotionMatchingSystemComponent::Activate() + { + MotionMatchingRequestBus::Handler::BusConnect(); + AZ::TickBus::Handler::BusConnect(); + + // Register the motion matching anim graph node + EMotionFX::AnimGraphObject* motionMatchNodeObject = EMotionFX::AnimGraphObjectFactory::Create(azrtti_typeid()); + auto motionMatchNode = azdynamic_cast(motionMatchNodeObject); + if (motionMatchNode) + { + EMotionFX::Integration::EMotionFXRequestBus::Broadcast(&EMotionFX::Integration::EMotionFXRequests::RegisterAnimGraphObjectType, motionMatchNode); + delete motionMatchNode; + } + + // Register the joint velocities pose data. + EMotionFX::GetPoseDataFactory().AddPoseDataType(azrtti_typeid()); + } + + void MotionMatchingSystemComponent::Deactivate() + { + AZ::TickBus::Handler::BusDisconnect(); + MotionMatchingRequestBus::Handler::BusDisconnect(); + } + + void MotionMatchingSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) + { + } +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/MotionMatchingSystemComponent.h b/Gems/MotionMatching/Code/Source/MotionMatchingSystemComponent.h new file mode 100644 index 0000000000..6a5b5a7b73 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/MotionMatchingSystemComponent.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include + +namespace EMotionFX::MotionMatching +{ + class MotionMatchingSystemComponent + : public AZ::Component + , protected MotionMatchingRequestBus::Handler + , public AZ::TickBus::Handler + { + public: + AZ_COMPONENT(MotionMatchingSystemComponent, "{158cd35c-b548-4d7b-9493-9a3c5c359e49}"); + + static void Reflect(AZ::ReflectContext* context); + + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); + + MotionMatchingSystemComponent(); + ~MotionMatchingSystemComponent(); + + protected: + //////////////////////////////////////////////////////////////////////// + // MotionMatchingRequestBus interface implementation + + //////////////////////////////////////////////////////////////////////// + // AZ::Component interface implementation + void Init() override; + void Activate() override; + void Deactivate() override; + //////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////// + // AZTickBus interface implementation + void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; + //////////////////////////////////////////////////////////////////////// + }; +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/PoseDataJointVelocities.cpp b/Gems/MotionMatching/Code/Source/PoseDataJointVelocities.cpp new file mode 100644 index 0000000000..b74e8d9df7 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/PoseDataJointVelocities.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include + +namespace EMotionFX::MotionMatching +{ + AZ_CLASS_ALLOCATOR_IMPL(PoseDataJointVelocities, MotionMatchAllocator, 0) + + PoseDataJointVelocities::PoseDataJointVelocities() + : PoseData() + { + } + + PoseDataJointVelocities::~PoseDataJointVelocities() + { + Clear(); + } + + void PoseDataJointVelocities::Clear() + { + m_velocities.clear(); + m_angularVelocities.clear(); + } + + void PoseDataJointVelocities::LinkToActorInstance(const ActorInstance* actorInstance) + { + m_velocities.resize(actorInstance->GetNumNodes()); + m_angularVelocities.resize(actorInstance->GetNumNodes()); + + SetRelativeToJointIndex(actorInstance->GetActor()->GetMotionExtractionNodeIndex()); + } + + void PoseDataJointVelocities::SetRelativeToJointIndex(size_t relativeToJointIndex) + { + if (relativeToJointIndex == InvalidIndex) + { + m_relativeToJointIndex = 0; + } + else + { + m_relativeToJointIndex = relativeToJointIndex; + } + } + + void PoseDataJointVelocities::LinkToActor(const Actor* actor) + { + AZ_UNUSED(actor); + Clear(); + } + + void PoseDataJointVelocities::Reset() + { + const size_t numJoints = m_velocities.size(); + for (size_t i = 0; i < numJoints; ++i) + { + m_velocities[i] = AZ::Vector3::CreateZero(); + m_angularVelocities[i] = AZ::Vector3::CreateZero(); + } + } + + void PoseDataJointVelocities::CopyFrom(const PoseData* from) + { + AZ_Assert(from->RTTI_GetType() == azrtti_typeid(), "Cannot copy from pose data other than joint velocity pose data."); + const PoseDataJointVelocities* fromVelocityPoseData = static_cast(from); + + m_isUsed = fromVelocityPoseData->m_isUsed; + m_velocities = fromVelocityPoseData->m_velocities; + m_angularVelocities = fromVelocityPoseData->m_angularVelocities; + m_relativeToJointIndex = fromVelocityPoseData->m_relativeToJointIndex; + } + + void PoseDataJointVelocities::Blend(const Pose* destPose, float weight) + { + PoseDataJointVelocities* destPoseData = destPose->GetPoseData(); + + if (destPoseData && destPoseData->IsUsed()) + { + AZ_Assert(m_velocities.size() == destPoseData->m_velocities.size(), "Expected the same number of joints and velocities in the destination pose data."); + + if (m_isUsed) + { + // Blend while both, the destination pose as well as the current pose hold joint velocities. + for (size_t i = 0; i < m_velocities.size(); ++i) + { + m_velocities[i] = m_velocities[i].Lerp(destPoseData->m_velocities[i], weight); + m_angularVelocities[i] = m_angularVelocities[i].Lerp(destPoseData->m_angularVelocities[i], weight); + } + } + else + { + // The destination pose data is used while the current one is not. Just copy over the velocities from the destination. + m_velocities = destPoseData->m_velocities; + m_angularVelocities = destPoseData->m_angularVelocities; + } + } + else + { + // Destination pose either doesn't contain velocity pose data or it is unused. + // Don't do anything and keep the current velocities. + } + } + + void PoseDataJointVelocities::DebugDraw(AzFramework::DebugDisplayRequests& debugDisplay, const AZ::Color& color) const + { + AZ_Assert(m_pose->GetNumTransforms() == m_velocities.size(), "Expected a joint velocity for each joint in the pose."); + + const Pose* pose = m_pose; + for (size_t i = 0; i < m_velocities.size(); ++i) + { + const size_t jointIndex = i; + + // draw linear velocity + { + const Transform jointModelTM = pose->GetModelSpaceTransform(jointIndex); + const Transform relativeToWorldTM = pose->GetWorldSpaceTransform(m_relativeToJointIndex); + const AZ::Vector3 jointPosition = relativeToWorldTM.TransformPoint(jointModelTM.m_position); + + const AZ::Vector3& velocity = m_velocities[i]; + + const float scale = 0.15f; + const AZ::Vector3 velocityWorldSpace = relativeToWorldTM.TransformVector(velocity * scale); + + DebugDrawVelocity(debugDisplay, jointPosition, velocityWorldSpace, color); + } + } + } + + void PoseDataJointVelocities::CalculateVelocity(MotionInstance* motionInstance, size_t relativeToJointIndex) + { + SetRelativeToJointIndex(relativeToJointIndex); + ActorInstance* actorInstance = motionInstance->GetActorInstance(); + m_velocities.resize(actorInstance->GetNumNodes()); + m_angularVelocities.resize(actorInstance->GetNumNodes()); + for (size_t i = 0; i < m_velocities.size(); ++i) + { + Feature::CalculateVelocity(i, m_relativeToJointIndex, motionInstance, m_velocities[i]); + // TODO: Angular velocity not used yet. + } + } + + void PoseDataJointVelocities::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (serializeContext) + { + serializeContext->Class()->Version(1); + } + } +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/PoseDataJointVelocities.h b/Gems/MotionMatching/Code/Source/PoseDataJointVelocities.h new file mode 100644 index 0000000000..68404b41ec --- /dev/null +++ b/Gems/MotionMatching/Code/Source/PoseDataJointVelocities.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include + +namespace EMotionFX::MotionMatching +{ + /** + * Extends a given pose with joint-relative linear and angular velocities. + **/ + class EMFX_API PoseDataJointVelocities + : public PoseData + { + public: + AZ_RTTI(PoseDataJointVelocities, "{9C082B82-7225-4550-A52C-C920CCC2482C}", PoseData) + AZ_CLASS_ALLOCATOR_DECL + + PoseDataJointVelocities(); + ~PoseDataJointVelocities(); + + void Clear(); + + void LinkToActorInstance(const ActorInstance* actorInstance) override; + void LinkToActor(const Actor* actor) override; + void Reset() override; + + void CopyFrom(const PoseData* from) override; + void Blend(const Pose* destPose, float weight) override; + + void CalculateVelocity(MotionInstance* motionInstance, size_t relativeToJointIndex); + void DebugDraw(AzFramework::DebugDisplayRequests& debugDisplay, const AZ::Color& color) const override; + + AZStd::vector& GetVelocities() { return m_velocities; } + const AZStd::vector& GetVelocities() const { return m_velocities; } + const AZ::Vector3& GetVelocity(size_t jointIndex) { return m_velocities[jointIndex]; } + + AZStd::vector& GetAngularVelocities() { return m_angularVelocities; } + const AZStd::vector& GetAngularVelocities() const { return m_angularVelocities; } + const AZ::Vector3& GetAngularVelocity(size_t jointIndex) { return m_angularVelocities[jointIndex]; } + + static void Reflect(AZ::ReflectContext* context); + + void SetRelativeToJointIndex(size_t relativeToJointIndex); + + private: + AZStd::vector m_velocities; + AZStd::vector m_angularVelocities; + size_t m_relativeToJointIndex = InvalidIndex; + }; +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/TrajectoryHistory.cpp b/Gems/MotionMatching/Code/Source/TrajectoryHistory.cpp new file mode 100644 index 0000000000..3a5adedf48 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/TrajectoryHistory.cpp @@ -0,0 +1,167 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include + +namespace EMotionFX::MotionMatching +{ + TrajectoryHistory::Sample operator*(TrajectoryHistory::Sample sample, float weight) + { + return {sample.m_position * weight, sample.m_facingDirection * weight}; + } + + TrajectoryHistory::Sample operator*(float weight, TrajectoryHistory::Sample sample) + { + return {weight * sample.m_position, weight * sample.m_facingDirection}; + } + + TrajectoryHistory::Sample operator+(TrajectoryHistory::Sample lhs, const TrajectoryHistory::Sample& rhs) + { + return {lhs.m_position + rhs.m_position, lhs.m_facingDirection + rhs.m_facingDirection}; + } + + void TrajectoryHistory::Init(const Pose& pose, size_t jointIndex, const AZ::Vector3& facingAxisDir, float numSecondsToTrack) + { + AZ_Assert(numSecondsToTrack > 0.0f, "Number of seconds to track has to be greater than zero."); + Clear(); + m_jointIndex = jointIndex; + m_facingAxisDir = facingAxisDir; + m_numSecondsToTrack = numSecondsToTrack; + + // Pre-fill the history with samples from the current joint position. + PrefillSamples(pose, /*timeDelta=*/1.0f / 60.0f); + } + + void TrajectoryHistory::AddSample(const Pose& pose) + { + Sample sample; + const Transform worldSpaceTransform = pose.GetWorldSpaceTransform(m_jointIndex); + sample.m_position = worldSpaceTransform.m_position; + sample.m_facingDirection = worldSpaceTransform.TransformVector(m_facingAxisDir).GetNormalizedSafe(); + + // The new key will be added at the end of the keytrack. + m_keytrack.AddKey(m_currentTime, sample); + + while (m_keytrack.GetNumKeys() > 2 && + ((m_keytrack.GetKey(m_keytrack.GetNumKeys() - 2)->GetTime() - m_keytrack.GetFirstTime()) > m_numSecondsToTrack)) + { + m_keytrack.RemoveKey(0); // Remove first (oldest) key + } + } + + void TrajectoryHistory::PrefillSamples(const Pose& pose, float timeDelta) + { + const size_t numKeyframes = aznumeric_caster<>(m_numSecondsToTrack / timeDelta); + for (size_t i = 0; i < numKeyframes; ++i) + { + AddSample(pose); + Update(timeDelta); + } + } + + void TrajectoryHistory::Clear() + { + m_jointIndex = 0; + m_currentTime = 0.0f; + m_keytrack.ClearKeys(); + } + + void TrajectoryHistory::Update(float timeDelta) + { + m_currentTime += timeDelta; + } + + TrajectoryHistory::Sample TrajectoryHistory::Evaluate(float time) const + { + if (m_keytrack.GetNumKeys() == 0) + { + return {}; + } + + return m_keytrack.GetValueAtTime(m_keytrack.GetLastTime() - time); + } + + TrajectoryHistory::Sample TrajectoryHistory::EvaluateNormalized(float normalizedTime) const + { + const float firstTime = m_keytrack.GetFirstTime(); + const float lastTime = m_keytrack.GetLastTime(); + const float range = lastTime - firstTime; + + const float time = (1.0f - normalizedTime) * range + firstTime; + return m_keytrack.GetValueAtTime(time); + } + + void TrajectoryHistory::DebugDraw(AzFramework::DebugDisplayRequests& debugDisplay, const AZ::Color& color, float timeStart) const + { + const size_t numKeyframes = m_keytrack.GetNumKeys(); + if (numKeyframes == 0) + { + return; + } + + // Clip some of the newest samples. + const float adjustedLastTime = m_keytrack.GetLastTime() - timeStart; + size_t adjustedLastKey = m_keytrack.FindKeyNumber(adjustedLastTime); + if (adjustedLastKey == InvalidIndex) + { + adjustedLastKey = m_keytrack.GetNumKeys() - 1; + } + const float firstTime = m_keytrack.GetFirstTime(); + const float range = adjustedLastTime - firstTime; + + debugDisplay.DepthTestOff(); + + for (size_t i = 0; i < adjustedLastKey; ++i) + { + const float time = m_keytrack.GetKey(i)->GetTime(); + const float normalized = (time - firstTime) / range; + if (normalized < 0.3f) + { + continue; + } + + // Decrease size and fade out alpha the older the sample is. + AZ::Color finalColor = color; + finalColor.SetA(finalColor.GetA() * 0.6f * normalized); + const float markerSize = m_debugMarkerSize * 0.7f * normalized; + + const Sample currentSample = m_keytrack.GetKey(i)->GetValue(); + debugDisplay.SetColor(finalColor); + debugDisplay.DrawBall(currentSample.m_position, markerSize, /*drawShaded=*/false); + + const float facingDirectionLength = m_debugMarkerSize * 10.0f * normalized; + debugDisplay.DrawLine(currentSample.m_position, currentSample.m_position + currentSample.m_facingDirection * facingDirectionLength); + } + } + + void TrajectoryHistory::DebugDrawSampled(AzFramework::DebugDisplayRequests& debugDisplay, + size_t numSamples, + const AZ::Color& color) const + { + debugDisplay.DepthTestOff(); + debugDisplay.SetColor(color); + + Sample lastSample = EvaluateNormalized(0.0f); + for (size_t i = 0; i < numSamples; ++i) + { + const float sampleTime = i / static_cast(numSamples - 1); + const Sample currentSample = EvaluateNormalized(sampleTime); + if (i > 0) + { + debugDisplay.DrawLine(lastSample.m_position, currentSample.m_position); + } + + debugDisplay.DrawBall(currentSample.m_position, m_debugMarkerSize, /*drawShaded=*/false); + + lastSample = currentSample; + } + } +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/TrajectoryHistory.h b/Gems/MotionMatching/Code/Source/TrajectoryHistory.h new file mode 100644 index 0000000000..833125d27f --- /dev/null +++ b/Gems/MotionMatching/Code/Source/TrajectoryHistory.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +#include + +#include +#include + +namespace EMotionFX::MotionMatching +{ + //! Used to store the trajectory history for the root motion (motion extraction node). + //! The trajectory history is independent of the trajectory feature and captures a sample with every engine tick. + //! The recorded history needs to record and track at least the time the trajectory feature/query requires. + class EMFX_API TrajectoryHistory + { + public: + void Init(const Pose& pose, size_t jointIndex, const AZ::Vector3& facingAxisDir, float numSecondsToTrack); + void Clear(); + + void Update(float timeDelta); + void AddSample(const Pose& pose); + + struct EMFX_API Sample + { + AZ::Vector3 m_position = AZ::Vector3::CreateZero(); + AZ::Vector3 m_facingDirection = AZ::Vector3::CreateZero(); + }; + + //! time in range [0, m_numSecondsToTrack] + Sample Evaluate(float time) const; + + //! time in range [0, 1] where 0 is the current character position and 1 the oldest keyframe in the trajectory history + Sample EvaluateNormalized(float normalizedTime) const; + + float GetNumSecondsToTrack() const { return m_numSecondsToTrack; } + float GetCurrentTime() const { return m_currentTime; } + size_t GetJointIndex() const { return m_jointIndex; } + + void DebugDraw(AzFramework::DebugDisplayRequests& debugDisplay, const AZ::Color& color, float timeStart = 0.0f) const; + void DebugDrawSampled(AzFramework::DebugDisplayRequests& debugDisplay, size_t numSamples, const AZ::Color& color) const; + + private: + void PrefillSamples(const Pose& pose, float timeDelta); + + KeyTrackLinearDynamic m_keytrack; + float m_numSecondsToTrack = 0.0f; + size_t m_jointIndex = 0; + float m_currentTime = 0.0f; + AZ::Vector3 m_facingAxisDir; //! Facing direction of the character asset. (e.g. 0,1,0 when it is looking towards Y-axis) + + static constexpr float m_debugMarkerSize = 0.02f; + }; +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/TrajectoryQuery.cpp b/Gems/MotionMatching/Code/Source/TrajectoryQuery.cpp new file mode 100644 index 0000000000..803623a1af --- /dev/null +++ b/Gems/MotionMatching/Code/Source/TrajectoryQuery.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +namespace EMotionFX::MotionMatching +{ + AZ::Vector3 SampleFunction(TrajectoryQuery::EMode mode, float offset, float radius, float phase) + { + switch (mode) + { + case TrajectoryQuery::MODE_TWO: + { + AZ::Vector3 displacement = AZ::Vector3::CreateZero(); + displacement.SetX(radius * sinf(phase + offset) ); + displacement.SetY(cosf(phase + offset)); + return displacement; + } + + case TrajectoryQuery::MODE_THREE: + { + AZ::Vector3 displacement = AZ::Vector3::CreateZero(); + const float rad = radius * cosf(radius + phase*0.2f); + displacement.SetX(rad * sinf(phase + offset)); + displacement.SetY(rad * cosf(phase + offset)); + return displacement; + } + + case TrajectoryQuery::MODE_FOUR: + { + AZ::Vector3 displacement = AZ::Vector3::CreateZero(); + displacement.SetX(radius * sinf(phase + offset)); + displacement.SetY(radius*2.0f * cosf(phase + offset)); + return displacement; + } + + // MODE_ONE and default + default: + { + AZ::Vector3 displacement = AZ::Vector3::CreateZero(); + displacement.SetX(radius * sinf(phase * 0.7f + offset) + radius * 0.75f * cosf(phase * 2.0f + offset * 2.0f)); + displacement.SetY(radius * cosf(phase * 0.4f + offset)); + return displacement; + } + } + } + + void TrajectoryQuery::Update(const ActorInstance* actorInstance, + const FeatureTrajectory* trajectoryFeature, + const TrajectoryHistory& trajectoryHistory, + EMode mode, + [[maybe_unused]] AZ::Vector3 targetPos, + [[maybe_unused]] AZ::Vector3 targetFacingDir, + float timeDelta, + float pathRadius, + float pathSpeed) + { + // Build the future trajectory control points. + const size_t numFutureSamples = trajectoryFeature->GetNumFutureSamples(); + m_futureControlPoints.resize(numFutureSamples); + + if (mode == MODE_TARGETDRIVEN) + { + const AZ::Vector3 curPos = actorInstance->GetWorldSpaceTransform().m_position; + if (curPos.IsClose(targetPos, 0.1f)) + { + for (size_t i = 0; i < numFutureSamples; ++i) + { + m_futureControlPoints[i].m_position = curPos; + } + } + else + { + // NOTE: Improve it by using a curve to the target. + for (size_t i = 0; i < numFutureSamples; ++i) + { + const float sampleTime = static_cast(i) / (numFutureSamples - 1); + m_futureControlPoints[i].m_position = curPos.Lerp(targetPos, sampleTime); + } + } + } + else + { + static float phase = 0.0f; + phase += timeDelta * pathSpeed; + AZ::Vector3 base = SampleFunction(mode, 0.0f, pathRadius, phase); + for (size_t i = 0; i < numFutureSamples; ++i) + { + const float offset = i * 0.1f; + const AZ::Vector3 curSample = SampleFunction(mode, offset, pathRadius, phase); + AZ::Vector3 displacement = curSample - base; + m_futureControlPoints[i].m_position = actorInstance->GetWorldSpaceTransform().m_position + displacement; + + // Evaluate a control point slightly further into the future than the actual + // one and use the position difference as the facing direction. + const AZ::Vector3 deltaSample = SampleFunction(mode, offset + 0.01f, pathRadius, phase); + const AZ::Vector3 dir = deltaSample - curSample; + m_futureControlPoints[i].m_facingDirection = dir.GetNormalizedSafe(); + } + } + + // Build the past trajectory control points. + const size_t numPastSamples = trajectoryFeature->GetNumPastSamples(); + m_pastControlPoints.resize(numPastSamples); + const float pastTimeRange = trajectoryFeature->GetPastTimeRange(); + + for (size_t i = 0; i < numPastSamples; ++i) + { + const float sampleTimeNormalized = i / static_cast(numPastSamples - 1); + const TrajectoryHistory::Sample sample = trajectoryHistory.Evaluate(sampleTimeNormalized * pastTimeRange); + m_pastControlPoints[i] = { sample.m_position, sample.m_facingDirection }; + } + } + + void TrajectoryQuery::DebugDraw(AzFramework::DebugDisplayRequests& debugDisplay, const AZ::Color& color) const + { + DebugDrawControlPoints(debugDisplay, m_pastControlPoints, color); + DebugDrawControlPoints(debugDisplay, m_futureControlPoints, color); + } + + void TrajectoryQuery::DebugDrawControlPoints(AzFramework::DebugDisplayRequests& debugDisplay, + const AZStd::vector& controlPoints, + const AZ::Color& color) + { + const float markerSize = 0.02f; + + const size_t numControlPoints = controlPoints.size(); + if (numControlPoints > 1) + { + debugDisplay.DepthTestOff(); + debugDisplay.SetColor(color); + + for (size_t i = 0; i < numControlPoints - 1; ++i) + { + const ControlPoint& current = controlPoints[i]; + const AZ::Vector3& posA = current.m_position; + const AZ::Vector3& posB = controlPoints[i + 1].m_position; + const AZ::Vector3 diff = posB - posA; + + debugDisplay.DrawSolidCylinder(/*center=*/(posB + posA) * 0.5f, + /*direction=*/diff.GetNormalizedSafe(), + /*radius=*/0.0025f, + /*height=*/diff.GetLength(), + /*drawShaded=*/false); + + FeatureTrajectory::DebugDrawFacingDirection(debugDisplay, current.m_position, current.m_facingDirection); + } + + for (const ControlPoint& controlPoint : controlPoints) + { + debugDisplay.DrawBall(controlPoint.m_position, markerSize, /*drawShaded=*/false); + FeatureTrajectory::DebugDrawFacingDirection(debugDisplay, controlPoint.m_position, controlPoint.m_facingDirection); + } + } + } +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Source/TrajectoryQuery.h b/Gems/MotionMatching/Code/Source/TrajectoryQuery.h new file mode 100644 index 0000000000..55d9ecf797 --- /dev/null +++ b/Gems/MotionMatching/Code/Source/TrajectoryQuery.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +#include + +#include + +#include + +namespace EMotionFX::MotionMatching +{ + class FeatureTrajectory; + + //! Builds the input trajectory query data for the motion matching algorithm. + //! Reads the number of past and future samples and the time ranges from the trajectory feature, + //! constructs the future trajectory based on the target and the past trajectory based on the trajectory history. + class EMFX_API TrajectoryQuery + { + public: + struct ControlPoint + { + AZ::Vector3 m_position; + AZ::Vector3 m_facingDirection; + }; + + enum EMode : AZ::u8 + { + MODE_TARGETDRIVEN = 0, + MODE_ONE = 1, + MODE_TWO = 2, + MODE_THREE = 3, + MODE_FOUR = 4 + }; + + void Update(const ActorInstance* actorInstance, + const FeatureTrajectory* trajectoryFeature, + const TrajectoryHistory& trajectoryHistory, + EMode mode, + AZ::Vector3 targetPos, + AZ::Vector3 targetFacingDir, + float timeDelta, + float pathRadius, + float pathSpeed); + + void DebugDraw(AzFramework::DebugDisplayRequests& debugDisplay, const AZ::Color& color) const; + + const AZStd::vector& GetPastControlPoints() const { return m_pastControlPoints; } + const AZStd::vector& GetFutureControlPoints() const { return m_futureControlPoints; } + + private: + static void DebugDrawControlPoints(AzFramework::DebugDisplayRequests& debugDisplay, + const AZStd::vector& controlPoints, + const AZ::Color& color); + + AZStd::vector m_pastControlPoints; + AZStd::vector m_futureControlPoints; + }; +} // namespace EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Tests/FeatureMatrixTests.cpp b/Gems/MotionMatching/Code/Tests/FeatureMatrixTests.cpp new file mode 100644 index 0000000000..cfbb888580 --- /dev/null +++ b/Gems/MotionMatching/Code/Tests/FeatureMatrixTests.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include + +namespace EMotionFX::MotionMatching +{ + class FeatureMatrixFixture + : public Fixture + { + public: + void SetUp() override + { + Fixture::SetUp(); + + // Construct 3x3 matrix: + // 1 2 3 + // 4 5 6 + // 7 8 9 + m_featureMatrix.resize(3, 3); + + float counter = 1.0f; + for (size_t row = 0; row < 3; ++row) + { + for (size_t column = 0; column < 3; ++column) + { + m_featureMatrix(row, column) = counter; + counter++; + } + } + } + + FeatureMatrix m_featureMatrix; + }; + + TEST_F(FeatureMatrixFixture, AccessOperators) + { + EXPECT_FLOAT_EQ(m_featureMatrix(1, 1), 5.0f); + EXPECT_FLOAT_EQ(m_featureMatrix(0, 2), 3.0f); + EXPECT_FLOAT_EQ(m_featureMatrix.coeff(2, 1), 8.0f); + EXPECT_FLOAT_EQ(m_featureMatrix.coeff(1, 2), 6.0f); + } + + TEST_F(FeatureMatrixFixture, SetValue) + { + m_featureMatrix(1, 1) = 100.0f; + EXPECT_FLOAT_EQ(m_featureMatrix(1, 1), 100.0f); + } + + TEST_F(FeatureMatrixFixture, Size) + { + EXPECT_EQ(m_featureMatrix.size(), 9); + EXPECT_EQ(m_featureMatrix.rows(), 3); + EXPECT_EQ(m_featureMatrix.cols(), 3); + } +} // EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Tests/FeatureSchemaTests.cpp b/Gems/MotionMatching/Code/Tests/FeatureSchemaTests.cpp new file mode 100644 index 0000000000..306e42c5cc --- /dev/null +++ b/Gems/MotionMatching/Code/Tests/FeatureSchemaTests.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include + +namespace EMotionFX::MotionMatching +{ + class FeatureSchemaFixture + : public Fixture + { + public: + void SetUp() override + { + Fixture::SetUp(); + m_featureSchema = AZStd::make_unique(); + DefaultFeatureSchema(*m_featureSchema.get(), {}); + } + + void TearDown() override + { + Fixture::TearDown(); + m_featureSchema.reset(); + } + + AZStd::unique_ptr m_featureSchema; + }; + + TEST_F(FeatureSchemaFixture, AddFeature) + { + m_featureSchema->AddFeature(aznew FeaturePosition()); + m_featureSchema->AddFeature(aznew FeatureVelocity()); + m_featureSchema->AddFeature(aznew FeatureTrajectory()); + EXPECT_EQ(m_featureSchema->GetNumFeatures(), 9); + } + + TEST_F(FeatureSchemaFixture, Clear) + { + m_featureSchema->Clear(); + EXPECT_EQ(m_featureSchema->GetNumFeatures(), 0); + } + + TEST_F(FeatureSchemaFixture, GetNumFeatures) + { + EXPECT_EQ(m_featureSchema->GetNumFeatures(), 6); + } + + TEST_F(FeatureSchemaFixture, GetFeature) + { + EXPECT_EQ(m_featureSchema->GetFeature(1)->RTTI_GetType(), azrtti_typeid()); + EXPECT_STREQ(m_featureSchema->GetFeature(3)->GetName().c_str(), "Left Foot Velocity"); + } + + TEST_F(FeatureSchemaFixture, GetFeatures) + { + int counter = 0; + for (const Feature* feature : m_featureSchema->GetFeatures()) + { + AZ_UNUSED(feature); + counter++; + } + EXPECT_EQ(counter, 6); + } + + TEST_F(FeatureSchemaFixture, FindFeatureById) + { + const Feature* feature = m_featureSchema->GetFeature(1); + const AZ::TypeId id = feature->GetId(); + const Feature* result = m_featureSchema->FindFeatureById(id); + EXPECT_EQ(result, feature); + } +} // EMotionFX::MotionMatching diff --git a/Gems/MotionMatching/Code/Tests/Fixture.h b/Gems/MotionMatching/Code/Tests/Fixture.h new file mode 100644 index 0000000000..1edadadae5 --- /dev/null +++ b/Gems/MotionMatching/Code/Tests/Fixture.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +namespace EMotionFX::MotionMatching +{ + using Fixture = ComponentFixture< + AZ::MemoryComponent, + AZ::AssetManagerComponent, + AZ::JobManagerComponent, + AZ::StreamerComponent, + EMotionFX::Integration::SystemComponent, + MotionMatchingSystemComponent + >; +} diff --git a/Gems/MotionMatching/Code/Tests/MotionMatchingEditorTest.cpp b/Gems/MotionMatching/Code/Tests/MotionMatchingEditorTest.cpp new file mode 100644 index 0000000000..40217ff9bc --- /dev/null +++ b/Gems/MotionMatching/Code/Tests/MotionMatchingEditorTest.cpp @@ -0,0 +1,11 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + +AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV); diff --git a/Gems/MotionMatching/Code/Tests/MotionMatchingTest.cpp b/Gems/MotionMatching/Code/Tests/MotionMatchingTest.cpp new file mode 100644 index 0000000000..40217ff9bc --- /dev/null +++ b/Gems/MotionMatching/Code/Tests/MotionMatchingTest.cpp @@ -0,0 +1,11 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + +AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV); diff --git a/Gems/MotionMatching/Code/motionmatching_editor_files.cmake b/Gems/MotionMatching/Code/motionmatching_editor_files.cmake new file mode 100644 index 0000000000..e18de13f3c --- /dev/null +++ b/Gems/MotionMatching/Code/motionmatching_editor_files.cmake @@ -0,0 +1,12 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(FILES + Source/MotionMatchingEditorSystemComponent.cpp + Source/MotionMatchingEditorSystemComponent.h +) diff --git a/Gems/MotionMatching/Code/motionmatching_editor_shared_files.cmake b/Gems/MotionMatching/Code/motionmatching_editor_shared_files.cmake new file mode 100644 index 0000000000..6c797254dc --- /dev/null +++ b/Gems/MotionMatching/Code/motionmatching_editor_shared_files.cmake @@ -0,0 +1,11 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(FILES + Source/MotionMatchingEditorModule.cpp +) diff --git a/Gems/MotionMatching/Code/motionmatching_editor_tests_files.cmake b/Gems/MotionMatching/Code/motionmatching_editor_tests_files.cmake new file mode 100644 index 0000000000..cf91b5c3b5 --- /dev/null +++ b/Gems/MotionMatching/Code/motionmatching_editor_tests_files.cmake @@ -0,0 +1,11 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(FILES + Tests/MotionMatchingEditorTest.cpp +) diff --git a/Gems/MotionMatching/Code/motionmatching_files.cmake b/Gems/MotionMatching/Code/motionmatching_files.cmake new file mode 100644 index 0000000000..3a414467a4 --- /dev/null +++ b/Gems/MotionMatching/Code/motionmatching_files.cmake @@ -0,0 +1,52 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(FILES + Include/MotionMatching/MotionMatchingBus.h + Source/MotionMatchingModuleInterface.h + Source/MotionMatchingSystemComponent.cpp + Source/MotionMatchingSystemComponent.h + Source/Allocators.h + Source/BlendTreeMotionMatchNode.cpp + Source/BlendTreeMotionMatchNode.h + Source/EventData.cpp + Source/EventData.h + Source/Frame.cpp + Source/Frame.h + Source/Feature.cpp + Source/Feature.h + Source/FeatureMatrix.cpp + Source/FeatureMatrix.h + Source/FeaturePosition.cpp + Source/FeaturePosition.h + Source/FeatureSchema.cpp + Source/FeatureSchema.h + Source/FeatureSchemaDefault.cpp + Source/FeatureSchemaDefault.h + Source/FeatureTrajectory.h + Source/FeatureTrajectory.cpp + Source/FeatureVelocity.cpp + Source/FeatureVelocity.h + Source/PoseDataJointVelocities.cpp + Source/PoseDataJointVelocities.h + Source/TrajectoryHistory.cpp + Source/TrajectoryHistory.h + Source/TrajectoryQuery.cpp + Source/TrajectoryQuery.h + Source/FrameDatabase.cpp + Source/FrameDatabase.h + Source/ImGuiMonitor.cpp + Source/ImGuiMonitor.h + Source/ImGuiMonitorBus.h + Source/KdTree.cpp + Source/KdTree.h + Source/MotionMatchingData.cpp + Source/MotionMatchingData.h + Source/MotionMatchingInstance.cpp + Source/MotionMatchingInstance.h +) diff --git a/Gems/MotionMatching/Code/motionmatching_shared_files.cmake b/Gems/MotionMatching/Code/motionmatching_shared_files.cmake new file mode 100644 index 0000000000..ac0375129e --- /dev/null +++ b/Gems/MotionMatching/Code/motionmatching_shared_files.cmake @@ -0,0 +1,11 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(FILES + Source/MotionMatchingModule.cpp +) diff --git a/Gems/MotionMatching/Code/motionmatching_tests_files.cmake b/Gems/MotionMatching/Code/motionmatching_tests_files.cmake new file mode 100644 index 0000000000..e9d72ce9f9 --- /dev/null +++ b/Gems/MotionMatching/Code/motionmatching_tests_files.cmake @@ -0,0 +1,14 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(FILES + Tests/Fixture.h + Tests/FeatureMatrixTests.cpp + Tests/FeatureSchemaTests.cpp + Tests/MotionMatchingTest.cpp +) \ No newline at end of file diff --git a/Gems/MotionMatching/Docs/Diagrams/ArchitectureDiagram.drawio b/Gems/MotionMatching/Docs/Diagrams/ArchitectureDiagram.drawio new file mode 100644 index 0000000000..e986ab9682 --- /dev/null +++ b/Gems/MotionMatching/Docs/Diagrams/ArchitectureDiagram.drawio @@ -0,0 +1 @@ +7Vxbc5s6EP41nmkfnDEIA36M7aTNOcmctElvTx0FZFstIFfIid1fX0mIq3BCXOPLHPchNasLYi/frpYVHTAKl+8onM9uiI+Cjtnzlx0w7pim2bcc/p+grBSlZyjKlGI/oRk54Q7/RorYU9QF9lFc6sgICRiel4keiSLksRINUkqeyt0mJCjfdQ6nSCPceTDQqV+wz2YJ1e33cvp7hKez9M5GT7WEMO2sCPEM+uSpQAIXHTCihLDkV7gcoUBwL+VLMu5yTWu2MIoi1mTApyX9Ti+6lx/Il6+T7q+P6ObTY9dOZnmEwUI9cMe0Az7fcC6WzFaKD/avhVjnMIR0iqMOOOetvfmS/+VE+bSC3mVknrRZhTaGlqwLAzxV4zy+YETzOfmvqfpf3hkXCDDkEw4D/epKzDGBHsrI5SGlGTlTcPUuD1SjpIRLBNmCosLoh2pfTptXaTMqWJZqcPp0xvoH3YjFAZqwAo/1uUXfqwizN+LPHWIMR9P4bTLkkXBtqGGAGHOxZBR6TD38Z6ER8ZsRiYT4aobXcaC9BxrBwFsEkKERiVnjVZml9ZiPiDLMTfs80cWx1Nah0sxxso4h4b0mgTTUCeYGBIYTfjcFTIapri9hiAOBae9R8IjErEIBWBiITtm9i8ap7FWsAS0LJGWs7xAJEaMr3kW1dq2BQg6FnV03xZanHIkyeJkVUSjtCBX6TbPZc4DgPxRGvAIvHA0vlL7ckhgzTCKN6VtmSt+pMGVg60wxjBqm9NviibuOJ59RQDzMVm3zxAL9Q+PJYB1P7in8wf00oa1zBfQOT1MG2kMjn4cb6pJQNiNTEsHgIqcOKVlEPhLTCtzM+1wTAV+SXT84yq8URMEFI2UsQkvMvorhZ3119a3QMl6qmeXFSl3EDFJ2LuInTohIhFLapcTEZEDkV3pwSqF9rRBjsqAeeoZPWaTHPQRiz3QEIOkouPisUlDEnQd+LAd1W5dvpqWa2t9ARvFSk378hMMASt5JL6NajIJbUtESGNZ7L2+GA/8arshC8ImLyPuZXg1nhOLffFqYaoKUoNIT0y71uBMjleAoElHMbSo4o0K6gctSx2sYM0XwSBDAeYwfssdInPyQMEbCl/TiFcadub3UuG3D1Y3brjHuQVvGbfZ14VMYopPQtyV0p4rozqAG0XcqdNc5FkTnfKerwiBx+a3Ylg+TVwfiCewj9QT6xlqCwRgy+ADjEyi0BwoH4An0XVIe9r7HcW30e1KADRUgC+FTBTCt/p4VYAD26RXS3xLdz5zURbzkFkpOIfcRh+kW3IZuwTUOyy3ouYIcGT4s0AkXtocLfffwcKGnifdAo8U9m/fgSKM+Pe31r39P0Snc25ZVH96233VPNt3EpkHvOG06Xbee07vzZiiEJ9PelsO27IMzbud/lbDfNEX0F6AAmsbxvW2Dghp6SzBfdGE3WX2/aIKKciVrVeMq+pUt5C/wBmh4c0PEm9UbyDgwRFORQjqBzrZAB1iVd6eWue+cMtCjSB2FclMmcxRV8CNKC6lkvUIJGnwMQxL59zNRkVGSo2HVQcaLaFcPVr1nwWod0BjtAU3T6MPeDc4AqwIzRkWbkifSUKYGsPpVr1kt/mgZsCw9QNLUVdo3TIy5V1bWSvlNPTgF8AEFQw4sU+lcRyQgVM4MJvJfp1ylU68rzxqbqh9Ua+xkG43XJCKTEbsJSy39VfMmXKcJnh0x23fK9b7+jreunK4iB1GrVs/+tT45xL6fBJMVGc6FGcvH6g87/bGYi8ePqupwa0VWwK5Hp2LhWY1LNNtyiXYDjCm4xIeAiBik6AhtLdrd0/vU1+4fNvd5TZNoTV1eXUD0t+G2qdU4VouvmjtC8NJULTtC2zwp6auVtOmLnL0qqVZfWQ2xGispqG4vneo2om0l1beXJyV9SUmdI1BSTbOOWUmtVynpvnfAG+5mN9k5b67DjSup0jrG1lNtTnUPbFfjx8011t61xtZtDI5mE5za27Ftgu2642vHswneHtt3ynVHj3NvSSxLG/8R9qWOvWAUa8I4pak325FrexsDqDmKoYRTsyVv79WYHkheJC8qLr+Kc3ngXCjFSQPa0gDT1XMyu9UAV0/KlN9UXUWc/ZF3UoL23lY1rmkzWzsS6tZlorNDyG87IvAEyi0K8qe5Dxl6MwkIZHrrfws2X9QNu8SRP0QxS5VLltPX9Bujh8V0TOGTaKtPxMaMkp8odeTqVbg47FshNYrK4jn0+HKuZZ+xlVM+KuYLUuFQ8Qz7vtyuUMIgW5vZHXJxjcTOoM8XPuLXRn4t075zvhkZEW5hFGKpUIjr4xOKWa2qPW/ALytg+vkGu5m2uVZbytYgxxZgVf0gpJx+tMHYSMRZGl7J9F5WcHQNTe5AlzuokbGMJbOj0nnkqWf19yJeoybPXy/ftsSrBxVdfhl+/yVqo0ufJigYftIlQpwhMZO4oLdy4+ON4usBssNV5KOl1okLKvJvKZnyieJ7LM7uZT3Ozs5OeLJ9hWtY4ZXa/fZLsi1Nqns7qrHZSQ3nsI9qpOHBy3knlWU9kLrPQYOMzgHlIHeYd99cF5ym5X5piqH1HKSeOOxVfNvx5CAH28mG7SkHmdrbseUgB/phz024vq8c5PbYvlOuZ1842rHfPsrTku7+YHeNkPll/uG7BP/y7weCiz8= \ No newline at end of file diff --git a/Gems/MotionMatching/Docs/Diagrams/FeatureSchema.drawio b/Gems/MotionMatching/Docs/Diagrams/FeatureSchema.drawio new file mode 100644 index 0000000000..b2b17a1965 --- /dev/null +++ b/Gems/MotionMatching/Docs/Diagrams/FeatureSchema.drawio @@ -0,0 +1 @@ +5Z1dd6o4FIZ/jZd2QRJULmt72llrzqyeaS965hIlKlMEC7Ffv34SDCiJzhzt3nY0V+oGX0iel4+9ibFDr+Zvt0W0mP2RxzztEC9+69DrDiF+L+zLFxV5X0VCXwemRRLrldaBh+SD66Cno8sk5mVrRZHnqUgW7eA4zzI+Fq1YVBT5a3u1SZ62t7qIptwKPIyj1I4+JrGYraKDwFvHf+PJdFZv2ff0knlUr6wD5SyK89eNEP3WoVdFnovVu/nbFU9V59X9svrezY6lzY4VPBO/8oWP7OPHcDF4/pmO7srrx+w2e/y9S1YqL1G61A3ukF4q9YYjtcviXfdD73mp9nM4yTPRLStKl3IFP1hI0sP1cvluql5veCSWBVer1IJyz0b14mYjxae2cq86T/VBKV9uJku9SVFEf0sv5MW77vlmA0RCWKi34+VIvgxfZ4ngD4torGKv0sEyNhPzVH7y1e7lyyzm8fdRE4jGT9NCRe+WIk0yruNxVDzdSZlEKJt7F17QDpIqqtZcteqaqVYmaXqVp3lR7RoNY8bjWLdeHwh+ULXWBK3Zv/BC8LeNkAZ/y/M5F6r1Xr3UC1df0Udhbd/XtaUp1bHZhp3ZQAcjfRhNG+m10+QbbbY9jEfRjUcRjfedT0R1Nqn898LTvEJ/Sm4bTXqTCY7bgvA/3eazo7qNobuNHM1ti7xMRJJnp+S2ySSKI6RzG2P/N7f1drpNNf9XrECYtILttiKay3uhtdFWcrXXDDvI7hNt5KUo8ideY8lyhbhFSoeiNJlmykgSCpfxoYKRyHuiS71gnsRxustjxcpYyhXVJxFVbqXX3VAFmtsgDwZ/V95kXpCgZQEysD3ABuQisF1AsEzQxzJBc8oZyqvO0+naoWUGH+k+JwgsI4TUP6IN/MEWHxhoeBZfqlRl3eUblHb2Co9bmYvdJxttDrac/+pYwVOJ5KWd72zrB72FH3lS+fetLWOcfWuBMl8WY66/s5mdGDJN3rRDR0TFlAtLp2LStPlwTDV/VzD1gDCZOtiYfLcw9YEwmTrYmLaVFc4Yk3kneSgmUwcb07Yk/IwxhUCYTB1sTNuy1zPG1HT3ZzlZQtigAsdA+VCgTCFsUNsy9HMGZaY3B4MyhbBBbcuizxkUhQJlCmGDcizNbSqKnwZlCmGDCh0DBVWQsISQQdUHsDOgoEoSlhAyKOZYFuX3QihQ4XFBuZZH9aFAmULYoFzLowZQoEwhbFCu5VEhFChTCBuUY3kU8YBAWULYoBzLo+SRAATKFMIG5VgeRQgUKFMIGVTdDmdAUShQphA2KMee7RIGBcoUwgZFHAMVQIEyhbBBOVaZIFCVCUsIG5RjlQkCVZmwhLBBOVaZABsycewxE4FjlQkKNWbCEsIG5VhlgkKNmbCEsEE5VpmgUGMmLCFsUI5VJijUmAlLCBlU/ZzSGVBQYyYsIWxQjlUmKNSYCUsIGxRxDBTUmAlLCBuUY5UJCvVDDksIG5RjlQkK9VMOSwgblGOVCQpVmbCEsEE5VplgUJUJSwgZVN+xPIpBVSYsIWRQ9ZnWGVBQlQlLCBuUY3kUg6pMWELYoBzLoxjzgUCZQtigHMujWAAFyhTCBuVYHsV6UKBMIWxQjuVRrA8FyhTCBuVaHjWAAmUKYYNy7AkvC6FAmULYoBx7wht4QKAsIWxQjlUmAh8KlCmEDKouLToDylQ4GJQphA3KscpEQKFAmULYoPa7mRinUVkm42qyy6gQdviEEFpzWx46nIIx79+FsBnud59xTgytuYoPZRiEX8xwv1uQc2JozW5+KMP+8Y7D6C6cdp+v/yp/PvB7ckuHD/d/drdd82AnRQ8w//uhAnvSc/CP4hHBmoM/3DGf7lFmRd9qN4JuN3Y8u53kJPzxZDLq4ditb+XWX223/Z6MnNMFymJx6AVqUKdFX3SB2u+ZyTkhtM7ehw+dZlgM5cf1n1etVl//BRj99g8= \ No newline at end of file diff --git a/Gems/MotionMatching/Docs/Images/ArchitectureDiagram.png b/Gems/MotionMatching/Docs/Images/ArchitectureDiagram.png new file mode 100644 index 0000000000..5eb0507284 --- /dev/null +++ b/Gems/MotionMatching/Docs/Images/ArchitectureDiagram.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a4ae0e6c7e54ab84acd0582c7a5375ce96c73c4765cd69b542bc97609a3a25f +size 316431 diff --git a/Gems/MotionMatching/Docs/Images/FeatureSchema.png b/Gems/MotionMatching/Docs/Images/FeatureSchema.png new file mode 100644 index 0000000000..51fb8a9db4 --- /dev/null +++ b/Gems/MotionMatching/Docs/Images/FeatureSchema.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1e14441badf5f7c85d538aa09b1d1ae2af48c3263221930a058c6fd48cc60e3 +size 193077 diff --git a/Gems/MotionMatching/JupyterNotebooks/FeatureAnalysis.ipynb b/Gems/MotionMatching/JupyterNotebooks/FeatureAnalysis.ipynb new file mode 100644 index 0000000000..44059714f9 --- /dev/null +++ b/Gems/MotionMatching/JupyterNotebooks/FeatureAnalysis.ipynb @@ -0,0 +1,352 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d57026d3", + "metadata": {}, + "source": [ + "# Settings" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26e1d687", + "metadata": {}, + "outputs": [], + "source": [ + "featureMatrixFilePath = 'E:/MotionMatchingFeatureMatrix.csv'" + ] + }, + { + "cell_type": "markdown", + "id": "a45e3d25", + "metadata": {}, + "source": [ + "# Load feature matrix" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25a44238", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "import seaborn as sns\n", + "from sklearn import preprocessing\n", + "from sklearn.decomposition import PCA\n", + "\n", + "def PrintGreen(text):\n", + " print('\\x1b[6;30;42m' + text + '\\x1b[0m')\n", + " \n", + "def PrintRed(text):\n", + " print('\\33[41m' + text + '\\x1b[0m')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5bdc881e", + "metadata": {}, + "outputs": [], + "source": [ + "# Load the feature matrix from CSV\n", + "originalData = pd.read_csv(featureMatrixFilePath, na_values = 'null')\n", + "if originalData.shape[0] > 0 and originalData.shape[1] > 0:\n", + " PrintGreen(\"Loading succeeded\");\n", + "else:\n", + " PrintRed(\"Loading failed!\");\n", + "\n", + "print(\"frames = \" + str(originalData.shape[0]))\n", + "print(\"featureComponents = \" + str(originalData.shape[1]))\n", + "\n", + "# Ensure to show all columns\n", + "pd.set_option('max_columns', originalData.shape[1])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e41c7caf", + "metadata": {}, + "outputs": [], + "source": [ + "originalData.head(15)" + ] + }, + { + "cell_type": "markdown", + "id": "b3bbc348", + "metadata": {}, + "source": [ + "# Data preparation\n", + "\n", + "1. Data Cleaning: We will remove unused feature components that are zeroed out for now as they are not implemented yet.\n", + "2. Feature Selection: Happened in the motion matching gem. So far we have a position, velocity and a trajectory feature.\n", + "3. Data Transformation: We will change the scale of our features by normalizing it using min-max normalization. We do not modify the distribution for now.\n", + "4. Feature Engineering / Data Augmentation: We will not derive new variables for now.\n", + "5. Dimensionality Reduction: We will not create compact projections of the data for now.\n", + "\n", + "# Data cleaning\n", + "Remove columns containing only 0.0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2a64a465", + "metadata": {}, + "outputs": [], + "source": [ + "def CleanData(data):\n", + " # Remove columns with only zeros\n", + " cleanedData = data[data.columns[(data != 0).any()]]\n", + " \n", + " if cleanedData.shape[0] != data.shape[0]:\n", + " PrintRed(\"Frame count of original and cleaned data should match!\")\n", + " \n", + " if cleanedData.shape[1] < data.shape[1]:\n", + " PrintGreen(str(data.shape[1] - cleanedData.shape[1]) + \" feature components containing only 0.0 values removed\");\n", + " \n", + " print(\"frames = \" + str(cleanedData.shape[0]))\n", + " print(\"featureComponents = \" + str(cleanedData.shape[1]))\n", + " \n", + " return cleanedData\n", + "\n", + "\n", + "cleanedData = CleanData(originalData);\n", + "frameCount = cleanedData.shape[0]\n", + "cleanedFeatureComponentCount = cleanedData.shape[1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "81b759dc", + "metadata": {}, + "outputs": [], + "source": [ + "cleanedData.head(15)" + ] + }, + { + "cell_type": "markdown", + "id": "b9e9a8e2", + "metadata": {}, + "source": [ + "# Feature analysis visualizations" + ] + }, + { + "cell_type": "markdown", + "id": "643dd550", + "metadata": {}, + "source": [ + "## Histogram per feature component showing value distributions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f3cbe721", + "metadata": {}, + "outputs": [], + "source": [ + "def Histogram(data):\n", + " image = data.hist(figsize = [32, 32])\n", + "\n", + " \n", + "Histogram(cleanedData)" + ] + }, + { + "cell_type": "markdown", + "id": "06cc1e64", + "metadata": {}, + "source": [ + "## Boxplot per feature component\n", + "Median in orange inside the box
\n", + "Box = Interquartile range, which means 50% of the data lies within the box
\n", + "Black line range = 99,3% of the values
\n", + "Semi-transparent outliers represent the rest 0.7%
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4ff8c258", + "metadata": {}, + "outputs": [], + "source": [ + "def BoxPlot(data, featureComponentCount):\n", + " minValuePerColumn = data.min(axis=0)\n", + " maxValuePerColumn = data.max(axis=0)\n", + "\n", + " fig1, ax1 = plt.subplots(figsize=(20,20))\n", + " ax1.set_title('Feature Component Boxplot')\n", + "\n", + " # Render outliers\n", + " flierprops = dict(marker='o', markerfacecolor='gainsboro', markersize=1, linestyle='none', markeredgecolor='gainsboro', alpha=0.005)\n", + " ax1.boxplot(data, vert=False, flierprops=flierprops)\n", + "\n", + " # Create an array containing values ranging from 1 to featureComponentCount\n", + " elementNumbers = np.array([i+1 for i in range(featureComponentCount)])\n", + "\n", + " plt.yticks(elementNumbers, data.columns)\n", + " plt.show()\n", + "\n", + "\n", + "BoxPlot(cleanedData, cleanedData.shape[1])" + ] + }, + { + "cell_type": "markdown", + "id": "023ab81b", + "metadata": {}, + "source": [ + "## Feature correlation heatmap" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "290aff93", + "metadata": {}, + "outputs": [], + "source": [ + "# not used in drawing, this just prints the values\n", + "correlationMatrix = cleanedData.corr()\n", + "\n", + "# plot the correlation heatmap\n", + "plt.figure(figsize=[32, 32])\n", + "sns.heatmap(data=correlationMatrix)" + ] + }, + { + "cell_type": "markdown", + "id": "2ce964ae", + "metadata": {}, + "source": [ + "## Scatterplot using PCA\n", + "Use principal component analysis to project the multi-dimensional data down to 2D" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d77c43aa", + "metadata": {}, + "outputs": [], + "source": [ + "def ScatterPlotPCA(data):\n", + " pca = PCA(n_components=2)\n", + " pca.fit(data)\n", + " pcaData = pca.transform(data)\n", + " \n", + " pca_x = pcaData[:, 0]\n", + " pca_y = pcaData[:, 1]\n", + " plt.figure(figsize=(16, 16))\n", + " plt.scatter(pca_x, pca_y, s=2.0, alpha=0.5)\n", + "\n", + " \n", + "ScatterPlotPCA(cleanedData)" + ] + }, + { + "cell_type": "markdown", + "id": "e3c4c80a", + "metadata": {}, + "source": [ + "# Data Transformation\n", + "# Normalization" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8c71d0a5", + "metadata": {}, + "outputs": [], + "source": [ + "# mean normalization\n", + "# normalized_df=(df-df.mean())/df.std()\n", + "\n", + "# min-max normalization\n", + "# normalized_df=(df-df.min())/(df.max()-df.min())\n", + "\n", + "# Note: Pandas automatically applies colomn-wise function in the code above.\n", + "\n", + "# Using sklearn\n", + "x = cleanedData.values\n", + "min_max_scaler = preprocessing.MinMaxScaler(feature_range=(0, 1))\n", + "x_scaled = min_max_scaler.fit_transform(x)\n", + "\n", + "normalizedData = pd.DataFrame(data=x_scaled, columns=cleanedData.columns) # copy column names from source\n", + "\n", + "# min values per column used to normalize the data\n", + "print(\"Minimum values per feature component / column\")\n", + "print(min_max_scaler.data_min_)\n", + "print(\"\")\n", + "\n", + "# max values per column used to normalize the data\n", + "print(\"Maximum values per feature component / column\")\n", + "print(min_max_scaler.data_max_)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4b5bfd21", + "metadata": {}, + "outputs": [], + "source": [ + "normalizedData.head(15)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c094066c", + "metadata": {}, + "outputs": [], + "source": [ + "Histogram(normalizedData)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b81660c2", + "metadata": {}, + "outputs": [], + "source": [ + "ScatterPlotPCA(normalizedData)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/Gems/MotionMatching/README.md b/Gems/MotionMatching/README.md new file mode 100644 index 0000000000..ecad3387f2 --- /dev/null +++ b/Gems/MotionMatching/README.md @@ -0,0 +1,48 @@ +# Motion Matching + +Motion matching is a data-driven animation technique that synthesizes motions based on existing animation data and the current character and input contexts. + +# Features + +A feature is a property extracted from the animation data and is used by the motion matching algorithm to find the next best matching frame. Examples of features are the position of the feet joints, the linear or angular velocity of the knee joints or the trajectory history and future trajectory of the root joint. We can also encode environment sensations like obstacle positions and height, the location of the sword of an enemy character or a football's position and velocity. + +Their purpose is to describe a frame of the animation by their key characteristics and sometimes enhance the actual keyframe data (pos/rot/scale per joint) by e.g. taking the time domain into account and calculate the velocity or acceleration, or a whole trajectory to describe where the given joint came from to reach the frame and the path it moves along in the near future. + +Features are responsible for each of the following: + +1. Extract the feature values for a given frame in the motion database and store them in the feature matrix. For example calculate the left foot joint linear velocity, convert it to relative-to the root joint model space for frame 134 and place the XYZ components in the feature matrix starting at column 9. +1. Extract the feature from the current input context/pose and fill the query vector with it. For example calculate the linear velocity of the left foot joint of the current character pose in relative-to the root joint model space and place the XYZ components in the feature query vector starting at position 9. +1. Calculate the cost of the feature so that the motion matching algorithm can weight it into search for the next best matching frame. An example would be calculating the squared distance between a frame in the motion matching database and the current character pose for the left foot joint. + +# Feature schema + +The feature schema is a set of features that define the criteria used in the motion matching algorithm and influences the runtime speed, memory used, and the results of the synthesized motion. It is the most influential, user-defined input to the system. + +The schema defines which features are extracted from the motion database while the actual extracted data is stored in the feature matrix. Along with the feature type, settings like the joint to extract the data from, a debug visualization color, how the residual is calculated or a custom feature is specified. + +The more features are selected by the user, the bigger the chances are that the searched and matched pose hits the expected result but the slower the algorithm will be and the more memory will be used. The key is to use crucial and independent elements that define a pose and its movement without being too strict on the wrong end. The root trajectory along with the left and right foot positions and velocities have been proven to be a good start here. + +# Feature matrix + +The feature matrix is an NxN matrix which stores the extracted feature values for all frames in our motion database based upon a given feature schema. The feature schema defines the order of the columns and values and is used to identify values and find their location inside the matrix. + +A 3D position feature storing XYZ values e.g. will use three columns in the feature matrix. Every component of a feature is linked to a column index, so e.g. the left foot position Y value might be at column index 6. The group of values or columns that belong to a given feature is what we call a feature block. The accumulated number of dimensions for all features in the schema, while the number of dimensions might vary per feature, form the number of columns of the feature matrix. + +Each row represents the features of a single frame of the motion database. The number of rows of the feature matrix is defined by the number. + +![Feature Schema](Docs/Images/FeatureSchema.png) + +# Trajectory History + +The trajectory history stores world space position and facing direction data of the root joint (motion extraction joint) with each game tick. The maximum recording time is adjustable but needs to be at least as long as the past trajectory window from the trajectory feature as the trajectory history is used to build the query for the past trajectory feature. + +# Motion Matching data + +Data based on a given skeleton but independent of the instance like the motion capture database, the feature schema or feature matrix is stored in here. It is just a wrapper to group the sharable data. + +# Motion Matching instance + +The instance is where everything comes together. It stores the trajectory history, the trajectory query along with the query vector, knows about the last lowest cost frame frame index and stores the time of the animation that the instance is currently playing. It is responsible for motion extraction, blending towards a new frame in the motion capture database in case the algorithm found a better matching frame and executes the actual search. + +# Architecture Diagram +![Class Diagram](Docs/Images/ArchitectureDiagram.png) \ No newline at end of file diff --git a/Gems/MotionMatching/gem.json b/Gems/MotionMatching/gem.json new file mode 100644 index 0000000000..60e8be01c0 --- /dev/null +++ b/Gems/MotionMatching/gem.json @@ -0,0 +1,21 @@ +{ + "gem_name": "MotionMatching", + "display_name": "Motion Matching", + "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", + "origin": "Open 3D Engine - o3de.org", + "type": "Code", + "summary": "Motion matching is a data-driven animation technique that synthesizes motions based on existing animation data and the current character and input contexts.", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Animation", + "Tools", + "Simulation" + ], + "icon_path": "preview.png", + "requirements": "", + "dependencies": [ + "EMotionFX"] +} diff --git a/Gems/MotionMatching/preview.png b/Gems/MotionMatching/preview.png new file mode 100644 index 0000000000..0f393ac886 --- /dev/null +++ b/Gems/MotionMatching/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ac9dd09bde78f389e3725ac49d61eff109857e004840bc0bc3881739df9618d +size 2217 diff --git a/engine.json b/engine.json index 5c64eed308..a476c1b769 100644 --- a/engine.json +++ b/engine.json @@ -49,6 +49,7 @@ "Gems/MessagePopup", "Gems/Metastream", "Gems/Microphone", + "Gems/MotionMatching", "Gems/Multiplayer", "Gems/MultiplayerCompression", "Gems/NvCloth", From 8f48e4fcb6736a38f6cea6357b882846b7191d3c Mon Sep 17 00:00:00 2001 From: Sergey Pereslavtsev Date: Mon, 31 Jan 2022 11:04:13 +0000 Subject: [PATCH 31/53] Moved if section to the top Signed-off-by: Sergey Pereslavtsev --- Gems/PhysX/Code/Editor/DebugDraw.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Gems/PhysX/Code/Editor/DebugDraw.cpp b/Gems/PhysX/Code/Editor/DebugDraw.cpp index 78d216a21a..ca5d4e1694 100644 --- a/Gems/PhysX/Code/Editor/DebugDraw.cpp +++ b/Gems/PhysX/Code/Editor/DebugDraw.cpp @@ -687,12 +687,6 @@ namespace PhysX [[maybe_unused]] const AZ::Vector3& colliderScale, [[maybe_unused]] const bool forceUniformScaling) const { - const int numColumns = heightfieldShapeConfig.GetNumColumns(); - const int numRows = heightfieldShapeConfig.GetNumRows(); - - const float minXBounds = -(numColumns * heightfieldShapeConfig.GetGridResolution().GetX()) / 2.0f; - const float minYBounds = -(numRows * heightfieldShapeConfig.GetGridResolution().GetY()) / 2.0f; - auto heights = heightfieldShapeConfig.GetSamples(); if (heights.empty()) @@ -700,6 +694,12 @@ namespace PhysX return; } + const int numColumns = heightfieldShapeConfig.GetNumColumns(); + const int numRows = heightfieldShapeConfig.GetNumRows(); + + const float minXBounds = -(numColumns * heightfieldShapeConfig.GetGridResolution().GetX()) / 2.0f; + const float minYBounds = -(numRows * heightfieldShapeConfig.GetGridResolution().GetY()) / 2.0f; + for (int xIndex = 0; xIndex < numColumns - 1; xIndex++) { for (int yIndex = 0; yIndex < numRows - 1; yIndex++) From cb28a4cd9bedfd4ac12672c8c877b2922efd139d Mon Sep 17 00:00:00 2001 From: Benjamin Jillich Date: Mon, 31 Jan 2022 13:30:53 +0100 Subject: [PATCH 32/53] Motion Matching: Added images for readme Signed-off-by: Benjamin Jillich --- Gems/MotionMatching/Docs/Images/FeatureHistograms.png | 3 +++ Gems/MotionMatching/Docs/Images/FeaturePositionRPE.png | 3 +++ Gems/MotionMatching/Docs/Images/FeaturePositionVis.png | 3 +++ Gems/MotionMatching/Docs/Images/FeatureScatterplotPCA.png | 3 +++ Gems/MotionMatching/Docs/Images/FeatureSharedRPE.png | 3 +++ Gems/MotionMatching/Docs/Images/FeatureTrajectoryRPE.png | 3 +++ Gems/MotionMatching/Docs/Images/FeatureTrajectoryVis.png | 3 +++ Gems/MotionMatching/Docs/Images/FeatureVelocityRPE.png | 3 +++ Gems/MotionMatching/Docs/Images/FeatureVelocityVis.png | 3 +++ Gems/MotionMatching/Docs/Images/TrajectoryHistory.png | 3 +++ 10 files changed, 30 insertions(+) create mode 100644 Gems/MotionMatching/Docs/Images/FeatureHistograms.png create mode 100644 Gems/MotionMatching/Docs/Images/FeaturePositionRPE.png create mode 100644 Gems/MotionMatching/Docs/Images/FeaturePositionVis.png create mode 100644 Gems/MotionMatching/Docs/Images/FeatureScatterplotPCA.png create mode 100644 Gems/MotionMatching/Docs/Images/FeatureSharedRPE.png create mode 100644 Gems/MotionMatching/Docs/Images/FeatureTrajectoryRPE.png create mode 100644 Gems/MotionMatching/Docs/Images/FeatureTrajectoryVis.png create mode 100644 Gems/MotionMatching/Docs/Images/FeatureVelocityRPE.png create mode 100644 Gems/MotionMatching/Docs/Images/FeatureVelocityVis.png create mode 100644 Gems/MotionMatching/Docs/Images/TrajectoryHistory.png diff --git a/Gems/MotionMatching/Docs/Images/FeatureHistograms.png b/Gems/MotionMatching/Docs/Images/FeatureHistograms.png new file mode 100644 index 0000000000..703b182bba --- /dev/null +++ b/Gems/MotionMatching/Docs/Images/FeatureHistograms.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c66837b2ffcfd2e30cb732deb798786da47cc64279c80085da04309f40ffedf +size 173138 diff --git a/Gems/MotionMatching/Docs/Images/FeaturePositionRPE.png b/Gems/MotionMatching/Docs/Images/FeaturePositionRPE.png new file mode 100644 index 0000000000..45f36b5ee0 --- /dev/null +++ b/Gems/MotionMatching/Docs/Images/FeaturePositionRPE.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f5b83dff90690f8574d923ab6735303191be13b229bb448334cad5af8cf90ca +size 28817 diff --git a/Gems/MotionMatching/Docs/Images/FeaturePositionVis.png b/Gems/MotionMatching/Docs/Images/FeaturePositionVis.png new file mode 100644 index 0000000000..064065bc2f --- /dev/null +++ b/Gems/MotionMatching/Docs/Images/FeaturePositionVis.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c8d33ae9d94dd53bad827502370de20279cb10b1ce2f0a7b372f45e831724dc +size 169026 diff --git a/Gems/MotionMatching/Docs/Images/FeatureScatterplotPCA.png b/Gems/MotionMatching/Docs/Images/FeatureScatterplotPCA.png new file mode 100644 index 0000000000..36a1d78b33 --- /dev/null +++ b/Gems/MotionMatching/Docs/Images/FeatureScatterplotPCA.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0f656935e7c642064d3c65295df8830dda6cd67f30ea6fddea9d1d2bcef075a +size 252438 diff --git a/Gems/MotionMatching/Docs/Images/FeatureSharedRPE.png b/Gems/MotionMatching/Docs/Images/FeatureSharedRPE.png new file mode 100644 index 0000000000..363f2cd46d --- /dev/null +++ b/Gems/MotionMatching/Docs/Images/FeatureSharedRPE.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2671d3de458c4e799bcd34383f9630a242116c16f2a217d061e1b4e914ea3465 +size 57233 diff --git a/Gems/MotionMatching/Docs/Images/FeatureTrajectoryRPE.png b/Gems/MotionMatching/Docs/Images/FeatureTrajectoryRPE.png new file mode 100644 index 0000000000..5ff06f5d85 --- /dev/null +++ b/Gems/MotionMatching/Docs/Images/FeatureTrajectoryRPE.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b2d785f34da81b63fa90c15a6556fd4eb1aa796b4a00cda4371ded12cad5f016 +size 48699 diff --git a/Gems/MotionMatching/Docs/Images/FeatureTrajectoryVis.png b/Gems/MotionMatching/Docs/Images/FeatureTrajectoryVis.png new file mode 100644 index 0000000000..731bbec497 --- /dev/null +++ b/Gems/MotionMatching/Docs/Images/FeatureTrajectoryVis.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d342a61988f4e178e57941bf19007406422e638ece1091259b733a740ebae7af +size 149579 diff --git a/Gems/MotionMatching/Docs/Images/FeatureVelocityRPE.png b/Gems/MotionMatching/Docs/Images/FeatureVelocityRPE.png new file mode 100644 index 0000000000..5957cc7742 --- /dev/null +++ b/Gems/MotionMatching/Docs/Images/FeatureVelocityRPE.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91728049ab45766589cf09992b3d4e6e5fb3d4a9b51721cd505b8275cf45a620 +size 29802 diff --git a/Gems/MotionMatching/Docs/Images/FeatureVelocityVis.png b/Gems/MotionMatching/Docs/Images/FeatureVelocityVis.png new file mode 100644 index 0000000000..b8832c63e4 --- /dev/null +++ b/Gems/MotionMatching/Docs/Images/FeatureVelocityVis.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2991261105e34ecece1fbe27ada3f82487ef4de190fada75affb15242d05c29f +size 201487 diff --git a/Gems/MotionMatching/Docs/Images/TrajectoryHistory.png b/Gems/MotionMatching/Docs/Images/TrajectoryHistory.png new file mode 100644 index 0000000000..760a539a24 --- /dev/null +++ b/Gems/MotionMatching/Docs/Images/TrajectoryHistory.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de1e1c38355595592025ae6dc0f0a597218845d436b96ff4ab33dcd720c05149 +size 120561 From 73ca7006180639c2ddc303b236f009a31f727a90 Mon Sep 17 00:00:00 2001 From: Benjamin Jillich Date: Mon, 31 Jan 2022 16:08:03 +0100 Subject: [PATCH 33/53] Motion Matching: ReadMe.md update * Added several new sections (trajectory history, motion matching data, motion matching instance, etc. * Added images for the available feature visualizations and UI editors. * Added feature histogram and scatterplot. Signed-off-by: Benjamin Jillich --- Gems/MotionMatching/README.md | 119 ++++++++++++++++++++++++++++------ 1 file changed, 100 insertions(+), 19 deletions(-) diff --git a/Gems/MotionMatching/README.md b/Gems/MotionMatching/README.md index ecad3387f2..f07116fcd7 100644 --- a/Gems/MotionMatching/README.md +++ b/Gems/MotionMatching/README.md @@ -2,47 +2,128 @@ Motion matching is a data-driven animation technique that synthesizes motions based on existing animation data and the current character and input contexts. -# Features +https://user-images.githubusercontent.com/43751992/151820094-a7f0df93-bd09-4ea2-a34a-3d583815ff6c.mp4 -A feature is a property extracted from the animation data and is used by the motion matching algorithm to find the next best matching frame. Examples of features are the position of the feet joints, the linear or angular velocity of the knee joints or the trajectory history and future trajectory of the root joint. We can also encode environment sensations like obstacle positions and height, the location of the sword of an enemy character or a football's position and velocity. +## Setup -Their purpose is to describe a frame of the animation by their key characteristics and sometimes enhance the actual keyframe data (pos/rot/scale per joint) by e.g. taking the time domain into account and calculate the velocity or acceleration, or a whole trajectory to describe where the given joint came from to reach the frame and the path it moves along in the near future. +1. Add the `MotionMatching` gem to your project using the [Project Manager](https://docs.o3de.org/docs/user-guide/project-config/add-remove-gems/) or the [Command Line Interface (CLI)](https://docs.o3de.org/docs/user-guide/project-config/add-remove-gems/#using-the-command-line-interface-cli). See the documentation on [Adding and Removing Gems in a Project](https://docs.o3de.org/docs/user-guide/project-config/add-remove-gems/). +1. Compile your project and run. -Features are responsible for each of the following: +## Features + +A feature is a property extracted from the animation data and is used by the motion matching algorithm to find the next best matching frame. Examples of features are the position of the feet joints, the linear or angular velocity of the knee joints, or the trajectory history and future trajectory of the root joint. We can also encode environment sensations like obstacle positions and height, the location of the sword of an enemy character, or a football's position and velocity. + +Their purpose is to describe a frame of the animation by their key characteristics and sometimes enhance the actual keyframe data (pos/rot/scale per joint) by e.g. taking the time domain into account and calculating the velocity or acceleration, or a whole trajectory to describe where the given joint came from to reach the frame and the path it moves along in the near future. -1. Extract the feature values for a given frame in the motion database and store them in the feature matrix. For example calculate the left foot joint linear velocity, convert it to relative-to the root joint model space for frame 134 and place the XYZ components in the feature matrix starting at column 9. -1. Extract the feature from the current input context/pose and fill the query vector with it. For example calculate the linear velocity of the left foot joint of the current character pose in relative-to the root joint model space and place the XYZ components in the feature query vector starting at position 9. -1. Calculate the cost of the feature so that the motion matching algorithm can weight it into search for the next best matching frame. An example would be calculating the squared distance between a frame in the motion matching database and the current character pose for the left foot joint. +| Position Feature | Velocity Feature | Trajectory Feature | +| :------------- |:-------------| :-----| +| Matches joint positions | Matches joint velocities | Matches the trajectory history and future trajectory | +| ![Position Feature](https://user-images.githubusercontent.com/43751992/151818913-8ea11c40-3287-4fcf-aa7b-7209940cb852.png) | ![Velocity Feature](https://user-images.githubusercontent.com/43751992/151818945-546450ad-f970-4251-95d4-1d515e149d9b.png) | ![Trajectory Feature](https://user-images.githubusercontent.com/43751992/151819095-3cdb1524-957a-411e-9c0f-d2baa5a270c1.png) | + +Features are responsible for each of the following: -# Feature schema +1. Extract the feature values for a given frame in the motion database and store them in the feature matrix. For example, calculate the left foot joint linear velocity, convert it to relative to the root joint model space for frame 134 and place the XYZ components in the feature matrix starting at column 9. + +1. Extract the feature from the current input context/pose and fill the query vector with it. For example, calculate the linear velocity of the left foot joint of the current character pose in relative-to the root joint model space and place the XYZ components in the feature query vector starting at position 9. + +1. Calculate the cost of the feature so that the motion matching algorithm can weigh it in to search for the next best matching frame. An example would be calculating the squared distance between a frame in the motion matching database and the current character pose for the left foot joint. + +> Features are extracted and stored relative to a given joint, in most cases the motion extraction or root joint, and thus are in model-space. This makes the search algorithm invariant to the character location and orientation and the extracted features, like e.g. a joint position or velocity, translate and rotate along with the character. + + + + + + + + + + + + + + +
User-InterfaceProperty Descriptions
+ Shared Feature RPE + + Name: Display name used for feature identification and debug visualizations.
+ Joint: Joint name to extract the data from.
+ Relative To Joint: When extracting feature data, convert it to relative-space to the given joint.
+ Debug Draw: Are debug visualizations enabled for this feature?
+ Debug Draw Color: Color used for debug visualizations to identify the feature.
+ Cost Factor: The cost factor for the feature is multiplied with the actual and can be used to change a feature's influence in the motion matching search.
+ Residual: Use 'Squared' in case minimal differences should be ignored and larger differences should overweight others. Use 'Absolute' for linear differences and don't want the mentioned effect.
+
+ Trajectory Feature RPE + + Past Time Range: The time window the samples are distributed along for the trajectory history. [Default = 0.7 seconds]
+ Past Samples: The number of samples stored per frame for the past trajectory. [Default = 4 samples to represent the trajectory history]
+ Past Cost Factor: The cost factor is multiplied with the cost from the trajectory history and can be used to change the influence of the trajectory history match in the motion matching search.
+ Future Time Range: The time window the samples are distributed along for the future trajectory. [Default = 1.2 seconds]
+ Future Samples: The number of samples stored per frame for the future trajectory. [Default = 6 samples to represent the future trajectory]
+ Future Cost Factor: The cost factor is multiplied with the cost from the future trajectory and can be used to change the influence of the future trajectory match in the motion matching search.
+ Facing Axis: The facing direction of the character. Which axis of the joint transform is facing forward? [Default = Looking into Y-axis direction]
+
+ +## Feature schema The feature schema is a set of features that define the criteria used in the motion matching algorithm and influences the runtime speed, memory used, and the results of the synthesized motion. It is the most influential, user-defined input to the system. -The schema defines which features are extracted from the motion database while the actual extracted data is stored in the feature matrix. Along with the feature type, settings like the joint to extract the data from, a debug visualization color, how the residual is calculated or a custom feature is specified. +The schema defines which features are extracted from the motion database while the actual extracted data is stored in the feature matrix. Along with the feature type, settings like the joint to extract the data from, a debug visualization color, how the residual is calculated, or a custom feature is specified. The more features are selected by the user, the bigger the chances are that the searched and matched pose hits the expected result but the slower the algorithm will be and the more memory will be used. The key is to use crucial and independent elements that define a pose and its movement without being too strict on the wrong end. The root trajectory along with the left and right foot positions and velocities have been proven to be a good start here. -# Feature matrix +![Feature Schema](https://user-images.githubusercontent.com/43751992/151819276-7b5dedc0-475b-4eb4-bc27-f29d799646d0.png) -The feature matrix is an NxN matrix which stores the extracted feature values for all frames in our motion database based upon a given feature schema. The feature schema defines the order of the columns and values and is used to identify values and find their location inside the matrix. +## Feature matrix + +The feature matrix is a NxM matrix that stores the extracted feature values for all frames in our motion database based upon a given feature schema. The feature schema defines the order of the columns and values and is used to identify values and find their location inside the matrix. A 3D position feature storing XYZ values e.g. will use three columns in the feature matrix. Every component of a feature is linked to a column index, so e.g. the left foot position Y value might be at column index 6. The group of values or columns that belong to a given feature is what we call a feature block. The accumulated number of dimensions for all features in the schema, while the number of dimensions might vary per feature, form the number of columns of the feature matrix. Each row represents the features of a single frame of the motion database. The number of rows of the feature matrix is defined by the number. -![Feature Schema](Docs/Images/FeatureSchema.png) +> Memory usage: A motion capture database holding 1 hour of animation data together with a sample rate of 30 Hz to extract features, resulting in 108,000 frames, using the default feature schema having 59 features, will result in a feature matrix holding ~6.4 million values and use ~24.3 MB of memory. + +## Frame database (Motion database) + +A set of frames from your animations sampled at a given sample rate is stored in the frame database. A frame object knows about its index in the frame database, the animation it belongs to, and the sample time in seconds. It does not hold the sampled pose for memory reasons as the `EMotionFX::Motion` already stores the transform keyframes. + +The sample rate of the animation might differ from the sample rate used for the frame database. For example, your animations might be recorded with 60 Hz while we only want to extract the features with a sample rate of 30 Hz. As the motion matching algorithm is blending between the frames in the motion database while playing the animation window between the jumps/blends, it can make sense to have animations with a higher sample rate than we use to extract the features. + +A frame of the motion database can be used to sample a pose from which we can extract the features. It also provides functionality to sample a pose with a time offset to that frame. This can be handy to calculate joint velocities or trajectory samples. -# Trajectory History +When importing animations, frames that are within the range of a discard frame motion event are ignored and won't be added to the motion database. Discard motion events can be used to cut out sections of the imported animations that are unwanted like a stretching part between two dance cards. + +## Trajectory history The trajectory history stores world space position and facing direction data of the root joint (motion extraction joint) with each game tick. The maximum recording time is adjustable but needs to be at least as long as the past trajectory window from the trajectory feature as the trajectory history is used to build the query for the past trajectory feature. -# Motion Matching data +![Trajectory Feature](https://user-images.githubusercontent.com/43751992/151819315-beb8d9a1-69ca-49cd-bec0-ba2bae2dc469.png) + +## Motion Matching data + +Data based on a given skeleton but independent of the instance like the motion capture database, the feature schema or feature matrix is stored here. It is just a wrapper to group the sharable data. + +## Motion Matching instance + +The instance is where everything comes together. It stores the trajectory history, the trajectory query along with the query vector, knows about the last lowest cost frame index, and stores the time of the animation that the instance is currently playing. It is responsible for motion extraction, blending towards a new frame in the motion capture database in case the algorithm found a better matching frame and executes the actual search. + +## Architecture +![Class Diagram](https://user-images.githubusercontent.com/43751992/151819361-878edcb5-2b1f-4867-bb7f-8ed5c09a075a.png) + +## Jupyter notebook + +### Feature histograms + +In the image below you can see histograms per feature component showing their value distributions across the motion database. They can provide interesting insights, like e.g. if the motion database is holding more moving forward animations than it has strafing or backward moving animations, or how many fast vs slow turning animations are in the database. This information can be used to see if there is still a need to record some animations or if some type of animation is overrepresented and will lead to ambiguity and decrease the quality of the resulting synthesized animation. + +![Feature Histograms](https://user-images.githubusercontent.com/43751992/151819418-63580dc0-4358-4034-b60d-e8f1cbe138e2.png) -Data based on a given skeleton but independent of the instance like the motion capture database, the feature schema or feature matrix is stored in here. It is just a wrapper to group the sharable data. +### Scatterplot using PCA -# Motion Matching instance +The image below shows our high-dimensional feature matrix data projected down to two dimensions using principal component analysis. The density of the clusters and the distribution of the samples overall indicate how hard it is for the search algorithm to find a good matching frame candidate. -The instance is where everything comes together. It stores the trajectory history, the trajectory query along with the query vector, knows about the last lowest cost frame frame index and stores the time of the animation that the instance is currently playing. It is responsible for motion extraction, blending towards a new frame in the motion capture database in case the algorithm found a better matching frame and executes the actual search. +> Clusters in the image after multiple projections might still be separatable over one of the diminished dimensions. -# Architecture Diagram -![Class Diagram](Docs/Images/ArchitectureDiagram.png) \ No newline at end of file +![Feature Scatterplot PCA](https://user-images.githubusercontent.com/43751992/151819455-b1272ce8-423b-4037-9a7c-f4cd0044c25b.png) \ No newline at end of file From 5ec416ca1f90d1060fae4027411bec788c87be9e Mon Sep 17 00:00:00 2001 From: amzn-mike <80125227+amzn-mike@users.noreply.github.com> Date: Mon, 31 Jan 2022 10:41:18 -0600 Subject: [PATCH 34/53] Asset processor: separate modtime scanning tests (#7217) * Move modtime scanning tests out of APM tests file and into its own file. Changes were kept to a minimum to get things compiling, this is just a move of code Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com> * Fix rebase compile errors Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com> --- .../assetprocessor_test_files.cmake | 2 + .../tests/PathDependencyManagerTests.cpp | 8 +- .../native/tests/SourceFileRelocatorTests.cpp | 4 +- .../AssetProcessorManagerTest.cpp | 851 ------------------ .../assetmanager/AssetProcessorManagerTest.h | 141 ++- .../assetmanager/ModtimeScanningTests.cpp | 706 +++++++++++++++ .../tests/assetmanager/ModtimeScanningTests.h | 105 +++ 7 files changed, 927 insertions(+), 890 deletions(-) create mode 100644 Code/Tools/AssetProcessor/native/tests/assetmanager/ModtimeScanningTests.cpp create mode 100644 Code/Tools/AssetProcessor/native/tests/assetmanager/ModtimeScanningTests.h diff --git a/Code/Tools/AssetProcessor/assetprocessor_test_files.cmake b/Code/Tools/AssetProcessor/assetprocessor_test_files.cmake index 20ab4b706d..bc7b16cc79 100644 --- a/Code/Tools/AssetProcessor/assetprocessor_test_files.cmake +++ b/Code/Tools/AssetProcessor/assetprocessor_test_files.cmake @@ -30,6 +30,8 @@ set(FILES native/tests/assetBuilderSDK/SerializationDependenciesTests.cpp native/tests/assetmanager/AssetProcessorManagerTest.cpp native/tests/assetmanager/AssetProcessorManagerTest.h + native/tests/assetmanager/ModtimeScanningTests.cpp + native/tests/assetmanager/ModtimeScanningTests.h native/tests/utilities/assetUtilsTest.cpp native/tests/platformconfiguration/platformconfigurationtests.cpp native/tests/platformconfiguration/platformconfigurationtests.h diff --git a/Code/Tools/AssetProcessor/native/tests/PathDependencyManagerTests.cpp b/Code/Tools/AssetProcessor/native/tests/PathDependencyManagerTests.cpp index 1d6e92e47e..5103643833 100644 --- a/Code/Tools/AssetProcessor/native/tests/PathDependencyManagerTests.cpp +++ b/Code/Tools/AssetProcessor/native/tests/PathDependencyManagerTests.cpp @@ -49,7 +49,7 @@ namespace UnitTests } struct PathDependencyBase - : UnitTest::TraceBusRedirector + : ::UnitTest::TraceBusRedirector { void Init(); void Destroy(); @@ -65,7 +65,7 @@ namespace UnitTests }; struct PathDependencyDeletionTest - : UnitTest::ScopedAllocatorSetupFixture + : ::UnitTest::ScopedAllocatorSetupFixture , PathDependencyBase { void SetUp() override @@ -357,7 +357,7 @@ namespace UnitTests } struct PathDependencyBenchmarks - : UnitTest::ScopedAllocatorFixture + : ::UnitTest::ScopedAllocatorFixture , PathDependencyBase { static inline constexpr int NumTestDependencies = 4; // Must be a multiple of 4 @@ -530,7 +530,7 @@ namespace UnitTests BENCHMARK_F(PathDependencyBenchmarksWrapperClass, BM_DeferredWildcardDependencyResolution)(benchmark::State& state) { - for (auto _ : state) + for ([[maybe_unused]] auto unused : state) { m_benchmarks->m_stateData->SetProductDependencies(m_benchmarks->m_dependencies); diff --git a/Code/Tools/AssetProcessor/native/tests/SourceFileRelocatorTests.cpp b/Code/Tools/AssetProcessor/native/tests/SourceFileRelocatorTests.cpp index 8d52ba23bd..bd2ef8b3cd 100644 --- a/Code/Tools/AssetProcessor/native/tests/SourceFileRelocatorTests.cpp +++ b/Code/Tools/AssetProcessor/native/tests/SourceFileRelocatorTests.cpp @@ -191,7 +191,7 @@ namespace UnitTests m_data->m_perforceComponent = AZStd::make_unique(); m_data->m_perforceComponent->Activate(); - m_data->m_perforceComponent->SetConnection(new UnitTest::MockPerforceConnection(m_command)); + m_data->m_perforceComponent->SetConnection(new ::UnitTest::MockPerforceConnection(m_command)); } void TearDown() override @@ -876,7 +876,7 @@ namespace UnitTests QDir tempPath(m_tempDir.path()); auto filePath = QDir(tempPath.absoluteFilePath(m_data->m_scanFolder1.m_scanFolder.c_str())).absoluteFilePath("duplicate/file1.tif"); - + ASSERT_TRUE(AZ::IO::FileIOBase::GetInstance()->Exists(filePath.toUtf8().constData())); auto result = m_data->m_reporter->Delete(filePath.toUtf8().constData(), false); diff --git a/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp b/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp index 0908d0fdb9..347d2a6842 100644 --- a/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp +++ b/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp @@ -22,108 +22,6 @@ using namespace AssetProcessor; -class AssetProcessorManager_Test - : public AssetProcessorManager -{ -public: - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, AssetProcessedImpl_DifferentProductDependenciesPerProduct_SavesCorrectlyToDatabase); - - friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies); - friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies_DeferredResolution); - friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, SameFilenameForAllPlatforms); - - friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies_SourcePath); - - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, DeleteFolder_SignalsDeleteOfContainedFiles); - - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_BasicTest); - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_WithDifferentTypes_BasicTest); - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_Reverse_BasicTest); - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_MissingFiles_ReturnsNoPathWithPlaceholders); - - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_BeforeComputingDirtiness_AllDirty); - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_EmptyDatabase_AllDirty); - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_SameAsLastTime_NoneDirty); - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_MoreThanLastTime_NewOneIsDirty); - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_FewerThanLastTime_Dirty); - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_ChangedPattern_CountsAsNew); - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_ChangedPatternType_CountsAsNew); - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_NewPattern_CountsAsNewBuilder); - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_NewVersionNumber_IsNotANewBuilder); - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_NewAnalysisFingerprint_IsNotANewBuilder); - - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_BasicTest); - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_UpdateTest); - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByUuid); - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByName); - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByUuid_UpdatesWhenTheyAppear); - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByName_UpdatesWhenTheyAppear); - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_WildcardMissingFiles_ByName_UpdatesWhenTheyAppear); - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, JobDependencyOrderOnce_MultipleJobs_EmitOK); - - friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, SourceFileProcessFailure_ClearsFingerprint); - - friend class GTEST_TEST_CLASS_NAME_(AbsolutePathProductDependencyTest, UnresolvedProductPathDependency_AssetProcessedTwice_DoesNotDuplicateDependency); - friend class GTEST_TEST_CLASS_NAME_(AbsolutePathProductDependencyTest, AbsolutePathProductDependency_RetryDeferredDependenciesWithMatchingSource_DependencyResolves); - friend class GTEST_TEST_CLASS_NAME_(AbsolutePathProductDependencyTest, UnresolvedProductPathDependency_AssetProcessedTwice_ValidatePathDependenciesMap); - friend class GTEST_TEST_CLASS_NAME_(AbsolutePathProductDependencyTest, UnresolvedSourceFileTypeProductPathDependency_DependencyHasNoProductOutput_ValidatePathDependenciesMap); - - friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_FileUnchanged_WithoutModtimeSkipping); - - friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_FileUnchanged); - - friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_EnablePlatform_ShouldProcessFilesForPlatform); - - friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_ModifyFile); - friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_ModifyFile_AndThenRevert_ProcessesAgain); - friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_ModifyFilesSameHash_BothProcess); - friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_ModifyTimestamp); - friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_ModifyTimestampNoHashing_ProcessesFile); - friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_ModifyMetadataFile); - friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_DeleteFile); - friend class GTEST_TEST_CLASS_NAME_(DeleteTest, DeleteFolderSharedAcrossTwoScanFolders_CorrectFileAndFolderAreDeletedFromCache); - friend class GTEST_TEST_CLASS_NAME_(MetadataFileTest, MetadataFile_SourceFileExtensionDifferentCase); - - friend class AssetProcessorManagerTest; - friend struct ModtimeScanningTest; - friend struct JobDependencyTest; - friend struct ChainJobDependencyTest; - friend struct DeleteTest; - friend struct PathDependencyTest; - friend struct DuplicateProductsTest; - friend struct DuplicateProcessTest; - friend struct AbsolutePathProductDependencyTest; - friend struct WildcardSourceDependencyTest; - - explicit AssetProcessorManager_Test(PlatformConfiguration* config, QObject* parent = nullptr); - ~AssetProcessorManager_Test() override; - - bool CheckJobKeyToJobRunKeyMap(AZStd::string jobKey); - - int CountDirtyBuilders() const - { - int numDirty = 0; - for (const auto& element : m_builderDataCache) - { - if (element.second.m_isDirty) - { - ++numDirty; - } - } - return numDirty; - } - - bool IsBuilderDirty(const AZ::Uuid& builderBusId) const - { - auto finder = m_builderDataCache.find(builderBusId); - if (finder == m_builderDataCache.end()) - { - return true; - } - return finder->second.m_isDirty; - } -}; - AssetProcessorManager_Test::AssetProcessorManager_Test(AssetProcessor::PlatformConfiguration* config, QObject* parent /*= 0*/) :AssetProcessorManager(config, parent) { @@ -3839,632 +3737,6 @@ TEST_F(AssetProcessorManagerTest, SourceFileProcessFailure_ClearsFingerprint) ASSERT_EQ(source.m_analysisFingerprint, ""); } -void ModtimeScanningTest::SetUp() -{ - AssetProcessorManagerTest::SetUp(); - - m_data = AZStd::make_unique(); - - // We don't want the mock application manager to provide builder descriptors, mockBuilderInfoHandler will provide our own - m_mockApplicationManager->BusDisconnect(); - - m_data->m_mockBuilderInfoHandler.m_builderDesc = m_data->m_mockBuilderInfoHandler.CreateBuilderDesc("test builder", "{DF09DDC0-FD22-43B6-9E22-22C8574A6E1E}", { AssetBuilderSDK::AssetBuilderPattern("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard) }); - m_data->m_mockBuilderInfoHandler.BusConnect(); - - ASSERT_TRUE(m_mockApplicationManager->GetBuilderByID("txt files", m_data->m_builderTxtBuilder)); - - // Run this twice so the test builder doesn't get counted as a "new" builder and bypass the modtime skipping - m_assetProcessorManager->ComputeBuilderDirty(); - m_assetProcessorManager->ComputeBuilderDirty(); - - auto assetConnection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [this](JobDetails details) - { - m_data->m_processResults.push_back(AZStd::move(details)); - }); - - auto deletedConnection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::SourceDeleted, [this](QString file) - { - m_data->m_deletedSources.push_back(file); - }); - - // Create the test file - const auto& scanFolder = m_config->GetScanFolderAt(0); - m_data->m_relativePathFromWatchFolder[0] = "modtimeTestFile.txt"; - m_data->m_absolutePath.push_back(QDir(scanFolder.ScanPath()).absoluteFilePath(m_data->m_relativePathFromWatchFolder[0])); - - m_data->m_relativePathFromWatchFolder[1] = "modtimeTestDependency.txt"; - m_data->m_absolutePath.push_back(QDir(scanFolder.ScanPath()).absoluteFilePath(m_data->m_relativePathFromWatchFolder[1])); - - m_data->m_relativePathFromWatchFolder[2] = "modtimeTestDependency.txt.assetinfo"; - m_data->m_absolutePath.push_back(QDir(scanFolder.ScanPath()).absoluteFilePath(m_data->m_relativePathFromWatchFolder[2])); - - for (const auto& path : m_data->m_absolutePath) - { - ASSERT_TRUE(UnitTestUtils::CreateDummyFile(path, "")); - } - - m_data->m_mockBuilderInfoHandler.m_dependencyFilePath = m_data->m_absolutePath[1].toUtf8().data(); - - // Add file to database with no modtime - { - AssetDatabaseConnection connection; - ASSERT_TRUE(connection.OpenDatabase()); - AzToolsFramework::AssetDatabase::FileDatabaseEntry fileEntry; - fileEntry.m_fileName = m_data->m_relativePathFromWatchFolder[0].toUtf8().data(); - fileEntry.m_modTime = 0; - fileEntry.m_isFolder = false; - fileEntry.m_scanFolderPK = scanFolder.ScanFolderID(); - - bool entryAlreadyExists; - ASSERT_TRUE(connection.InsertFile(fileEntry, entryAlreadyExists)); - ASSERT_FALSE(entryAlreadyExists); - - fileEntry.m_fileID = AzToolsFramework::AssetDatabase::InvalidEntryId; // Reset the id so we make a new entry - fileEntry.m_fileName = m_data->m_relativePathFromWatchFolder[1].toUtf8().data(); - ASSERT_TRUE(connection.InsertFile(fileEntry, entryAlreadyExists)); - ASSERT_FALSE(entryAlreadyExists); - - fileEntry.m_fileID = AzToolsFramework::AssetDatabase::InvalidEntryId; // Reset the id so we make a new entry - fileEntry.m_fileName = m_data->m_relativePathFromWatchFolder[2].toUtf8().data(); - ASSERT_TRUE(connection.InsertFile(fileEntry, entryAlreadyExists)); - ASSERT_FALSE(entryAlreadyExists); - } - - QSet filePaths = BuildFileSet(); - SimulateAssetScanner(filePaths); - - ASSERT_TRUE(BlockUntilIdle(5000)); - ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 2); - ASSERT_EQ(m_data->m_processResults.size(), 2); - ASSERT_EQ(m_data->m_deletedSources.size(), 0); - - ProcessAssetJobs(); - - m_data->m_processResults.clear(); - m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0; - - m_isIdling = false; -} - -void ModtimeScanningTest::TearDown() -{ - m_data = nullptr; - - AssetProcessorManagerTest::TearDown(); -} - -void ModtimeScanningTest::ProcessAssetJobs() -{ - m_data->m_productPaths.clear(); - - for (const auto& processResult : m_data->m_processResults) - { - auto file = QDir(processResult.m_destinationPath).absoluteFilePath(processResult.m_jobEntry.m_databaseSourceName.toLower() + ".arc1"); - m_data->m_productPaths.emplace( - QDir(processResult.m_jobEntry.m_watchFolderPath) - .absoluteFilePath(processResult.m_jobEntry.m_databaseSourceName) - .toUtf8() - .constData(), - file); - - // Create the file on disk - ASSERT_TRUE(UnitTestUtils::CreateDummyFile(file, "products.")); - - AssetBuilderSDK::ProcessJobResponse response; - response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; - response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(file.toUtf8().constData(), AZ::Uuid::CreateNull(), 1)); - - QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, processResult.m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response)); - } - - ASSERT_TRUE(BlockUntilIdle(5000)); - - m_isIdling = false; -} - -void ModtimeScanningTest::SimulateAssetScanner(QSet filePaths) -{ - QMetaObject::invokeMethod(m_assetProcessorManager.get(), "OnAssetScannerStatusChange", Qt::QueuedConnection, Q_ARG(AssetProcessor::AssetScanningStatus, AssetProcessor::AssetScanningStatus::Started)); - QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessFilesFromScanner", Qt::QueuedConnection, Q_ARG(QSet, filePaths)); - QMetaObject::invokeMethod(m_assetProcessorManager.get(), "OnAssetScannerStatusChange", Qt::QueuedConnection, Q_ARG(AssetProcessor::AssetScanningStatus, AssetProcessor::AssetScanningStatus::Completed)); -} - -QSet ModtimeScanningTest::BuildFileSet() -{ - QSet filePaths; - - for (const auto& path : m_data->m_absolutePath) - { - QFileInfo fileInfo(path); - auto modtime = fileInfo.lastModified(); - AZ::u64 fileSize = fileInfo.size(); - filePaths.insert(AssetFileInfo(path, modtime, fileSize, m_config->GetScanFolderForFile(path), false)); - } - - return filePaths; -} - -void ModtimeScanningTest::ExpectWork(int createJobs, int processJobs) -{ - ASSERT_TRUE(BlockUntilIdle(5000)); - - EXPECT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, createJobs); - EXPECT_EQ(m_data->m_processResults.size(), processJobs); - EXPECT_FALSE(m_data->m_processResults[0].m_autoFail); - EXPECT_FALSE(m_data->m_processResults[1].m_autoFail); - EXPECT_EQ(m_data->m_deletedSources.size(), 0); - - m_isIdling = false; -} - -void ModtimeScanningTest::ExpectNoWork() -{ - // Since there's no work to do, the idle event isn't going to trigger, just process events a couple times - for (int i = 0; i < 10; ++i) - { - QCoreApplication::processEvents(QEventLoop::AllEvents, 10); - } - - ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 0); - ASSERT_EQ(m_data->m_processResults.size(), 0); - ASSERT_EQ(m_data->m_deletedSources.size(), 0); - - m_isIdling = false; -} - -void ModtimeScanningTest::SetFileContents(QString filePath, QString contents) -{ - QFile file(filePath); - file.open(QIODevice::WriteOnly | QIODevice::Truncate); - file.write(contents.toUtf8().constData()); - file.close(); -} - -TEST_F(ModtimeScanningTest, ModtimeSkipping_FileUnchanged_WithoutModtimeSkipping) -{ - using namespace AzToolsFramework::AssetSystem; - - // Make sure modtime skipping is disabled - // We're just going to do 1 quick sanity test to make sure the files are still processed when modtime skipping is turned off - m_assetProcessorManager->m_allowModtimeSkippingFeature = false; - - QSet filePaths = BuildFileSet(); - SimulateAssetScanner(filePaths); - - // 2 create jobs but 0 process jobs because the file has already been processed before in SetUp - ExpectWork(2, 0); -} - -TEST_F(ModtimeScanningTest, ModtimeSkipping_FileUnchanged) -{ - using namespace AzToolsFramework::AssetSystem; - - // Enable the features we're testing - m_assetProcessorManager->m_allowModtimeSkippingFeature = true; - AssetUtilities::SetUseFileHashOverride(true, true); - - QSet filePaths = BuildFileSet(); - SimulateAssetScanner(filePaths); - - ExpectNoWork(); -} - -TEST_F(ModtimeScanningTest, ModtimeSkipping_EnablePlatform_ShouldProcessFilesForPlatform) -{ - using namespace AzToolsFramework::AssetSystem; - - // Enable the features we're testing - m_assetProcessorManager->m_allowModtimeSkippingFeature = true; - AssetUtilities::SetUseFileHashOverride(true, true); - - // Enable android platform after the initial SetUp has already processed the files for pc - QDir tempPath(m_tempDir.path()); - AssetBuilderSDK::PlatformInfo androidPlatform("android", { "host", "renderer" }); - m_config->EnablePlatform(androidPlatform, true); - - // There's no way to remove scanfolders and adding a new one after enabling the platform will cause the pc assets to build as well, which we don't want - // Instead we'll just const cast the vector and modify the enabled platforms for the scanfolder - auto& platforms = const_cast&>(m_config->GetScanFolderAt(0).GetPlatforms()); - platforms.push_back(androidPlatform); - - // We need the builder fingerprints to be updated to reflect the newly enabled platform - m_assetProcessorManager->ComputeBuilderDirty(); - - QSet filePaths = BuildFileSet(); - SimulateAssetScanner(filePaths); - - ExpectWork(4, 2); // CreateJobs = 4, 2 files * 2 platforms. ProcessJobs = 2, just the android platform jobs (pc is already processed) - - ASSERT_TRUE(m_data->m_processResults[0].m_destinationPath.contains("android")); - ASSERT_TRUE(m_data->m_processResults[1].m_destinationPath.contains("android")); -} - -TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyTimestamp) -{ - // Update the timestamp on a file without changing its contents - // This should not cause any job to run since the hash of the file is the same before/after - // Additionally, the timestamp stored in the database should be updated - using namespace AzToolsFramework::AssetSystem; - - uint64_t timestamp = 1594923423; - - QString databaseName, scanfolderName; - m_config->ConvertToRelativePath(m_data->m_absolutePath[1], databaseName, scanfolderName); - auto* scanFolder = m_config->GetScanFolderForFile(m_data->m_absolutePath[1]); - - AzToolsFramework::AssetDatabase::FileDatabaseEntry fileEntry; - - m_assetProcessorManager.get()->m_stateData->GetFileByFileNameAndScanFolderId(databaseName, scanFolder->ScanFolderID(), fileEntry); - - ASSERT_NE(fileEntry.m_modTime, timestamp); - uint64_t existingTimestamp = fileEntry.m_modTime; - - // Modify the timestamp on just one file - AzToolsFramework::ToolsFileUtils::SetModificationTime(m_data->m_absolutePath[1].toUtf8().data(), timestamp); - - // Enable the features we're testing - m_assetProcessorManager->m_allowModtimeSkippingFeature = true; - AssetUtilities::SetUseFileHashOverride(true, true); - - QSet filePaths = BuildFileSet(); - SimulateAssetScanner(filePaths); - - ExpectNoWork(); - - m_assetProcessorManager.get()->m_stateData->GetFileByFileNameAndScanFolderId(databaseName, scanFolder->ScanFolderID(), fileEntry); - - // The timestamp should be updated even though nothing processed - ASSERT_NE(fileEntry.m_modTime, existingTimestamp); -} - -TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyTimestampNoHashing_ProcessesFile) -{ - // Update the timestamp on a file without changing its contents - // This should not cause any job to run since the hash of the file is the same before/after - // Additionally, the timestamp stored in the database should be updated - using namespace AzToolsFramework::AssetSystem; - - uint64_t timestamp = 1594923423; - - // Modify the timestamp on just one file - AzToolsFramework::ToolsFileUtils::SetModificationTime(m_data->m_absolutePath[1].toUtf8().data(), timestamp); - - // Enable the features we're testing - m_assetProcessorManager->m_allowModtimeSkippingFeature = true; - AssetUtilities::SetUseFileHashOverride(true, false); - - QSet filePaths = BuildFileSet(); - SimulateAssetScanner(filePaths); - - ExpectWork(2, 2); -} - -TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyFile) -{ - using namespace AzToolsFramework::AssetSystem; - - SetFileContents(m_data->m_absolutePath[1].toUtf8().constData(), "hello world"); - - // Enable the features we're testing - m_assetProcessorManager->m_allowModtimeSkippingFeature = true; - AssetUtilities::SetUseFileHashOverride(true, true); - - QSet filePaths = BuildFileSet(); - SimulateAssetScanner(filePaths); - - // Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a dependency that triggers the other test file to process as well - ExpectWork(2, 2); -} - -TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyFile_AndThenRevert_ProcessesAgain) -{ - using namespace AzToolsFramework::AssetSystem; - auto theFile = m_data->m_absolutePath[1].toUtf8(); - const char* theFileString = theFile.constData(); - - SetFileContents(theFileString, "hello world"); - - // Enable the features we're testing - m_assetProcessorManager->m_allowModtimeSkippingFeature = true; - AssetUtilities::SetUseFileHashOverride(true, true); - - QSet filePaths = BuildFileSet(); - SimulateAssetScanner(filePaths); - - // Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a dependency that triggers the other test file to process as well - ExpectWork(2, 2); - ProcessAssetJobs(); - - m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0; - m_data->m_processResults.clear(); - m_data->m_deletedSources.clear(); - - SetFileContents(theFileString, ""); - - filePaths = BuildFileSet(); - SimulateAssetScanner(filePaths); - - // Expect processing to happen again - ExpectWork(2, 2); -} - -struct LockedFileTest - : ModtimeScanningTest - , AssetProcessor::ConnectionBus::Handler -{ - MOCK_METHOD3(SendRaw, size_t (unsigned, unsigned, const QByteArray&)); - MOCK_METHOD3(SendPerPlatform, size_t (unsigned, const AzFramework::AssetSystem::BaseAssetProcessorMessage&, const QString&)); - MOCK_METHOD4(SendRawPerPlatform, size_t (unsigned, unsigned, const QByteArray&, const QString&)); - MOCK_METHOD2(SendRequest, unsigned (const AzFramework::AssetSystem::BaseAssetProcessorMessage&, const ResponseCallback&)); - MOCK_METHOD2(SendResponse, size_t (unsigned, const AzFramework::AssetSystem::BaseAssetProcessorMessage&)); - MOCK_METHOD1(RemoveResponseHandler, void (unsigned)); - - size_t Send(unsigned, const AzFramework::AssetSystem::BaseAssetProcessorMessage& message) override - { - using SourceFileNotificationMessage = AzToolsFramework::AssetSystem::SourceFileNotificationMessage; - switch (message.GetMessageType()) - { - case SourceFileNotificationMessage::MessageType: - if (const auto sourceFileMessage = azrtti_cast(&message); sourceFileMessage != nullptr && - sourceFileMessage->m_type == SourceFileNotificationMessage::NotificationType::FileRemoved) - { - // The File Remove message will occur before an attempt to delete the file - // Wait for more than 1 File Remove message. - // This indicates the AP has attempted to delete the file once, failed to do so and is now retrying - ++m_deleteCounter; - - if(m_deleteCounter > 1 && m_callback) - { - m_callback(); - m_callback = {}; // Unset it to be safe, we only intend to run the callback once - } - } - break; - default: - break; - } - - return 0; - } - - void SetUp() override - { - ModtimeScanningTest::SetUp(); - - ConnectionBus::Handler::BusConnect(0); - } - - void TearDown() override - { - ConnectionBus::Handler::BusDisconnect(); - - ModtimeScanningTest::TearDown(); - } - - AZStd::atomic_int m_deleteCounter{ 0 }; - AZStd::function m_callback; -}; - -TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeleteFails) -{ - auto theFile = m_data->m_absolutePath[1].toUtf8(); - const char* theFileString = theFile.constData(); - auto [sourcePath, productPath] = *m_data->m_productPaths.find(theFileString); - - { - QFile file(theFileString); - file.remove(); - } - - ASSERT_GT(m_data->m_productPaths.size(), 0); - QFile product(productPath); - - ASSERT_TRUE(product.open(QIODevice::ReadOnly)); - - // Check if we can delete the file now, if we can't, proceed with the test - // If we can, it means the OS running this test doesn't lock open files so there's nothing to test - if (!AZ::IO::SystemFile::Delete(productPath.toUtf8().constData())) - { - QMetaObject::invokeMethod( - m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, QString(theFileString))); - - EXPECT_TRUE(BlockUntilIdle(5000)); - - EXPECT_TRUE(QFile::exists(productPath)); - EXPECT_EQ(m_data->m_deletedSources.size(), 0); - } - else - { - SUCCEED() << "Skipping test. OS does not lock open files."; - } -} - -TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeletesWhenReleased) -{ - // This test is intended to verify the AP will successfully retry deleting a source asset - // when one of its product assets is locked temporarily - // We'll lock the file by holding it open - - auto theFile = m_data->m_absolutePath[1].toUtf8(); - const char* theFileString = theFile.constData(); - auto [sourcePath, productPath] = *m_data->m_productPaths.find(theFileString); - - { - QFile file(theFileString); - file.remove(); - } - - ASSERT_GT(m_data->m_productPaths.size(), 0); - QFile product(productPath); - - // Open the file and keep it open to lock it - // We'll start a thread later to unlock the file - // This will allow us to test how AP handles trying to delete a locked file - ASSERT_TRUE(product.open(QIODevice::ReadOnly)); - - // Check if we can delete the file now, if we can't, proceed with the test - // If we can, it means the OS running this test doesn't lock open files so there's nothing to test - if (!AZ::IO::SystemFile::Delete(productPath.toUtf8().constData())) - { - m_deleteCounter = 0; - - // Set up a callback which will fire after at least 1 retry - // Unlock the file at that point so AP can successfully delete it - m_callback = [&product]() - { - product.close(); - }; - - QMetaObject::invokeMethod( - m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, QString(theFileString))); - - EXPECT_TRUE(BlockUntilIdle(5000)); - - EXPECT_FALSE(QFile::exists(productPath)); - EXPECT_EQ(m_data->m_deletedSources.size(), 1); - - EXPECT_GT(m_deleteCounter, 1); // Make sure the AP tried more than once to delete the file - m_errorAbsorber->ExpectAsserts(0); - } - else - { - SUCCEED() << "Skipping test. OS does not lock open files."; - } -} - -TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyFilesSameHash_BothProcess) -{ - using namespace AzToolsFramework::AssetSystem; - - SetFileContents(m_data->m_absolutePath[1].toUtf8().constData(), "hello world"); - - // Enable the features we're testing - m_assetProcessorManager->m_allowModtimeSkippingFeature = true; - AssetUtilities::SetUseFileHashOverride(true, true); - - QSet filePaths = BuildFileSet(); - SimulateAssetScanner(filePaths); - - // Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a dependency that triggers the other test file to process as well - ExpectWork(2, 2); - ProcessAssetJobs(); - - m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0; - m_data->m_processResults.clear(); - m_data->m_deletedSources.clear(); - - // Make file 0 have the same contents as file 1 - SetFileContents(m_data->m_absolutePath[0].toUtf8().constData(), "hello world"); - - filePaths = BuildFileSet(); - SimulateAssetScanner(filePaths); - - ExpectWork(1, 1); -} - -TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyMetadataFile) -{ - using namespace AzToolsFramework::AssetSystem; - - SetFileContents(m_data->m_absolutePath[2].toUtf8().constData(), "hello world"); - - // Enable the features we're testing - m_assetProcessorManager->m_allowModtimeSkippingFeature = true; - AssetUtilities::SetUseFileHashOverride(true, true); - - QSet filePaths = BuildFileSet(); - SimulateAssetScanner(filePaths); - - // Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a metadata file - // that triggers the source file which is a dependency that triggers the other test file to process as well - ExpectWork(2, 2); -} - -TEST_F(ModtimeScanningTest, ModtimeSkipping_DeleteFile) -{ - using namespace AzToolsFramework::AssetSystem; - - // Enable the features we're testing - m_assetProcessorManager->m_allowModtimeSkippingFeature = true; - AssetUtilities::SetUseFileHashOverride(true, true); - - ASSERT_TRUE(QFile::remove(m_data->m_absolutePath[0])); - - // Feed in ONLY one file (the one we didn't delete) - QSet filePaths; - QFileInfo fileInfo(m_data->m_absolutePath[1]); - auto modtime = fileInfo.lastModified(); - AZ::u64 fileSize = fileInfo.size(); - filePaths.insert(AssetFileInfo(m_data->m_absolutePath[1], modtime, fileSize, &m_config->GetScanFolderAt(0), false)); - - SimulateAssetScanner(filePaths); - - QElapsedTimer timer; - timer.start(); - - do - { - QCoreApplication::processEvents(QEventLoop::AllEvents, 10); - } while (m_data->m_deletedSources.size() < m_data->m_relativePathFromWatchFolder[0].size() && timer.elapsed() < 5000); - - ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 0); - ASSERT_EQ(m_data->m_processResults.size(), 0); - ASSERT_THAT(m_data->m_deletedSources, testing::ElementsAre(m_data->m_relativePathFromWatchFolder[0])); -} - -TEST_F(ModtimeScanningTest, ReprocessRequest_FileNotModified_FileProcessed) -{ - using namespace AzToolsFramework::AssetSystem; - - m_assetProcessorManager->RequestReprocess(m_data->m_absolutePath[0]); - - ASSERT_TRUE(BlockUntilIdle(5000)); - - ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 1); - ASSERT_EQ(m_data->m_processResults.size(), 1); -} - -TEST_F(ModtimeScanningTest, ReprocessRequest_SourceWithDependency_BothWillProcess) -{ - using namespace AzToolsFramework::AssetSystem; - - using SourceFileDependencyEntry = AzToolsFramework::AssetDatabase::SourceFileDependencyEntry; - - SourceFileDependencyEntry newEntry1; - newEntry1.m_sourceDependencyID = AzToolsFramework::AssetDatabase::InvalidEntryId; - newEntry1.m_builderGuid = AZ::Uuid::CreateRandom(); - newEntry1.m_source = m_data->m_absolutePath[0].toUtf8().constData(); - newEntry1.m_dependsOnSource = m_data->m_absolutePath[1].toUtf8().constData(); - newEntry1.m_typeOfDependency = SourceFileDependencyEntry::DEP_SourceToSource; - - m_assetProcessorManager->RequestReprocess(m_data->m_absolutePath[0]); - ASSERT_TRUE(BlockUntilIdle(5000)); - - ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 1); - ASSERT_EQ(m_data->m_processResults.size(), 1); - - m_assetProcessorManager->RequestReprocess(m_data->m_absolutePath[1]); - ASSERT_TRUE(BlockUntilIdle(5000)); - - ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 3); - ASSERT_EQ(m_data->m_processResults.size(), 3); -} - -TEST_F(ModtimeScanningTest, ReprocessRequest_RequestFolder_SourceAssetsWillProcess) -{ - using namespace AzToolsFramework::AssetSystem; - - const auto& scanFolder = m_config->GetScanFolderAt(0); - - QString scanPath = scanFolder.ScanPath(); - m_assetProcessorManager->RequestReprocess(scanPath); - ASSERT_TRUE(BlockUntilIdle(5000)); - - // two text files are source assets, assetinfo is not - ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 2); - ASSERT_EQ(m_data->m_processResults.size(), 2); -} - ////////////////////////////////////////////////////////////////////////// MockBuilderInfoHandler::~MockBuilderInfoHandler() @@ -5205,130 +4477,7 @@ TEST_F(ChainJobDependencyTest, TestChainDependency_Multi) } } -void DeleteTest::SetUp() -{ - AssetProcessorManagerTest::SetUp(); - - m_data = AZStd::make_unique(); - - m_assetProcessorManager->m_allowModtimeSkippingFeature = true; - - // We don't want the mock application manager to provide builder descriptors, mockBuilderInfoHandler will provide our own - m_mockApplicationManager->BusDisconnect(); - - m_data->m_mockBuilderInfoHandler.m_builderDesc = m_data->m_mockBuilderInfoHandler.CreateBuilderDesc("test builder", "{DF09DDC0-FD22-43B6-9E22-22C8574A6E1E}", { AssetBuilderSDK::AssetBuilderPattern("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard) }); - m_data->m_mockBuilderInfoHandler.BusConnect(); - - ASSERT_TRUE(m_mockApplicationManager->GetBuilderByID("txt files", m_data->m_builderTxtBuilder)); - - // Run this twice so the test builder doesn't get counted as a "new" builder and bypass the modtime skipping - m_assetProcessorManager->ComputeBuilderDirty(); - m_assetProcessorManager->ComputeBuilderDirty(); - - auto setupConnectionsFunc = [this]() - { - QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [this](JobDetails details) - { - m_data->m_processResults.push_back(AZStd::move(details)); - }); - - QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::SourceDeleted, [this](QString file) - { - m_data->m_deletedSources.push_back(file); - }); - }; - - auto createFileAndAddToDatabaseFunc = [this](const AssetProcessor::ScanFolderInfo* scanFolder, QString file) - { - using namespace AzToolsFramework::AssetDatabase; - - QString watchFolderPath = scanFolder->ScanPath(); - QString absPath(QDir(watchFolderPath).absoluteFilePath(file)); - UnitTestUtils::CreateDummyFile(absPath); - - m_data->m_absolutePath.push_back(absPath); - - AzToolsFramework::AssetDatabase::FileDatabaseEntry fileEntry; - fileEntry.m_fileName = file.toUtf8().constData(); - fileEntry.m_modTime = 0; - fileEntry.m_isFolder = false; - fileEntry.m_scanFolderPK = scanFolder->ScanFolderID(); - - bool entryAlreadyExists; - ASSERT_TRUE(m_assetProcessorManager->m_stateData->InsertFile(fileEntry, entryAlreadyExists)); - ASSERT_FALSE(entryAlreadyExists); - }; - - setupConnectionsFunc(); - - // Create test files - QDir tempPath(m_tempDir.path()); - const auto* scanFolder1 = m_config->GetScanFolderByPath(tempPath.absoluteFilePath("subfolder1")); - const auto* scanFolder4 = m_config->GetScanFolderByPath(tempPath.absoluteFilePath("subfolder4")); - - createFileAndAddToDatabaseFunc(scanFolder1, QString("textures/a.txt")); - createFileAndAddToDatabaseFunc(scanFolder4, QString("textures/b.txt")); - - // Run the test files through AP all the way to processing stage - QSet filePaths = BuildFileSet(); - SimulateAssetScanner(filePaths); - - ASSERT_TRUE(BlockUntilIdle(5000)); - ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 2); - ASSERT_EQ(m_data->m_processResults.size(), 2); - ASSERT_EQ(m_data->m_deletedSources.size(), 0); - - ProcessAssetJobs(); - - m_data->m_processResults.clear(); - m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0; - - // Reboot the APM since we added stuff to the database that needs to be loaded on-startup of the APM - m_assetProcessorManager.reset(new AssetProcessorManager_Test(m_config.get())); - - m_idleConnection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessor::AssetProcessorManager::AssetProcessorManagerIdleState, [this](bool newState) - { - m_isIdling = newState; - }); - - setupConnectionsFunc(); - - m_assetProcessorManager->ComputeBuilderDirty(); -} - -TEST_F(DeleteTest, DeleteFolderSharedAcrossTwoScanFolders_CorrectFileAndFolderAreDeletedFromCache) -{ - // There was a bug where AP wasn't repopulating the "known folders" list when modtime skipping was enabled and no work was needed - // As a result, deleting a folder didn't count as a "folder", so the wrong code path was taken. This test makes sure the correct deletion events fire - - using namespace AzToolsFramework::AssetSystem; - // Modtime skipping has to be on for this - m_assetProcessorManager->m_allowModtimeSkippingFeature = true; - - // Feed in the files from the asset scanner, no jobs should run since they're already up-to-date - QSet filePaths = BuildFileSet(); - SimulateAssetScanner(filePaths); - - ExpectNoWork(); - - // Delete one of the folders - QDir tempPath(m_tempDir.path()); - QString absPath(tempPath.absoluteFilePath("subfolder1/textures")); - QDir(absPath).removeRecursively(); - - AZStd::vector deletedFolders; - QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::SourceFolderDeleted, [&deletedFolders](QString file) - { - deletedFolders.push_back(file.toUtf8().constData()); - }); - - m_assetProcessorManager->AssessDeletedFile(absPath); - ASSERT_TRUE(BlockUntilIdle(5000)); - - ASSERT_THAT(m_data->m_deletedSources, testing::UnorderedElementsAre("textures/a.txt")); - ASSERT_THAT(deletedFolders, testing::UnorderedElementsAre("textures")); -} void DuplicateProcessTest::SetUp() { diff --git a/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.h b/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.h index c532b08016..4e644ea457 100644 --- a/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.h +++ b/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.h @@ -37,6 +37,114 @@ public: MOCK_METHOD1(GetAssetDatabaseLocation, bool(AZStd::string&)); }; +class AssetProcessorManager_Test : public AssetProcessor::AssetProcessorManager +{ +public: + friend class GTEST_TEST_CLASS_NAME_( + AssetProcessorManagerTest, AssetProcessedImpl_DifferentProductDependenciesPerProduct_SavesCorrectlyToDatabase); + + friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies); + friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies_DeferredResolution); + friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, SameFilenameForAllPlatforms); + + friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies_SourcePath); + + friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, DeleteFolder_SignalsDeleteOfContainedFiles); + + friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_BasicTest); + friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_WithDifferentTypes_BasicTest); + friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_Reverse_BasicTest); + friend class GTEST_TEST_CLASS_NAME_( + AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_MissingFiles_ReturnsNoPathWithPlaceholders); + + friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_BeforeComputingDirtiness_AllDirty); + friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_EmptyDatabase_AllDirty); + friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_SameAsLastTime_NoneDirty); + friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_MoreThanLastTime_NewOneIsDirty); + friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_FewerThanLastTime_Dirty); + friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_ChangedPattern_CountsAsNew); + friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_ChangedPatternType_CountsAsNew); + friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_NewPattern_CountsAsNewBuilder); + friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_NewVersionNumber_IsNotANewBuilder); + friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_NewAnalysisFingerprint_IsNotANewBuilder); + + friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_BasicTest); + friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_UpdateTest); + friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByUuid); + friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByName); + friend class GTEST_TEST_CLASS_NAME_( + AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByUuid_UpdatesWhenTheyAppear); + friend class GTEST_TEST_CLASS_NAME_( + AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFiles_ByName_UpdatesWhenTheyAppear); + friend class GTEST_TEST_CLASS_NAME_( + AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_WildcardMissingFiles_ByName_UpdatesWhenTheyAppear); + friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, JobDependencyOrderOnce_MultipleJobs_EmitOK); + + friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, SourceFileProcessFailure_ClearsFingerprint); + + friend class GTEST_TEST_CLASS_NAME_( + AbsolutePathProductDependencyTest, UnresolvedProductPathDependency_AssetProcessedTwice_DoesNotDuplicateDependency); + friend class GTEST_TEST_CLASS_NAME_( + AbsolutePathProductDependencyTest, AbsolutePathProductDependency_RetryDeferredDependenciesWithMatchingSource_DependencyResolves); + friend class GTEST_TEST_CLASS_NAME_( + AbsolutePathProductDependencyTest, UnresolvedProductPathDependency_AssetProcessedTwice_ValidatePathDependenciesMap); + friend class GTEST_TEST_CLASS_NAME_( + AbsolutePathProductDependencyTest, + UnresolvedSourceFileTypeProductPathDependency_DependencyHasNoProductOutput_ValidatePathDependenciesMap); + + friend class GTEST_TEST_CLASS_NAME_(DeleteTest, DeleteFolderSharedAcrossTwoScanFolders_CorrectFileAndFolderAreDeletedFromCache); + friend class GTEST_TEST_CLASS_NAME_(MetadataFileTest, MetadataFile_SourceFileExtensionDifferentCase); + + friend class AssetProcessorManagerTest; + friend struct JobDependencyTest; + friend struct ChainJobDependencyTest; + friend struct DeleteTest; + friend struct PathDependencyTest; + friend struct DuplicateProductsTest; + friend struct DuplicateProcessTest; + friend struct AbsolutePathProductDependencyTest; + friend struct WildcardSourceDependencyTest; + + explicit AssetProcessorManager_Test(AssetProcessor::PlatformConfiguration* config, QObject* parent = nullptr); + ~AssetProcessorManager_Test() override; + + bool CheckJobKeyToJobRunKeyMap(AZStd::string jobKey); + + int CountDirtyBuilders() const + { + int numDirty = 0; + for (const auto& element : m_builderDataCache) + { + if (element.second.m_isDirty) + { + ++numDirty; + } + } + return numDirty; + } + + bool IsBuilderDirty(const AZ::Uuid& builderBusId) const + { + auto finder = m_builderDataCache.find(builderBusId); + if (finder == m_builderDataCache.end()) + { + return true; + } + return finder->second.m_isDirty; + } + + void RecomputeDirtyBuilders() + { + // Run this twice so the test builder doesn't get counted as a "new" builder and bypass the modtime skipping + ComputeBuilderDirty(); + ComputeBuilderDirty(); + } + + using AssetProcessorManager::m_stateData; + using AssetProcessorManager::ComputeBuilderDirty; +}; + + class AssetProcessorManagerTest : public AssetProcessor::AssetProcessorTest { @@ -165,33 +273,6 @@ struct MockBuilderInfoHandler int m_createJobsCount = 0; }; -struct ModtimeScanningTest - : public AssetProcessorManagerTest -{ - void SetUp() override; - void TearDown() override; - - void ProcessAssetJobs(); - void SimulateAssetScanner(QSet filePaths); - QSet BuildFileSet(); - void ExpectWork(int createJobs, int processJobs); - void ExpectNoWork(); - void SetFileContents(QString filePath, QString contents); - - struct StaticData - { - QString m_relativePathFromWatchFolder[3]; - AZStd::vector m_absolutePath; - AZStd::vector m_processResults; - AZStd::unordered_multimap m_productPaths; - AZStd::vector m_deletedSources; - AZStd::shared_ptr m_builderTxtBuilder; - MockBuilderInfoHandler m_mockBuilderInfoHandler; - }; - - AZStd::unique_ptr m_data; -}; - struct MetadataFileTest : public AssetProcessorManagerTest @@ -274,9 +355,3 @@ struct DuplicateProductsTest { void SetupDuplicateProductsTest(QString& sourceFile, QDir& tempPath, QString& productFile, AZStd::vector& jobDetails, AssetBuilderSDK::ProcessJobResponse& response, bool multipleOutputs, QString extension); }; - -struct DeleteTest - : public ModtimeScanningTest -{ - void SetUp() override; -}; diff --git a/Code/Tools/AssetProcessor/native/tests/assetmanager/ModtimeScanningTests.cpp b/Code/Tools/AssetProcessor/native/tests/assetmanager/ModtimeScanningTests.cpp new file mode 100644 index 0000000000..6b3124ca00 --- /dev/null +++ b/Code/Tools/AssetProcessor/native/tests/assetmanager/ModtimeScanningTests.cpp @@ -0,0 +1,706 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include + +namespace UnitTests +{ + using AssetFileInfo = AssetProcessor::AssetFileInfo; + + void ModtimeScanningTest::SetUpAssetProcessorManager() + { + using namespace AssetProcessor; + + m_assetProcessorManager->SetEnableModtimeSkippingFeature(true); + m_assetProcessorManager->RecomputeDirtyBuilders(); + + QObject::connect( + m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, + [this](JobDetails details) + { + m_data->m_processResults.push_back(AZStd::move(details)); + }); + + QObject::connect( + m_assetProcessorManager.get(), &AssetProcessorManager::SourceDeleted, + [this](QString file) + { + m_data->m_deletedSources.push_back(file); + }); + + m_idleConnection = QObject::connect( + m_assetProcessorManager.get(), &AssetProcessor::AssetProcessorManager::AssetProcessorManagerIdleState, + [this](bool newState) + { + m_isIdling = newState; + }); + } + + void ModtimeScanningTest::SetUp() + { + using namespace AssetProcessor; + + AssetProcessorManagerTest::SetUp(); + + m_data = AZStd::make_unique(); + + // We don't want the mock application manager to provide builder descriptors, mockBuilderInfoHandler will provide our own + m_mockApplicationManager->BusDisconnect(); + + m_data->m_mockBuilderInfoHandler.m_builderDesc = m_data->m_mockBuilderInfoHandler.CreateBuilderDesc( + "test builder", "{DF09DDC0-FD22-43B6-9E22-22C8574A6E1E}", + { AssetBuilderSDK::AssetBuilderPattern("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard) }); + m_data->m_mockBuilderInfoHandler.BusConnect(); + + ASSERT_TRUE(m_mockApplicationManager->GetBuilderByID("txt files", m_data->m_builderTxtBuilder)); + + SetUpAssetProcessorManager(); + + // Create the test file + const auto& scanFolder = m_config->GetScanFolderAt(0); + m_data->m_relativePathFromWatchFolder[0] = "modtimeTestFile.txt"; + m_data->m_absolutePath.push_back(QDir(scanFolder.ScanPath()).absoluteFilePath(m_data->m_relativePathFromWatchFolder[0])); + + m_data->m_relativePathFromWatchFolder[1] = "modtimeTestDependency.txt"; + m_data->m_absolutePath.push_back(QDir(scanFolder.ScanPath()).absoluteFilePath(m_data->m_relativePathFromWatchFolder[1])); + + m_data->m_relativePathFromWatchFolder[2] = "modtimeTestDependency.txt.assetinfo"; + m_data->m_absolutePath.push_back(QDir(scanFolder.ScanPath()).absoluteFilePath(m_data->m_relativePathFromWatchFolder[2])); + + for (const auto& path : m_data->m_absolutePath) + { + ASSERT_TRUE(UnitTestUtils::CreateDummyFile(path, "")); + } + + m_data->m_mockBuilderInfoHandler.m_dependencyFilePath = m_data->m_absolutePath[1].toUtf8().data(); + + // Add file to database with no modtime + { + AssetDatabaseConnection connection; + ASSERT_TRUE(connection.OpenDatabase()); + AzToolsFramework::AssetDatabase::FileDatabaseEntry fileEntry; + fileEntry.m_fileName = m_data->m_relativePathFromWatchFolder[0].toUtf8().data(); + fileEntry.m_modTime = 0; + fileEntry.m_isFolder = false; + fileEntry.m_scanFolderPK = scanFolder.ScanFolderID(); + + bool entryAlreadyExists; + ASSERT_TRUE(connection.InsertFile(fileEntry, entryAlreadyExists)); + ASSERT_FALSE(entryAlreadyExists); + + fileEntry.m_fileID = AzToolsFramework::AssetDatabase::InvalidEntryId; // Reset the id so we make a new entry + fileEntry.m_fileName = m_data->m_relativePathFromWatchFolder[1].toUtf8().data(); + ASSERT_TRUE(connection.InsertFile(fileEntry, entryAlreadyExists)); + ASSERT_FALSE(entryAlreadyExists); + + fileEntry.m_fileID = AzToolsFramework::AssetDatabase::InvalidEntryId; // Reset the id so we make a new entry + fileEntry.m_fileName = m_data->m_relativePathFromWatchFolder[2].toUtf8().data(); + ASSERT_TRUE(connection.InsertFile(fileEntry, entryAlreadyExists)); + ASSERT_FALSE(entryAlreadyExists); + } + + QSet filePaths = BuildFileSet(); + SimulateAssetScanner(filePaths); + + ASSERT_TRUE(BlockUntilIdle(5000)); + ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 2); + ASSERT_EQ(m_data->m_processResults.size(), 2); + ASSERT_EQ(m_data->m_deletedSources.size(), 0); + + ProcessAssetJobs(); + + m_data->m_processResults.clear(); + m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0; + + m_isIdling = false; + } + + void ModtimeScanningTest::TearDown() + { + m_data = nullptr; + + AssetProcessorManagerTest::TearDown(); + } + + void ModtimeScanningTest::ProcessAssetJobs() + { + m_data->m_productPaths.clear(); + + for (const auto& processResult : m_data->m_processResults) + { + auto file = + QDir(processResult.m_destinationPath).absoluteFilePath(processResult.m_jobEntry.m_databaseSourceName.toLower() + ".arc1"); + m_data->m_productPaths.emplace( + QDir(processResult.m_jobEntry.m_watchFolderPath) + .absoluteFilePath(processResult.m_jobEntry.m_databaseSourceName) + .toUtf8() + .constData(), + file); + + // Create the file on disk + ASSERT_TRUE(UnitTestUtils::CreateDummyFile(file, "products.")); + + AssetBuilderSDK::ProcessJobResponse response; + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; + response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(file.toUtf8().constData(), AZ::Uuid::CreateNull(), 1)); + + using JobEntry = AssetProcessor::JobEntry; + + QMetaObject::invokeMethod( + m_assetProcessorManager.get(), "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, processResult.m_jobEntry), + Q_ARG(AssetBuilderSDK::ProcessJobResponse, response)); + } + + ASSERT_TRUE(BlockUntilIdle(5000)); + + m_isIdling = false; + } + + void ModtimeScanningTest::SimulateAssetScanner(QSet filePaths) + { + QMetaObject::invokeMethod( + m_assetProcessorManager.get(), "OnAssetScannerStatusChange", Qt::QueuedConnection, + Q_ARG(AssetProcessor::AssetScanningStatus, AssetProcessor::AssetScanningStatus::Started)); + QMetaObject::invokeMethod( + m_assetProcessorManager.get(), "AssessFilesFromScanner", Qt::QueuedConnection, Q_ARG(QSet, filePaths)); + QMetaObject::invokeMethod( + m_assetProcessorManager.get(), "OnAssetScannerStatusChange", Qt::QueuedConnection, + Q_ARG(AssetProcessor::AssetScanningStatus, AssetProcessor::AssetScanningStatus::Completed)); + } + + QSet ModtimeScanningTest::BuildFileSet() + { + QSet filePaths; + + for (const auto& path : m_data->m_absolutePath) + { + QFileInfo fileInfo(path); + auto modtime = fileInfo.lastModified(); + AZ::u64 fileSize = fileInfo.size(); + filePaths.insert(AssetFileInfo(path, modtime, fileSize, m_config->GetScanFolderForFile(path), false)); + } + + return filePaths; + } + + void ModtimeScanningTest::ExpectWork(int createJobs, int processJobs) + { + ASSERT_TRUE(BlockUntilIdle(5000)); + + EXPECT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, createJobs); + EXPECT_EQ(m_data->m_processResults.size(), processJobs); + for (int i = 0; i < processJobs; ++i) + { + EXPECT_FALSE(m_data->m_processResults[i].m_autoFail); + } + EXPECT_EQ(m_data->m_deletedSources.size(), 0); + + m_isIdling = false; + } + + void ModtimeScanningTest::ExpectNoWork() + { + // Since there's no work to do, the idle event isn't going to trigger, just process events a couple times + for (int i = 0; i < 10; ++i) + { + QCoreApplication::processEvents(QEventLoop::AllEvents, 10); + } + + ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 0); + ASSERT_EQ(m_data->m_processResults.size(), 0); + ASSERT_EQ(m_data->m_deletedSources.size(), 0); + + m_isIdling = false; + } + + void ModtimeScanningTest::SetFileContents(QString filePath, QString contents) + { + QFile file(filePath); + file.open(QIODevice::WriteOnly | QIODevice::Truncate); + file.write(contents.toUtf8().constData()); + file.close(); + } + + TEST_F(ModtimeScanningTest, ModtimeSkipping_FileUnchanged_WithoutModtimeSkipping) + { + using namespace AzToolsFramework::AssetSystem; + + // Make sure modtime skipping is disabled + // We're just going to do 1 quick sanity test to make sure the files are still processed when modtime skipping is turned off + m_assetProcessorManager->SetEnableModtimeSkippingFeature(false); + + QSet filePaths = BuildFileSet(); + SimulateAssetScanner(filePaths); + + // 2 create jobs but 0 process jobs because the file has already been processed before in SetUp + ExpectWork(2, 0); + } + + TEST_F(ModtimeScanningTest, ModtimeSkipping_FileUnchanged) + { + using namespace AzToolsFramework::AssetSystem; + + AssetUtilities::SetUseFileHashOverride(true, true); + + QSet filePaths = BuildFileSet(); + SimulateAssetScanner(filePaths); + + ExpectNoWork(); + } + + TEST_F(ModtimeScanningTest, ModtimeSkipping_EnablePlatform_ShouldProcessFilesForPlatform) + { + using namespace AzToolsFramework::AssetSystem; + + AssetUtilities::SetUseFileHashOverride(true, true); + + // Enable android platform after the initial SetUp has already processed the files for pc + QDir tempPath(m_tempDir.path()); + AssetBuilderSDK::PlatformInfo androidPlatform("android", { "host", "renderer" }); + m_config->EnablePlatform(androidPlatform, true); + + // There's no way to remove scanfolders and adding a new one after enabling the platform will cause the pc assets to build as well, + // which we don't want Instead we'll just const cast the vector and modify the enabled platforms for the scanfolder + auto& platforms = const_cast&>(m_config->GetScanFolderAt(0).GetPlatforms()); + platforms.push_back(androidPlatform); + + // We need the builder fingerprints to be updated to reflect the newly enabled platform + m_assetProcessorManager->ComputeBuilderDirty(); + + QSet filePaths = BuildFileSet(); + SimulateAssetScanner(filePaths); + + ExpectWork( + 4, 2); // CreateJobs = 4, 2 files * 2 platforms. ProcessJobs = 2, just the android platform jobs (pc is already processed) + + ASSERT_TRUE(m_data->m_processResults[0].m_destinationPath.contains("android")); + ASSERT_TRUE(m_data->m_processResults[1].m_destinationPath.contains("android")); + } + + TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyTimestamp) + { + // Update the timestamp on a file without changing its contents + // This should not cause any job to run since the hash of the file is the same before/after + // Additionally, the timestamp stored in the database should be updated + using namespace AzToolsFramework::AssetSystem; + + uint64_t timestamp = 1594923423; + + QString databaseName, scanfolderName; + m_config->ConvertToRelativePath(m_data->m_absolutePath[1], databaseName, scanfolderName); + auto* scanFolder = m_config->GetScanFolderForFile(m_data->m_absolutePath[1]); + + AzToolsFramework::AssetDatabase::FileDatabaseEntry fileEntry; + + m_assetProcessorManager->m_stateData->GetFileByFileNameAndScanFolderId(databaseName, scanFolder->ScanFolderID(), fileEntry); + + ASSERT_NE(fileEntry.m_modTime, timestamp); + uint64_t existingTimestamp = fileEntry.m_modTime; + + // Modify the timestamp on just one file + AzToolsFramework::ToolsFileUtils::SetModificationTime(m_data->m_absolutePath[1].toUtf8().data(), timestamp); + + AssetUtilities::SetUseFileHashOverride(true, true); + + QSet filePaths = BuildFileSet(); + SimulateAssetScanner(filePaths); + + ExpectNoWork(); + + m_assetProcessorManager->m_stateData->GetFileByFileNameAndScanFolderId(databaseName, scanFolder->ScanFolderID(), fileEntry); + + // The timestamp should be updated even though nothing processed + ASSERT_NE(fileEntry.m_modTime, existingTimestamp); + } + + TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyTimestampNoHashing_ProcessesFile) + { + // Update the timestamp on a file without changing its contents + // This should not cause any job to run since the hash of the file is the same before/after + // Additionally, the timestamp stored in the database should be updated + using namespace AzToolsFramework::AssetSystem; + + uint64_t timestamp = 1594923423; + + // Modify the timestamp on just one file + AzToolsFramework::ToolsFileUtils::SetModificationTime(m_data->m_absolutePath[1].toUtf8().data(), timestamp); + + AssetUtilities::SetUseFileHashOverride(true, false); + + QSet filePaths = BuildFileSet(); + SimulateAssetScanner(filePaths); + + ExpectWork(2, 2); + } + + TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyFile) + { + using namespace AzToolsFramework::AssetSystem; + + SetFileContents(m_data->m_absolutePath[1].toUtf8().constData(), "hello world"); + + AssetUtilities::SetUseFileHashOverride(true, true); + + QSet filePaths = BuildFileSet(); + SimulateAssetScanner(filePaths); + + // Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a dependency that triggers + // the other test file to process as well + ExpectWork(2, 2); + } + + TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyFile_AndThenRevert_ProcessesAgain) + { + using namespace AzToolsFramework::AssetSystem; + auto theFile = m_data->m_absolutePath[1].toUtf8(); + const char* theFileString = theFile.constData(); + + SetFileContents(theFileString, "hello world"); + + AssetUtilities::SetUseFileHashOverride(true, true); + + QSet filePaths = BuildFileSet(); + SimulateAssetScanner(filePaths); + + // Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a dependency that triggers + // the other test file to process as well + ExpectWork(2, 2); + ProcessAssetJobs(); + + m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0; + m_data->m_processResults.clear(); + m_data->m_deletedSources.clear(); + + SetFileContents(theFileString, ""); + + filePaths = BuildFileSet(); + SimulateAssetScanner(filePaths); + + // Expect processing to happen again + ExpectWork(2, 2); + } + + TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyFilesSameHash_BothProcess) + { + using namespace AzToolsFramework::AssetSystem; + + SetFileContents(m_data->m_absolutePath[1].toUtf8().constData(), "hello world"); + + AssetUtilities::SetUseFileHashOverride(true, true); + + QSet filePaths = BuildFileSet(); + SimulateAssetScanner(filePaths); + + // Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a dependency that triggers + // the other test file to process as well + ExpectWork(2, 2); + ProcessAssetJobs(); + + m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0; + m_data->m_processResults.clear(); + m_data->m_deletedSources.clear(); + + // Make file 0 have the same contents as file 1 + SetFileContents(m_data->m_absolutePath[0].toUtf8().constData(), "hello world"); + + filePaths = BuildFileSet(); + SimulateAssetScanner(filePaths); + + ExpectWork(1, 1); + } + + TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyMetadataFile) + { + using namespace AzToolsFramework::AssetSystem; + + SetFileContents(m_data->m_absolutePath[2].toUtf8().constData(), "hello world"); + + AssetUtilities::SetUseFileHashOverride(true, true); + + QSet filePaths = BuildFileSet(); + SimulateAssetScanner(filePaths); + + // Even though we're only updating one file, we're expecting 2 createJob calls because our test file is a metadata file + // that triggers the source file which is a dependency that triggers the other test file to process as well + ExpectWork(2, 2); + } + + TEST_F(ModtimeScanningTest, ModtimeSkipping_DeleteFile) + { + using namespace AzToolsFramework::AssetSystem; + + AssetUtilities::SetUseFileHashOverride(true, true); + + ASSERT_TRUE(QFile::remove(m_data->m_absolutePath[0])); + + // Feed in ONLY one file (the one we didn't delete) + QSet filePaths; + QFileInfo fileInfo(m_data->m_absolutePath[1]); + auto modtime = fileInfo.lastModified(); + AZ::u64 fileSize = fileInfo.size(); + filePaths.insert(AssetFileInfo(m_data->m_absolutePath[1], modtime, fileSize, &m_config->GetScanFolderAt(0), false)); + + SimulateAssetScanner(filePaths); + + QElapsedTimer timer; + timer.start(); + + do + { + QCoreApplication::processEvents(QEventLoop::AllEvents, 10); + } while (m_data->m_deletedSources.size() < m_data->m_relativePathFromWatchFolder[0].size() && timer.elapsed() < 5000); + + ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 0); + ASSERT_EQ(m_data->m_processResults.size(), 0); + ASSERT_THAT(m_data->m_deletedSources, testing::ElementsAre(m_data->m_relativePathFromWatchFolder[0])); + } + + TEST_F(ModtimeScanningTest, ReprocessRequest_FileNotModified_FileProcessed) + { + using namespace AzToolsFramework::AssetSystem; + + m_assetProcessorManager->RequestReprocess(m_data->m_absolutePath[0]); + + ASSERT_TRUE(BlockUntilIdle(5000)); + + ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 1); + ASSERT_EQ(m_data->m_processResults.size(), 1); + } + + TEST_F(ModtimeScanningTest, ReprocessRequest_SourceWithDependency_BothWillProcess) + { + using namespace AzToolsFramework::AssetSystem; + + using SourceFileDependencyEntry = AzToolsFramework::AssetDatabase::SourceFileDependencyEntry; + + SourceFileDependencyEntry newEntry1; + newEntry1.m_sourceDependencyID = AzToolsFramework::AssetDatabase::InvalidEntryId; + newEntry1.m_builderGuid = AZ::Uuid::CreateRandom(); + newEntry1.m_source = m_data->m_absolutePath[0].toUtf8().constData(); + newEntry1.m_dependsOnSource = m_data->m_absolutePath[1].toUtf8().constData(); + newEntry1.m_typeOfDependency = SourceFileDependencyEntry::DEP_SourceToSource; + + m_assetProcessorManager->RequestReprocess(m_data->m_absolutePath[0]); + ASSERT_TRUE(BlockUntilIdle(5000)); + + ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 1); + ASSERT_EQ(m_data->m_processResults.size(), 1); + + m_assetProcessorManager->RequestReprocess(m_data->m_absolutePath[1]); + ASSERT_TRUE(BlockUntilIdle(5000)); + + ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 3); + ASSERT_EQ(m_data->m_processResults.size(), 3); + } + + TEST_F(ModtimeScanningTest, ReprocessRequest_RequestFolder_SourceAssetsWillProcess) + { + using namespace AzToolsFramework::AssetSystem; + + const auto& scanFolder = m_config->GetScanFolderAt(0); + + QString scanPath = scanFolder.ScanPath(); + m_assetProcessorManager->RequestReprocess(scanPath); + ASSERT_TRUE(BlockUntilIdle(5000)); + + // two text files are source assets, assetinfo is not + ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 2); + ASSERT_EQ(m_data->m_processResults.size(), 2); + } + + void DeleteTest::SetUp() + { + AssetProcessorManagerTest::SetUp(); + + m_data = AZStd::make_unique(); + + // We don't want the mock application manager to provide builder descriptors, mockBuilderInfoHandler will provide our own + m_mockApplicationManager->BusDisconnect(); + + m_data->m_mockBuilderInfoHandler.m_builderDesc = m_data->m_mockBuilderInfoHandler.CreateBuilderDesc( + "test builder", "{DF09DDC0-FD22-43B6-9E22-22C8574A6E1E}", + { AssetBuilderSDK::AssetBuilderPattern("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard) }); + m_data->m_mockBuilderInfoHandler.BusConnect(); + + ASSERT_TRUE(m_mockApplicationManager->GetBuilderByID("txt files", m_data->m_builderTxtBuilder)); + + SetUpAssetProcessorManager(); + + auto createFileAndAddToDatabaseFunc = [this](const AssetProcessor::ScanFolderInfo* scanFolder, QString file) + { + using namespace AzToolsFramework::AssetDatabase; + + QString watchFolderPath = scanFolder->ScanPath(); + QString absPath(QDir(watchFolderPath).absoluteFilePath(file)); + UnitTestUtils::CreateDummyFile(absPath); + + m_data->m_absolutePath.push_back(absPath); + + AzToolsFramework::AssetDatabase::FileDatabaseEntry fileEntry; + fileEntry.m_fileName = file.toUtf8().constData(); + fileEntry.m_modTime = 0; + fileEntry.m_isFolder = false; + fileEntry.m_scanFolderPK = scanFolder->ScanFolderID(); + + bool entryAlreadyExists; + ASSERT_TRUE(m_assetProcessorManager->m_stateData->InsertFile(fileEntry, entryAlreadyExists)); + ASSERT_FALSE(entryAlreadyExists); + }; + + // Create test files + QDir tempPath(m_tempDir.path()); + const auto* scanFolder1 = m_config->GetScanFolderByPath(tempPath.absoluteFilePath("subfolder1")); + const auto* scanFolder4 = m_config->GetScanFolderByPath(tempPath.absoluteFilePath("subfolder4")); + + createFileAndAddToDatabaseFunc(scanFolder1, QString("textures/a.txt")); + createFileAndAddToDatabaseFunc(scanFolder4, QString("textures/b.txt")); + + // Run the test files through AP all the way to processing stage + QSet filePaths = BuildFileSet(); + SimulateAssetScanner(filePaths); + + ASSERT_TRUE(BlockUntilIdle(5000)); + ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 2); + ASSERT_EQ(m_data->m_processResults.size(), 2); + ASSERT_EQ(m_data->m_deletedSources.size(), 0); + + ProcessAssetJobs(); + + m_data->m_processResults.clear(); + m_data->m_mockBuilderInfoHandler.m_createJobsCount = 0; + + // Reboot the APM since we added stuff to the database that needs to be loaded on-startup of the APM + m_assetProcessorManager.reset(new AssetProcessorManager_Test(m_config.get())); + + SetUpAssetProcessorManager(); + } + + TEST_F(DeleteTest, DeleteFolderSharedAcrossTwoScanFolders_CorrectFileAndFolderAreDeletedFromCache) + { + // There was a bug where AP wasn't repopulating the "known folders" list when modtime skipping was enabled and no work was needed + // As a result, deleting a folder didn't count as a "folder", so the wrong code path was taken. This test makes sure the correct + // deletion events fire + + using namespace AzToolsFramework::AssetSystem; + + // Feed in the files from the asset scanner, no jobs should run since they're already up-to-date + QSet filePaths = BuildFileSet(); + SimulateAssetScanner(filePaths); + + ExpectNoWork(); + + // Delete one of the folders + QDir tempPath(m_tempDir.path()); + QString absPath(tempPath.absoluteFilePath("subfolder1/textures")); + QDir(absPath).removeRecursively(); + + AZStd::vector deletedFolders; + QObject::connect( + m_assetProcessorManager.get(), &AssetProcessor::AssetProcessorManager::SourceFolderDeleted, + [&deletedFolders](QString file) + { + deletedFolders.push_back(file.toUtf8().constData()); + }); + + m_assetProcessorManager->AssessDeletedFile(absPath); + ASSERT_TRUE(BlockUntilIdle(5000)); + + ASSERT_THAT(m_data->m_deletedSources, testing::UnorderedElementsAre("textures/a.txt")); + ASSERT_THAT(deletedFolders, testing::UnorderedElementsAre("textures")); + } + + TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeleteFails) + { + auto theFile = m_data->m_absolutePath[1].toUtf8(); + const char* theFileString = theFile.constData(); + auto [sourcePath, productPath] = *m_data->m_productPaths.find(theFileString); + + { + QFile file(theFileString); + file.remove(); + } + + ASSERT_GT(m_data->m_productPaths.size(), 0); + QFile product(productPath); + + ASSERT_TRUE(product.open(QIODevice::ReadOnly)); + + // Check if we can delete the file now, if we can't, proceed with the test + // If we can, it means the OS running this test doesn't lock open files so there's nothing to test + if (!AZ::IO::SystemFile::Delete(productPath.toUtf8().constData())) + { + QMetaObject::invokeMethod( + m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, QString(theFileString))); + + EXPECT_TRUE(BlockUntilIdle(5000)); + + EXPECT_TRUE(QFile::exists(productPath)); + EXPECT_EQ(m_data->m_deletedSources.size(), 0); + } + else + { + SUCCEED() << "Skipping test. OS does not lock open files."; + } + } + + TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeletesWhenReleased) + { + // This test is intended to verify the AP will successfully retry deleting a source asset + // when one of its product assets is locked temporarily + // We'll lock the file by holding it open + + auto theFile = m_data->m_absolutePath[1].toUtf8(); + const char* theFileString = theFile.constData(); + auto [sourcePath, productPath] = *m_data->m_productPaths.find(theFileString); + + { + QFile file(theFileString); + file.remove(); + } + + ASSERT_GT(m_data->m_productPaths.size(), 0); + QFile product(productPath); + + // Open the file and keep it open to lock it + // We'll start a thread later to unlock the file + // This will allow us to test how AP handles trying to delete a locked file + ASSERT_TRUE(product.open(QIODevice::ReadOnly)); + + // Check if we can delete the file now, if we can't, proceed with the test + // If we can, it means the OS running this test doesn't lock open files so there's nothing to test + if (!AZ::IO::SystemFile::Delete(productPath.toUtf8().constData())) + { + m_deleteCounter = 0; + + // Set up a callback which will fire after at least 1 retry + // Unlock the file at that point so AP can successfully delete it + m_callback = [&product]() + { + product.close(); + }; + + QMetaObject::invokeMethod( + m_assetProcessorManager.get(), "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, QString(theFileString))); + + EXPECT_TRUE(BlockUntilIdle(5000)); + + EXPECT_FALSE(QFile::exists(productPath)); + EXPECT_EQ(m_data->m_deletedSources.size(), 1); + + EXPECT_GT(m_deleteCounter, 1); // Make sure the AP tried more than once to delete the file + m_errorAbsorber->ExpectAsserts(0); + } + else + { + SUCCEED() << "Skipping test. OS does not lock open files."; + } + } +} diff --git a/Code/Tools/AssetProcessor/native/tests/assetmanager/ModtimeScanningTests.h b/Code/Tools/AssetProcessor/native/tests/assetmanager/ModtimeScanningTests.h new file mode 100644 index 0000000000..ef57c70536 --- /dev/null +++ b/Code/Tools/AssetProcessor/native/tests/assetmanager/ModtimeScanningTests.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +namespace UnitTests +{ + struct ModtimeScanningTest : AssetProcessorManagerTest + { + void SetUpAssetProcessorManager(); + void SetUp() override; + void TearDown() override; + + void ProcessAssetJobs(); + void SimulateAssetScanner(QSet filePaths); + QSet BuildFileSet(); + void ExpectWork(int createJobs, int processJobs); + void ExpectNoWork(); + void SetFileContents(QString filePath, QString contents); + + struct StaticData + { + QString m_relativePathFromWatchFolder[3]; + AZStd::vector m_absolutePath; + AZStd::vector m_processResults; + AZStd::unordered_multimap m_productPaths; + AZStd::vector m_deletedSources; + AZStd::shared_ptr m_builderTxtBuilder; + MockBuilderInfoHandler m_mockBuilderInfoHandler; + }; + + AZStd::unique_ptr m_data; + }; + + struct DeleteTest : ModtimeScanningTest + { + void SetUp() override; + }; + + + struct LockedFileTest + : ModtimeScanningTest + , AssetProcessor::ConnectionBus::Handler + { + MOCK_METHOD3(SendRaw, size_t(unsigned, unsigned, const QByteArray&)); + MOCK_METHOD3(SendPerPlatform, size_t(unsigned, const AzFramework::AssetSystem::BaseAssetProcessorMessage&, const QString&)); + MOCK_METHOD4(SendRawPerPlatform, size_t(unsigned, unsigned, const QByteArray&, const QString&)); + MOCK_METHOD2(SendRequest, unsigned(const AzFramework::AssetSystem::BaseAssetProcessorMessage&, const ResponseCallback&)); + MOCK_METHOD2(SendResponse, size_t(unsigned, const AzFramework::AssetSystem::BaseAssetProcessorMessage&)); + MOCK_METHOD1(RemoveResponseHandler, void(unsigned)); + + size_t Send(unsigned, const AzFramework::AssetSystem::BaseAssetProcessorMessage& message) override + { + using SourceFileNotificationMessage = AzToolsFramework::AssetSystem::SourceFileNotificationMessage; + switch (message.GetMessageType()) + { + case SourceFileNotificationMessage::MessageType: + if (const auto sourceFileMessage = azrtti_cast(&message); + sourceFileMessage != nullptr && + sourceFileMessage->m_type == SourceFileNotificationMessage::NotificationType::FileRemoved) + { + // The File Remove message will occur before an attempt to delete the file + // Wait for more than 1 File Remove message. + // This indicates the AP has attempted to delete the file once, failed to do so and is now retrying + ++m_deleteCounter; + + if (m_deleteCounter > 1 && m_callback) + { + m_callback(); + m_callback = {}; // Unset it to be safe, we only intend to run the callback once + } + } + break; + default: + break; + } + + return 0; + } + + void SetUp() override + { + ModtimeScanningTest::SetUp(); + + AssetProcessor::ConnectionBus::Handler::BusConnect(0); + } + + void TearDown() override + { + AssetProcessor::ConnectionBus::Handler::BusDisconnect(); + + ModtimeScanningTest::TearDown(); + } + + AZStd::atomic_int m_deleteCounter{ 0 }; + AZStd::function m_callback; + }; +} From 8263a50f9709aa9f7cedaf927589940026bc3926 Mon Sep 17 00:00:00 2001 From: Chris Galvan Date: Mon, 31 Jan 2022 11:11:42 -0600 Subject: [PATCH 35/53] Fixed issue with viewport not rendering when creating a new level Signed-off-by: Chris Galvan --- Code/Editor/EditorViewportWidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Code/Editor/EditorViewportWidget.cpp b/Code/Editor/EditorViewportWidget.cpp index 9a9e9f5ad3..7314908580 100644 --- a/Code/Editor/EditorViewportWidget.cpp +++ b/Code/Editor/EditorViewportWidget.cpp @@ -586,6 +586,7 @@ void EditorViewportWidget::OnEditorNotifyEvent(EEditorNotifyEvent event) break; case eNotify_OnEndLoad: + case eNotify_OnEndCreate: UpdateScene(); SetDefaultCamera(); break; From 4c426ea5a611ea8904554e9c4271c552ac2eaa6e Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Mon, 31 Jan 2022 09:44:41 -0800 Subject: [PATCH 36/53] Fixed compile issue in unity builds. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h index 40f82c8ec7..79c73495e0 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h @@ -22,6 +22,7 @@ namespace AZ namespace RPI { class MaterialTypeAsset; + class MaterialTypeAssetCreator; class MaterialFunctorSourceDataHolder; class JsonMaterialPropertySerializer; From 8710130d759287dab02ec7e20fb90c015a6f7d45 Mon Sep 17 00:00:00 2001 From: Allen Jackson <23512001+jackalbe@users.noreply.github.com> Date: Mon, 31 Jan 2022 11:53:05 -0600 Subject: [PATCH 37/53] {ghi7197} update the prefab path for procedural prefabs (#7260) update the prefab path for procedural prefabs so that the prefab template can be found from the level prefab load event. It is important that it points to the realative/path/to/the.procprefab using the correct front slashes for the source asset path Signed-off-by: Allen Jackson <23512001+jackalbe@users.noreply.github.com> --- .../PrefabGroup/PrefabGroupBehavior.cpp | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.cpp b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.cpp index af21e7297c..5fce7a674a 100644 --- a/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.cpp +++ b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.cpp @@ -318,13 +318,17 @@ namespace AZ::SceneAPI::Behaviors { AZ::Interface::Get()->RemoveAllTemplates(); + AZStd::string prefabTemplateName { relativeSourcePath }; + AZ::StringFunc::Path::ReplaceFullName(prefabTemplateName, filenameOnly.c_str()); + AZ::StringFunc::Replace(prefabTemplateName, "\\", "/"); // the source folder uses forward slash + // create prefab group for entire stack AzToolsFramework::Prefab::TemplateId prefabTemplateId; AzToolsFramework::Prefab::PrefabSystemScriptingBus::BroadcastResult( prefabTemplateId, &AzToolsFramework::Prefab::PrefabSystemScriptingBus::Events::CreatePrefabTemplate, entities, - filenameOnly); + prefabTemplateName); if (prefabTemplateId == AzToolsFramework::Prefab::InvalidTemplateId) { @@ -349,12 +353,12 @@ namespace AZ::SceneAPI::Behaviors prefabDom.Parse(outcome.GetValue().c_str()); auto prefabGroup = AZStd::make_shared(); - prefabGroup->SetName(relativeSourcePath); + prefabGroup->SetName(prefabTemplateName); prefabGroup->SetPrefabDom(AZStd::move(prefabDom)); prefabGroup->SetId(DataTypes::Utilities::CreateStableUuid( scene, azrtti_typeid(), - relativeSourcePath)); + prefabTemplateName)); manifestUpdates.emplace_back(prefabGroup); @@ -414,7 +418,7 @@ namespace AZ::SceneAPI::Behaviors AZ::StringFunc::Replace(relativeSourcePath, ".", "_"); AZStd::string filenameOnly{ relativeSourcePath }; AZ::StringFunc::Path::GetFileName(filenameOnly.c_str(), filenameOnly); - AZ::StringFunc::Path::ReplaceExtension(filenameOnly, "prefab"); + AZ::StringFunc::Path::ReplaceExtension(filenameOnly, "procprefab"); ManifestUpdates manifestUpdates; @@ -482,7 +486,10 @@ namespace AZ::SceneAPI::Behaviors // The originPath we pass to LoadTemplateFromString must be the relative path of the file AZ::IO::Path templateName(prefabGroup->GetName()); templateName.ReplaceExtension(AZ::Prefab::PrefabGroupAssetHandler::s_Extension); - templateName = relativePath / templateName; + if (!AZ::StringFunc::StartsWith(templateName.c_str(), relativePath.c_str())) + { + templateName = relativePath / templateName; + } auto templateId = prefabLoaderInterface->LoadTemplateFromString(sb.GetString(), templateName.Native().c_str()); if (templateId == InvalidTemplateId) @@ -569,11 +576,12 @@ namespace AZ::SceneAPI::Behaviors // Get the relative path of the source and then take just the path portion of it (no file name) AZ::IO::Path relativePath = context.GetScene().GetSourceFilename(); relativePath = relativePath.LexicallyRelative(AZStd::string_view(context.GetScene().GetWatchFolder())); - relativePath = relativePath.ParentPath(); + AZStd::string relativeSourcePath { AZStd::move(relativePath.ParentPath().Native()) }; + AZ::StringFunc::Replace(relativeSourcePath, "\\", "/"); // the source paths use forward slashes for (const auto* prefabGroup : prefabGroupCollection) { - auto result = CreateProductAssetData(prefabGroup, relativePath); + auto result = CreateProductAssetData(prefabGroup, relativeSourcePath); if (!result) { return Events::ProcessingResult::Failure; From b975111a93425731a540b3c1182421e15c77d72c Mon Sep 17 00:00:00 2001 From: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> Date: Mon, 31 Jan 2022 12:54:16 -0600 Subject: [PATCH 38/53] Add benchmarks and unit tests for GetSurfacePoints*. (#7216) * Add benchmarks and unit tests for GetSurfacePoints*. The benchmarks are very enlightening - the existing implementation of GetSurfacePointsFromRegion (and GetSurfacePointsFromList) is currently measurably *slower* than just calling GetSurfacePoints() many times in a loop. This is due to all of the extra allocation overhead that's currently happening with the way these data structures are built. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Small syntax improvement Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Small update to the benchmark to use filtered results. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Removed accidental extra include. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> --- Gems/SurfaceData/Code/CMakeLists.txt | 4 + .../SurfaceData/SurfaceDataSystemRequestBus.h | 7 + .../SurfaceData/Tests/SurfaceDataTestMocks.h | 54 ++-- .../SurfaceDataColliderComponent.cpp | 7 +- .../Components/SurfaceDataShapeComponent.cpp | 7 +- .../Source/SurfaceDataSystemComponent.cpp | 49 ++-- .../Code/Source/SurfaceDataSystemComponent.h | 4 + .../Code/Tests/SurfaceDataBenchmarks.cpp | 276 ++++++++++++++++++ .../SurfaceDataColliderComponentTest.cpp | 18 -- .../Code/Tests/SurfaceDataTest.cpp | 150 +++++++--- .../Code/Tests/SurfaceDataTestFixtures.cpp | 36 +++ .../Code/Tests/SurfaceDataTestFixtures.h | 43 +++ .../Code/surfacedata_tests_files.cmake | 3 + Gems/Vegetation/Code/Tests/VegetationMocks.h | 7 + 14 files changed, 552 insertions(+), 113 deletions(-) create mode 100644 Gems/SurfaceData/Code/Tests/SurfaceDataBenchmarks.cpp create mode 100644 Gems/SurfaceData/Code/Tests/SurfaceDataTestFixtures.cpp create mode 100644 Gems/SurfaceData/Code/Tests/SurfaceDataTestFixtures.h diff --git a/Gems/SurfaceData/Code/CMakeLists.txt b/Gems/SurfaceData/Code/CMakeLists.txt index 860053ca42..6cef8b6e25 100644 --- a/Gems/SurfaceData/Code/CMakeLists.txt +++ b/Gems/SurfaceData/Code/CMakeLists.txt @@ -102,4 +102,8 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) ly_add_googletest( NAME Gem::SurfaceData.Tests ) + ly_add_googlebenchmark( + NAME Gem::SurfaceData.Benchmarks + TARGET Gem::SurfaceData.Tests + ) endif() diff --git a/Gems/SurfaceData/Code/Include/SurfaceData/SurfaceDataSystemRequestBus.h b/Gems/SurfaceData/Code/Include/SurfaceData/SurfaceDataSystemRequestBus.h index 8dd3e02a53..616f069184 100644 --- a/Gems/SurfaceData/Code/Include/SurfaceData/SurfaceDataSystemRequestBus.h +++ b/Gems/SurfaceData/Code/Include/SurfaceData/SurfaceDataSystemRequestBus.h @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace SurfaceData @@ -41,6 +42,12 @@ namespace SurfaceData virtual void GetSurfacePointsFromRegion(const AZ::Aabb& inRegion, const AZ::Vector2 stepSize, const SurfaceTagVector& desiredTags, SurfacePointLists& surfacePointLists) const = 0; + // Get all surface points for every passed-in input position. Only the XY dimensions of each position are used. + virtual void GetSurfacePointsFromList( + AZStd::span inPositions, + const SurfaceTagVector& desiredTags, + SurfacePointLists& surfacePointLists) const = 0; + virtual SurfaceDataRegistryHandle RegisterSurfaceDataProvider(const SurfaceDataRegistryEntry& entry) = 0; virtual void UnregisterSurfaceDataProvider(const SurfaceDataRegistryHandle& handle) = 0; virtual void UpdateSurfaceDataProvider(const SurfaceDataRegistryHandle& handle, const SurfaceDataRegistryEntry& entry) = 0; diff --git a/Gems/SurfaceData/Code/Include/SurfaceData/Tests/SurfaceDataTestMocks.h b/Gems/SurfaceData/Code/Include/SurfaceData/Tests/SurfaceDataTestMocks.h index 5e8acdc4c5..82a321756d 100644 --- a/Gems/SurfaceData/Code/Include/SurfaceData/Tests/SurfaceDataTestMocks.h +++ b/Gems/SurfaceData/Code/Include/SurfaceData/Tests/SurfaceDataTestMocks.h @@ -8,14 +8,15 @@ #pragma once #include -#include + +#include #include #include #include +#include #include #include -#include namespace UnitTest { @@ -23,23 +24,6 @@ namespace UnitTest : public ::testing::Test { protected: - AZ::ComponentApplication m_app; - AZ::Entity* m_systemEntity = nullptr; - - void SetUp() override - { - AZ::ComponentApplication::Descriptor appDesc; - appDesc.m_memoryBlocksByteSize = 128 * 1024 * 1024; - m_systemEntity = m_app.Create(appDesc); - m_app.AddEntity(m_systemEntity); - } - - void TearDown() override - { - m_app.Destroy(); - m_systemEntity = nullptr; - } - AZStd::unique_ptr CreateEntity() { return AZStd::make_unique(); @@ -57,14 +41,12 @@ namespace UnitTest template AZ::Component* CreateComponent(AZ::Entity* entity, const Configuration& config) { - m_app.RegisterComponentDescriptor(Component::CreateDescriptor()); return entity->CreateComponent(config); } template AZ::Component* CreateComponent(AZ::Entity* entity) { - m_app.RegisterComponentDescriptor(Component::CreateDescriptor()); return entity->CreateComponent(); } }; @@ -129,6 +111,29 @@ namespace UnitTest } }; + // Mock out a generic Physics Collider Component, which is a required dependency for adding a SurfaceDataColliderComponent. + struct MockPhysicsColliderComponent : public AZ::Component + { + public: + AZ_COMPONENT(MockPhysicsColliderComponent, "{4F7C36DE-6475-4E0A-96A7-BFAF21C07C95}", AZ::Component); + + void Activate() override + { + } + void Deactivate() override + { + } + + static void Reflect(AZ::ReflectContext* reflect) + { + AZ_UNUSED(reflect); + } + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC("PhysXColliderService", 0x4ff43f7c)); + } + }; + struct MockTransformHandler : public AZ::TransformBus::Handler { @@ -204,6 +209,13 @@ namespace UnitTest { } + void GetSurfacePointsFromList( + [[maybe_unused]] AZStd::span inPositions, + [[maybe_unused]] const SurfaceData::SurfaceTagVector& desiredTags, + [[maybe_unused]] SurfaceData::SurfacePointLists& surfacePointLists) const override + { + } + SurfaceData::SurfaceDataRegistryHandle RegisterSurfaceDataProvider(const SurfaceData::SurfaceDataRegistryEntry& entry) override { return RegisterEntry(entry, m_providers); diff --git a/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.cpp b/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.cpp index 31bf4e7028..d7de299829 100644 --- a/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.cpp +++ b/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.cpp @@ -240,15 +240,16 @@ namespace SurfaceData point.m_entityId = GetEntityId(); point.m_position = hitPosition; point.m_normal = hitNormal; - AddMaxValueForMasks(point.m_masks, m_configuration.m_providerTags, 1.0f); + for (auto& tag : m_configuration.m_providerTags) + { + point.m_masks[tag] = 1.0f; + } surfacePointList.push_back(point); } } void SurfaceDataColliderComponent::ModifySurfacePoints(SurfacePointList& surfacePointList) const { - AZ_PROFILE_FUNCTION(Entity); - AZStd::lock_guard lock(m_cacheMutex); if (m_colliderBounds.IsValid() && !m_configuration.m_modifierTags.empty()) diff --git a/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.cpp b/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.cpp index 890f8fba54..a45903498e 100644 --- a/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.cpp +++ b/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.cpp @@ -143,8 +143,6 @@ namespace SurfaceData void SurfaceDataShapeComponent::GetSurfacePoints(const AZ::Vector3& inPosition, SurfacePointList& surfacePointList) const { - AZ_PROFILE_FUNCTION(Entity); - AZStd::lock_guard lock(m_cacheMutex); if (m_shapeBoundsIsValid) @@ -160,7 +158,10 @@ namespace SurfaceData point.m_entityId = GetEntityId(); point.m_position = rayOrigin + intersectionDistance * rayDirection; point.m_normal = AZ::Vector3::CreateAxisZ(); - AddMaxValueForMasks(point.m_masks, m_configuration.m_providerTags, 1.0f); + for (auto& tag : m_configuration.m_providerTags) + { + point.m_masks[tag] = 1.0f; + } surfacePointList.push_back(point); } } diff --git a/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.cpp b/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.cpp index 9c53b13fdc..a2a3bc352e 100644 --- a/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.cpp +++ b/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.cpp @@ -181,8 +181,6 @@ namespace SurfaceData void SurfaceDataSystemComponent::GetSurfacePoints(const AZ::Vector3& inPosition, const SurfaceTagVector& desiredTags, SurfacePointList& surfacePointList) const { - AZ_PROFILE_FUNCTION(Entity); - const bool hasDesiredTags = HasValidTags(desiredTags); const bool hasModifierTags = hasDesiredTags && HasMatchingTags(desiredTags, m_registeredModifierTags); @@ -228,42 +226,47 @@ namespace SurfaceData void SurfaceDataSystemComponent::GetSurfacePointsFromRegion(const AZ::Aabb& inRegion, const AZ::Vector2 stepSize, const SurfaceTagVector& desiredTags, SurfacePointLists& surfacePointLists) const { - AZStd::lock_guard registrationLock(m_registrationMutex); - const size_t totalQueryPositions = aznumeric_cast(ceil(inRegion.GetXExtent() / stepSize.GetX())) * aznumeric_cast(ceil(inRegion.GetYExtent() / stepSize.GetY())); AZStd::vector inPositions; inPositions.reserve(totalQueryPositions); - surfacePointLists.clear(); - surfacePointLists.reserve(totalQueryPositions); - // Initialize our list-per-position list with every input position to query from the region. // This is inclusive on the min sides of inRegion, and exclusive on the max sides. for (float y = inRegion.GetMin().GetY(); y < inRegion.GetMax().GetY(); y += stepSize.GetY()) { for (float x = inRegion.GetMin().GetX(); x < inRegion.GetMax().GetX(); x += stepSize.GetX()) { - inPositions.emplace_back(AZ::Vector3(x, y, AZ::Constants::FloatMax)); - surfacePointLists.emplace_back(SurfaceData::SurfacePointList{}); + inPositions.emplace_back(x, y, AZ::Constants::FloatMax); } } + GetSurfacePointsFromList(inPositions, desiredTags, surfacePointLists); + } + + void SurfaceDataSystemComponent::GetSurfacePointsFromList( + AZStd::span inPositions, const SurfaceTagVector& desiredTags, SurfacePointLists& surfacePointLists) const + { + AZStd::lock_guard registrationLock(m_registrationMutex); + + const size_t totalQueryPositions = inPositions.size(); + + surfacePointLists.clear(); + surfacePointLists.resize(totalQueryPositions); + const bool hasDesiredTags = HasValidTags(desiredTags); const bool hasModifierTags = hasDesiredTags && HasMatchingTags(desiredTags, m_registeredModifierTags); // Loop through each data provider, and query all the points for each one. This allows us to check the tags and the overall - // AABB bounds just once per provider, instead of once per point. It also allows for an eventual optimization in which we could send - // the list of points directly into each SurfaceDataProvider. + // AABB bounds just once per provider, instead of once per point. It also allows for an eventual optimization in which we could + // send the list of points directly into each SurfaceDataProvider. for (const auto& entryPair : m_registeredSurfaceDataProviders) { const SurfaceDataRegistryEntry& entry = entryPair.second; bool alwaysApplies = !entry.m_bounds.IsValid(); - if ((!hasDesiredTags || hasModifierTags || HasMatchingTags(desiredTags, entry.m_tags)) && - ( alwaysApplies || AabbOverlaps2D(entry.m_bounds, inRegion) ) - ) + if (!hasDesiredTags || hasModifierTags || HasMatchingTags(desiredTags, entry.m_tags)) { for (size_t index = 0; index < totalQueryPositions; index++) { @@ -288,18 +291,16 @@ namespace SurfaceData const SurfaceDataRegistryEntry& entry = entryPair.second; bool alwaysApplies = !entry.m_bounds.IsValid(); - if (alwaysApplies || AabbOverlaps2D(entry.m_bounds, inRegion)) + for (size_t index = 0; index < totalQueryPositions; index++) { - for (size_t index = 0; index < totalQueryPositions; index++) + const auto& inPosition = inPositions[index]; + SurfacePointList& surfacePointList = surfacePointLists[index]; + if (!surfacePointList.empty()) { - const auto& inPosition = inPositions[index]; - SurfacePointList& surfacePointList = surfacePointLists[index]; - if (!surfacePointList.empty()) + if (alwaysApplies || AabbContains2D(entry.m_bounds, inPosition)) { - if (alwaysApplies || AabbContains2D(entry.m_bounds, inPosition)) - { - SurfaceDataModifierRequestBus::Event(entryPair.first, &SurfaceDataModifierRequestBus::Events::ModifySurfacePoints, surfacePointList); - } + SurfaceDataModifierRequestBus::Event( + entryPair.first, &SurfaceDataModifierRequestBus::Events::ModifySurfacePoints, surfacePointList); } } } @@ -318,6 +319,8 @@ namespace SurfaceData } } + + void SurfaceDataSystemComponent::CombineSortAndFilterNeighboringPoints(SurfacePointList& sourcePointList, bool hasDesiredTags, const SurfaceTagVector& desiredTags) const { AZ_PROFILE_FUNCTION(Entity); diff --git a/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.h b/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.h index c329103efb..8914ee7972 100644 --- a/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.h +++ b/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.h @@ -42,6 +42,10 @@ namespace SurfaceData void GetSurfacePointsFromRegion( const AZ::Aabb& inRegion, const AZ::Vector2 stepSize, const SurfaceTagVector& desiredTags, SurfacePointLists& surfacePointListPerPosition) const override; + void GetSurfacePointsFromList( + AZStd::span inPositions, + const SurfaceTagVector& desiredTags, + SurfacePointLists& surfacePointLists) const override; SurfaceDataRegistryHandle RegisterSurfaceDataProvider(const SurfaceDataRegistryEntry& entry) override; void UnregisterSurfaceDataProvider(const SurfaceDataRegistryHandle& handle) override; diff --git a/Gems/SurfaceData/Code/Tests/SurfaceDataBenchmarks.cpp b/Gems/SurfaceData/Code/Tests/SurfaceDataBenchmarks.cpp new file mode 100644 index 0000000000..65487c155b --- /dev/null +++ b/Gems/SurfaceData/Code/Tests/SurfaceDataBenchmarks.cpp @@ -0,0 +1,276 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#ifdef HAVE_BENCHMARK + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace UnitTest +{ + class SurfaceDataBenchmark : public ::benchmark::Fixture + { + public: + void internalSetUp() + { + m_surfaceDataSystemEntity = AZStd::make_unique(); + m_surfaceDataSystemEntity->CreateComponent(); + m_surfaceDataSystemEntity->Init(); + m_surfaceDataSystemEntity->Activate(); + } + + void internalTearDown() + { + m_surfaceDataSystemEntity.reset(); + } + + // Create an entity with a Transform component and a SurfaceDataShape component at the given position with the given tags. + AZStd::unique_ptr CreateBenchmarkEntity( + AZ::Vector3 worldPos, AZStd::span providerTags, AZStd::span modifierTags) + { + AZStd::unique_ptr entity = AZStd::make_unique(); + + auto transform = entity->CreateComponent(); + transform->SetLocalTM(AZ::Transform::CreateTranslation(worldPos)); + transform->SetWorldTM(AZ::Transform::CreateTranslation(worldPos)); + + SurfaceData::SurfaceDataShapeConfig surfaceConfig; + for (auto& providerTag : providerTags) + { + surfaceConfig.m_providerTags.push_back(SurfaceData::SurfaceTag(providerTag)); + } + for (auto& modifierTag : modifierTags) + { + surfaceConfig.m_modifierTags.push_back(SurfaceData::SurfaceTag(modifierTag)); + } + entity->CreateComponent(surfaceConfig); + + return entity; + } + + /* Create a set of shape surfaces in the world that we can use for benchmarking. + Each shape is centered in XY and is the XY size of the world, but with different Z heights and placements. + There are two boxes and one cylinder, layered like this: + + Top: + --- + |O| <- two boxes of equal XY size with a cylinder face-up in the center + --- + + Side: + |-----------| + | |<- entity 3, box that contains the other shapes + | |-------| | <- entity 2, cylinder inside entity 3 and intersecting entity 1 + | | | | + |-----------|<- entity 1, thin box + | |-------| | + | | + |-----------| + + This will give us either 2 or 3 generated surface points at every query point. The entity 1 surface will get the entity 2 and 3 + modifier tags added to it. The entity 2 surface will get the entity 3 modifier tag added to it. The entity 3 surface won't get + modified. + */ + AZStd::vector> CreateBenchmarkEntities(float worldSize) + { + AZStd::vector> testEntities; + float halfWorldSize = worldSize / 2.0f; + + // Create a large flat box with 1 provider tag. + AZStd::unique_ptr surface1 = CreateBenchmarkEntity( + AZ::Vector3(halfWorldSize, halfWorldSize, 10.0f), AZStd::array{ "surface1" }, {}); + { + LmbrCentral::BoxShapeConfig boxConfig(AZ::Vector3(worldSize, worldSize, 1.0f)); + auto shapeComponent = surface1->CreateComponent(LmbrCentral::BoxShapeComponentTypeId); + shapeComponent->SetConfiguration(boxConfig); + + surface1->Init(); + surface1->Activate(); + } + testEntities.push_back(AZStd::move(surface1)); + + // Create a large cylinder with 1 provider tag and 1 modifier tag. + AZStd::unique_ptr surface2 = CreateBenchmarkEntity( + AZ::Vector3(halfWorldSize, halfWorldSize, 20.0f), AZStd::array{ "surface2" }, AZStd::array{ "modifier2" }); + { + LmbrCentral::CylinderShapeConfig cylinderConfig; + cylinderConfig.m_height = 30.0f; + cylinderConfig.m_radius = halfWorldSize; + auto shapeComponent = surface2->CreateComponent(LmbrCentral::CylinderShapeComponentTypeId); + shapeComponent->SetConfiguration(cylinderConfig); + + surface2->Init(); + surface2->Activate(); + } + testEntities.push_back(AZStd::move(surface2)); + + // Create a large box with 1 provider tag and 1 modifier tag. + AZStd::unique_ptr surface3 = CreateBenchmarkEntity( + AZ::Vector3(halfWorldSize, halfWorldSize, 30.0f), AZStd::array{ "surface3" }, AZStd::array{ "modifier3" }); + { + LmbrCentral::BoxShapeConfig boxConfig(AZ::Vector3(worldSize, worldSize, 100.0f)); + auto shapeComponent = surface3->CreateComponent(LmbrCentral::BoxShapeComponentTypeId); + shapeComponent->SetConfiguration(boxConfig); + + surface3->Init(); + surface3->Activate(); + } + testEntities.push_back(AZStd::move(surface3)); + + return testEntities; + } + + SurfaceData::SurfaceTagVector CreateBenchmarkTagFilterList() + { + SurfaceData::SurfaceTagVector tagFilterList; + tagFilterList.emplace_back("surface1"); + tagFilterList.emplace_back("surface2"); + tagFilterList.emplace_back("surface3"); + tagFilterList.emplace_back("modifier2"); + tagFilterList.emplace_back("modifier3"); + return tagFilterList; + } + + protected: + void SetUp([[maybe_unused]] const benchmark::State& state) override + { + internalSetUp(); + } + void SetUp([[maybe_unused]] benchmark::State& state) override + { + internalSetUp(); + } + + void TearDown([[maybe_unused]] const benchmark::State& state) override + { + internalTearDown(); + } + void TearDown([[maybe_unused]] benchmark::State& state) override + { + internalTearDown(); + } + + AZStd::unique_ptr m_surfaceDataSystemEntity; + }; + + BENCHMARK_DEFINE_F(SurfaceDataBenchmark, BM_GetSurfacePoints)(benchmark::State& state) + { + AZ_PROFILE_FUNCTION(Entity); + + // Create our benchmark world + const float worldSize = aznumeric_cast(state.range(0)); + AZStd::vector> benchmarkEntities = CreateBenchmarkEntities(worldSize); + SurfaceData::SurfaceTagVector filterTags = CreateBenchmarkTagFilterList(); + + // Query every point in our world at 1 meter intervals. + for (auto _ : state) + { + // This is declared outside the loop so that the list of points doesn't fully reallocate on every query. + SurfaceData::SurfacePointList points; + + for (float y = 0.0f; y < worldSize; y += 1.0f) + { + for (float x = 0.0f; x < worldSize; x += 1.0f) + { + AZ::Vector3 queryPosition(x, y, 0.0f); + points.clear(); + + SurfaceData::SurfaceDataSystemRequestBus::Broadcast( + &SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, queryPosition, filterTags, points); + benchmark::DoNotOptimize(points); + } + } + } + } + + BENCHMARK_DEFINE_F(SurfaceDataBenchmark, BM_GetSurfacePointsFromRegion)(benchmark::State& state) + { + AZ_PROFILE_FUNCTION(Entity); + + // Create our benchmark world + float worldSize = aznumeric_cast(state.range(0)); + AZStd::vector> benchmarkEntities = CreateBenchmarkEntities(worldSize); + SurfaceData::SurfaceTagVector filterTags = CreateBenchmarkTagFilterList(); + + // Query every point in our world at 1 meter intervals. + for (auto _ : state) + { + SurfaceData::SurfacePointLists points; + + AZ::Aabb inRegion = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f), AZ::Vector3(worldSize)); + AZ::Vector2 stepSize(1.0f); + SurfaceData::SurfaceDataSystemRequestBus::Broadcast( + &SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromRegion, inRegion, stepSize, filterTags, + points); + benchmark::DoNotOptimize(points); + } + } + + BENCHMARK_DEFINE_F(SurfaceDataBenchmark, BM_GetSurfacePointsFromList)(benchmark::State& state) + { + AZ_PROFILE_FUNCTION(Entity); + + // Create our benchmark world + const float worldSize = aznumeric_cast(state.range(0)); + const int64_t worldSizeInt = state.range(0); + AZStd::vector> benchmarkEntities = CreateBenchmarkEntities(worldSize); + SurfaceData::SurfaceTagVector filterTags = CreateBenchmarkTagFilterList(); + + // Query every point in our world at 1 meter intervals. + for (auto _ : state) + { + AZStd::vector queryPositions; + queryPositions.reserve(worldSizeInt * worldSizeInt); + + for (float y = 0.0f; y < worldSize; y += 1.0f) + { + for (float x = 0.0f; x < worldSize; x += 1.0f) + { + queryPositions.emplace_back(x, y, 0.0f); + } + } + + SurfaceData::SurfacePointLists points; + + SurfaceData::SurfaceDataSystemRequestBus::Broadcast( + &SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromList, queryPositions, filterTags, points); + benchmark::DoNotOptimize(points); + } + } + + BENCHMARK_REGISTER_F(SurfaceDataBenchmark, BM_GetSurfacePoints) + ->Arg( 1024 ) + ->Arg( 2048 ) + ->Unit(::benchmark::kMillisecond); + + BENCHMARK_REGISTER_F(SurfaceDataBenchmark, BM_GetSurfacePointsFromRegion) + ->Arg( 1024 ) + ->Arg( 2048 ) + ->Unit(::benchmark::kMillisecond); + + BENCHMARK_REGISTER_F(SurfaceDataBenchmark, BM_GetSurfacePointsFromList) + ->Arg( 1024 ) + ->Arg( 2048 ) + ->Unit(::benchmark::kMillisecond); + +#endif +} + + diff --git a/Gems/SurfaceData/Code/Tests/SurfaceDataColliderComponentTest.cpp b/Gems/SurfaceData/Code/Tests/SurfaceDataColliderComponentTest.cpp index 9b31c98759..b0aa5dd38f 100644 --- a/Gems/SurfaceData/Code/Tests/SurfaceDataColliderComponentTest.cpp +++ b/Gems/SurfaceData/Code/Tests/SurfaceDataColliderComponentTest.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include @@ -23,23 +22,6 @@ namespace UnitTest { - // Mock out a generic Physics Collider Component, which is a required dependency for adding a SurfaceDataColliderComponent. - struct MockPhysicsColliderComponent - : public AZ::Component - { - public: - AZ_COMPONENT(MockPhysicsColliderComponent, "{4F7C36DE-6475-4E0A-96A7-BFAF21C07C95}", AZ::Component); - - void Activate() override {} - void Deactivate() override {} - - static void Reflect(AZ::ReflectContext* reflect) { AZ_UNUSED(reflect); } - static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) - { - provided.push_back(AZ_CRC("PhysXColliderService", 0x4ff43f7c)); - } - }; - class MockPhysicsWorldBusProvider : public AzPhysics::SimulatedBodyComponentRequestsBus::Handler { diff --git a/Gems/SurfaceData/Code/Tests/SurfaceDataTest.cpp b/Gems/SurfaceData/Code/Tests/SurfaceDataTest.cpp index 177abd3824..1d339edc71 100644 --- a/Gems/SurfaceData/Code/Tests/SurfaceDataTest.cpp +++ b/Gems/SurfaceData/Code/Tests/SurfaceDataTest.cpp @@ -8,7 +8,6 @@ #include -#include #include #include #include @@ -22,6 +21,7 @@ #include #include #include +#include // Simple class for mocking out a surface provider, so that we can control exactly what points we expect to query in our tests. // This can be used to either provide a surface or modify a surface. @@ -175,61 +175,33 @@ class MockSurfaceProvider }; - - - TEST(SurfaceDataTest, ComponentsWithComponentApplication) { - AZ::ComponentApplication::Descriptor appDesc; - appDesc.m_memoryBlocksByteSize = 10 * 1024 * 1024; - appDesc.m_recordingMode = AZ::Debug::AllocationRecords::RECORD_FULL; - appDesc.m_stackRecordLevels = 20; - - AZ::ComponentApplication app; - AZ::Entity* systemEntity = app.Create(appDesc); - ASSERT_TRUE(systemEntity != nullptr); - app.RegisterComponentDescriptor(SurfaceData::SurfaceDataSystemComponent::CreateDescriptor()); - systemEntity->CreateComponent(); - - systemEntity->Init(); - systemEntity->Activate(); - - app.Destroy(); - ASSERT_TRUE(true); + AZ::Entity* testSystemEntity = new AZ::Entity(); + testSystemEntity->CreateComponent(); + + testSystemEntity->Init(); + testSystemEntity->Activate(); + EXPECT_EQ(testSystemEntity->GetState(), AZ::Entity::State::Active); + testSystemEntity->Deactivate(); + delete testSystemEntity; } class SurfaceDataTestApp : public ::testing::Test { public: - SurfaceDataTestApp() - : m_application() - , m_systemEntity(nullptr) - { - } - void SetUp() override { - AZ::ComponentApplication::Descriptor appDesc; - appDesc.m_memoryBlocksByteSize = 50 * 1024 * 1024; - appDesc.m_recordingMode = AZ::Debug::AllocationRecords::RECORD_FULL; - appDesc.m_stackRecordLevels = 20; - - AZ::ComponentApplication::StartupParameters appStartup; - appStartup.m_createStaticModulesCallback = - [](AZStd::vector& modules) - { - modules.emplace_back(new SurfaceData::SurfaceDataModule); - }; - - m_systemEntity = m_application.Create(appDesc, appStartup); - m_systemEntity->Init(); - m_systemEntity->Activate(); + m_surfaceDataSystemEntity = AZStd::make_unique(); + m_surfaceDataSystemEntity->CreateComponent(); + m_surfaceDataSystemEntity->Init(); + m_surfaceDataSystemEntity->Activate(); } void TearDown() override { - m_application.Destroy(); + m_surfaceDataSystemEntity.reset(); } bool ValidateRegionListSize(AZ::Aabb bounds, AZ::Vector2 stepSize, const SurfaceData::SurfacePointLists& outputLists) @@ -240,15 +212,43 @@ public: return (outputLists.size() == aznumeric_cast(ceil(bounds.GetXExtent() * stepSize.GetX()) * ceil(bounds.GetYExtent() * stepSize.GetY()))); } + void CompareSurfacePointListWithGetSurfacePoints( + SurfaceData::SurfacePointLists surfacePointLists, const SurfaceData::SurfaceTagVector& testTags) + { + for (auto& pointList : surfacePointLists) + { + AZ::Vector3 queryPosition(pointList[0].m_position.GetX(), pointList[0].m_position.GetY(), 16.0f); + SurfaceData::SurfacePointList singleQueryPointList; + + SurfaceData::SurfaceDataSystemRequestBus::Broadcast( + &SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, queryPosition, testTags, singleQueryPointList); + + // Verify the two point lists are the same size, then verify that each point in each list is equal. + ASSERT_EQ(pointList.size(), singleQueryPointList.size()); + for (size_t index = 0; index < pointList.size(); index++) + { + SurfaceData::SurfacePoint& point1 = pointList[index]; + SurfaceData::SurfacePoint& point2 = singleQueryPointList[index]; + + EXPECT_EQ(point1.m_entityId, point2.m_entityId); + EXPECT_EQ(point1.m_position, point2.m_position); + EXPECT_EQ(point1.m_normal, point2.m_normal); + ASSERT_EQ(point1.m_masks.size(), point2.m_masks.size()); + for (auto& mask : point1.m_masks) + { + EXPECT_EQ(mask.second, point2.m_masks[mask.first]); + } + } + } + } - AZ::ComponentApplication m_application; - AZ::Entity* m_systemEntity; // Test Surface Data tags that we can use for testing query functionality const AZ::Crc32 m_testSurface1Crc = AZ::Crc32("test_surface1"); const AZ::Crc32 m_testSurface2Crc = AZ::Crc32("test_surface2"); const AZ::Crc32 m_testSurfaceNoMatchCrc = AZ::Crc32("test_surface_no_match"); + AZStd::unique_ptr m_surfaceDataSystemEntity; }; TEST_F(SurfaceDataTestApp, SurfaceData_TestRegisteredTags) @@ -706,4 +706,64 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_DissimilarPoi } } -AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV); +TEST_F(SurfaceDataTestApp, SurfaceData_VerifyGetSurfacePointsFromRegionAndGetSurfacePointsMatch) +{ + // This ensures that both GetSurfacePointsFromRegion and GetSurfacePoints produce the same results. + + // Create a mock Surface Provider that covers from (0, 0) - (8, 8) in space. + // It defines points spaced 0.25 apart, with heights of 0 and 4, and with the tags "test_surface1" and "test_surface2". + // (We're creating points spaced more densely than we'll query just to verify that we only get back the queried points) + SurfaceData::SurfaceTagVector providerTags = { SurfaceData::SurfaceTag(m_testSurface1Crc), SurfaceData::SurfaceTag(m_testSurface2Crc) }; + MockSurfaceProvider mockProvider( + MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, providerTags, AZ::Vector3(0.0f), AZ::Vector3(8.0f), + AZ::Vector3(0.25f, 0.25f, 4.0f)); + + // Query for all the surface points from (0, 0, 16) - (4, 4, 16) with a step size of 1. + SurfaceData::SurfacePointLists availablePointsPerPosition; + AZ::Vector2 stepSize(1.0f, 1.0f); + AZ::Aabb regionBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f, 0.0f, 16.0f), AZ::Vector3(4.0f, 4.0f, 16.0f)); + + SurfaceData::SurfaceDataSystemRequestBus::Broadcast( + &SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromRegion, regionBounds, stepSize, providerTags, + availablePointsPerPosition); + + EXPECT_TRUE(ValidateRegionListSize(regionBounds, stepSize, availablePointsPerPosition)); + + // For each point entry returned from GetSurfacePointsFromRegion, call GetSurfacePoints and verify the results match. + CompareSurfacePointListWithGetSurfacePoints(availablePointsPerPosition, providerTags); +} + +TEST_F(SurfaceDataTestApp, SurfaceData_VerifyGetSurfacePointsFromListAndGetSurfacePointsMatch) +{ + // This ensures that both GetSurfacePointsFromList and GetSurfacePoints produce the same results. + + // Create a mock Surface Provider that covers from (0, 0) - (8, 8) in space. + // It defines points spaced 0.25 apart, with heights of 0 and 4, and with the tags "test_surface1" and "test_surface2". + // (We're creating points spaced more densely than we'll query just to verify that we only get back the queried points) + SurfaceData::SurfaceTagVector providerTags = { SurfaceData::SurfaceTag(m_testSurface1Crc), SurfaceData::SurfaceTag(m_testSurface2Crc) }; + MockSurfaceProvider mockProvider( + MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, providerTags, AZ::Vector3(0.0f), AZ::Vector3(8.0f), + AZ::Vector3(0.25f, 0.25f, 4.0f)); + + // Query for all the surface points from (0, 0, 16) - (4, 4, 16) with a step size of 1. + SurfaceData::SurfacePointLists availablePointsPerPosition; + AZStd::vector queryPositions; + for (float y = 0.0f; y < 4.0f; y += 1.0f) + { + for (float x = 0.0f; x < 4.0f; x += 1.0f) + { + queryPositions.push_back(AZ::Vector3(x, y, 16.0f)); + } + } + + SurfaceData::SurfaceDataSystemRequestBus::Broadcast( + &SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromList, + queryPositions, providerTags, availablePointsPerPosition); + + EXPECT_EQ(availablePointsPerPosition.size(), 16); + + // For each point entry returned from GetSurfacePointsFromList, call GetSurfacePoints and verify the results match. + CompareSurfacePointListWithGetSurfacePoints(availablePointsPerPosition, providerTags); +} +// This uses custom test / benchmark hooks so that we can load LmbrCentral and use Shape components in our unit tests and benchmarks. +AZ_UNIT_TEST_HOOK(new UnitTest::SurfaceDataTestEnvironment, UnitTest::SurfaceDataBenchmarkEnvironment); diff --git a/Gems/SurfaceData/Code/Tests/SurfaceDataTestFixtures.cpp b/Gems/SurfaceData/Code/Tests/SurfaceDataTestFixtures.cpp new file mode 100644 index 0000000000..2ec531d2e4 --- /dev/null +++ b/Gems/SurfaceData/Code/Tests/SurfaceDataTestFixtures.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + + +#include +#include + +#include +#include +#include +#include + + +namespace UnitTest +{ + void SurfaceDataTestEnvironment::AddGemsAndComponents() + { + AddDynamicModulePaths({ "LmbrCentral" }); + + AddComponentDescriptors({ + AzFramework::TransformComponent::CreateDescriptor(), + + SurfaceData::SurfaceDataSystemComponent::CreateDescriptor(), + SurfaceData::SurfaceDataColliderComponent::CreateDescriptor(), + SurfaceData::SurfaceDataShapeComponent::CreateDescriptor(), + + MockPhysicsColliderComponent::CreateDescriptor() + }); + } +} + diff --git a/Gems/SurfaceData/Code/Tests/SurfaceDataTestFixtures.h b/Gems/SurfaceData/Code/Tests/SurfaceDataTestFixtures.h new file mode 100644 index 0000000000..fc27effd8b --- /dev/null +++ b/Gems/SurfaceData/Code/Tests/SurfaceDataTestFixtures.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include + +namespace UnitTest +{ + // SurfaceData needs to use the GemTestEnvironment to load the LmbrCentral Gem so that Shape components can be used + // in the unit tests and benchmarks. + class SurfaceDataTestEnvironment + : public AZ::Test::GemTestEnvironment + { + public: + void AddGemsAndComponents() override; + }; + +#ifdef HAVE_BENCHMARK + //! The Benchmark environment is used for one time setup and tear down of shared resources + class SurfaceDataBenchmarkEnvironment + : public AZ::Test::BenchmarkEnvironmentBase + , public SurfaceDataTestEnvironment + + { + protected: + void SetUpBenchmark() override + { + SetupEnvironment(); + } + + void TearDownBenchmark() override + { + TeardownEnvironment(); + } + }; +#endif + +} diff --git a/Gems/SurfaceData/Code/surfacedata_tests_files.cmake b/Gems/SurfaceData/Code/surfacedata_tests_files.cmake index a65d51c47c..a334f7bf40 100644 --- a/Gems/SurfaceData/Code/surfacedata_tests_files.cmake +++ b/Gems/SurfaceData/Code/surfacedata_tests_files.cmake @@ -8,8 +8,11 @@ set(FILES Include/SurfaceData/Tests/SurfaceDataTestMocks.h + Tests/SurfaceDataBenchmarks.cpp Tests/SurfaceDataColliderComponentTest.cpp Tests/SurfaceDataTest.cpp + Tests/SurfaceDataTestFixtures.cpp + Tests/SurfaceDataTestFixtures.h Source/SurfaceDataModule.cpp Source/SurfaceDataModule.h ) diff --git a/Gems/Vegetation/Code/Tests/VegetationMocks.h b/Gems/Vegetation/Code/Tests/VegetationMocks.h index 8a8bfe6b76..c1d83fb68b 100644 --- a/Gems/Vegetation/Code/Tests/VegetationMocks.h +++ b/Gems/Vegetation/Code/Tests/VegetationMocks.h @@ -345,6 +345,13 @@ namespace UnitTest { } + void GetSurfacePointsFromList( + [[maybe_unused]] AZStd::span inPositions, + [[maybe_unused]] const SurfaceData::SurfaceTagVector& desiredTags, + [[maybe_unused]] SurfaceData::SurfacePointLists& surfacePointLists) const override + { + } + SurfaceData::SurfaceDataRegistryHandle RegisterSurfaceDataProvider([[maybe_unused]] const SurfaceData::SurfaceDataRegistryEntry& entry) override { ++m_count; From c514e1b49017b91b9565c6ecad1ca156a7bbbffe Mon Sep 17 00:00:00 2001 From: hershey5045 <43485729+hershey5045@users.noreply.github.com> Date: Mon, 31 Jan 2022 12:14:27 -0800 Subject: [PATCH 39/53] Small refactor on ImageComparison utils. (#7133) * Small refactor on ImageComparison utils. Signed-off-by: hershey5045 <43485729+hershey5045@users.noreply.github.com> * Add aznumeric cast. Signed-off-by: hershey5045 <43485729+hershey5045@users.noreply.github.com> * Correction on aznumeric cast. Signed-off-by: hershey5045 <43485729+hershey5045@users.noreply.github.com> * Add unit test for new image comparison function. Signed-off-by: hershey5045 <43485729+hershey5045@users.noreply.github.com> --- .../Code/Include/Atom/Utils/ImageComparison.h | 3 +++ .../Utils/Code/Source/ImageComparison.cpp | 19 +++++++++++-------- .../Utils/Code/Tests/ImageComparisonTests.cpp | 10 ++++++++++ 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImageComparison.h b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImageComparison.h index 4126e9eb4a..58c3a1bd48 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImageComparison.h +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImageComparison.h @@ -23,6 +23,9 @@ namespace AZ UnsupportedFormat }; + //! Calculates the maximum difference of the rgb channels between two image buffers. + int16_t CalcMaxChannelDifference(AZStd::array_view bufferA, AZStd::array_view bufferB, size_t index); + //! Compares two images and returns the RMS (root mean square) of the difference. //! @param buffer[A|B] the raw buffer of image data //! @param size[A|B] the dimensions of the image in the buffer diff --git a/Gems/Atom/Utils/Code/Source/ImageComparison.cpp b/Gems/Atom/Utils/Code/Source/ImageComparison.cpp index 0bad268ba5..6fcc316791 100644 --- a/Gems/Atom/Utils/Code/Source/ImageComparison.cpp +++ b/Gems/Atom/Utils/Code/Source/ImageComparison.cpp @@ -14,6 +14,16 @@ namespace AZ { namespace Utils { + int16_t CalcMaxChannelDifference(AZStd::array_view bufferA, AZStd::array_view bufferB, size_t index) + { + // We use the max error from a single channel instead of accumulating the error from each channel. + // This normalizes differences so that for example black vs red has the same weight as black vs yellow. + const int16_t diffR = static_cast(abs(aznumeric_cast(bufferA[index]) - aznumeric_cast(bufferB[index]))); + const int16_t diffG = static_cast(abs(aznumeric_cast(bufferA[index + 1]) - aznumeric_cast(bufferB[index + 1]))); + const int16_t diffB = static_cast(abs(aznumeric_cast(bufferA[index + 2]) - aznumeric_cast(bufferB[index + 2]))); + return AZ::GetMax(AZ::GetMax(diffR, diffG), diffB); + } + ImageDiffResultCode CalcImageDiffRms( AZStd::span bufferA, const RHI::Size& sizeA, RHI::Format formatA, AZStd::span bufferB, const RHI::Size& sizeB, RHI::Format formatB, @@ -67,14 +77,7 @@ namespace AZ for (size_t i = 0; i < bufferA.size(); i += BytesPerPixel) { - // We use the max error from a single channel instead of accumulating the error from each channel. - // This normalizes differences so that for example black vs red has the same weight as black vs yellow. - const int16_t diffR = static_cast(abs(aznumeric_cast(bufferA[i]) - aznumeric_cast(bufferB[i]))); - const int16_t diffG = static_cast(abs(aznumeric_cast(bufferA[i + 1]) - aznumeric_cast(bufferB[i + 1]))); - const int16_t diffB = static_cast(abs(aznumeric_cast(bufferA[i + 2]) - aznumeric_cast(bufferB[i + 2]))); - const int16_t maxDiff = AZ::GetMax(AZ::GetMax(diffR, diffG), diffB); - - const float finalDiffNormalized = maxDiff / 255.0f; + const float finalDiffNormalized = aznumeric_cast(CalcMaxChannelDifference(bufferA, bufferB, i)) / 255.0f; const float squared = finalDiffNormalized * finalDiffNormalized; if (diffScore) diff --git a/Gems/Atom/Utils/Code/Tests/ImageComparisonTests.cpp b/Gems/Atom/Utils/Code/Tests/ImageComparisonTests.cpp index 29861e1dbb..2ec71ff848 100644 --- a/Gems/Atom/Utils/Code/Tests/ImageComparisonTests.cpp +++ b/Gems/Atom/Utils/Code/Tests/ImageComparisonTests.cpp @@ -127,6 +127,16 @@ namespace UnitTest EXPECT_EQ(0.0f, diffScore); } + + TEST_F(ImageComparisonTests, CheckMaxChannelDifference) + { + const AZStd::vector imageA = { 255, 255, 255 }; + const AZStd::vector imageB = { 0, 125, 255 }; + const int16_t maxChannelDiff = 255; + const int16_t res = CalcMaxChannelDifference(imageA, imageB, 0); + EXPECT_EQ(res, maxChannelDiff); + } + TEST_F(ImageComparisonTests, CheckThreshold_SmallImagesWithDifferences) { AZ::RHI::Size size{2, 2, 1}; From 0a5e61b8342e86223c3eda1fa3739f074a701c4a Mon Sep 17 00:00:00 2001 From: rgba16f <82187279+rgba16f@users.noreply.github.com> Date: Mon, 31 Jan 2022 14:17:20 -0600 Subject: [PATCH 40/53] Update default AZStd thread priority on Apple platforms to avoid being prevented from using 100% of a core (#7295) PAL-ified default thread priority for pthread platforms. Fix threads not getting named on Apple platforms by setting the thread name ptr on the thread_info struct. Signed-off-by: rgba16f <82187279+rgba16f@users.noreply.github.com> --- .../std/parallel/internal/thread_Android.cpp | 7 +++++++ .../AzCore/std/parallel/internal/thread_Apple.cpp | 15 ++++++++++++++- .../std/parallel/internal/thread_UnixLike.cpp | 4 +++- .../AzCore/std/parallel/internal/thread_Linux.cpp | 7 +++++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/Code/Framework/AzCore/Platform/Android/AzCore/std/parallel/internal/thread_Android.cpp b/Code/Framework/AzCore/Platform/Android/AzCore/std/parallel/internal/thread_Android.cpp index 612296ebba..865d84516f 100644 --- a/Code/Framework/AzCore/Platform/Android/AzCore/std/parallel/internal/thread_Android.cpp +++ b/Code/Framework/AzCore/Platform/Android/AzCore/std/parallel/internal/thread_Android.cpp @@ -36,5 +36,12 @@ namespace AZStd { pthread_setname_np(tId, name); } + + uint8_t GetDefaultThreadPriority() + { + // pthread priority is an integer between >=1 and <=99 (although only range 1<=>32 is guaranteed) + // Don't use a scheduling policy value (e.g. SCHED_OTHER or SCHED_FIFO) here. + return 1; + } } } diff --git a/Code/Framework/AzCore/Platform/Common/Apple/AzCore/std/parallel/internal/thread_Apple.cpp b/Code/Framework/AzCore/Platform/Common/Apple/AzCore/std/parallel/internal/thread_Apple.cpp index bc89545faa..c39447b2f2 100644 --- a/Code/Framework/AzCore/Platform/Common/Apple/AzCore/std/parallel/internal/thread_Apple.cpp +++ b/Code/Framework/AzCore/Platform/Common/Apple/AzCore/std/parallel/internal/thread_Apple.cpp @@ -35,7 +35,7 @@ namespace AZStd void SetThreadPriority(int priority, pthread_attr_t& attr) { - if (priority == -1) + if (priority <= -1) { pthread_attr_setinheritsched(&attr, PTHREAD_INHERIT_SCHED); } @@ -59,5 +59,18 @@ namespace AZStd thread_policy_set(mach_thread, THREAD_AFFINITY_POLICY, (thread_policy_t)& policyData, 1); } } + + ////////////////////////////////////////////////////////////////////////////////// + // Apple pthread -> NSThread quality of service level map + // QOS class name | min pthread priority | max pthread priority | comment + // QOS_CLASS_USER_INTERACTIVE | 38 | 47 | Per-frame work + // QOS_CLASS_USER_INITIATED | 32 | 37 | Asynchronous / Cross frame work + // QOS_CLASS_DEFAULT | 21 | 31 | Streaming / Multiple frames deadline + // QOS_CLASS_UTILITY | 5 | 20 | Background asset download + // QOS_CLASS_BACKGROUN | 0 | 4 | Will be prevented from using whole core. + uint8_t GetDefaultThreadPriority() + { + return 10; + } } } diff --git a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/std/parallel/internal/thread_UnixLike.cpp b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/std/parallel/internal/thread_UnixLike.cpp index da5d95b9a4..5ff3b82b3f 100644 --- a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/std/parallel/internal/thread_UnixLike.cpp +++ b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/std/parallel/internal/thread_UnixLike.cpp @@ -21,6 +21,7 @@ namespace AZStd void PreCreateSetThreadAffinity(int cpuId, pthread_attr_t& attr); void SetThreadPriority(int priority, pthread_attr_t& attr); void PostCreateThread(pthread_t tId, const char * name, int cpuId); + uint8_t GetDefaultThreadPriority(); } namespace Internal @@ -60,12 +61,13 @@ namespace AZStd } else { - priority = SCHED_OTHER; + priority = Platform::GetDefaultThreadPriority(); } if (desc->m_name) { name = desc->m_name; } + ti->m_name = name; cpuId = desc->m_cpuId; pthread_attr_setdetachstate(&attr, desc->m_isJoinable ? PTHREAD_CREATE_JOINABLE : PTHREAD_CREATE_DETACHED); diff --git a/Code/Framework/AzCore/Platform/Linux/AzCore/std/parallel/internal/thread_Linux.cpp b/Code/Framework/AzCore/Platform/Linux/AzCore/std/parallel/internal/thread_Linux.cpp index 619658057b..10121cc2ed 100644 --- a/Code/Framework/AzCore/Platform/Linux/AzCore/std/parallel/internal/thread_Linux.cpp +++ b/Code/Framework/AzCore/Platform/Linux/AzCore/std/parallel/internal/thread_Linux.cpp @@ -55,5 +55,12 @@ namespace AZStd { pthread_setname_np(tId, name); } + + uint8_t GetDefaultThreadPriority() + { + // pthread priority is an integer between >=1 and <=99 (although only range 1<=>32 is guaranteed) + // Don't use a scheduling policy value (e.g. SCHED_OTHER or SCHED_FIFO) here. + return 1; + } } } From 564596fcf0ad4b802e92d148e4a7053bba05ee72 Mon Sep 17 00:00:00 2001 From: Chris Galvan Date: Mon, 31 Jan 2022 15:34:42 -0600 Subject: [PATCH 41/53] Removed legacy PropertyResourceCtrl Signed-off-by: Chris Galvan --- Code/Editor/Controls/BitmapToolTip.cpp | 369 ---------- Code/Editor/Controls/BitmapToolTip.h | 88 --- Code/Editor/Controls/QBitmapPreviewDialog.cpp | 172 ----- Code/Editor/Controls/QBitmapPreviewDialog.h | 64 -- Code/Editor/Controls/QBitmapPreviewDialog.ui | 390 ----------- .../Controls/QBitmapPreviewDialogImp.cpp | 528 -------------- .../Editor/Controls/QBitmapPreviewDialogImp.h | 89 --- Code/Editor/Controls/QToolTipWidget.cpp | 642 ------------------ Code/Editor/Controls/QToolTipWidget.h | 148 ---- .../ReflectedPropertyControl/PropertyCtrl.cpp | 2 - .../PropertyResourceCtrl.cpp | 383 ----------- .../PropertyResourceCtrl.h | 118 ---- .../ReflectedPropertyControl/ReflectedVar.cpp | 6 - Code/Editor/editor_core_files.cmake | 7 - Code/Editor/editor_lib_files.cmake | 4 - 15 files changed, 3010 deletions(-) delete mode 100644 Code/Editor/Controls/BitmapToolTip.cpp delete mode 100644 Code/Editor/Controls/BitmapToolTip.h delete mode 100644 Code/Editor/Controls/QBitmapPreviewDialog.cpp delete mode 100644 Code/Editor/Controls/QBitmapPreviewDialog.h delete mode 100644 Code/Editor/Controls/QBitmapPreviewDialog.ui delete mode 100644 Code/Editor/Controls/QBitmapPreviewDialogImp.cpp delete mode 100644 Code/Editor/Controls/QBitmapPreviewDialogImp.h delete mode 100644 Code/Editor/Controls/QToolTipWidget.cpp delete mode 100644 Code/Editor/Controls/QToolTipWidget.h delete mode 100644 Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.cpp delete mode 100644 Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.h diff --git a/Code/Editor/Controls/BitmapToolTip.cpp b/Code/Editor/Controls/BitmapToolTip.cpp deleted file mode 100644 index d639ac0677..0000000000 --- a/Code/Editor/Controls/BitmapToolTip.cpp +++ /dev/null @@ -1,369 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - - -// Description : Tooltip that displays bitmap. - - -#include "EditorDefs.h" - -#include "BitmapToolTip.h" - -// Qt -#include - -// Editor -#include "Util/Image.h" -#include "Util/ImageUtil.h" - - -static const int STATIC_TEXT_C_HEIGHT = 42; -static const int HISTOGRAM_C_HEIGHT = 130; - -///////////////////////////////////////////////////////////////////////////// -// CBitmapToolTip -CBitmapToolTip::CBitmapToolTip(QWidget* parent) - : QWidget(parent, Qt::ToolTip) - , m_staticBitmap(new QLabel(this)) - , m_staticText(new QLabel(this)) - , m_rgbaHistogram(new CImageHistogramCtrl(this)) - , m_alphaChannelHistogram(new CImageHistogramCtrl(this)) -{ - m_nTimer = 0; - m_hToolWnd = nullptr; - m_bShowHistogram = true; - m_bShowFullsize = false; - m_eShowMode = ESHOW_RGB; - - connect(&m_timer, &QTimer::timeout, this, &CBitmapToolTip::OnTimer); - - auto* layout = new QVBoxLayout(this); - layout->setSizeConstraint(QLayout::SetFixedSize); - - layout->addWidget(m_staticBitmap); - layout->addWidget(m_staticText); - - auto* histogramLayout = new QHBoxLayout(); - histogramLayout->addWidget(m_rgbaHistogram); - histogramLayout->addWidget(m_alphaChannelHistogram); - m_alphaChannelHistogram->setVisible(false); - - layout->addLayout(histogramLayout); - - setLayout(layout); -} - -CBitmapToolTip::~CBitmapToolTip() -{ -} - -////////////////////////////////////////////////////////////////////////// -void CBitmapToolTip::GetShowMode(EShowMode& eShowMode, bool& bShowInOriginalSize) const -{ - bShowInOriginalSize = CheckVirtualKey(Qt::Key_Space); - eShowMode = ESHOW_RGB; - - if (m_bHasAlpha) - { - if (CheckVirtualKey(Qt::Key_Control)) - { - eShowMode = ESHOW_RGB_ALPHA; - } - else if (CheckVirtualKey(Qt::Key_Alt)) - { - eShowMode = ESHOW_ALPHA; - } - else if (CheckVirtualKey(Qt::Key_Shift)) - { - eShowMode = ESHOW_RGBA; - } - } - else if (m_bIsLimitedHDR) - { - if (CheckVirtualKey(Qt::Key_Shift)) - { - eShowMode = ESHOW_RGBE; - } - } -} - -const char* CBitmapToolTip::GetShowModeDescription(EShowMode eShowMode, [[maybe_unused]] bool bShowInOriginalSize) const -{ - switch (eShowMode) - { - case ESHOW_RGB: - return "RGB"; - case ESHOW_RGB_ALPHA: - return "RGB+A"; - case ESHOW_ALPHA: - return "Alpha"; - case ESHOW_RGBA: - return "RGBA"; - case ESHOW_RGBE: - return "RGBExp"; - } - - return ""; -} - -void CBitmapToolTip::RefreshViewmode() -{ - LoadImage(m_filename); - - if (m_eShowMode == ESHOW_RGB_ALPHA || m_eShowMode == ESHOW_RGBA) - { - m_rgbaHistogram->setVisible(true); - m_alphaChannelHistogram->setVisible(true); - } - else if (m_eShowMode == ESHOW_ALPHA) - { - m_rgbaHistogram->setVisible(false); - m_alphaChannelHistogram->setVisible(true); - } - else - { - m_rgbaHistogram->setVisible(true); - m_alphaChannelHistogram->setVisible(false); - } -} - -bool CBitmapToolTip::LoadImage(const QString& imageFilename) -{ - EShowMode eShowMode = ESHOW_RGB; - const char* pShowModeDescription = "RGB"; - bool bShowInOriginalSize = false; - - GetShowMode(eShowMode, bShowInOriginalSize); - pShowModeDescription = GetShowModeDescription(eShowMode, bShowInOriginalSize); - - QString convertedFileName = Path::GamePathToFullPath(Path::ReplaceExtension(imageFilename, ".dds")); - - // We need to check against both the image filename and the converted filename as it is possible that the - // converted file existed but failed to load previously and we reverted to loading the source asset. - bool alreadyLoadedImage = ((m_filename == convertedFileName) || (m_filename == imageFilename)); - if (alreadyLoadedImage && (m_eShowMode == eShowMode) && (m_bShowFullsize == bShowInOriginalSize)) - { - return true; - } - - CCryFile fileCheck; - if (!fileCheck.Open(convertedFileName.toUtf8().data(), "rb")) - { - // if we didn't find it, then default back to just using what we can find (if any) - convertedFileName = imageFilename; - } - else - { - fileCheck.Close(); - } - - m_eShowMode = eShowMode; - m_bShowFullsize = bShowInOriginalSize; - - CImageEx image; - image.SetHistogramEqualization(CheckVirtualKey(Qt::Key_Shift)); - bool loadedRequestedAsset = true; - if (!CImageUtil::LoadImage(convertedFileName, image)) - { - //Failed to load the requested asset, let's try loading the source asset if available. - loadedRequestedAsset = false; - if (!CImageUtil::LoadImage(imageFilename, image)) - { - m_staticBitmap->clear(); - return false; - } - } - - QString imginfo; - - m_filename = loadedRequestedAsset ? convertedFileName : imageFilename; - m_bHasAlpha = image.HasAlphaChannel(); - m_bIsLimitedHDR = image.IsLimitedHDR(); - - GetShowMode(eShowMode, bShowInOriginalSize); - pShowModeDescription = GetShowModeDescription(eShowMode, bShowInOriginalSize); - - if (m_bHasAlpha) - { - imginfo = tr("%1x%2 %3\nShowing %4 (ALT=Alpha, SHIFT=RGBA, CTRL=RGB+A, SPACE=see in original size)"); - } - else if (m_bIsLimitedHDR) - { - imginfo = tr("%1x%2 %3\nShowing %4 (SHIFT=see hist.-equalized, SPACE=see in original size)"); - } - else - { - imginfo = tr("%1x%2 %3\nShowing %4 (SPACE=see in original size)"); - } - - imginfo = imginfo.arg(image.GetWidth()).arg(image.GetHeight()).arg(image.GetFormatDescription()).arg(pShowModeDescription); - - m_staticText->setText(imginfo); - - int w = image.GetWidth(); - int h = image.GetHeight(); - int multiplier = (m_eShowMode == ESHOW_RGB_ALPHA ? 2 : 1); - int originalW = w * multiplier; - int originalH = h; - - if (!bShowInOriginalSize || (w == 0)) - { - w = 256; - } - if (!bShowInOriginalSize || (h == 0)) - { - h = 256; - } - - w *= multiplier; - - resize(w + 4, h + 4 + STATIC_TEXT_C_HEIGHT + HISTOGRAM_C_HEIGHT); - setVisible(true); - - CImageEx scaledImage; - - if (bShowInOriginalSize && (originalW < w)) - { - w = originalW; - } - if (bShowInOriginalSize && (originalH < h)) - { - h = originalH; - } - - scaledImage.Allocate(w, h); - - if (m_eShowMode == ESHOW_RGB_ALPHA) - { - CImageUtil::ScaleToDoubleFit(image, scaledImage); - } - else - { - CImageUtil::ScaleToFit(image, scaledImage); - } - - if (m_eShowMode == ESHOW_RGB || m_eShowMode == ESHOW_RGBE) - { - scaledImage.SwapRedAndBlue(); - scaledImage.FillAlpha(); - } - else if (m_eShowMode == ESHOW_ALPHA) - { - for (int hh = 0; hh < scaledImage.GetHeight(); hh++) - { - for (int ww = 0; ww < scaledImage.GetWidth(); ww++) - { - int a = scaledImage.ValueAt(ww, hh) >> 24; - scaledImage.ValueAt(ww, hh) = RGB(a, a, a); - } - } - } - else if (m_eShowMode == ESHOW_RGB_ALPHA) - { - int halfWidth = scaledImage.GetWidth() / 2; - for (int hh = 0; hh < scaledImage.GetHeight(); hh++) - { - for (int ww = 0; ww < halfWidth; ww++) - { - int r = GetRValue(scaledImage.ValueAt(ww, hh)); - int g = GetGValue(scaledImage.ValueAt(ww, hh)); - int b = GetBValue(scaledImage.ValueAt(ww, hh)); - int a = scaledImage.ValueAt(ww, hh) >> 24; - scaledImage.ValueAt(ww, hh) = RGB(b, g, r); - scaledImage.ValueAt(ww + halfWidth, hh) = RGB(a, a, a); - } - } - } - else //if (m_showMode == ESHOW_RGBA) - { - scaledImage.SwapRedAndBlue(); - } - - QImage qImage(scaledImage.GetWidth(), scaledImage.GetHeight(), QImage::Format_RGB32); - memcpy(qImage.bits(), scaledImage.GetData(), qImage.sizeInBytes()); - m_staticBitmap->setPixmap(QPixmap::fromImage(qImage)); - - if (m_bShowHistogram && scaledImage.GetData()) - { - m_rgbaHistogram->ComputeHistogram(image, CImageHistogram::eImageFormat_32BPP_BGRA); - m_rgbaHistogram->setDrawMode(EHistogramDrawMode::OverlappedRGB); - - m_alphaChannelHistogram->histogramDisplay()->CopyComputedDataFrom(m_rgbaHistogram->histogramDisplay()); - m_alphaChannelHistogram->setDrawMode(EHistogramDrawMode::AlphaChannel); - } - - return true; -} - -void CBitmapToolTip::OnTimer() -{ - /* - if (IsWindowVisible()) - { - if (m_bHaveAnythingToRender) - Invalidate(); - } - */ - if (m_hToolWnd) - { - QRect toolRc(m_toolRect); - QRect rc = geometry(); - QPoint cursorPos = QCursor::pos(); - toolRc.moveTopLeft(m_hToolWnd->mapToGlobal(toolRc.topLeft())); - if (!toolRc.contains(cursorPos) && !rc.contains(cursorPos)) - { - setVisible(false); - } - else - { - RefreshViewmode(); - } - } -} - -////////////////////////////////////////////////////////////////////////// -void CBitmapToolTip::showEvent([[maybe_unused]] QShowEvent* event) -{ - QPoint cursorPos = QCursor::pos(); - move(cursorPos); - m_timer.start(500); -} - -////////////////////////////////////////////////////////////////////////// -void CBitmapToolTip::hideEvent([[maybe_unused]] QHideEvent* event) -{ - m_timer.stop(); -} - -////////////////////////////////////////////////////////////////////////// - -void CBitmapToolTip::keyPressEvent(QKeyEvent* event) -{ - if (event->key() == Qt::Key_Control || event->key() == Qt::Key_Alt || event->key() == Qt::Key_Shift) - { - RefreshViewmode(); - } -} - -void CBitmapToolTip::keyReleaseEvent(QKeyEvent* event) -{ - if (event->key() == Qt::Key_Control || event->key() == Qt::Key_Alt || event->key() == Qt::Key_Shift) - { - RefreshViewmode(); - } -} - -////////////////////////////////////////////////////////////////////////// -void CBitmapToolTip::SetTool(QWidget* pWnd, const QRect& rect) -{ - assert(pWnd); - m_hToolWnd = pWnd; - m_toolRect = rect; -} - -#include diff --git a/Code/Editor/Controls/BitmapToolTip.h b/Code/Editor/Controls/BitmapToolTip.h deleted file mode 100644 index 5b7c56cbf3..0000000000 --- a/Code/Editor/Controls/BitmapToolTip.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - - -// Description : Tooltip that displays bitmap. - - -#ifndef CRYINCLUDE_EDITOR_CONTROLS_BITMAPTOOLTIP_H -#define CRYINCLUDE_EDITOR_CONTROLS_BITMAPTOOLTIP_H -#pragma once - - -#if !defined(Q_MOC_RUN) -#include "Controls/ImageHistogramCtrl.h" - -#include -#include -#endif - -////////////////////////////////////////////////////////////////////////// -class CBitmapToolTip - : public QWidget -{ - Q_OBJECT - // Construction -public: - - enum EShowMode - { - ESHOW_RGB = 0, - ESHOW_ALPHA, - ESHOW_RGBA, - ESHOW_RGB_ALPHA, - ESHOW_RGBE - }; - - CBitmapToolTip(QWidget* parent = nullptr); - virtual ~CBitmapToolTip(); - - bool Create(const RECT& rect); - - // Attributes -public: - - // Operations -public: - void RefreshViewmode(); - - bool LoadImage(const QString& imageFilename); - void SetTool(QWidget* pWnd, const QRect& rect); - - // Generated message map functions -protected: - void OnTimer(); - - void keyPressEvent(QKeyEvent* event) override; - void keyReleaseEvent(QKeyEvent* event) override; - - void showEvent(QShowEvent* event) override; - void hideEvent(QHideEvent* event) override; - -private: - void GetShowMode(EShowMode& showMode, bool& showInOriginalSize) const; - const char* GetShowModeDescription(EShowMode showMode, bool showInOriginalSize) const; - - QLabel* m_staticBitmap; - QLabel* m_staticText; - QString m_filename; - bool m_bShowHistogram; - EShowMode m_eShowMode; - bool m_bShowFullsize; - bool m_bHasAlpha; - bool m_bIsLimitedHDR; - CImageHistogramCtrl* m_rgbaHistogram; - CImageHistogramCtrl* m_alphaChannelHistogram; - int m_nTimer; - QWidget* m_hToolWnd; - QRect m_toolRect; - QTimer m_timer; -}; - - -#endif // CRYINCLUDE_EDITOR_CONTROLS_BITMAPTOOLTIP_H diff --git a/Code/Editor/Controls/QBitmapPreviewDialog.cpp b/Code/Editor/Controls/QBitmapPreviewDialog.cpp deleted file mode 100644 index 912db3708c..0000000000 --- a/Code/Editor/Controls/QBitmapPreviewDialog.cpp +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ -#include "EditorDefs.h" - -#include "QBitmapPreviewDialog.h" -#include - -#include -#include -#include -#include - -void QBitmapPreviewDialog::ImageData::setRgba8888(const void* buffer, const int& w, const int& h) -{ - const unsigned long bytes = w * h * 4; - m_buffer.resize(bytes); - memcpy(m_buffer.data(), buffer, bytes); - m_image = QImage((uchar*)m_buffer.constData(), w, h, QImage::Format::Format_RGBA8888); -} - -static void fillChecker(int w, int h, unsigned int* dst) -{ - for (int y = 0; y < h; y++) - { - for (int x = 0; x < w; x++) - { - dst[y * w + x] = 0xFF000000 | (((x >> 2) + (y >> 2)) % 2 == 0 ? 0x007F7F7F : 0x00000000); - } - } -} - -QBitmapPreviewDialog::QBitmapPreviewDialog(QWidget* parent) - : QWidget(parent) - , ui(new Ui::QBitmapTooltip) -{ - ui->setupUi(this); - setAttribute(Qt::WA_TranslucentBackground); - setAttribute(Qt::WA_ShowWithoutActivating); - - // Clear label text - ui->m_placeholderBitmap->setText(""); - ui->m_placeholderHistogram->setText(""); - - ui->m_bitmapSize->setProperty("tableRow", "Odd"); - ui->m_Mips->setProperty("tableRow", "Even"); - ui->m_Mean->setProperty("tableRow", "Odd"); - ui->m_StdDev->setProperty("tableRow", "Even"); - ui->m_Median->setProperty("tableRow", "Odd"); - ui->m_labelForBitmapSize->setProperty("tooltipLabel", "content"); - ui->m_labelForMean->setProperty("tooltipLabel", "content"); - ui->m_labelForMedian->setProperty("tooltipLabel", "content"); - ui->m_labelForMips->setProperty("tooltipLabel", "content"); - ui->m_labelForStdDev->setProperty("tooltipLabel", "content"); - ui->m_vBitmapSize->setProperty("tooltipLabel", "content"); - ui->m_vMean->setProperty("tooltipLabel", "content"); - ui->m_vMedian->setProperty("tooltipLabel", "content"); - ui->m_vMips->setProperty("tooltipLabel", "content"); - ui->m_vStdDev->setProperty("tooltipLabel", "content"); - - // Initialize placeholder images - const int w = 64; - const int h = 64; - QByteArray buffer; - buffer.resize(w * h * 4); - unsigned int* dst = (unsigned int*)buffer.data(); - fillChecker(w, h, dst); - m_checker.setRgba8888(buffer.constData(), w, h); - - m_initialSize = window()->window()->geometry().size(); -} - -QBitmapPreviewDialog::~QBitmapPreviewDialog() -{ - delete ui; -} - -void QBitmapPreviewDialog::setImageRgba8888(const void* buffer, const int& w, const int& h, [[maybe_unused]] const QString& info) -{ - m_imageMain.setRgba8888(buffer, w, h); -} - - -QRect QBitmapPreviewDialog::getHistogramArea() -{ - return QRect(ui->m_placeholderHistogram->pos(), ui->m_placeholderHistogram->size()); -} - -void QBitmapPreviewDialog::setFullSize(const bool& fullSize) -{ - if (fullSize) - { - QSize desktop = QApplication::screenAt(ui->m_placeholderBitmap->pos())->availableGeometry().size(); - QSize image = m_imageMain.m_image.size(); - QPoint location = mapToGlobal(ui->m_placeholderBitmap->pos()); - QSize finalSize; - finalSize.setWidth((image.width() < (desktop.width() - location.x())) ? image.width() : (desktop.width() - location.x())); - finalSize.setHeight((image.height() < (desktop.height() - location.y())) ? image.height() : (desktop.height() - location.y())); - float scale = (finalSize.width() < finalSize.height()) ? finalSize.width() / float(m_imageMain.m_image.width()) : finalSize.height() / float(m_imageMain.m_image.height()); - ui->m_placeholderBitmap->setFixedSize(scale * m_imageMain.m_image.size()); - } - else - { - ui->m_placeholderBitmap->setFixedSize(256, 256); - } - - adjustSize(); - - update(); -} - -void QBitmapPreviewDialog::paintEvent(QPaintEvent* e) -{ - QWidget::paintEvent(e); - QRect rect(ui->m_placeholderBitmap->pos(), ui->m_placeholderBitmap->size()); - drawImageData(rect, m_imageMain); -} - -void QBitmapPreviewDialog::drawImageData(const QRect& rect, const ImageData& imgData) -{ - // Draw the - QPainter p(this); - p.drawImage(rect.topLeft(), m_checker.m_image.scaled(rect.size())); - p.drawImage(rect.topLeft(), imgData.m_image.scaled(rect.size())); - - // Draw border - QPen pen; - pen.setColor(QColor(0, 0, 0)); - p.drawRect(rect.top(), rect.left(), rect.width() - 1, rect.height()); -} - -void QBitmapPreviewDialog::setSize(QString _value) -{ - ui->m_vBitmapSize->setText(_value); -} - -void QBitmapPreviewDialog::setMips(QString _value) -{ - ui->m_vMips->setText(_value); -} - -void QBitmapPreviewDialog::setMean(QString _value) -{ - ui->m_vMean->setText(_value); -} - -void QBitmapPreviewDialog::setMedian(QString _value) -{ - ui->m_vMedian->setText(_value); -} - -void QBitmapPreviewDialog::setStdDev(QString _value) -{ - ui->m_vStdDev->setText(_value); -} - -QSize QBitmapPreviewDialog::GetCurrentBitmapSize() -{ - return ui->m_placeholderBitmap->size(); -} - -QSize QBitmapPreviewDialog::GetOriginalImageSize() -{ - return m_imageMain.m_image.size(); -} - - -#include diff --git a/Code/Editor/Controls/QBitmapPreviewDialog.h b/Code/Editor/Controls/QBitmapPreviewDialog.h deleted file mode 100644 index a5429bc8f0..0000000000 --- a/Code/Editor/Controls/QBitmapPreviewDialog.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ -#ifndef QBITMAPPREVIEWDIALOG_H -#define QBITMAPPREVIEWDIALOG_H - -#if !defined(Q_MOC_RUN) -#include -#include -#include -#endif - -class QLabel; - -namespace Ui { - class QBitmapTooltip; -} - -class QBitmapPreviewDialog - : public QWidget -{ - Q_OBJECT - - struct ImageData - { - QByteArray m_buffer; - QImage m_image; - - void setRgba8888(const void* buffer, const int& w, const int& h); - }; - -public: - explicit QBitmapPreviewDialog(QWidget* parent = 0); - virtual ~QBitmapPreviewDialog(); - QSize GetCurrentBitmapSize(); - QSize GetOriginalImageSize(); - -protected: - void setImageRgba8888(const void* buffer, const int& w, const int& h, const QString& info); - void setSize(QString _value); - void setMips(QString _value); - void setMean(QString _value); - void setMedian(QString _value); - void setStdDev(QString _value); - QRect getHistogramArea(); - void setFullSize(const bool& fullSize); - - void paintEvent(QPaintEvent* e) override; - -private: - void drawImageData(const QRect& rect, const ImageData& imgData); - -protected: - Ui::QBitmapTooltip* ui; - QSize m_initialSize; - ImageData m_checker; - ImageData m_imageMain; -}; - -#endif // QBITMAPPREVIEWDIALOG_H diff --git a/Code/Editor/Controls/QBitmapPreviewDialog.ui b/Code/Editor/Controls/QBitmapPreviewDialog.ui deleted file mode 100644 index 87b3a310ef..0000000000 --- a/Code/Editor/Controls/QBitmapPreviewDialog.ui +++ /dev/null @@ -1,390 +0,0 @@ - - - QBitmapTooltip - - - - 0 - 0 - 256 - 510 - - - - - 256 - 0 - - - - - 16777215 - 16777215 - - - - Form - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 0 - 256 - - - - - 16777215 - 16777215 - - - - false - - - QFrame::NoFrame - - - QFrame::Sunken - - - Bitmap Area - - - Qt::AlignCenter - - - false - - - - - - - - 0 - 0 - - - - - 0 - 128 - - - - QFrame::NoFrame - - - QFrame::Sunken - - - Histogram Area - - - Qt::AlignCenter - - - - - - - - 0 - 24 - - - - - 16777215 - 24 - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Size: - - - - - - - Qt::RightToLeft - - - Size Value - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - 0 - 24 - - - - - 16777215 - 24 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - DXT5 Mips: - - - - - - - Qt::RightToLeft - - - Size Value - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - 0 - 24 - - - - - 16777215 - 24 - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Mean: - - - - - - - Qt::RightToLeft - - - Size Value - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - 0 - 24 - - - - - 16777215 - 24 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - StdDev: - - - - - - - Qt::RightToLeft - - - Size Value - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - 0 - 24 - - - - - 16777215 - 24 - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Median: - - - - - - - Qt::RightToLeft - - - Size Value - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - diff --git a/Code/Editor/Controls/QBitmapPreviewDialogImp.cpp b/Code/Editor/Controls/QBitmapPreviewDialogImp.cpp deleted file mode 100644 index b47a535d2a..0000000000 --- a/Code/Editor/Controls/QBitmapPreviewDialogImp.cpp +++ /dev/null @@ -1,528 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ -#include "EditorDefs.h" - -#include "QBitmapPreviewDialogImp.h" - -// Cry -#include - -// EditorCore -#include -#include - -// QT -AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: class '...' needs to have dll-interface to be used by clients of class '...' -#include -#include -#include -#include -#include -AZ_POP_DISABLE_WARNING - -#include - -static const int kDefaultWidth = 256; -static const int kDefaultHeight = 256; - -QBitmapPreviewDialogImp::QBitmapPreviewDialogImp(QWidget* parent) - : QBitmapPreviewDialog(parent) - , m_image(new CImageEx()) - , m_showOriginalSize(false) - , m_showMode(ESHOW_RGB) - , m_histrogramMode(eHistogramMode_OverlappedRGB) -{ - setMouseTracking(true); - setImage(""); - ui->m_placeholderBitmap->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); - ui->m_placeholderHistogram->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); - - ui->m_labelForBitmapSize->setProperty("tooltipLabel", "Content"); - ui->m_labelForMean->setProperty("tooltipLabel", "Content"); - ui->m_labelForMedian->setProperty("tooltipLabel", "Content"); - ui->m_labelForMips->setProperty("tooltipLabel", "Content"); - ui->m_labelForStdDev->setProperty("tooltipLabel", "Content"); - - ui->m_vBitmapSize->setProperty("tooltipLabel", "Content"); - ui->m_vMean->setProperty("tooltipLabel", "Content"); - ui->m_vMedian->setProperty("tooltipLabel", "Content"); - ui->m_vMips->setProperty("tooltipLabel", "Content"); - ui->m_vStdDev->setProperty("tooltipLabel", "Content"); - - setUIStyleMode(EUISTYLE_IMAGE_ONLY); -} - -QBitmapPreviewDialogImp::~QBitmapPreviewDialogImp() -{ - SAFE_DELETE(m_image); -} - -void QBitmapPreviewDialogImp::setImage(const QString path) -{ - if (path.isEmpty() - || m_path == path - || !GetIEditor()->GetImageUtil()->LoadImage(path.toUtf8().data(), *m_image)) - { - return; - } - - m_showOriginalSize = isSizeSmallerThanDefault(); - m_path = path; - refreshData(); -} - -void QBitmapPreviewDialogImp::setShowMode(EShowMode mode) -{ - if (mode == ESHOW_NumModes) - { - return; - } - - m_showMode = mode; - refreshData(); - update(); -} - -void QBitmapPreviewDialogImp::toggleShowMode() -{ - m_showMode = (EShowMode)(((int)m_showMode + 1) % ESHOW_NumModes); - refreshData(); - update(); -} - -void QBitmapPreviewDialogImp::setUIStyleMode(EUIStyle mode) -{ - if (mode >= EUISTYLE_NumModes) - { - return; - } - - m_uiStyle = mode; - if (m_uiStyle == EUISTYLE_IMAGE_ONLY) - { - ui->m_placeholderHistogram->hide(); - - ui->m_labelForBitmapSize->hide(); - ui->m_labelForMean->hide(); - ui->m_labelForMedian->hide(); - ui->m_labelForMips->hide(); - ui->m_labelForStdDev->hide(); - - ui->m_vBitmapSize->hide(); - ui->m_vMean->hide(); - ui->m_vMedian->hide(); - ui->m_vMips->hide(); - ui->m_vStdDev->hide(); - } - else - { - ui->m_placeholderHistogram->show(); - - ui->m_labelForBitmapSize->show(); - ui->m_labelForMean->show(); - ui->m_labelForMedian->show(); - ui->m_labelForMips->show(); - ui->m_labelForStdDev->show(); - - ui->m_vBitmapSize->show(); - ui->m_vMean->show(); - ui->m_vMedian->show(); - ui->m_vMips->show(); - ui->m_vStdDev->show(); - } -} - -const QBitmapPreviewDialogImp::EShowMode& QBitmapPreviewDialogImp::getShowMode() const -{ - return m_showMode; -} - -void QBitmapPreviewDialogImp::setHistogramMode(EHistogramMode mode) -{ - if (mode == eHistogramMode_NumModes) - { - return; - } - - m_histrogramMode = mode; -} - -void QBitmapPreviewDialogImp::toggleHistrogramMode() -{ - m_histrogramMode = (EHistogramMode)(((int)m_histrogramMode + 1) % eHistogramMode_NumModes); - update(); -} - -const QBitmapPreviewDialogImp::EHistogramMode& QBitmapPreviewDialogImp::getHistogramMode() const -{ - return m_histrogramMode; -} - -void QBitmapPreviewDialogImp::toggleOriginalSize() -{ - m_showOriginalSize = !m_showOriginalSize; - - refreshData(); - update(); -} - -bool QBitmapPreviewDialogImp::isSizeSmallerThanDefault() -{ - return m_image->GetWidth() < kDefaultWidth && m_image->GetHeight() < kDefaultHeight; -} - -void QBitmapPreviewDialogImp::setOriginalSize(bool value) -{ - m_showOriginalSize = value; - - refreshData(); - update(); -} - - -const char* QBitmapPreviewDialogImp::GetShowModeDescription(EShowMode eShowMode, [[maybe_unused]] bool bShowInOriginalSize) const -{ - switch (eShowMode) - { - case ESHOW_RGB: - return "RGB"; - case ESHOW_RGB_ALPHA: - return "RGB+A"; - case ESHOW_ALPHA: - return "Alpha"; - case ESHOW_RGBA: - return "RGBA"; - case ESHOW_RGBE: - return "RGBExp"; - } - - return ""; -} - -const char* getHistrogramModeStr(QBitmapPreviewDialogImp::EHistogramMode mode, bool shortName) -{ - switch (mode) - { - case QBitmapPreviewDialogImp::eHistogramMode_Luminosity: - return shortName ? "Lum" : "Luminosity"; - case QBitmapPreviewDialogImp::eHistogramMode_OverlappedRGB: - return shortName ? "Overlap" : "Overlapped RGBA"; - case QBitmapPreviewDialogImp::eHistogramMode_SplitRGB: - return shortName ? "R|G|B" : "Split RGB"; - case QBitmapPreviewDialogImp::eHistogramMode_RedChannel: - return shortName ? "Red" : "Red Channel"; - case QBitmapPreviewDialogImp::eHistogramMode_GreenChannel: - return shortName ? "Green" : "Green Channel"; - case QBitmapPreviewDialogImp::eHistogramMode_BlueChannel: - return shortName ? "Blue" : "Blue Channel"; - case QBitmapPreviewDialogImp::eHistogramMode_AlphaChannel: - return shortName ? "Alpha" : "Alpha Channel"; - default: - break; - } - - return ""; -} - -void QBitmapPreviewDialogImp::refreshData() -{ - // Check if we have some usefull data loaded - if (m_image->GetWidth() * m_image->GetHeight() == 0) - { - return; - } - - int w = m_image->GetWidth(); - int h = m_image->GetHeight(); - - int multiplier = (m_showMode == ESHOW_RGB_ALPHA ? 2 : 1); - int originalW = w * multiplier; - int originalH = h; - - if (!m_showOriginalSize || (w == 0)) - { - w = kDefaultWidth; - } - if (!m_showOriginalSize || (h == 0)) - { - h = kDefaultHeight; - } - - w *= multiplier; - - CImageEx scaledImage; - - if (m_showOriginalSize && (originalW < w)) - { - w = originalW; - } - if (m_showOriginalSize && (originalH < h)) - { - h = originalH; - } - - scaledImage.Allocate(w, h); - - if (m_showMode == ESHOW_RGB_ALPHA) - { - GetIEditor()->GetImageUtil()->ScaleToDoubleFit(*m_image, scaledImage); - } - else - { - GetIEditor()->GetImageUtil()->ScaleToFit(*m_image, scaledImage); - } - - if (m_showMode == ESHOW_RGB || m_showMode == ESHOW_RGBE) - { - scaledImage.FillAlpha(); - } - else if (m_showMode == ESHOW_ALPHA) - { - for (int h2 = 0; h2 < scaledImage.GetHeight(); h2++) - { - for (int w2 = 0; w2 < scaledImage.GetWidth(); w2++) - { - int a = scaledImage.ValueAt(w2, h2) >> 24; - scaledImage.ValueAt(w2, h2) = RGB(a, a, a) | (a << 24); - } - } - } - else if (m_showMode == ESHOW_RGB_ALPHA) - { - int halfWidth = scaledImage.GetWidth() / 2; - for (int h2 = 0; h2 < scaledImage.GetHeight(); h2++) - { - for (int w2 = 0; w2 < halfWidth; w2++) - { - int r = GetRValue(scaledImage.ValueAt(w2, h2)); - int g = GetGValue(scaledImage.ValueAt(w2, h2)); - int b = GetBValue(scaledImage.ValueAt(w2, h2)); - int a = scaledImage.ValueAt(w2, h2) >> 24; - scaledImage.ValueAt(w2, h2) = RGB(r, g, b) | (a << 24); - scaledImage.ValueAt(w2 + halfWidth, h2) = RGB(a, a, a) | (a << 24); - } - } - } - - - setImageRgba8888(scaledImage.GetData(), w, h, ""); - setSize(QString().asprintf("%d x %d", m_image->GetWidth(), m_image->GetHeight())); - setMips(QString().asprintf("%d", m_image->GetNumberOfMipMaps())); - - setFullSize(m_showOriginalSize); - - // Compute histogram - m_histogram.ComputeHistogram((BYTE*)scaledImage.GetData(), w, h, CImageHistogram::eImageFormat_32BPP_RGBA); -} - -void QBitmapPreviewDialogImp::paintEvent(QPaintEvent* e) -{ - QBitmapPreviewDialog::paintEvent(e); - - //if showing original size hide other information so it's easier to see - if (m_showOriginalSize) - { - return; - } - if (m_uiStyle == EUISTYLE_IMAGE_ONLY) - { - return; - } - - QPainter p(this); - QPen pen; - QPainterPath path[4]; - - // Fill background color - QRect histogramRect = getHistogramArea(); - p.fillRect(histogramRect, QColor(255, 255, 255)); - - // Draw borders - pen.setColor(QColor(0, 0, 0)); - p.setPen(pen); - p.drawRect(histogramRect); - - // Draw histogram - - QVector drawChannels; - - switch (m_histrogramMode) - { - case eHistogramMode_Luminosity: - drawChannels.push_back(3); - break; - case eHistogramMode_SplitRGB: - drawChannels.push_back(0); - drawChannels.push_back(1); - drawChannels.push_back(2); - break; - case eHistogramMode_OverlappedRGB: - drawChannels.push_back(0); - drawChannels.push_back(1); - drawChannels.push_back(2); - break; - case eHistogramMode_RedChannel: - drawChannels.push_back(0); - break; - case eHistogramMode_GreenChannel: - drawChannels.push_back(1); - break; - case eHistogramMode_BlueChannel: - drawChannels.push_back(2); - break; - case eHistogramMode_AlphaChannel: - drawChannels.push_back(3); - break; - } - - int graphWidth = qMax(histogramRect.width(), 1); - int graphHeight = qMax(histogramRect.height() - 2, 0); - int graphBottom = histogramRect.bottom() + 1; - int currX[4] = {0, 0, 0, 0}; - int prevX[4] = {0, 0, 0, 0}; - float scale = 0.0f; - static const int numSubGraphs = 3; - const int subGraph = qCeil(graphWidth / numSubGraphs); - - // Fill background for Split RGB histogram - if (m_histrogramMode == eHistogramMode_SplitRGB) - { - const static QColor backgroundColor[numSubGraphs] = - { - QColor(255, 220, 220), - QColor(220, 255, 220), - QColor(220, 220, 255) - }; - - for (int i = 0; i < numSubGraphs; i++) - { - p.fillRect(histogramRect.left() + subGraph * i, - histogramRect.top(), - subGraph + (i == numSubGraphs - 1 ? 1 : 0), - histogramRect.height(), backgroundColor[i]); - } - } - - int lastHeight[CImageHistogram::kNumChannels] = { INT_MAX, INT_MAX, INT_MAX, INT_MAX }; - - for (int x = 0; x < graphWidth; ++x) - { - for (int j = 0; j < drawChannels.size(); j++) - { - const int c = drawChannels[j]; - int& curr_x = currX[c]; - int& prev_x = prevX[c]; - int& last_height = lastHeight[c]; - QPainterPath& curr_path = path[c]; - - - curr_x = histogramRect.left() + x + 1; - int i = static_cast(((float)x / (graphWidth - 1)) * (CImageHistogram::kNumColorLevels - 1)); - if (m_histrogramMode == eHistogramMode_SplitRGB) - { - // Filter out to area which we are interested - const int k = x / subGraph; - if (k != c) - { - continue; - } - - i = qCeil((i - (subGraph * c)) * numSubGraphs); - i = qMin(i, CImageHistogram::kNumColorLevels - 1); - i = qMax(i, 0); - } - - if (m_histrogramMode == eHistogramMode_Luminosity) - { - scale = (float)m_histogram.m_lumCount[i] / m_histogram.m_maxLumCount; - } - else if (m_histogram.m_maxCount[c]) - { - scale = (float)m_histogram.m_count[c][i] / m_histogram.m_maxCount[c]; - } - - int height = static_cast(graphBottom - graphHeight * scale); - if (last_height == INT_MAX) - { - last_height = height; - } - - curr_path.moveTo(prev_x, last_height); - curr_path.lineTo(curr_x, height); - last_height = height; - - if (prev_x == INT_MAX) - { - prev_x = curr_x; - } - - prev_x = curr_x; - } - } - - static const QColor kChannelColor[4] = - { - QColor(255, 0, 0), - QColor(0, 255, 0), - QColor(0, 0, 255), - QColor(120, 120, 120) - }; - - for (int i = 0; i < drawChannels.size(); i++) - { - const int c = drawChannels[i]; - pen.setColor(kChannelColor[c]); - p.setPen(pen); - p.drawPath(path[c]); - } - - // Update histogram info - { - float mean = 0, stdDev = 0, median = 0; - - switch (m_histrogramMode) - { - case eHistogramMode_Luminosity: - case eHistogramMode_SplitRGB: - case eHistogramMode_OverlappedRGB: - mean = m_histogram.m_meanAvg; - stdDev = m_histogram.m_stdDevAvg; - median = m_histogram.m_medianAvg; - break; - case eHistogramMode_RedChannel: - mean = m_histogram.m_mean[0]; - stdDev = m_histogram.m_stdDev[0]; - median = m_histogram.m_median[0]; - break; - case eHistogramMode_GreenChannel: - mean = m_histogram.m_mean[1]; - stdDev = m_histogram.m_stdDev[1]; - median = m_histogram.m_median[1]; - break; - case eHistogramMode_BlueChannel: - mean = m_histogram.m_mean[2]; - stdDev = m_histogram.m_stdDev[2]; - median = m_histogram.m_median[2]; - break; - case eHistogramMode_AlphaChannel: - mean = m_histogram.m_mean[3]; - stdDev = m_histogram.m_stdDev[3]; - median = m_histogram.m_median[3]; - break; - } - QString val; - val.setNum(mean); - setMean(val); - val.setNum(stdDev); - setStdDev(val); - val.setNum(median); - setMedian(val); - } -} - -#include diff --git a/Code/Editor/Controls/QBitmapPreviewDialogImp.h b/Code/Editor/Controls/QBitmapPreviewDialogImp.h deleted file mode 100644 index 63de867e6c..0000000000 --- a/Code/Editor/Controls/QBitmapPreviewDialogImp.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ -#ifndef QBITMAPPREVIEWDIALOG_IMP_H -#define QBITMAPPREVIEWDIALOG_IMP_H - -#if !defined(Q_MOC_RUN) -#include "QBitmapPreviewDialog.h" -#include -#endif - -class CImageEx; - -class QBitmapPreviewDialogImp - : public QBitmapPreviewDialog -{ - Q_OBJECT; -public: - - enum EUIStyle - { - EUISTYLE_IMAGE_ONLY, - EUISTYLE_IMAGE_HISTOGRAM, - EUISTYLE_NumModes - }; - - enum EShowMode - { - ESHOW_RGB = 0, - ESHOW_ALPHA, - ESHOW_RGBA, - ESHOW_RGB_ALPHA, - ESHOW_RGBE, - ESHOW_NumModes, - }; - - enum EHistogramMode - { - eHistogramMode_Luminosity, - eHistogramMode_OverlappedRGB, - eHistogramMode_SplitRGB, - eHistogramMode_RedChannel, - eHistogramMode_GreenChannel, - eHistogramMode_BlueChannel, - eHistogramMode_AlphaChannel, - eHistogramMode_NumModes, - }; - - explicit QBitmapPreviewDialogImp(QWidget* parent = 0); - virtual ~QBitmapPreviewDialogImp(); - - void setImage(const QString path); - - void setShowMode(EShowMode mode); - void toggleShowMode(); - void setUIStyleMode(EUIStyle mode); - const EShowMode& getShowMode() const; - - void setHistogramMode(EHistogramMode mode); - void toggleHistrogramMode(); - const EHistogramMode& getHistogramMode() const; - - void setOriginalSize(bool value); - void toggleOriginalSize(); - - bool isSizeSmallerThanDefault(); - void paintEvent(QPaintEvent* e) override; - -protected: - void refreshData(); - -private: - const char* GetShowModeDescription(EShowMode eShowMode, bool bShowInOriginalSize) const; - -private: - CImageEx* m_image; - QString m_path; - CImageHistogram m_histogram; - bool m_showOriginalSize; - EShowMode m_showMode; - EHistogramMode m_histrogramMode; - EUIStyle m_uiStyle; -}; - -#endif // QBITMAPPREVIEWDIALOG_IMP_H diff --git a/Code/Editor/Controls/QToolTipWidget.cpp b/Code/Editor/Controls/QToolTipWidget.cpp deleted file mode 100644 index 12a66a5d67..0000000000 --- a/Code/Editor/Controls/QToolTipWidget.cpp +++ /dev/null @@ -1,642 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ -#include "EditorDefs.h" - -#include - -#include "QBitmapPreviewDialogImp.h" -#include "qcoreapplication.h" -#include "qguiapplication.h" -#include "qapplication.h" -#include -#include -#include -#include - -void QToolTipWidget::RebuildLayout() -{ - if (m_title != nullptr) - { - m_title->hide(); - } - if (m_content != nullptr) - { - m_content->hide(); - } - if (m_specialContent != nullptr) - { - m_specialContent->hide(); - } - - //empty layout - while (m_layout->count() > 0) - { - m_layout->takeAt(0); - } - qDeleteAll(m_currentShortcuts); - m_currentShortcuts.clear(); - if (m_includeTextureShortcuts) - { - m_currentShortcuts.append(new QLabel(tr("Alt - Alpha"), this)); - m_currentShortcuts.back()->setProperty("tooltipLabel", "Shortcut"); - m_currentShortcuts.append(new QLabel(tr("Shift - RGBA"), this)); - m_currentShortcuts.back()->setProperty("tooltipLabel", "Shortcut"); - } - - if (m_title != nullptr && !m_title->text().isEmpty()) - { - m_layout->addWidget(m_title); - m_title->show(); - } - - for (QLabel* var : m_currentShortcuts) - { - if (var != nullptr) - { - m_layout->addWidget(var); - var->show(); - } - } - if (m_specialContent != nullptr) - { - m_layout->addWidget(m_specialContent); - m_specialContent->show(); - } - if (m_content != nullptr && !m_content->text().isEmpty()) - { - m_layout->addWidget(m_content); - m_content->show(); - } - m_background->adjustSize(); - adjustSize(); -} - -void QToolTipWidget::Hide() -{ - m_currentShortcuts.clear(); - hide(); -} - -void QToolTipWidget::Show(QPoint pos, ArrowDirection dir) -{ - if (!IsValid()) - { - return; - } - m_arrow->m_direction = dir; - pos = AdjustTipPosByArrowSize(pos, dir); - m_normalPos = pos; - move(pos); - RebuildLayout(); - show(); - m_arrow->show(); -} - -void QToolTipWidget::Display(QRect targetRect, ArrowDirection preferredArrowDir) -{ - if (!IsValid()) - { - return; - } - - KeepTipOnScreen(targetRect, preferredArrowDir); - - RebuildLayout(); - show(); - m_arrow->show(); -} - -void QToolTipWidget::TryDisplay(QPoint mousePos, const QRect& rect, [[maybe_unused]] ArrowDirection preferredArrowDir) -{ - if (rect.contains(mousePos)) - { - Display(rect, QToolTipWidget::ArrowDirection::ARROW_RIGHT); - } - else - { - hide(); - } - } - -void QToolTipWidget::TryDisplay(QPoint mousePos, const QWidget* widget, ArrowDirection preferredArrowDir) -{ - const QRect rect(widget->mapToGlobal(QPoint(0,0)), widget->size()); - TryDisplay(mousePos, rect, preferredArrowDir); -} - -void QToolTipWidget::SetTitle(QString title) -{ - if (!title.isEmpty()) - { - m_title->setText(title); - } - m_title->setProperty("tooltipLabel", "Title"); - - setWindowTitle("ToolTip - " + title); -} - -void QToolTipWidget::SetContent(QString content) -{ - m_content->setWordWrap(true); - - m_content->setProperty("tooltipLabel", "Content"); - //line-height is not supported via stylesheet so we use the html rich-text subset in QT for it. - m_content->setText(QString("%1").arg(content)); -} - -void QToolTipWidget::AppendContent(QString content) -{ - m_content->setText(m_content->text() + "\n\n" + content); - update(); - RebuildLayout(); - m_content->update(); - m_content->repaint(); -} - -QToolTipWidget::QToolTipWidget(QWidget* parent) - : QWidget(parent) -{ - m_background = new QWidget(this); - m_background->setProperty("tooltip", "Background"); - m_background->stackUnder(this); - m_title = new QLabel(this); - m_currentShortcuts = QVector(); - m_content = new QLabel(this); - m_specialContent = nullptr; - setWindowTitle("ToolTip"); - setObjectName("ToolTip"); - m_layout = new QVBoxLayout(this); - m_normalPos = QPoint(0, 0); - m_arrow = new QArrow(m_background); - setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint); - m_arrow->setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint); - m_arrow->setAttribute(Qt::WA_TranslucentBackground, true); - m_background->setLayout(m_layout); - m_arrow->setObjectName("ToolTipArrow"); - m_background->setObjectName("ToolTipBackground"); - - //we need a drop shadow for the background - QGraphicsDropShadowEffect* dropShadow = new QGraphicsDropShadowEffect(this); - dropShadow->setBlurRadius(m_shadowRadius); - dropShadow->setColor(Qt::black); - dropShadow->setOffset(0); - dropShadow->setEnabled(true); - m_background->setGraphicsEffect(dropShadow); - //we need a second drop shadow effect for the arrow - dropShadow = new QGraphicsDropShadowEffect(m_arrow); - dropShadow->setBlurRadius(m_shadowRadius); - dropShadow->setColor(Qt::black); - dropShadow->setOffset(0); - dropShadow->setEnabled(true); - m_arrow->setGraphicsEffect(dropShadow); -} - -QToolTipWidget::~QToolTipWidget() -{ -} - -void QToolTipWidget::AddSpecialContent(QString type, QString dataStream) -{ - if (type.isEmpty()) - { - m_includeTextureShortcuts = false; - if (m_specialContent != nullptr) - { - delete m_specialContent; - m_specialContent = nullptr; - } - return; - } - if (type == "TEXTURE") - { - if (m_specialContent == nullptr) - { - QCoreApplication::instance()->installEventFilter(this); //grab the event filter while displaying the advanced texture tooltip - m_specialContent = new QBitmapPreviewDialogImp(this); - } - QString path(dataStream); - qobject_cast(m_specialContent)->setImage(path); - // set default showmode to RGB - qobject_cast(m_specialContent)->setShowMode(QBitmapPreviewDialogImp::EShowMode::ESHOW_RGB); - QString dir = (path.split("/").count() > path.split("\\").count()) ? path.split("/").back() : path.split("\\").back(); - SetTitle(dir); - //always use default size but not image size - qobject_cast(m_specialContent)->setOriginalSize(false); - m_includeTextureShortcuts = true; - } - else if (type == "ADD TO CONTENT") - { - AppendContent(dataStream); - - m_includeTextureShortcuts = false; - if (m_specialContent != nullptr) - { - delete m_specialContent; - m_specialContent = nullptr; - } - } - else if (type == "REPLACE TITLE") - { - SetTitle(dataStream); - m_includeTextureShortcuts = false; - if (m_specialContent != nullptr) - { - delete m_specialContent; - m_specialContent = nullptr; - } - } - else if (type == "REPLACE CONTENT") - { - SetContent(dataStream); - m_includeTextureShortcuts = false; - if (m_specialContent != nullptr) - { - delete m_specialContent; - m_specialContent = nullptr; - } - } - else - { - m_includeTextureShortcuts = false; - if (m_specialContent != nullptr) - { - delete m_specialContent; - m_specialContent = nullptr; - } - return; - } - - m_special = type; -} - - -bool QToolTipWidget::eventFilter(QObject* obj, QEvent* event) -{ - if (event->type() == QEvent::KeyPress) - { - if (m_special == "TEXTURE" && m_specialContent != nullptr) - { - const QKeyEvent* ke = static_cast(event); - Qt::KeyboardModifiers mods = ke->modifiers(); - if (mods & Qt::KeyboardModifier::AltModifier) - { - ((QBitmapPreviewDialogImp*)m_specialContent)->setShowMode(QBitmapPreviewDialogImp::ESHOW_ALPHA); - } - else if (mods & Qt::KeyboardModifier::ShiftModifier && !(mods & Qt::KeyboardModifier::ControlModifier)) - { - ((QBitmapPreviewDialogImp*)m_specialContent)->setShowMode(QBitmapPreviewDialogImp::ESHOW_RGBA); - } - } - } - if (event->type() == QEvent::KeyRelease) - { - if (m_special == "TEXTURE" && m_specialContent != nullptr) - { - const QKeyEvent* ke = static_cast(event); - Qt::KeyboardModifiers mods = ke->modifiers(); - if (!(mods& Qt::KeyboardModifier::AltModifier) && !(mods & Qt::KeyboardModifier::ShiftModifier)) - { - ((QBitmapPreviewDialogImp*)m_specialContent)->setShowMode(QBitmapPreviewDialogImp::ESHOW_RGB); - } - } - } - return QWidget::eventFilter(obj, event); -} - -void QToolTipWidget::hideEvent(QHideEvent* event) -{ - QWidget::hideEvent(event); - m_arrow->hide(); -} - -void QToolTipWidget::UpdateOptionalData(QString optionalData) -{ - AddSpecialContent(m_special, optionalData); -} - - - -QPoint QToolTipWidget::AdjustTipPosByArrowSize(QPoint pos, ArrowDirection dir) -{ - switch (dir) - { - case QToolTipWidget::ArrowDirection::ARROW_UP: - { - m_arrow->move(pos); - pos.setY(pos.y() + 10); - m_arrow->setFixedSize(20, 10); - pos -= QPoint(m_shadowRadius, m_shadowRadius); - break; - } - case QToolTipWidget::ArrowDirection::ARROW_LEFT: - { - m_arrow->move(pos); - pos.setX(pos.x() + 10); - m_arrow->setFixedSize(10, 20); - pos -= QPoint(m_shadowRadius, m_shadowRadius); - break; - } - case QToolTipWidget::ArrowDirection::ARROW_RIGHT: - { - pos.setX(pos.x() - 10); - m_arrow->move(QPoint(pos.x() + width(), pos.y())); - m_arrow->setFixedSize(10, 20); - pos -= QPoint(-m_shadowRadius, m_shadowRadius); - break; - } - case QToolTipWidget::ArrowDirection::ARROW_DOWN: - { - pos.setY(pos.y() - 10); - m_arrow->move(QPoint(pos.x(), pos.y() + height())); - m_arrow->setFixedSize(20, 10); - pos -= QPoint(m_shadowRadius, -m_shadowRadius); - break; - } - default: - m_arrow->move(-10, -10); - break; - } - return pos; -} - -bool QToolTipWidget::IsValid() -{ - if (m_title->text().isEmpty() || - (m_content->text().isEmpty() && m_specialContent == nullptr)) - { - return false; - } - return true; -} - -void QToolTipWidget::KeepTipOnScreen(QRect targetRect, ArrowDirection preferredArrowDir) -{ - QRect desktop = QApplication::desktop()->availableGeometry(this); - - if (this->isHidden()) - { - setAttribute(Qt::WA_DontShowOnScreen, true); - Show(QPoint(0, 0), preferredArrowDir); - hide(); - setAttribute(Qt::WA_DontShowOnScreen, false); - } - //else assume the size is right - - //calculate initial rect - QRect tipRect = QRect(0, 0, 0, 0); - switch (preferredArrowDir) - { - case QToolTipWidget::ArrowDirection::ARROW_UP: - { - //tip is below the widget with a left alignment - tipRect.setTopLeft(AdjustTipPosByArrowSize(targetRect.bottomLeft(), preferredArrowDir)); - break; - } - case QToolTipWidget::ArrowDirection::ARROW_LEFT: - { - //tip is on the right with the top being even - tipRect.setTopLeft(AdjustTipPosByArrowSize(targetRect.topRight(), preferredArrowDir)); - break; - } - case QToolTipWidget::ArrowDirection::ARROW_RIGHT: - { - //tip is on the left with the top being even - tipRect.setY(targetRect.top()); - tipRect.setX(targetRect.left() - width()); - tipRect.setTopLeft(AdjustTipPosByArrowSize(tipRect.topLeft(), preferredArrowDir)); - break; - } - case QToolTipWidget::ArrowDirection::ARROW_DOWN: - { - //tip is above the widget with a left alignment - tipRect.setX(targetRect.left()); - tipRect.setY(targetRect.top() - height()); - tipRect.setTopLeft(AdjustTipPosByArrowSize(tipRect.topLeft(), preferredArrowDir)); - break; - } - default: - { - //tip is on the right with the top being even - preferredArrowDir = QToolTipWidget::ArrowDirection::ARROW_LEFT; - tipRect.setTopLeft(AdjustTipPosByArrowSize(targetRect.topRight(), QToolTipWidget::ArrowDirection::ARROW_LEFT)); - break; - } - } - tipRect.setSize(size()); - - //FixPositioning - if (preferredArrowDir == ArrowDirection::ARROW_LEFT || preferredArrowDir == ArrowDirection::ARROW_RIGHT) - { - if (tipRect.left() <= desktop.left()) - { - m_arrow->m_direction = ArrowDirection::ARROW_LEFT; - tipRect.setTopLeft(AdjustTipPosByArrowSize(targetRect.topRight(), m_arrow->m_direction)); - } - else if (tipRect.right() >= desktop.right()) - { - m_arrow->m_direction = ArrowDirection::ARROW_RIGHT; - tipRect.setLeft(targetRect.left() - width()); - tipRect.setTopLeft(AdjustTipPosByArrowSize(tipRect.topLeft(), m_arrow->m_direction)); - } - } - else if (preferredArrowDir == ArrowDirection::ARROW_UP || preferredArrowDir == ArrowDirection::ARROW_DOWN) - { - if (tipRect.top() <= desktop.top()) - { - m_arrow->m_direction = ArrowDirection::ARROW_UP; - tipRect.setTopLeft(AdjustTipPosByArrowSize(targetRect.bottomLeft(), m_arrow->m_direction)); - } - else if (tipRect.bottom() >= desktop.bottom()) - { - m_arrow->m_direction = ArrowDirection::ARROW_DOWN; - tipRect.setY(targetRect.top() - height()); - tipRect.setTopLeft(AdjustTipPosByArrowSize(tipRect.topLeft(), m_arrow->m_direction)); - } - } - - //Nudge tip without arrow - if (preferredArrowDir == ArrowDirection::ARROW_UP || preferredArrowDir == ArrowDirection::ARROW_DOWN) - { - if (tipRect.left() <= desktop.left()) - { - tipRect.setLeft(desktop.left()); - } - else if (tipRect.right() >= desktop.right()) - { - tipRect.setLeft(desktop.right() - width()); - } - } - else if (preferredArrowDir == ArrowDirection::ARROW_RIGHT || preferredArrowDir == ArrowDirection::ARROW_LEFT) - { - if (tipRect.top() <= desktop.top()) - { - tipRect.setTop(desktop.top()); - } - else if (tipRect.bottom() >= desktop.bottom()) - { - tipRect.setTop(desktop.bottom() - height()); - } - } - - - m_normalPos = tipRect.topLeft(); - move(m_normalPos); -} - -QPolygonF QToolTipWidget::QArrow::CreateArrow() -{ - QVector vertex; - //3 points in triangle - vertex.reserve(3); - //all magic number below are given in order to draw smooth transitions between tooltip and arrow - if (m_direction == ArrowDirection::ARROW_UP) - { - vertex.push_back(QPointF(10, 1)); - vertex.push_back(QPointF(19, 10)); - vertex.push_back(QPointF(0, 10)); - } - else if (m_direction == ArrowDirection::ARROW_RIGHT) - { - vertex.push_back(QPointF(9, 10)); - vertex.push_back(QPointF(0, 19)); - vertex.push_back(QPointF(0, 1)); - } - else if (m_direction == ArrowDirection::ARROW_LEFT) - { - vertex.push_back(QPointF(1, 10)); - vertex.push_back(QPointF(10, 19)); - vertex.push_back(QPointF(10, 0)); - } - else //ArrowDirection::ARROW_DOWN - { - vertex.push_back(QPointF(10, 10)); - vertex.push_back(QPointF(19, 0)); - vertex.push_back(QPointF(0, 0)); - } - return QPolygonF(vertex); -} - -void QToolTipWidget::QArrow::paintEvent([[maybe_unused]] QPaintEvent* event) -{ - QColor color(255, 255, 255, 255); - QPainter painter(this); - painter.fillRect(rect(), Qt::transparent); //force transparency - painter.setRenderHint(QPainter::Antialiasing, false); - painter.setBrush(color); - painter.setPen(Qt::NoPen); - painter.drawPolygon(CreateArrow()); - //painter.setRenderHint(QPainter::Antialiasing, false); -} - -QToolTipWrapper::QToolTipWrapper(QWidget* parent) - : QObject(parent) -{ -} - -void QToolTipWrapper::SetTitle(QString title) -{ - m_title = title; -} - -void QToolTipWrapper::SetContent(QString content) -{ - AddSpecialContent("REPLACE CONTENT", content); -} - -void QToolTipWrapper::AppendContent(QString content) -{ - AddSpecialContent("ADD TO CONTENT", content); -} - -void QToolTipWrapper::AddSpecialContent(QString type, QString dataStream) -{ - if (type == "REPLACE CONTENT") - { - m_contentOperations.clear(); - } - m_contentOperations.push_back({type, dataStream}); -} - -void QToolTipWrapper::UpdateOptionalData(QString optionalData) -{ - m_contentOperations.push_back({"UPDATE OPTIONAL", optionalData}); -} - -void QToolTipWrapper::Display(QRect targetRect, QToolTipWidget::ArrowDirection preferredArrowDir) -{ - GetOrCreateToolTip()->Display(targetRect, preferredArrowDir); -} - -void QToolTipWrapper::TryDisplay(QPoint mousePos, const QWidget * widget, QToolTipWidget::ArrowDirection preferredArrowDir) -{ - GetOrCreateToolTip()->TryDisplay(mousePos, widget, preferredArrowDir); -} - -void QToolTipWrapper::TryDisplay(QPoint mousePos, const QRect & widget, QToolTipWidget::ArrowDirection preferredArrowDir) -{ - GetOrCreateToolTip()->TryDisplay(mousePos, widget, preferredArrowDir); -} - -void QToolTipWrapper::hide() -{ - DestroyToolTip(); -} - -void QToolTipWrapper::show() -{ - GetOrCreateToolTip()->show(); -} - -bool QToolTipWrapper::isVisible() const -{ - return m_actualTooltip && m_actualTooltip->isVisible(); -} - -void QToolTipWrapper::update() -{ - if (m_actualTooltip) - { - m_actualTooltip->update(); - } -} - -void QToolTipWrapper::ReplayContentOperations(QToolTipWidget* tooltipWidget) -{ - tooltipWidget->SetTitle(m_title); - for (const auto& operation : m_contentOperations) - { - if (operation.first == "UPDATE OPTIONAL") - { - tooltipWidget->UpdateOptionalData(operation.second); - } - else - { - tooltipWidget->AddSpecialContent(operation.first, operation.second); - } - } -} - -QToolTipWidget * QToolTipWrapper::GetOrCreateToolTip() -{ - if (!m_actualTooltip) - { - QToolTipWidget* tooltipWidget = new QToolTipWidget(static_cast(parent())); - tooltipWidget->setAttribute(Qt::WA_DeleteOnClose); - ReplayContentOperations(tooltipWidget); - m_actualTooltip = tooltipWidget; - } - return m_actualTooltip.data(); -} - -void QToolTipWrapper::DestroyToolTip() -{ - if (m_actualTooltip) - { - m_actualTooltip->deleteLater(); - } -} diff --git a/Code/Editor/Controls/QToolTipWidget.h b/Code/Editor/Controls/QToolTipWidget.h deleted file mode 100644 index 813f2a75fb..0000000000 --- a/Code/Editor/Controls/QToolTipWidget.h +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ -#ifndef QToolTipWidget_h__ -#define QToolTipWidget_h__ - -#include "EditorCoreAPI.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -class IQToolTip -{ -public: - virtual void SetTitle(QString title) = 0; - virtual void SetContent(QString content) = 0; - virtual void AppendContent(QString content) = 0; - virtual void AddSpecialContent(QString type, QString dataStream) = 0; - virtual void UpdateOptionalData(QString optionalData) = 0; -}; - -class EDITOR_CORE_API QToolTipWidget - : public QWidget - , public IQToolTip -{ -public: - enum class ArrowDirection - { - ARROW_UP, - ARROW_LEFT, - ARROW_RIGHT, - ARROW_DOWN - }; - class QArrow - : public QWidget - { - public: - ArrowDirection m_direction; - QPoint m_pos; - QArrow(QWidget* parent) - : QWidget(parent){ setWindowFlags(Qt::ToolTip); } - virtual ~QArrow(){} - - QPolygonF CreateArrow(); - virtual void paintEvent(QPaintEvent*) override; - }; - QToolTipWidget(QWidget* parent); - ~QToolTipWidget(); - void SetTitle(QString title) override; - void SetContent(QString content) override; - void AppendContent(QString content) override; - void AddSpecialContent(QString type, QString dataStream) override; - void UpdateOptionalData(QString optionalData) override; - void Display(QRect targetRect, ArrowDirection preferredArrowDir); - - //! Displays the tooltip on the given widget, only if the mouse is over it. - void TryDisplay(QPoint mousePos, const QWidget* widget, ArrowDirection preferredArrowDir); - - //! Displays the tooltip on the given rect, only if the mouse is over it. - void TryDisplay(QPoint mousePos, const QRect& widget, ArrowDirection preferredArrowDir); - - void Hide(); - -protected: - void Show(QPoint pos, ArrowDirection dir); - bool IsValid(); - void KeepTipOnScreen(QRect targetRect, ArrowDirection preferredArrowDir); - QPoint AdjustTipPosByArrowSize(QPoint pos, ArrowDirection dir); - virtual bool eventFilter(QObject* obj, QEvent* event) override; - void RebuildLayout(); - virtual void hideEvent(QHideEvent*) override; - - QLabel* m_title; - AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING - QVector m_currentShortcuts; - AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING - //can be anything from QLabel to QBitMapPreviewDialog - //must allow movement, and show/hide calls - QLabel* m_content; - QWidget* m_specialContent; - QWidget* m_background; - QVBoxLayout* m_layout; - QString m_special; - QPoint m_normalPos; - QArrow* m_arrow; - const int m_shadowRadius = 5; - bool m_includeTextureShortcuts; //added since Qt does not support modifier only shortcuts -}; - -// HACK: The EditorUI_QT classes all were keeping persistent references to QToolTipWidgets around -// This led to many, many top-level widget creations, which led to many platform-side window allocations -// which led to crashes in Qt5.15. As this is legacy code, this is a drop-in replacement that only -// allocates the actual QToolTipWidget (and thus platform window) while the tooltip is visible -class EDITOR_CORE_API QToolTipWrapper - : public QObject - , public IQToolTip -{ -public: - QToolTipWrapper(QWidget* parent); - - void SetTitle(QString title) override; - void SetContent(QString content) override; - void AppendContent(QString content) override; - void AddSpecialContent(QString type, QString dataStream) override; - void UpdateOptionalData(QString optionalData) override; - - void Display(QRect targetRect, QToolTipWidget::ArrowDirection preferredArrowDir); - void TryDisplay(QPoint mousePos, const QWidget* widget, QToolTipWidget::ArrowDirection preferredArrowDir); - void TryDisplay(QPoint mousePos, const QRect& widget, QToolTipWidget::ArrowDirection preferredArrowDir); - void hide(); - void show(); - bool isVisible() const; - void update(); - void repaint(){update();} //Things really shouldn't be calling repaint on these... - - void Hide(){hide();} - void close(){hide();} - -private: - void ReplayContentOperations(QToolTipWidget* tooltipWidget); - - QToolTipWidget* GetOrCreateToolTip(); - void DestroyToolTip(); - - AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // conditional expression is constant, needs to have dll-interface to be used by clients of class 'AzQtComponents::FilteredSearchWidget' - QPointer m_actualTooltip; - AZ_POP_DISABLE_WARNING - - QString m_title; - AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // conditional expression is constant, needs to have dll-interface to be used by clients of class 'AzQtComponents::FilteredSearchWidget' - QVector> m_contentOperations; - AZ_POP_DISABLE_WARNING -}; - - -#endif // QToolTipWidget_h__ diff --git a/Code/Editor/Controls/ReflectedPropertyControl/PropertyCtrl.cpp b/Code/Editor/Controls/ReflectedPropertyControl/PropertyCtrl.cpp index 68c4acb95e..50d053ab9e 100644 --- a/Code/Editor/Controls/ReflectedPropertyControl/PropertyCtrl.cpp +++ b/Code/Editor/Controls/ReflectedPropertyControl/PropertyCtrl.cpp @@ -10,7 +10,6 @@ // Editor #include "PropertyCtrl.h" -#include "PropertyResourceCtrl.h" #include "PropertyGenericCtrl.h" #include "PropertyMiscCtrl.h" #include "PropertyMotionCtrl.h" @@ -21,7 +20,6 @@ void RegisterReflectedVarHandlers() if (!registered) { registered = true; - EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew FileResourceSelectorWidgetHandler()); EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew SequencePropertyHandler()); EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew SequenceIdPropertyHandler()); EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew LocalStringPropertyHandler()); diff --git a/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.cpp b/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.cpp deleted file mode 100644 index d26e978ae8..0000000000 --- a/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.cpp +++ /dev/null @@ -1,383 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#include "EditorDefs.h" - -#include "PropertyResourceCtrl.h" - -// Qt -#include -#include - -// AzToolsFramework -#include -#include -#include - -// Editor -#include "Controls/QToolTipWidget.h" -#include "Controls/BitmapToolTip.h" - - -BrowseButton::BrowseButton(PropertyType type, QWidget* parent /*= nullptr*/) - : QToolButton(parent) - , m_propertyType(type) -{ - setAutoRaise(true); - setIcon(QIcon(QStringLiteral(":/stylesheet/img/UI20/browse-edit.svg"))); - connect(this, &QAbstractButton::clicked, this, &BrowseButton::OnClicked); -} - -void BrowseButton::SetPathAndEmit(const QString& path) -{ - //only emit if path changes. Old property control - if (path != m_path) - { - m_path = path; - emit PathChanged(m_path); - } -} - -class FileBrowseButton - : public BrowseButton -{ -public: - AZ_CLASS_ALLOCATOR(FileBrowseButton, AZ::SystemAllocator, 0); - FileBrowseButton(PropertyType type, QWidget* pParent = nullptr) - : BrowseButton(type, pParent) - { - setToolTip("Browse..."); - } - -private: - void OnClicked() override - { - QString tempValue(""); - if (!m_path.isEmpty() && !Path::GetExt(m_path).isEmpty()) - { - tempValue = m_path; - } - - AssetSelectionModel selection; - - if (m_propertyType == ePropertyTexture) - { - // Filters for texture. - selection = AssetSelectionModel::AssetGroupSelection("Texture"); - } - else - { - return; - } - - AzToolsFramework::EditorRequests::Bus::Broadcast(&AzToolsFramework::EditorRequests::BrowseForAssets, selection); - if (selection.IsValid()) - { - QString newPath = Path::FullPathToGamePath(selection.GetResult()->GetFullPath().c_str()).c_str(); - - switch (m_propertyType) - { - case ePropertyTexture: - newPath.replace("\\\\", "/"); - if (newPath.size() > MAX_PATH) - { - newPath.resize(MAX_PATH); - } - } - - SetPathAndEmit(newPath); - } - } -}; - -class AudioControlSelectorButton - : public BrowseButton -{ -public: - AZ_CLASS_ALLOCATOR(AudioControlSelectorButton, AZ::SystemAllocator, 0); - - AudioControlSelectorButton(PropertyType type, QWidget* pParent = nullptr) - : BrowseButton(type, pParent) - { - setToolTip(tr("Select Audio Control")); - } - -private: - void OnClicked() override - { - AZStd::string resourceResult; - auto ConvertLegacyAudioPropertyType = [](const PropertyType type) -> AzToolsFramework::AudioPropertyType - { - switch (type) - { - case ePropertyAudioTrigger: - return AzToolsFramework::AudioPropertyType::Trigger; - case ePropertyAudioRTPC: - return AzToolsFramework::AudioPropertyType::Rtpc; - case ePropertyAudioSwitch: - return AzToolsFramework::AudioPropertyType::Switch; - case ePropertyAudioSwitchState: - return AzToolsFramework::AudioPropertyType::SwitchState; - case ePropertyAudioEnvironment: - return AzToolsFramework::AudioPropertyType::Environment; - case ePropertyAudioPreloadRequest: - return AzToolsFramework::AudioPropertyType::Preload; - default: - return AzToolsFramework::AudioPropertyType::NumTypes; - } - }; - - auto propType = ConvertLegacyAudioPropertyType(m_propertyType); - if (propType != AzToolsFramework::AudioPropertyType::NumTypes) - { - AzToolsFramework::AudioControlSelectorRequestBus::EventResult( - resourceResult, propType, &AzToolsFramework::AudioControlSelectorRequestBus::Events::SelectResource, - AZStd::string_view{ m_path.toUtf8().constData() }); - SetPathAndEmit(QString{ resourceResult.c_str() }); - } - } -}; - -class TextureEditButton - : public BrowseButton -{ -public: - AZ_CLASS_ALLOCATOR(TextureEditButton, AZ::SystemAllocator, 0); - TextureEditButton(QWidget* pParent = nullptr) - : BrowseButton(ePropertyTexture, pParent) - { - setIcon(QIcon(QStringLiteral(":/stylesheet/img/UI20/open-in-internal-app.svg"))); - setToolTip(tr("Launch default editor")); - } - -private: - void OnClicked() override - { - CFileUtil::EditTextureFile(m_path.toUtf8().data(), true); - } -}; - -FileResourceSelectorWidget::FileResourceSelectorWidget(QWidget* pParent /*= nullptr*/) - : QWidget(pParent) - , m_propertyType(ePropertyInvalid) - , m_tooltip(nullptr) -{ - m_pathEdit = new QLineEdit; - m_mainLayout = new QHBoxLayout(this); - m_mainLayout->addWidget(m_pathEdit, 1); - - m_mainLayout->setContentsMargins(0, 0, 0, 0); - -// KDAB just ported the MFC texture preview tooltip, but looks like Amazon added their own. Not sure which to use. -// To switch to Amazon QToolTipWidget, remove FileResourceSelectorWidget::event and m_previewTooltip -#ifdef USE_QTOOLTIPWIDGET - m_tooltip = new QToolTipWidget(this); - - installEventFilter(this); -#endif - connect(m_pathEdit, &QLineEdit::editingFinished, this, [this]() { OnPathChanged(m_pathEdit->text()); }); -} - -bool FileResourceSelectorWidget::eventFilter([[maybe_unused]] QObject* obj, QEvent* event) -{ - if (m_propertyType == ePropertyTexture) - { - if (event->type() == QEvent::ToolTip) - { - QHelpEvent* e = (QHelpEvent*)event; - - m_tooltip->AddSpecialContent("TEXTURE", m_path); - m_tooltip->TryDisplay(e->globalPos(), m_pathEdit, QToolTipWidget::ArrowDirection::ARROW_RIGHT); - - return true; - } - - if (event->type() == QEvent::Leave) - { - m_tooltip->hide(); - } - } - - return false; -} - -void FileResourceSelectorWidget::SetPropertyType(PropertyType type) -{ - if (m_propertyType == type) - { - return; - } - - //if the property type changed for some reason, delete all the existing widgets - if (!m_buttons.isEmpty()) - { - qDeleteAll(m_buttons.begin(), m_buttons.end()); - m_buttons.clear(); - } - - m_previewToolTip.reset(); - m_propertyType = type; - - switch (type) - { - case ePropertyTexture: - AddButton(new FileBrowseButton(type)); - AddButton(new TextureEditButton); - m_previewToolTip.reset(new CBitmapToolTip); - break; - case ePropertyAudioTrigger: - case ePropertyAudioSwitch: - case ePropertyAudioSwitchState: - case ePropertyAudioRTPC: - case ePropertyAudioEnvironment: - case ePropertyAudioPreloadRequest: - AddButton(new AudioControlSelectorButton(type)); - break; - default: - break; - } - - m_mainLayout->invalidate(); -} - -void FileResourceSelectorWidget::AddButton(BrowseButton* button) -{ - m_mainLayout->addWidget(button); - m_buttons.push_back(button); - connect(button, &BrowseButton::PathChanged, this, &FileResourceSelectorWidget::OnPathChanged); -} - -void FileResourceSelectorWidget::OnPathChanged(const QString& path) -{ - bool changed = SetPath(path); - if (changed) - { - emit PathChanged(m_path); - } -} - -bool FileResourceSelectorWidget::SetPath(const QString& path) -{ - bool changed = false; - - const QString newPath = path.toLower(); - if (m_path != newPath) - { - m_path = newPath; - UpdateWidgets(); - - changed = true; - } - - return changed; -} - - -void FileResourceSelectorWidget::UpdateWidgets() -{ - m_pathEdit->setText(m_path); - - foreach(BrowseButton * button, m_buttons) - { - button->SetPath(m_path); - } - - if (m_previewToolTip) - { - m_previewToolTip->SetTool(this, rect()); - } -} - -QString FileResourceSelectorWidget::GetPath() const -{ - return m_path; -} - - - -QWidget* FileResourceSelectorWidget::GetLastInTabOrder() -{ - return m_buttons.empty() ? nullptr : m_buttons.last(); -} - -QWidget* FileResourceSelectorWidget::GetFirstInTabOrder() -{ - return m_buttons.empty() ? nullptr : m_buttons.first(); -} - -void FileResourceSelectorWidget::UpdateTabOrder() -{ - if (m_buttons.count() >= 2) - { - for (int i = 0; i < m_buttons.count() - 1; ++i) - { - setTabOrder(m_buttons[i], m_buttons[i + 1]); - } - } -} - -bool FileResourceSelectorWidget::event(QEvent* event) -{ - if (event->type() == QEvent::ToolTip && m_previewToolTip && !m_previewToolTip->isVisible()) - { - if (!m_path.isEmpty()) - { - m_previewToolTip->LoadImage(m_path); - m_previewToolTip->setVisible(true); - } - event->accept(); - return true; - } - - if (event->type() == QEvent::Resize && m_previewToolTip) - { - m_previewToolTip->SetTool(this, rect()); - } - - return QWidget::event(event); -} - -QWidget* FileResourceSelectorWidgetHandler::CreateGUI(QWidget* pParent) -{ - FileResourceSelectorWidget* newCtrl = aznew FileResourceSelectorWidget(pParent); - connect(newCtrl, &FileResourceSelectorWidget::PathChanged, newCtrl, [newCtrl]() - { - EBUS_EVENT(AzToolsFramework::PropertyEditorGUIMessages::Bus, RequestWrite, newCtrl); - }); - return newCtrl; -} - -void FileResourceSelectorWidgetHandler::ConsumeAttribute(FileResourceSelectorWidget* GUI, AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName) -{ - Q_UNUSED(GUI); - Q_UNUSED(attrib); - Q_UNUSED(attrValue); - Q_UNUSED(debugName); -} - -void FileResourceSelectorWidgetHandler::WriteGUIValuesIntoProperty(size_t index, FileResourceSelectorWidget* GUI, property_t& instance, AzToolsFramework::InstanceDataNode* node) -{ - Q_UNUSED(index); - Q_UNUSED(node); - CReflectedVarResource val = instance; - val.m_propertyType = GUI->GetPropertyType(); - val.m_path = GUI->GetPath().toUtf8().data(); - instance = static_cast(val); -} - -bool FileResourceSelectorWidgetHandler::ReadValuesIntoGUI(size_t index, FileResourceSelectorWidget* GUI, const property_t& instance, AzToolsFramework::InstanceDataNode* node) -{ - Q_UNUSED(index); - Q_UNUSED(node); - CReflectedVarResource val = instance; - GUI->SetPropertyType(val.m_propertyType); - GUI->SetPath(val.m_path.c_str()); - return false; -} - -#include diff --git a/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.h b/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.h deleted file mode 100644 index 087ee9f1db..0000000000 --- a/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.h +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#ifndef CRYINCLUDE_EDITOR_UTILS_PROPERTYRESOURCECTRL_H -#define CRYINCLUDE_EDITOR_UTILS_PROPERTYRESOURCECTRL_H -#pragma once - -#if !defined(Q_MOC_RUN) -#include -#include -#include -#include "ReflectedVar.h" -#include "Util/VariablePropertyType.h" -#include -#include -#include -#endif - -class QLineEdit; -class QHBoxLayout; -class CBitmapToolTip; -class QToolTipWidget; - -class BrowseButton - : public QToolButton -{ - Q_OBJECT -public: - AZ_CLASS_ALLOCATOR(BrowseButton, AZ::SystemAllocator, 0); - - BrowseButton(PropertyType type, QWidget* parent = nullptr); - - void SetPath(const QString& path) { m_path = path; } - QString GetPath() const { return m_path; } - - PropertyType GetPropertyType() const {return m_propertyType; } - -signals: - void PathChanged(const QString& path); - -protected: - void SetPathAndEmit(const QString& path); - virtual void OnClicked() = 0; - - PropertyType m_propertyType; - QString m_path; -}; - -class FileResourceSelectorWidget - : public QWidget -{ - Q_OBJECT -public: - AZ_CLASS_ALLOCATOR(FileResourceSelectorWidget, AZ::SystemAllocator, 0); - FileResourceSelectorWidget(QWidget* pParent = nullptr); - - bool SetPath(const QString& path); - QString GetPath() const; - void SetPropertyType(PropertyType type); - PropertyType GetPropertyType() const { return m_propertyType; } - - QWidget* GetFirstInTabOrder(); - QWidget* GetLastInTabOrder(); - void UpdateTabOrder(); - - bool eventFilter(QObject* obj, QEvent* event) override; - -signals: - void PathChanged(const QString& path); - -protected: - bool event(QEvent* event) override; - -private: - void OnAssignClicked(); - void OnMaterialClicked(); - - void UpdateWidgets(); - void AddButton(BrowseButton* button); - void OnPathChanged(const QString& path); - -private: - QLineEdit* m_pathEdit; - PropertyType m_propertyType; - QString m_path; - - QHBoxLayout* m_mainLayout; - QVector m_buttons; - QScopedPointer m_previewToolTip; - QToolTipWidget* m_tooltip; -}; - -class FileResourceSelectorWidgetHandler - : QObject - , public AzToolsFramework::PropertyHandler < CReflectedVarResource, FileResourceSelectorWidget > -{ - Q_OBJECT -public: - AZ_CLASS_ALLOCATOR(FileResourceSelectorWidgetHandler, AZ::SystemAllocator, 0); - - virtual AZ::u32 GetHandlerName(void) const override { return AZ_CRC("Resource", 0xbc91f416); } - virtual bool IsDefaultHandler() const override { return true; } - virtual QWidget* GetFirstInTabOrder(FileResourceSelectorWidget* widget) override { return widget->GetFirstInTabOrder(); } - virtual QWidget* GetLastInTabOrder(FileResourceSelectorWidget* widget) override { return widget->GetLastInTabOrder(); } - virtual void UpdateWidgetInternalTabbing(FileResourceSelectorWidget* widget) override { widget->UpdateTabOrder(); } - - virtual QWidget* CreateGUI(QWidget* pParent) override; - virtual void ConsumeAttribute(FileResourceSelectorWidget* GUI, AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName) override; - virtual void WriteGUIValuesIntoProperty(size_t index, FileResourceSelectorWidget* GUI, property_t& instance, AzToolsFramework::InstanceDataNode* node) override; - virtual bool ReadValuesIntoGUI(size_t index, FileResourceSelectorWidget* GUI, const property_t& instance, AzToolsFramework::InstanceDataNode* node) override; -}; - -#endif // CRYINCLUDE_EDITOR_UTILS_PROPERTYRESOURCECTRL_H diff --git a/Code/Editor/Controls/ReflectedPropertyControl/ReflectedVar.cpp b/Code/Editor/Controls/ReflectedPropertyControl/ReflectedVar.cpp index 263c17a8bb..268faaa335 100644 --- a/Code/Editor/Controls/ReflectedPropertyControl/ReflectedVar.cpp +++ b/Code/Editor/Controls/ReflectedPropertyControl/ReflectedVar.cpp @@ -70,12 +70,6 @@ void ReflectedVarInit::setupReflection(AZ::SerializeContext* serializeContext) AZ::EditContext* ec = serializeContext->GetEditContext(); if (ec) { - ec->Class< CReflectedVarResource >("VarResource", "Resource") - ->ClassElement(AZ::Edit::ClassElements::EditorData, "") - ->Attribute(AZ::Edit::Attributes::NameLabelOverride, &CReflectedVarResource::varName) - ->Attribute(AZ::Edit::Attributes::DescriptionTextOverride, &CReflectedVarResource::description) - ; - ec->Class< CReflectedVarUser >("VarUser", "") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::NameLabelOverride, &CReflectedVarUser::varName) diff --git a/Code/Editor/editor_core_files.cmake b/Code/Editor/editor_core_files.cmake index 4cf092e774..447d763a63 100644 --- a/Code/Editor/editor_core_files.cmake +++ b/Code/Editor/editor_core_files.cmake @@ -22,13 +22,6 @@ set(FILES Controls/ReflectedPropertyControl/ReflectedVar.h Controls/ReflectedPropertyControl/ReflectedVarWrapper.cpp Controls/ReflectedPropertyControl/ReflectedVarWrapper.h - Controls/QBitmapPreviewDialog.cpp - Controls/QBitmapPreviewDialog.h - Controls/QBitmapPreviewDialog.ui - Controls/QBitmapPreviewDialogImp.cpp - Controls/QBitmapPreviewDialogImp.h - Controls/QToolTipWidget.h - Controls/QToolTipWidget.cpp UsedResources.cpp LyViewPaneNames.h QtViewPaneManager.cpp diff --git a/Code/Editor/editor_lib_files.cmake b/Code/Editor/editor_lib_files.cmake index 7908d1e720..53cfe243b1 100644 --- a/Code/Editor/editor_lib_files.cmake +++ b/Code/Editor/editor_lib_files.cmake @@ -313,8 +313,6 @@ set(FILES AssetEditor/AssetEditorWindow.ui Commands/CommandManager.cpp Commands/CommandManager.h - Controls/BitmapToolTip.cpp - Controls/BitmapToolTip.h Controls/ConsoleSCB.cpp Controls/ConsoleSCB.h Controls/ConsoleSCB.ui @@ -336,8 +334,6 @@ set(FILES Controls/ReflectedPropertyControl/PropertyMiscCtrl.h Controls/ReflectedPropertyControl/PropertyMotionCtrl.cpp Controls/ReflectedPropertyControl/PropertyMotionCtrl.h - Controls/ReflectedPropertyControl/PropertyResourceCtrl.cpp - Controls/ReflectedPropertyControl/PropertyResourceCtrl.h Controls/ReflectedPropertyControl/PropertyCtrl.cpp Controls/ReflectedPropertyControl/PropertyCtrl.h MainStatusBar.cpp From b455b915a87792dc040b8ec154455d6bbe57ac31 Mon Sep 17 00:00:00 2001 From: Ken Pruiksma Date: Mon, 31 Jan 2022 16:10:03 -0600 Subject: [PATCH 42/53] Making terrain query resolution a single float instead of a Vector2 (#7186) * Making terrain query resolution a single float instead of a Vector2 Signed-off-by: Ken Pruiksma * Keeping the concept of different x/y step sizes in region queries since that may be useful and is separate from query resolution. Also keeping the concept of different x/y step sizes in physics since that's independent of the terrain gem. Signed-off-by: Ken Pruiksma * Formatting cleanups Signed-off-by: Ken Pruiksma * A few more minor cleanups Signed-off-by: Ken Pruiksma * Added support to convert serialized Vector2 query resolution to a single float. Signed-off-by: Ken Pruiksma * Switch ray intersection check back to using separate values for x and y resolution Signed-off-by: Ken Pruiksma * Fixing new unit tests added to use float query resolution. Signed-off-by: Ken Pruiksma * Updating automated test Signed-off-by: Ken Pruiksma --- .../Terrain_World_ConfigurationWorks.py | 5 +- Code/Editor/GameExporter.cpp | 4 +- .../Physics/ShapeConfiguration.cpp | 2 +- .../AzFramework/Physics/ShapeConfiguration.h | 4 +- .../Terrain/TerrainDataRequestBus.h | 4 +- .../Mocks/Terrain/MockTerrainDataRequestBus.h | 4 +- Gems/PhysX/Code/Source/Utils.cpp | 2 +- .../TerrainHeightGradientListComponent.cpp | 2 +- .../TerrainHeightGradientListComponent.h | 2 +- .../TerrainPhysicsColliderComponent.cpp | 4 +- .../Components/TerrainWorldComponent.cpp | 63 ++++++++++++++++--- .../Source/Components/TerrainWorldComponent.h | 18 +++++- .../TerrainWorldDebuggerComponent.cpp | 21 ++++--- .../TerrainWorldDebuggerComponent.h | 2 +- .../TerrainRaycast/TerrainRaycastContext.cpp | 5 +- .../TerrainFeatureProcessor.cpp | 7 +-- .../TerrainRenderer/TerrainMeshManager.cpp | 5 +- .../Source/TerrainSystem/TerrainSystem.cpp | 16 ++--- .../Code/Source/TerrainSystem/TerrainSystem.h | 6 +- .../Tests/TerrainHeightGradientListTests.cpp | 4 +- .../Tests/TerrainPhysicsColliderTests.cpp | 16 ++--- .../Code/Tests/TerrainSystemBenchmarks.cpp | 54 ++++++++-------- Gems/Terrain/Code/Tests/TerrainSystemTest.cpp | 26 +++----- 23 files changed, 167 insertions(+), 109 deletions(-) diff --git a/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/Terrain_World_ConfigurationWorks.py b/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/Terrain_World_ConfigurationWorks.py index bb5dcb3bea..41dc6b92af 100644 --- a/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/Terrain_World_ConfigurationWorks.py +++ b/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/Terrain_World_ConfigurationWorks.py @@ -93,7 +93,7 @@ def Terrain_World_ConfigurationWorks(): # 5) Set the base Terrain World values world_bounds_max = azmath.Vector3(1100.0, 1100.0, 1100.0) world_bounds_min = azmath.Vector3(10.0, 10.0, 10.0) - height_query_resolution = azmath.Vector2(1.0, 1.0) + height_query_resolution = 1.0 hydra.set_component_property_value(terrain_world_component, "Configuration|World Bounds (Max)", world_bounds_max) hydra.set_component_property_value(terrain_world_component, "Configuration|World Bounds (Min)", world_bounds_min) hydra.set_component_property_value(terrain_world_component, "Configuration|Height Query Resolution (m)", height_query_resolution) @@ -148,7 +148,7 @@ def Terrain_World_ConfigurationWorks(): # 13) Check height value is the expected one when query resolution is changed testpoint = terrain.TerrainDataRequestBus(bus.Broadcast, 'GetHeightFromFloats', 10.5, 10.5, CLAMP) - height_query_resolution = azmath.Vector2(0.5, 0.5) + height_query_resolution = 0.5 hydra.set_component_property_value(terrain_world_component, "Configuration|Height Query Resolution (m)", height_query_resolution) general.idle_wait_frames(1) testpoint2 = terrain.TerrainDataRequestBus(bus.Broadcast, 'GetHeightFromFloats', 10.5, 10.5, CLAMP) @@ -165,4 +165,3 @@ if __name__ == "__main__": from editor_python_test_tools.utils import Report Report.start_test(Terrain_World_ConfigurationWorks) - diff --git a/Code/Editor/GameExporter.cpp b/Code/Editor/GameExporter.cpp index a768861b90..635281aee5 100644 --- a/Code/Editor/GameExporter.cpp +++ b/Code/Editor/GameExporter.cpp @@ -317,8 +317,8 @@ void CGameExporter::ExportLevelInfo(const QString& path) root->setAttr("Name", levelName.toUtf8().data()); auto terrain = AzFramework::Terrain::TerrainDataRequestBus::FindFirstHandler(); const AZ::Aabb terrainAabb = terrain ? terrain->GetTerrainAabb() : AZ::Aabb::CreateFromPoint(AZ::Vector3::CreateZero()); - const AZ::Vector2 terrainGridResolution = terrain ? terrain->GetTerrainHeightQueryResolution() : AZ::Vector2::CreateOne(); - const int compiledHeightmapSize = static_cast(terrainAabb.GetXExtent() / terrainGridResolution.GetX()); + const float terrainGridResolution = terrain ? terrain->GetTerrainHeightQueryResolution() : 1.0f; + const int compiledHeightmapSize = static_cast(terrainAabb.GetXExtent() / terrainGridResolution); root->setAttr("HeightmapSize", compiledHeightmapSize); ////////////////////////////////////////////////////////////////////////// diff --git a/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.cpp b/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.cpp index db8004d83a..bc38fe78fd 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.cpp +++ b/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.cpp @@ -378,7 +378,7 @@ namespace Physics m_cachedNativeHeightfield = cachedNativeHeightfield; } - AZ::Vector2 HeightfieldShapeConfiguration::GetGridResolution() const + const AZ::Vector2& HeightfieldShapeConfiguration::GetGridResolution() const { return m_gridResolution; } diff --git a/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.h b/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.h index bd9d6a6aa7..26af746128 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.h @@ -221,7 +221,7 @@ namespace Physics const void* GetCachedNativeHeightfield() const; void* GetCachedNativeHeightfield(); void SetCachedNativeHeightfield(void* cachedNativeHeightfield); - AZ::Vector2 GetGridResolution() const; + const AZ::Vector2& GetGridResolution() const; void SetGridResolution(const AZ::Vector2& gridSpacing); int32_t GetNumColumns() const; void SetNumColumns(int32_t numColumns); @@ -235,7 +235,7 @@ namespace Physics void SetMaxHeightBounds(float maxBounds); private: - //! The number of meters between each heightfield sample. + //! The number of meters between each heightfield sample in x and y. AZ::Vector2 m_gridResolution{ 1.0f }; //! The number of columns in the heightfield sample grid. int32_t m_numColumns{ 0 }; diff --git a/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.h b/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.h index f9ef264ab1..7909a7ec83 100644 --- a/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.h +++ b/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.h @@ -50,8 +50,8 @@ namespace AzFramework static AZ::Vector3 GetDefaultTerrainNormal() { return AZ::Vector3::CreateAxisZ(); } // System-level queries to understand world size and resolution - virtual AZ::Vector2 GetTerrainHeightQueryResolution() const = 0; - virtual void SetTerrainHeightQueryResolution(AZ::Vector2 queryResolution) = 0; + virtual float GetTerrainHeightQueryResolution() const = 0; + virtual void SetTerrainHeightQueryResolution(float queryResolution) = 0; virtual AZ::Aabb GetTerrainAabb() const = 0; virtual void SetTerrainAabb(const AZ::Aabb& worldBounds) = 0; diff --git a/Code/Framework/AzFramework/Tests/Mocks/Terrain/MockTerrainDataRequestBus.h b/Code/Framework/AzFramework/Tests/Mocks/Terrain/MockTerrainDataRequestBus.h index f5af0ff486..4b9658eebb 100644 --- a/Code/Framework/AzFramework/Tests/Mocks/Terrain/MockTerrainDataRequestBus.h +++ b/Code/Framework/AzFramework/Tests/Mocks/Terrain/MockTerrainDataRequestBus.h @@ -49,8 +49,8 @@ namespace UnitTest AzFramework::Terrain::TerrainDataRequestBus::Handler::BusDisconnect(); } - MOCK_CONST_METHOD0(GetTerrainHeightQueryResolution, AZ::Vector2()); - MOCK_METHOD1(SetTerrainHeightQueryResolution, void(AZ::Vector2)); + MOCK_CONST_METHOD0(GetTerrainHeightQueryResolution, float()); + MOCK_METHOD1(SetTerrainHeightQueryResolution, void(float)); MOCK_CONST_METHOD0(GetTerrainAabb, AZ::Aabb()); MOCK_METHOD1(SetTerrainAabb, void(const AZ::Aabb&)); MOCK_CONST_METHOD3(GetHeight, float(const AZ::Vector3&, Sampler, bool*)); diff --git a/Gems/PhysX/Code/Source/Utils.cpp b/Gems/PhysX/Code/Source/Utils.cpp index 950404820d..cc4ef2a12c 100644 --- a/Gems/PhysX/Code/Source/Utils.cpp +++ b/Gems/PhysX/Code/Source/Utils.cpp @@ -133,7 +133,7 @@ namespace PhysX { physx::PxHeightField* heightfield = nullptr; - const AZ::Vector2 gridSpacing = heightfieldConfig.GetGridResolution(); + const AZ::Vector2& gridSpacing = heightfieldConfig.GetGridResolution(); const int32_t numCols = heightfieldConfig.GetNumColumns(); const int32_t numRows = heightfieldConfig.GetNumRows(); diff --git a/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp index 1e7b66ab9d..69cc37c85d 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp @@ -266,7 +266,7 @@ namespace Terrain LmbrCentral::ShapeComponentRequestsBus::EventResult(m_cachedShapeBounds, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb); // Get the height range of the entire world - m_cachedHeightQueryResolution = AZ::Vector2(1.0f); + m_cachedHeightQueryResolution = 1.0f; AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( m_cachedHeightQueryResolution, &AzFramework::Terrain::TerrainDataRequestBus::Events::GetTerrainHeightQueryResolution); diff --git a/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.h b/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.h index c90f4e04d9..4424a67593 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.h +++ b/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.h @@ -92,7 +92,7 @@ namespace Terrain float m_cachedMinWorldHeight{ 0.0f }; float m_cachedMaxWorldHeight{ 0.0f }; - AZ::Vector2 m_cachedHeightQueryResolution{ 1.0f, 1.0f }; + float m_cachedHeightQueryResolution{ 1.0f }; AZ::Aabb m_cachedShapeBounds; // prevent recursion in case user attaches cyclic dependences diff --git a/Gems/Terrain/Code/Source/Components/TerrainPhysicsColliderComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainPhysicsColliderComponent.cpp index d70915a861..457c74a6dd 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainPhysicsColliderComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainPhysicsColliderComponent.cpp @@ -383,11 +383,11 @@ namespace Terrain AZ::Vector2 TerrainPhysicsColliderComponent::GetHeightfieldGridSpacing() const { - AZ::Vector2 gridResolution = AZ::Vector2(1.0f); + float gridResolution = 1.0f; AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( gridResolution, &AzFramework::Terrain::TerrainDataRequests::GetTerrainHeightQueryResolution); - return gridResolution; + return AZ::Vector2(gridResolution); } void TerrainPhysicsColliderComponent::GetHeightfieldGridSize(int32_t& numColumns, int32_t& numRows) const diff --git a/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.cpp index 8b612b86ce..74ae7ed24f 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.cpp @@ -7,9 +7,9 @@ */ #include +#include #include #include -#include #include #include #include @@ -17,13 +17,60 @@ namespace Terrain { + + AZ::JsonSerializationResult::Result JsonTerrainWorldConfigSerializer::Load( + void* outputValue, [[maybe_unused]] const AZ::Uuid& outputValueTypeId, + const rapidjson::Value& inputValue, AZ::JsonDeserializerContext& context) + { + namespace JSR = AZ::JsonSerializationResult; + + auto configInstance = reinterpret_cast(outputValue); + AZ_Assert(configInstance, "Output value for JsonTerrainWorldConfigSerializer can't be null."); + + JSR::ResultCode result(JSR::Tasks::ReadField); + + result.Combine(ContinueLoadingFromJsonObjectField( + &configInstance->m_worldMin, azrtti_typeidm_worldMin)>(), inputValue, "WorldMin", context)); + + result.Combine(ContinueLoadingFromJsonObjectField( + &configInstance->m_worldMax, azrtti_typeidm_worldMax)>(), inputValue, "WorldMax", context)); + + rapidjson::Value::ConstMemberIterator itr = inputValue.FindMember("HeightQueryResolution"); + if (itr != inputValue.MemberEnd()) + { + if (itr->value.IsArray()) + { + // Version 1 stored a Vector2 (serialized as a json array) to have a separate x and y + // query resolution. Now this is only one value, so just take the x value from the Vector2. + configInstance->m_heightQueryResolution = itr->value.GetArray().Begin()->GetFloat(); + } + else + { + result.Combine(ContinueLoadingFromJsonObjectField( + &configInstance->m_heightQueryResolution, azrtti_typeidm_heightQueryResolution)>(), inputValue, "HeightQueryResolution", context)); + } + } + + return context.Report(result, + result.GetProcessing() != JSR::Processing::Halted ? + "Successfully loaded TerrainWorldConfig information." : + "Failed to load TerrainWorldConfig information."); + } + + AZ_CLASS_ALLOCATOR_IMPL(JsonTerrainWorldConfigSerializer, AZ::SystemAllocator, 0); + void TerrainWorldConfig::Reflect(AZ::ReflectContext* context) { + if (auto jsonContext = azrtti_cast(context)) + { + jsonContext->Serializer()->HandlesType(); + } + AZ::SerializeContext* serialize = azrtti_cast(context); if (serialize) { serialize->Class() - ->Version(1) + ->Version(2) ->Field("WorldMin", &TerrainWorldConfig::m_worldMin) ->Field("WorldMax", &TerrainWorldConfig::m_worldMax) ->Field("HeightQueryResolution", &TerrainWorldConfig::m_heightQueryResolution) @@ -131,9 +178,9 @@ namespace Terrain return false; } - float TerrainWorldConfig::NumberOfSamples(AZ::Vector3* min, AZ::Vector3* max, AZ::Vector2* heightQuery) + float TerrainWorldConfig::NumberOfSamples(const AZ::Vector3& min, const AZ::Vector3& max, float heightQuery) { - float numberOfSamples = ((max->GetX() - min->GetX()) / heightQuery->GetX()) * ((max->GetY() - min->GetY()) / heightQuery->GetY()); + float numberOfSamples = ((max.GetX() - min.GetX()) / heightQuery) * ((max.GetY() - min.GetY()) / heightQuery); return numberOfSamples; } @@ -151,21 +198,21 @@ namespace Terrain { AZ::Vector3 minValue = *static_cast(newValue); - return DetermineMessage(NumberOfSamples(&minValue, &m_worldMax, &m_heightQueryResolution)); + return DetermineMessage(NumberOfSamples(minValue, m_worldMax, m_heightQueryResolution)); } AZ::Outcome TerrainWorldConfig::ValidateWorldMax(void* newValue, [[maybe_unused]] const AZ::Uuid& valueType) { AZ::Vector3 maxValue = *static_cast(newValue); - return DetermineMessage(NumberOfSamples(&m_worldMin, &maxValue, &m_heightQueryResolution)); + return DetermineMessage(NumberOfSamples(m_worldMin, maxValue, m_heightQueryResolution)); } AZ::Outcome TerrainWorldConfig::ValidateWorldHeight(void* newValue, [[maybe_unused]] const AZ::Uuid& valueType) { - AZ::Vector2 heightValue = *static_cast(newValue); + float heightValue = *static_cast(newValue); - return DetermineMessage(NumberOfSamples(&m_worldMin, &m_worldMax, &heightValue)); + return DetermineMessage(NumberOfSamples(m_worldMin, m_worldMax, heightValue)); } } // namespace Terrain diff --git a/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.h b/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.h index a396bcefc8..e8f464a748 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.h +++ b/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.h @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include namespace LmbrCentral @@ -21,6 +23,18 @@ namespace LmbrCentral namespace Terrain { + // Custom JSON serializer for TerrainWorldConfig to handle version conversion + class JsonTerrainWorldConfigSerializer : public AZ::BaseJsonSerializer + { + public: + AZ_RTTI(Terrain::JsonTerrainWorldConfigSerializer, "{910BC31F-CD49-488E-8004-227D9FEB5A16}", AZ::BaseJsonSerializer); + AZ_CLASS_ALLOCATOR_DECL; + + AZ::JsonSerializationResult::Result Load( + void* outputValue, const AZ::Uuid& outputValueTypeId, const rapidjson::Value& inputValue, + AZ::JsonDeserializerContext& context) override; + }; + class TerrainWorldConfig : public AZ::ComponentConfig { @@ -31,13 +45,13 @@ namespace Terrain AZ::Vector3 m_worldMin{ 0.0f, 0.0f, 0.0f }; AZ::Vector3 m_worldMax{ 1024.0f, 1024.0f, 1024.0f }; - AZ::Vector2 m_heightQueryResolution{ 1.0f, 1.0f }; + float m_heightQueryResolution{ 1.0f }; private: AZ::Outcome ValidateWorldMin(void* newValue, const AZ::Uuid& valueType); AZ::Outcome ValidateWorldMax(void* newValue, const AZ::Uuid& valueType); AZ::Outcome ValidateWorldHeight(void* newValue, const AZ::Uuid& valueType); - float NumberOfSamples(AZ::Vector3* min, AZ::Vector3* max, AZ::Vector2* heightQuery); + float NumberOfSamples(const AZ::Vector3& min, const AZ::Vector3& max, float heightQuery); AZ::Outcome DetermineMessage(float numSamples); }; diff --git a/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.cpp index f684727d33..7b8020c3ad 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.cpp @@ -227,12 +227,12 @@ namespace Terrain float worldMinZ = worldBounds.GetMin().GetZ(); // Get the terrain height data resolution - AZ::Vector2 heightDataResolution = AZ::Vector2(1.0f); + float heightDataResolution = 1.0f; AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( heightDataResolution, &AzFramework::Terrain::TerrainDataRequests::GetTerrainHeightQueryResolution); // Get the size of a wireframe sector in world space - const AZ::Vector2 sectorSize = heightDataResolution * SectorSizeInGridPoints; + const AZ::Vector2 sectorSize = AZ::Vector2(heightDataResolution * SectorSizeInGridPoints); // Try to get the current camera position, or default to (0,0) if we can't. AZ::Vector3 cameraPos = AZ::Vector3::CreateZero(); @@ -317,7 +317,7 @@ namespace Terrain } - void TerrainWorldDebuggerComponent::RebuildSectorWireframe(WireframeSector& sector, const AZ::Vector2& gridResolution) + void TerrainWorldDebuggerComponent::RebuildSectorWireframe(WireframeSector& sector, float gridResolution) { if (!sector.m_isDirty) { @@ -337,11 +337,11 @@ namespace Terrain // Since we're processing lines based on the grid points and going backwards, this will give us (*--*--*). AZ::Aabb region = sector.m_aabb; - region.SetMax(region.GetMax() + AZ::Vector3(gridResolution.GetX(), gridResolution.GetY(), 0.0f)); + region.SetMax(region.GetMax() + AZ::Vector3(gridResolution, gridResolution, 0.0f)); // We need 4 vertices for each grid point in our sector to hold the _| shape. - const size_t numSamplesX = aznumeric_cast(ceil(region.GetExtents().GetX() / gridResolution.GetX())); - const size_t numSamplesY = aznumeric_cast(ceil(region.GetExtents().GetY() / gridResolution.GetY())); + const size_t numSamplesX = aznumeric_cast(ceil(region.GetExtents().GetX() / gridResolution)); + const size_t numSamplesY = aznumeric_cast(ceil(region.GetExtents().GetY() / gridResolution)); sector.m_lineVertices.clear(); sector.m_lineVertices.reserve(numSamplesX * numSamplesY * 4); @@ -360,8 +360,8 @@ namespace Terrain // there is one. if ((xIndex > 0) && (yIndex > 0)) { - float x = surfacePoint.m_position.GetX() - gridResolution.GetX(); - float y = surfacePoint.m_position.GetY() - gridResolution.GetY(); + float x = surfacePoint.m_position.GetX() - gridResolution; + float y = surfacePoint.m_position.GetY() - gridResolution; sector.m_lineVertices.emplace_back(AZ::Vector3(x, surfacePoint.m_position.GetY(), previousHeight)); sector.m_lineVertices.emplace_back(surfacePoint.m_position); @@ -374,9 +374,10 @@ namespace Terrain previousHeight = surfacePoint.m_position.GetZ(); rowHeights[xIndex] = surfacePoint.m_position.GetZ(); }; - + + AZ::Vector2 stepSize = AZ::Vector2(gridResolution); AzFramework::Terrain::TerrainDataRequestBus::Broadcast(&AzFramework::Terrain::TerrainDataRequests::ProcessHeightsFromRegion, - region, gridResolution, ProcessHeightValue, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT); + region, stepSize, ProcessHeightValue, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT); } void TerrainWorldDebuggerComponent::OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask) diff --git a/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.h b/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.h index cb308effe6..13c602c48d 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.h +++ b/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.h @@ -93,7 +93,7 @@ namespace Terrain bool m_isDirty{ true }; }; - void RebuildSectorWireframe(WireframeSector& sector, const AZ::Vector2& gridResolution); + void RebuildSectorWireframe(WireframeSector& sector, float gridResolution); void MarkDirtySectors(const AZ::Aabb& dirtyRegion); void DrawWorldBounds(AzFramework::DebugDisplayRequests& debugDisplay); void DrawWireframe(const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay); diff --git a/Gems/Terrain/Code/Source/TerrainRaycast/TerrainRaycastContext.cpp b/Gems/Terrain/Code/Source/TerrainRaycast/TerrainRaycastContext.cpp index d42388db60..1f05c44314 100644 --- a/Gems/Terrain/Code/Source/TerrainRaycast/TerrainRaycastContext.cpp +++ b/Gems/Terrain/Code/Source/TerrainRaycast/TerrainRaycastContext.cpp @@ -377,10 +377,11 @@ AzFramework::RenderGeometry::RayResult TerrainRaycastContext::RayIntersect( const AzFramework::RenderGeometry::RayRequest& ray) { const AZ::Aabb terrainWorldBounds = m_terrainSystem.GetTerrainAabb(); - const AZ::Vector2 terrainResolution = m_terrainSystem.GetTerrainHeightQueryResolution(); + const float terrainResolution = m_terrainSystem.GetTerrainHeightQueryResolution(); + const AZ::Vector2 terrainResolution2d(terrainResolution); AzFramework::RenderGeometry::RayResult rayIntersectionResult; FindNearestIntersectionIterative(m_terrainSystem, - terrainResolution, + terrainResolution2d, terrainWorldBounds, ray.m_startWorldPosition, ray.m_endWorldPosition, diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp index d9bdcb97bc..17cded8c97 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp @@ -159,11 +159,10 @@ namespace Terrain m_dirtyRegion.AddAabb(regionToUpdate); m_dirtyRegion.Clamp(worldBounds); - AZ::Vector2 queryResolution2D = AZ::Vector2(1.0f); + float queryResolution = 1.0f; AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( - queryResolution2D, &AzFramework::Terrain::TerrainDataRequests::GetTerrainHeightQueryResolution); + queryResolution, &AzFramework::Terrain::TerrainDataRequests::GetTerrainHeightQueryResolution); // Currently query resolution is multidimensional but the rendering system only supports this changing in one dimension. - float queryResolution = queryResolution2D.GetX(); m_terrainBounds = worldBounds; m_sampleSpacing = queryResolution; @@ -209,7 +208,7 @@ namespace Terrain int32_t xStart = aznumeric_cast(AZStd::ceilf(m_dirtyRegion.GetMin().GetX() / m_sampleSpacing)); int32_t yStart = aznumeric_cast(AZStd::ceilf(m_dirtyRegion.GetMin().GetY() / m_sampleSpacing)); - + AZ::Vector2 stepSize(m_sampleSpacing); AZ::Vector3 maxBound( m_dirtyRegion.GetMax().GetX() + m_sampleSpacing, m_dirtyRegion.GetMax().GetY() + m_sampleSpacing, 0.0f); diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainMeshManager.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainMeshManager.cpp index 7a36110579..78d0338ca5 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainMeshManager.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainMeshManager.cpp @@ -203,11 +203,10 @@ namespace Terrain AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( worldBounds, &AzFramework::Terrain::TerrainDataRequests::GetTerrainAabb); - AZ::Vector2 queryResolution2D = AZ::Vector2(1.0f); + float queryResolution = 1.0f; AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( - queryResolution2D, &AzFramework::Terrain::TerrainDataRequests::GetTerrainHeightQueryResolution); + queryResolution, &AzFramework::Terrain::TerrainDataRequests::GetTerrainHeightQueryResolution); // Currently query resolution is multidimensional but the rendering system only supports this changing in one dimension. - float queryResolution = queryResolution2D.GetX(); // Sectors need to be rebuilt if the world bounds change in the x/y, or the sample spacing changes. m_rebuildSectors = m_rebuildSectors || diff --git a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp index 35e6992db3..8976faadac 100644 --- a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp +++ b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp @@ -134,7 +134,7 @@ void TerrainSystem::SetTerrainAabb(const AZ::Aabb& worldBounds) m_terrainSettingsDirty = true; } -void TerrainSystem::SetTerrainHeightQueryResolution(AZ::Vector2 queryResolution) +void TerrainSystem::SetTerrainHeightQueryResolution(float queryResolution) { m_requestedSettings.m_heightQueryResolution = queryResolution; m_terrainSettingsDirty = true; @@ -145,7 +145,7 @@ AZ::Aabb TerrainSystem::GetTerrainAabb() const return m_currentSettings.m_worldBounds; } -AZ::Vector2 TerrainSystem::GetTerrainHeightQueryResolution() const +float TerrainSystem::GetTerrainHeightQueryResolution() const { return m_currentSettings.m_heightQueryResolution; } @@ -204,7 +204,7 @@ float TerrainSystem::GetHeightSynchronous(float x, float y, Sampler sampler, boo AZ::Vector2 normalizedDelta; AZ::Vector2 pos0; ClampPosition(x, y, pos0, normalizedDelta); - const AZ::Vector2 pos1 = pos0 + m_currentSettings.m_heightQueryResolution; + const AZ::Vector2 pos1 = pos0 + AZ::Vector2(m_currentSettings.m_heightQueryResolution); const float heightX0Y0 = GetTerrainAreaHeight(pos0.GetX(), pos0.GetY(), terrainExists); const float heightX1Y0 = GetTerrainAreaHeight(pos1.GetX(), pos0.GetY(), terrainExists); @@ -331,11 +331,11 @@ AZ::Vector3 TerrainSystem::GetNormalSynchronous(float x, float y, Sampler sample return outNormal; } } - const AZ::Vector2 range = (m_currentSettings.m_heightQueryResolution / 2.0f); - const AZ::Vector2 left (x - range.GetX(), y); - const AZ::Vector2 right(x + range.GetX(), y); - const AZ::Vector2 up (x, y - range.GetY()); - const AZ::Vector2 down (x, y + range.GetY()); + float range = m_currentSettings.m_heightQueryResolution / 2.0f; + const AZ::Vector2 left (x - range, y); + const AZ::Vector2 right(x + range, y); + const AZ::Vector2 up (x, y - range); + const AZ::Vector2 down (x, y + range); AZ::Vector3 v1(up.GetX(), up.GetY(), GetHeightSynchronous(up.GetX(), up.GetY(), sampler, &terrainExists)); AZ::Vector3 v2(left.GetX(), left.GetY(), GetHeightSynchronous(left.GetX(), left.GetY(), sampler, &terrainExists)); diff --git a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h index 1cdb6e52b1..e0457b80af 100644 --- a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h +++ b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h @@ -54,8 +54,8 @@ namespace Terrain /////////////////////////////////////////// // TerrainDataRequestBus::Handler Impl - AZ::Vector2 GetTerrainHeightQueryResolution() const override; - void SetTerrainHeightQueryResolution(AZ::Vector2 queryResolution) override; + float GetTerrainHeightQueryResolution() const override; + void SetTerrainHeightQueryResolution(float queryResolution) override; AZ::Aabb GetTerrainAabb() const override; void SetTerrainAabb(const AZ::Aabb& worldBounds) override; @@ -213,7 +213,7 @@ namespace Terrain struct TerrainSystemSettings { AZ::Aabb m_worldBounds; - AZ::Vector2 m_heightQueryResolution{ 1.0f }; + float m_heightQueryResolution{ 1.0f }; bool m_systemActive{ false }; }; diff --git a/Gems/Terrain/Code/Tests/TerrainHeightGradientListTests.cpp b/Gems/Terrain/Code/Tests/TerrainHeightGradientListTests.cpp index 77139a5209..976129030a 100644 --- a/Gems/Terrain/Code/Tests/TerrainHeightGradientListTests.cpp +++ b/Gems/Terrain/Code/Tests/TerrainHeightGradientListTests.cpp @@ -149,7 +149,7 @@ TEST_F(TerrainHeightGradientListComponentTest, TerrainHeightGradientListReturnsH const float worldMax = 10000.0f; const AZ::Aabb worldAabb = AZ::Aabb::CreateFromMinMax(AZ::Vector3(min), AZ::Vector3(worldMax)); NiceMock mockterrainDataRequests; - ON_CALL(mockterrainDataRequests, GetTerrainHeightQueryResolution).WillByDefault(Return(AZ::Vector2(1.0f))); + ON_CALL(mockterrainDataRequests, GetTerrainHeightQueryResolution).WillByDefault(Return(1.0f)); ON_CALL(mockterrainDataRequests, GetTerrainAabb).WillByDefault(Return(worldAabb)); // Ensure the cached values in the HeightGradientListComponent are up to date. @@ -198,7 +198,7 @@ TEST_F(TerrainHeightGradientListComponentTest, TerrainHeightGradientListGetHeigh const float worldMax = 10000.0f; const AZ::Aabb worldAabb = AZ::Aabb::CreateFromMinMax(AZ::Vector3(min), AZ::Vector3(worldMax)); NiceMock mockterrainDataRequests; - ON_CALL(mockterrainDataRequests, GetTerrainHeightQueryResolution).WillByDefault(Return(AZ::Vector2(1.0f))); + ON_CALL(mockterrainDataRequests, GetTerrainHeightQueryResolution).WillByDefault(Return(1.0f)); ON_CALL(mockterrainDataRequests, GetTerrainAabb).WillByDefault(Return(worldAabb)); // Ensure the cached values in the HeightGradientListComponent are up to date. diff --git a/Gems/Terrain/Code/Tests/TerrainPhysicsColliderTests.cpp b/Gems/Terrain/Code/Tests/TerrainPhysicsColliderTests.cpp index bc4818e62a..43b122c6e9 100644 --- a/Gems/Terrain/Code/Tests/TerrainPhysicsColliderTests.cpp +++ b/Gems/Terrain/Code/Tests/TerrainPhysicsColliderTests.cpp @@ -169,7 +169,7 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderReturnsAligned const AZ::Aabb bounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(boundsMin), AZ::Vector3(boundsMax)); ON_CALL(boxShape, GetEncompassingAabb).WillByDefault(Return(bounds)); - const AZ::Vector2 mockHeightResolution = AZ::Vector2(1.0f); + float mockHeightResolution = 1.0f; NiceMock terrainListener; ON_CALL(terrainListener, GetTerrainHeightQueryResolution).WillByDefault(Return(mockHeightResolution)); @@ -197,7 +197,7 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderExpandsMinBoun const AZ::Aabb bounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(boundsMin), AZ::Vector3(boundsMax)); ON_CALL(boxShape, GetEncompassingAabb).WillByDefault(Return(bounds)); - AZ::Vector2 mockHeightResolution = AZ::Vector2(1.0f); + float mockHeightResolution = 1.0f; NiceMock terrainListener; ON_CALL(terrainListener, GetTerrainHeightQueryResolution).WillByDefault(Return(mockHeightResolution)); @@ -226,7 +226,7 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderExpandsMaxBoun const AZ::Aabb bounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(boundsMin), AZ::Vector3(boundsMax)); ON_CALL(boxShape, GetEncompassingAabb).WillByDefault(Return(bounds)); - AZ::Vector2 mockHeightResolution = AZ::Vector2(1.0f); + float mockHeightResolution = 1.0f; NiceMock terrainListener; ON_CALL(terrainListener, GetTerrainHeightQueryResolution).WillByDefault(Return(mockHeightResolution)); @@ -254,7 +254,7 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderGetHeightsRetu const AZ::Aabb bounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(boundsMin), AZ::Vector3(boundsMax)); ON_CALL(boxShape, GetEncompassingAabb).WillByDefault(Return(bounds)); - AZ::Vector2 mockHeightResolution = AZ::Vector2(1.0f); + float mockHeightResolution = 1.0f; NiceMock terrainListener; ON_CALL(terrainListener, GetTerrainHeightQueryResolution).WillByDefault(Return(mockHeightResolution)); ON_CALL(terrainListener, ProcessHeightsFromRegion).WillByDefault( @@ -291,7 +291,7 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderReturnsRelativ const AZ::Vector3 boundsMax = AZ::Vector3(256.0f, 256.0f, 32768.0f); const float mockHeight = 32768.0f; - AZ::Vector2 mockHeightResolution = AZ::Vector2(1.0f); + float mockHeightResolution = 1.0f; NiceMock terrainListener; ON_CALL(terrainListener, GetTerrainHeightQueryResolution).WillByDefault(Return(mockHeightResolution)); @@ -410,7 +410,7 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderGetHeightsAndM ON_CALL(boxShape, GetEncompassingAabb).WillByDefault(Return(bounds)); const float mockHeight = 32768.0f; - AZ::Vector2 mockHeightResolution = AZ::Vector2(1.0f); + float mockHeightResolution = 1.0f; AzFramework::SurfaceData::SurfaceTagWeight tagWeight1(tag1, 1.0f); AzFramework::SurfaceData::SurfaceTagWeight tagWeight2(tag2, 1.0f); @@ -490,7 +490,7 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderDefaultMateria ON_CALL(boxShape, GetEncompassingAabb).WillByDefault(Return(bounds)); const float mockHeight = 32768.0f; - AZ::Vector2 mockHeightResolution = AZ::Vector2(1.0f); + float mockHeightResolution = 1.0f; AzFramework::SurfaceData::SurfaceTagWeight tagWeight1(tag1, 1.0f); AzFramework::SurfaceData::SurfaceTagWeight tagWeight2(tag2, 1.0f); @@ -554,7 +554,7 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderDefaultMateria ON_CALL(boxShape, GetEncompassingAabb).WillByDefault(Return(bounds)); const float mockHeight = 32768.0f; - AZ::Vector2 mockHeightResolution = AZ::Vector2(1.0f); + float mockHeightResolution = 1.0f; const SurfaceData::SurfaceTag tag1 = SurfaceData::SurfaceTag("tag1"); AzFramework::SurfaceData::SurfaceTagWeight tagWeight1(tag1, 1.0f); diff --git a/Gems/Terrain/Code/Tests/TerrainSystemBenchmarks.cpp b/Gems/Terrain/Code/Tests/TerrainSystemBenchmarks.cpp index 85dd5dc0c4..e54747abcc 100644 --- a/Gems/Terrain/Code/Tests/TerrainSystemBenchmarks.cpp +++ b/Gems/Terrain/Code/Tests/TerrainSystemBenchmarks.cpp @@ -117,7 +117,7 @@ namespace UnitTest // Create a terrain system with reasonable defaults for testing, but with the ability to override the defaults // on a test-by-test basis. AZStd::unique_ptr CreateAndActivateTerrainSystem( - AZ::Vector2 queryResolution = AZ::Vector2(1.0f), + float queryResolution = 1.0f, AZ::Aabb worldBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-128.0f), AZ::Vector3(128.0f))) { // Create the terrain system and give it one tick to fully initialize itself. @@ -216,7 +216,7 @@ namespace UnitTest void RunTerrainApiBenchmark( benchmark::State& state, AZStd::function ApiCaller) { @@ -228,7 +228,7 @@ namespace UnitTest // Set up our world bounds and query resolution AZ::Aabb worldBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-boundsRange / 2.0f), AZ::Vector3(boundsRange / 2.0f)); - AZ::Vector2 queryResolution = AZ::Vector2(1.0f); + float queryResolution = 1.0f; // Create a Random Gradient to use as our height provider const uint32_t heightRandomSeed = 12345; @@ -281,17 +281,17 @@ namespace UnitTest surfaceGradientShapeRequests.clear(); } - void GenerateInputPositionsList(const AZ::Vector2& queryResolution, const AZ::Aabb& worldBounds, AZStd::vector& positions) + void GenerateInputPositionsList(float queryResolution, const AZ::Aabb& worldBounds, AZStd::vector& positions) { - const size_t numSamplesX = aznumeric_cast(ceil(worldBounds.GetExtents().GetX() / queryResolution.GetX())); - const size_t numSamplesY = aznumeric_cast(ceil(worldBounds.GetExtents().GetY() / queryResolution.GetY())); + const size_t numSamplesX = aznumeric_cast(ceil(worldBounds.GetExtents().GetX() / queryResolution)); + const size_t numSamplesY = aznumeric_cast(ceil(worldBounds.GetExtents().GetY() / queryResolution)); for (size_t y = 0; y < numSamplesY; y++) { - float fy = aznumeric_cast(worldBounds.GetMin().GetY() + (y * queryResolution.GetY())); + float fy = aznumeric_cast(worldBounds.GetMin().GetY() + (y * queryResolution)); for (size_t x = 0; x < numSamplesX; x++) { - float fx = aznumeric_cast(worldBounds.GetMin().GetX() + (x * queryResolution.GetX())); + float fx = aznumeric_cast(worldBounds.GetMin().GetX() + (x * queryResolution)); positions.emplace_back(fx, fy, 0.0f); } } @@ -307,7 +307,7 @@ namespace UnitTest // Run the benchmark RunTerrainApiBenchmark( state, - []([[maybe_unused]] const AZ::Vector2& queryResolution, const AZ::Aabb& worldBounds, + []([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { float worldMinZ = worldBounds.GetMin().GetZ(); @@ -343,7 +343,7 @@ namespace UnitTest // Run the benchmark RunTerrainApiBenchmark( state, - []([[maybe_unused]] const AZ::Vector2& queryResolution, const AZ::Aabb& worldBounds, + []([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { auto perPositionCallback = []([[maybe_unused]] size_t xIndex, [[maybe_unused]] size_t yIndex, @@ -351,9 +351,10 @@ namespace UnitTest { benchmark::DoNotOptimize(surfacePoint.m_position.GetZ()); }; - + + AZ::Vector2 stepSize = AZ::Vector2(queryResolution); AzFramework::Terrain::TerrainDataRequestBus::Broadcast( - &AzFramework::Terrain::TerrainDataRequests::ProcessHeightsFromRegion, worldBounds, queryResolution, perPositionCallback, sampler); + &AzFramework::Terrain::TerrainDataRequests::ProcessHeightsFromRegion, worldBounds, stepSize, perPositionCallback, sampler); } ); } @@ -375,7 +376,7 @@ namespace UnitTest // Run the benchmark RunTerrainApiBenchmark( state, - [this]([[maybe_unused]] const AZ::Vector2& queryResolution, const AZ::Aabb& worldBounds, + [this]([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { AZStd::vector inPositions; @@ -409,7 +410,7 @@ namespace UnitTest // Run the benchmark RunTerrainApiBenchmark( state, - []([[maybe_unused]] const AZ::Vector2& queryResolution, const AZ::Aabb& worldBounds, + []([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { for (float y = worldBounds.GetMin().GetY(); y < worldBounds.GetMax().GetY(); y += 1.0f) @@ -440,7 +441,7 @@ namespace UnitTest // Run the benchmark RunTerrainApiBenchmark( state, - []([[maybe_unused]] const AZ::Vector2& queryResolution, const AZ::Aabb& worldBounds, + []([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { auto perPositionCallback = []([[maybe_unused]] size_t xIndex, [[maybe_unused]] size_t yIndex, @@ -449,8 +450,9 @@ namespace UnitTest benchmark::DoNotOptimize(surfacePoint.m_normal); }; + AZ::Vector2 stepSize = AZ::Vector2(queryResolution); AzFramework::Terrain::TerrainDataRequestBus::Broadcast( - &AzFramework::Terrain::TerrainDataRequests::ProcessNormalsFromRegion, worldBounds, queryResolution, perPositionCallback, sampler); + &AzFramework::Terrain::TerrainDataRequests::ProcessNormalsFromRegion, worldBounds, stepSize, perPositionCallback, sampler); } ); } @@ -469,7 +471,7 @@ namespace UnitTest // Run the benchmark RunTerrainApiBenchmark( state, - [this]([[maybe_unused]] const AZ::Vector2& queryResolution, const AZ::Aabb& worldBounds, + [this]([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { AZStd::vector inPositions; @@ -500,7 +502,7 @@ namespace UnitTest // Run the benchmark RunTerrainApiBenchmark( state, - []([[maybe_unused]] const AZ::Vector2& queryResolution, const AZ::Aabb& worldBounds, + []([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { AzFramework::SurfaceData::SurfaceTagWeightList surfaceWeights; @@ -532,7 +534,7 @@ namespace UnitTest // Run the benchmark RunTerrainApiBenchmark( state, - []([[maybe_unused]] const AZ::Vector2& queryResolution, const AZ::Aabb& worldBounds, + []([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { auto perPositionCallback = []([[maybe_unused]] size_t xIndex, [[maybe_unused]] size_t yIndex, @@ -541,8 +543,9 @@ namespace UnitTest benchmark::DoNotOptimize(surfacePoint.m_surfaceTags); }; + AZ::Vector2 stepSize = AZ::Vector2(queryResolution); AzFramework::Terrain::TerrainDataRequestBus::Broadcast( - &AzFramework::Terrain::TerrainDataRequests::ProcessSurfaceWeightsFromRegion, worldBounds, queryResolution, perPositionCallback, sampler); + &AzFramework::Terrain::TerrainDataRequests::ProcessSurfaceWeightsFromRegion, worldBounds, stepSize, perPositionCallback, sampler); } ); } @@ -561,7 +564,7 @@ namespace UnitTest // Run the benchmark RunTerrainApiBenchmark( state, - [this]([[maybe_unused]] const AZ::Vector2& queryResolution, const AZ::Aabb& worldBounds, + [this]([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { AZStd::vector inPositions; @@ -592,7 +595,7 @@ namespace UnitTest // Run the benchmark RunTerrainApiBenchmark( state, - []([[maybe_unused]] const AZ::Vector2& queryResolution, const AZ::Aabb& worldBounds, + []([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { AzFramework::SurfaceData::SurfacePoint surfacePoint; @@ -624,7 +627,7 @@ namespace UnitTest // Run the benchmark RunTerrainApiBenchmark( state, - []([[maybe_unused]] const AZ::Vector2& queryResolution, const AZ::Aabb& worldBounds, + []([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { auto perPositionCallback = []([[maybe_unused]] size_t xIndex, [[maybe_unused]] size_t yIndex, @@ -633,8 +636,9 @@ namespace UnitTest benchmark::DoNotOptimize(surfacePoint); }; + AZ::Vector2 stepSize = AZ::Vector2(queryResolution); AzFramework::Terrain::TerrainDataRequestBus::Broadcast( - &AzFramework::Terrain::TerrainDataRequests::ProcessSurfacePointsFromRegion, worldBounds, queryResolution, perPositionCallback, sampler); + &AzFramework::Terrain::TerrainDataRequests::ProcessSurfacePointsFromRegion, worldBounds, stepSize, perPositionCallback, sampler); } ); } @@ -653,7 +657,7 @@ namespace UnitTest // Run the benchmark RunTerrainApiBenchmark( state, - [this]([[maybe_unused]] const AZ::Vector2& queryResolution, const AZ::Aabb& worldBounds, + [this]([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, AzFramework::Terrain::TerrainDataRequests::Sampler sampler) { AZStd::vector inPositions; diff --git a/Gems/Terrain/Code/Tests/TerrainSystemTest.cpp b/Gems/Terrain/Code/Tests/TerrainSystemTest.cpp index ddccdbc49c..2c01a3d455 100644 --- a/Gems/Terrain/Code/Tests/TerrainSystemTest.cpp +++ b/Gems/Terrain/Code/Tests/TerrainSystemTest.cpp @@ -120,7 +120,7 @@ namespace UnitTest // Create a terrain system with reasonable defaults for testing, but with the ability to override the defaults // on a test-by-test basis. AZStd::unique_ptr CreateAndActivateTerrainSystem( - AZ::Vector2 queryResolution = AZ::Vector2(1.0f), + float queryResolution = 1.0f, AZ::Aabb worldBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-128.0f), AZ::Vector3(128.0f))) { // Create the terrain system and give it one tick to fully initialize itself. @@ -363,8 +363,7 @@ namespace UnitTest // Create and activate the terrain system with our testing defaults for world bounds, and a query resolution that exactly matches // the frequency of our sine wave. If our height queries rely on the query resolution, we should always get a value of 0. - const AZ::Vector2 queryResolution(frequencyMeters); - auto terrainSystem = CreateAndActivateTerrainSystem(queryResolution); + auto terrainSystem = CreateAndActivateTerrainSystem(frequencyMeters); // Test an arbitrary set of points that should all produce non-zero heights with the EXACT sampler. They're not aligned with the // query resolution, or with the 0 points on the sine wave. @@ -414,7 +413,7 @@ namespace UnitTest // Create and activate the terrain system with our testing defaults for world bounds, and a query resolution at 0.25 meter // intervals. - const AZ::Vector2 queryResolution(0.25f); + const float queryResolution = 0.25f; auto terrainSystem = CreateAndActivateTerrainSystem(queryResolution); // Test some points and verify that the results always go "downward", whether they're in positive or negative space. @@ -476,8 +475,7 @@ namespace UnitTest }); // Create and activate the terrain system with our testing defaults for world bounds, and a query resolution at 1 meter intervals. - const AZ::Vector2 queryResolution(frequencyMeters); - auto terrainSystem = CreateAndActivateTerrainSystem(queryResolution); + auto terrainSystem = CreateAndActivateTerrainSystem(frequencyMeters); // Test some points and verify that the results are the expected bilinear filtered result, // whether they're in positive or negative space. @@ -664,8 +662,7 @@ namespace UnitTest }); // Create and activate the terrain system with our testing defaults for world bounds, and a query resolution at 1 meter intervals. - const AZ::Vector2 queryResolution(frequencyMeters); - auto terrainSystem = CreateAndActivateTerrainSystem(queryResolution); + auto terrainSystem = CreateAndActivateTerrainSystem(frequencyMeters); // Test some points and verify that the results are the expected bilinear filtered result, // whether they're in positive or negative space. @@ -762,8 +759,7 @@ namespace UnitTest }); // Create and activate the terrain system with our testing defaults for world bounds, and a query resolution at 1 meter intervals. - const AZ::Vector2 queryResolution(frequencyMeters); - auto terrainSystem = CreateAndActivateTerrainSystem(queryResolution); + auto terrainSystem = CreateAndActivateTerrainSystem(frequencyMeters); const NormalTestPoint testPoints[] = { @@ -852,8 +848,7 @@ namespace UnitTest }); // Create and activate the terrain system with our testing defaults for world bounds, and a query resolution at 1 meter intervals. - const AZ::Vector2 queryResolution(frequencyMeters); - auto terrainSystem = CreateAndActivateTerrainSystem(queryResolution); + auto terrainSystem = CreateAndActivateTerrainSystem(frequencyMeters); const AZ::Aabb testRegionBox = AZ::Aabb::CreateFromMinMaxValues(-1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f); const AZ::Vector2 stepSize(1.0f); @@ -911,8 +906,7 @@ namespace UnitTest }); // Create and activate the terrain system with our testing defaults for world bounds, and a query resolution at 1 meter intervals. - const AZ::Vector2 queryResolution(frequencyMeters); - auto terrainSystem = CreateAndActivateTerrainSystem(queryResolution); + auto terrainSystem = CreateAndActivateTerrainSystem(frequencyMeters); const AZ::Aabb testRegionBox = AZ::Aabb::CreateFromMinMaxValues(-1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f); const AZ::Vector2 stepSize(1.0f); @@ -960,7 +954,7 @@ namespace UnitTest }); // Create and activate the terrain system with our testing defaults for world bounds, and a query resolution at 1 meter intervals. - const AZ::Vector2 queryResolution(1.0f); + const float queryResolution = 1.0f; auto terrainSystem = CreateAndActivateTerrainSystem(queryResolution); const AZ::Aabb testRegionBox = AZ::Aabb::CreateFromMinMaxValues(-3.0f, -3.0f, -1.0f, 3.0f, 3.0f, 1.0f); @@ -1006,7 +1000,7 @@ namespace UnitTest }); // Create and activate the terrain system with our testing defaults for world bounds, and a query resolution at 1 meter intervals. - const AZ::Vector2 queryResolution(1.0f); + const float queryResolution = 1.0f; auto terrainSystem = CreateAndActivateTerrainSystem(queryResolution); const AZ::Aabb testRegionBox = AZ::Aabb::CreateFromMinMaxValues(-3.0f, -3.0f, -1.0f, 3.0f, 3.0f, 1.0f); From 96dcd1fc265f705d0ca5c22db3a5f7fdc581b371 Mon Sep 17 00:00:00 2001 From: hershey5045 <43485729+hershey5045@users.noreply.github.com> Date: Mon, 31 Jan 2022 14:27:55 -0800 Subject: [PATCH 43/53] Atom/rbarrand/export screenshot diff (#7300) * Small refactor on ImageComparison utils. Signed-off-by: hershey5045 <43485729+hershey5045@users.noreply.github.com> * Add aznumeric cast. Signed-off-by: hershey5045 <43485729+hershey5045@users.noreply.github.com> * Correction on aznumeric cast. Signed-off-by: hershey5045 <43485729+hershey5045@users.noreply.github.com> * Add unit test for new image comparison function. Signed-off-by: hershey5045 <43485729+hershey5045@users.noreply.github.com> * Use span instead of array_view Signed-off-by: hershey5045 <43485729+hershey5045@users.noreply.github.com> --- Gems/Atom/Utils/Code/Include/Atom/Utils/ImageComparison.h | 2 +- Gems/Atom/Utils/Code/Source/ImageComparison.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImageComparison.h b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImageComparison.h index 58c3a1bd48..975d9a4321 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImageComparison.h +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImageComparison.h @@ -24,7 +24,7 @@ namespace AZ }; //! Calculates the maximum difference of the rgb channels between two image buffers. - int16_t CalcMaxChannelDifference(AZStd::array_view bufferA, AZStd::array_view bufferB, size_t index); + int16_t CalcMaxChannelDifference(AZStd::span bufferA, AZStd::span bufferB, size_t index); //! Compares two images and returns the RMS (root mean square) of the difference. //! @param buffer[A|B] the raw buffer of image data diff --git a/Gems/Atom/Utils/Code/Source/ImageComparison.cpp b/Gems/Atom/Utils/Code/Source/ImageComparison.cpp index 6fcc316791..16cb449516 100644 --- a/Gems/Atom/Utils/Code/Source/ImageComparison.cpp +++ b/Gems/Atom/Utils/Code/Source/ImageComparison.cpp @@ -14,7 +14,7 @@ namespace AZ { namespace Utils { - int16_t CalcMaxChannelDifference(AZStd::array_view bufferA, AZStd::array_view bufferB, size_t index) + int16_t CalcMaxChannelDifference(AZStd::span bufferA, AZStd::span bufferB, size_t index) { // We use the max error from a single channel instead of accumulating the error from each channel. // This normalizes differences so that for example black vs red has the same weight as black vs yellow. From 2dbc961ea8a776d108620cac746ae9962f603fdf Mon Sep 17 00:00:00 2001 From: Steve Pham <82231385+spham-amzn@users.noreply.github.com> Date: Mon, 31 Jan 2022 14:31:24 -0800 Subject: [PATCH 44/53] Reorder and Remove unnecessary GCC ignore warning flags - Reordered warning flags (#7297) - Removed unnecessary ignore warning flags Signed-off-by: Steve Pham <82231385+spham-amzn@users.noreply.github.com> --- .../Common/GCC/Configurations_gcc.cmake | 52 ++++++++----------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/cmake/Platform/Common/GCC/Configurations_gcc.cmake b/cmake/Platform/Common/GCC/Configurations_gcc.cmake index 3db96306ee..9963c62d39 100644 --- a/cmake/Platform/Common/GCC/Configurations_gcc.cmake +++ b/cmake/Platform/Common/GCC/Configurations_gcc.cmake @@ -33,6 +33,8 @@ ly_append_configurations_options( COMPILATION_CXX -fno-exceptions -fvisibility=hidden + -fvisibility-inlines-hidden + -Wall -Werror @@ -40,40 +42,33 @@ ly_append_configurations_options( ${LY_GCC_GPROF_FLAGS} # Disabled warnings - -Wno-format-security - -Wno-multichar - -Wno-parentheses - -Wno-switch - -Wno-tautological-compare - -Wno-unknown-pragmas - -Wno-unused-function - -Wno-unused-value - -Wno-unused-variable - -Wno-format-truncation - -Wno-uninitialized -Wno-array-bounds + -Wno-attributes + -Wno-class-memaccess + -Wno-comment + -Wno-delete-non-virtual-dtor + -Wno-enum-compare + -Wno-format-overflow + -Wno-format-truncation + -Wno-int-in-bool-context + -Wno-logical-not-parentheses + -Wno-memset-elt-size -Wno-nonnull-compare - -Wno-strict-aliasing - -Wno-unused-result - -Wno-sign-compare + -Wno-parentheses + -Wno-reorder + -Wno-restrict -Wno-return-local-addr + -Wno-sequence-point + -Wno-sign-compare + -Wno-strict-aliasing -Wno-stringop-overflow - -Wno-attributes - -Wno-logical-not-parentheses -Wno-stringop-truncation - -Wno-memset-elt-size + -Wno-switch + -Wno-uninitialized -Wno-unused-but-set-variable - -Wno-enum-compare - -Wno-int-in-bool-context - -Wno-sequence-point - -Wno-comment - -Wno-restrict - -Wno-format-overflow - -fvisibility-inlines-hidden - -Wno-invalid-offsetof - -Wno-class-memaccess - -Wno-delete-non-virtual-dtor - -Wno-reorder + -Wno-unused-result + -Wno-unused-value + -Wno-unused-variable COMPILATION_DEBUG -O0 # No optimization @@ -88,4 +83,3 @@ ly_append_configurations_options( ) include(cmake/Platform/Common/TargetIncludeSystemDirectories_supported.cmake) - From e1c7dce7a737b68876b8054072e72cdde12c8545 Mon Sep 17 00:00:00 2001 From: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Date: Mon, 31 Jan 2022 15:45:34 -0800 Subject: [PATCH 45/53] Fixes issue with painters being saved and not restored in some cases, which would print numerous warnings in the VS console. (#7296) Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> --- .../UI/Prefab/PrefabUiHandler.cpp | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp index cc0018753e..69d6ae82ca 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp @@ -306,41 +306,37 @@ namespace AzToolsFramework { // Only show the close icon if the prefab is expanded. // This allows the prefab container to be opened if it was collapsed during propagation. - if (!isExpanded) + if (isExpanded) { - return; - } - - // Use the same color as the background. - QColor backgroundColor = m_backgroundColor; - if (isSelected) - { - backgroundColor = m_backgroundSelectedColor; - } - else if (isHovered) - { - backgroundColor = m_backgroundHoverColor; - } + // Use the same color as the background. + QColor backgroundColor = m_backgroundColor; + if (isSelected) + { + backgroundColor = m_backgroundSelectedColor; + } + else if (isHovered) + { + backgroundColor = m_backgroundHoverColor; + } - // Paint a rect to cover up the expander. - QRect rect = QRect(0, 0, 16, 16); - rect.translate(option.rect.topLeft() + offset); - painter->fillRect(rect, backgroundColor); + // Paint a rect to cover up the expander. + QRect rect = QRect(0, 0, 16, 16); + rect.translate(option.rect.topLeft() + offset); + painter->fillRect(rect, backgroundColor); - // Paint the icon. - QIcon closeIcon = QIcon(m_prefabEditCloseIconPath); - painter->drawPixmap(option.rect.topLeft() + offset, closeIcon.pixmap(iconSize)); + // Paint the icon. + QIcon closeIcon = QIcon(m_prefabEditCloseIconPath); + painter->drawPixmap(option.rect.topLeft() + offset, closeIcon.pixmap(iconSize)); + } } else { // Only show the edit icon on hover. - if (!isHovered) + if (isHovered) { - return; + QIcon openIcon = QIcon(m_prefabEditOpenIconPath); + painter->drawPixmap(option.rect.topLeft() + offset, openIcon.pixmap(iconSize)); } - - QIcon openIcon = QIcon(m_prefabEditOpenIconPath); - painter->drawPixmap(option.rect.topLeft() + offset, openIcon.pixmap(iconSize)); } painter->restore(); From f5fcab75d686f3a304e5a02675d1280b07e57252 Mon Sep 17 00:00:00 2001 From: AMZN-nggieber <52797929+AMZN-nggieber@users.noreply.github.com> Date: Mon, 31 Jan 2022 17:06:06 -0800 Subject: [PATCH 46/53] Display Gem Icons in Gem Catalog (#7294) Signed-off-by: nggieber <52797929+AMZN-nggieber@users.noreply.github.com> Co-authored-by: Alex Peterson <26804013+AMZN-alexpete@users.noreply.github.com> --- .../Gem/PythonCoverage/preview.png | 4 +-- AutomatedTesting/Gem/Sponza/preview.png | 3 ++ AutomatedTesting/Gem/preview.png | 4 +-- .../Source/GemCatalog/GemCatalogScreen.cpp | 8 +++-- .../Source/GemCatalog/GemFilterWidget.cpp | 2 +- .../Source/GemCatalog/GemInspector.cpp | 7 ++++- .../Source/GemCatalog/GemInspector.h | 3 +- .../Source/GemCatalog/GemItemDelegate.cpp | 31 +++++++++++++------ .../Source/GemCatalog/GemItemDelegate.h | 13 +++++--- .../Source/ProjectManagerDefs.h | 2 ++ Gems/AssetValidation/preview.png | 4 +-- .../Asset/ImageProcessingAtom/preview.png | 3 ++ Gems/Atom/Asset/Shader/preview.png | 3 ++ Gems/Atom/Bootstrap/preview.png | 3 ++ Gems/Atom/Component/DebugCamera/preview.png | 3 ++ Gems/Atom/Feature/Common/preview.png | 3 ++ Gems/Atom/RHI/DX12/preview.png | 3 ++ Gems/Atom/RHI/Metal/preview.png | 3 ++ Gems/Atom/RHI/Null/preview.png | 3 ++ Gems/Atom/RHI/Vulkan/preview.png | 3 ++ Gems/Atom/RHI/preview.png | 3 ++ Gems/Atom/RPI/preview.png | 3 ++ .../Atom/Tools/AtomToolsFramework/preview.png | 3 ++ Gems/Atom/Tools/MaterialEditor/preview.png | 3 ++ Gems/Atom/preview.png | 3 ++ .../ReferenceMaterials/preview.png | 3 ++ Gems/AtomContent/Sponza/preview.png | 3 ++ Gems/AtomContent/preview.png | 3 ++ Gems/AtomLyIntegration/AtomBridge/preview.png | 3 ++ Gems/AtomLyIntegration/AtomFont/preview.png | 3 ++ .../AtomImGuiTools/preview.png | 3 ++ .../AtomViewportDisplayIcons/preview.png | 3 ++ .../AtomViewportDisplayInfo/preview.png | 3 ++ .../CommonFeatures/preview.png | 3 ++ .../EMotionFXAtom/preview.png | 3 ++ Gems/AtomLyIntegration/ImguiAtom/preview.png | 3 ++ .../DccScriptingInterface/preview.png | 3 ++ Gems/AtomLyIntegration/preview.png | 3 ++ Gems/AtomTressFX/preview.png | 3 ++ Gems/AudioEngineWwise/preview.png | 4 +-- Gems/AudioSystem/preview.png | 4 +-- Gems/CrashReporting/preview.png | 4 +-- Gems/CustomAssetExample/preview.png | 4 +-- Gems/DebugDraw/preview.png | 4 +-- Gems/EMotionFX/preview.png | 3 ++ Gems/EditorPythonBindings/preview.png | 4 +-- Gems/ExpressionEvaluation/preview.png | 4 +-- Gems/GraphModel/preview.png | 3 ++ Gems/LandscapeCanvas/preview.png | 3 ++ Gems/LmbrCentral/preview.png | 4 +-- Gems/Maestro/preview.png | 4 +-- Gems/MotionMatching/preview.png | 4 +-- Gems/MultiplayerCompression/preview.png | 4 +-- Gems/NvCloth/preview.png | 4 +-- Gems/Prefab/PrefabBuilder/preview.png | 3 ++ Gems/Presence/preview.png | 4 +-- Gems/PrimitiveAssets/preview.png | 4 +-- Gems/Profiler/preview.png | 4 +-- Gems/PythonAssetBuilder/preview.png | 4 +-- Gems/QtForPython/preview.png | 4 +-- Gems/ScriptedEntityTweener/preview.png | 4 +-- Gems/SliceFavorites/preview.png | 4 +-- Gems/StartingPointCamera/preview.png | 4 +-- Gems/StartingPointInput/preview.png | 4 +-- Gems/StartingPointMovement/preview.png | 4 +-- Gems/Terrain/preview.png | 3 ++ Gems/TestAssetBuilder/preview.png | 4 +-- Gems/TextureAtlas/preview.png | 4 +-- Gems/VideoPlaybackFramework/preview.png | 4 +-- Gems/WhiteBox/preview.png | 4 +-- 70 files changed, 205 insertions(+), 79 deletions(-) create mode 100644 AutomatedTesting/Gem/Sponza/preview.png create mode 100644 Gems/Atom/Asset/ImageProcessingAtom/preview.png create mode 100644 Gems/Atom/Asset/Shader/preview.png create mode 100644 Gems/Atom/Bootstrap/preview.png create mode 100644 Gems/Atom/Component/DebugCamera/preview.png create mode 100644 Gems/Atom/Feature/Common/preview.png create mode 100644 Gems/Atom/RHI/DX12/preview.png create mode 100644 Gems/Atom/RHI/Metal/preview.png create mode 100644 Gems/Atom/RHI/Null/preview.png create mode 100644 Gems/Atom/RHI/Vulkan/preview.png create mode 100644 Gems/Atom/RHI/preview.png create mode 100644 Gems/Atom/RPI/preview.png create mode 100644 Gems/Atom/Tools/AtomToolsFramework/preview.png create mode 100644 Gems/Atom/Tools/MaterialEditor/preview.png create mode 100644 Gems/Atom/preview.png create mode 100644 Gems/AtomContent/ReferenceMaterials/preview.png create mode 100644 Gems/AtomContent/Sponza/preview.png create mode 100644 Gems/AtomContent/preview.png create mode 100644 Gems/AtomLyIntegration/AtomBridge/preview.png create mode 100644 Gems/AtomLyIntegration/AtomFont/preview.png create mode 100644 Gems/AtomLyIntegration/AtomImGuiTools/preview.png create mode 100644 Gems/AtomLyIntegration/AtomViewportDisplayIcons/preview.png create mode 100644 Gems/AtomLyIntegration/AtomViewportDisplayInfo/preview.png create mode 100644 Gems/AtomLyIntegration/CommonFeatures/preview.png create mode 100644 Gems/AtomLyIntegration/EMotionFXAtom/preview.png create mode 100644 Gems/AtomLyIntegration/ImguiAtom/preview.png create mode 100644 Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/preview.png create mode 100644 Gems/AtomLyIntegration/preview.png create mode 100644 Gems/AtomTressFX/preview.png create mode 100644 Gems/EMotionFX/preview.png create mode 100644 Gems/GraphModel/preview.png create mode 100644 Gems/LandscapeCanvas/preview.png create mode 100644 Gems/Prefab/PrefabBuilder/preview.png create mode 100644 Gems/Terrain/preview.png diff --git a/AutomatedTesting/Gem/PythonCoverage/preview.png b/AutomatedTesting/Gem/PythonCoverage/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/AutomatedTesting/Gem/PythonCoverage/preview.png +++ b/AutomatedTesting/Gem/PythonCoverage/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/AutomatedTesting/Gem/Sponza/preview.png b/AutomatedTesting/Gem/Sponza/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/AutomatedTesting/Gem/preview.png b/AutomatedTesting/Gem/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/AutomatedTesting/Gem/preview.png +++ b/AutomatedTesting/Gem/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index ebabff45cc..8f8c8fb3cb 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -98,15 +98,17 @@ namespace O3DE::ProjectManager constexpr int minHeaderSectionWidth = 100; AdjustableHeaderWidget* listHeaderWidget = new AdjustableHeaderWidget( - QStringList{ tr("Gem Name"), tr("Gem Summary"), tr("Status") }, + QStringList{ tr("Gem Image"), tr("Gem Name"), tr("Gem Summary"), tr("Status") }, QVector{ - GemItemDelegate::s_defaultSummaryStartX - 30, + GemPreviewImageWidth + AdjustableHeaderWidget::s_headerTextIndent, + -GemPreviewImageWidth - AdjustableHeaderWidget::s_headerTextIndent + GemItemDelegate::s_defaultSummaryStartX - 30, 0, // Section is set to stretch to fit - GemItemDelegate::s_buttonWidth + GemItemDelegate::s_itemMargins.left() + GemItemDelegate::s_itemMargins.right() + GemItemDelegate::s_contentMargins.right() + GemItemDelegate::s_statusIconSize + GemItemDelegate::s_statusButtonSpacing + GemItemDelegate::s_buttonWidth + GemItemDelegate::s_contentMargins.right() }, minHeaderSectionWidth, QVector { + QHeaderView::ResizeMode::Fixed, QHeaderView::ResizeMode::Interactive, QHeaderView::ResizeMode::Stretch, QHeaderView::ResizeMode::Fixed diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp index d960145057..e46f71106b 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp @@ -116,7 +116,7 @@ namespace O3DE::ProjectManager // Separating line QFrame* hLine = new QFrame(); hLine->setFrameShape(QFrame::HLine); - hLine->setStyleSheet("color: #666666;"); + hLine->setObjectName("horizontalSeparatingLine"); vLayout->addWidget(hLine); UpdateCollapseState(); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp index 8bdd1f0f0f..f6689281b8 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp @@ -8,7 +8,9 @@ #include #include +#include +#include #include #include #include @@ -118,10 +120,12 @@ namespace O3DE::ProjectManager { m_dependingGems->Update(tr("Depending Gems"), tr("The following Gems will be automatically enabled with this Gem."), dependingGemTags); m_dependingGems->show(); + m_dependingGemsSpacer->changeSize(0, 20, QSizePolicy::Fixed, QSizePolicy::Fixed); } else { m_dependingGems->hide(); + m_dependingGemsSpacer->changeSize(0, 0, QSizePolicy::Fixed, QSizePolicy::Fixed); } // Additional information @@ -246,7 +250,8 @@ namespace O3DE::ProjectManager m_dependingGems = new GemsSubWidget(); connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [this](const Tag& tag){ emit TagClicked(tag); }); m_mainLayout->addWidget(m_dependingGems); - m_mainLayout->addSpacing(20); + m_dependingGemsSpacer = new QSpacerItem(0, 20, QSizePolicy::Fixed, QSizePolicy::Fixed); + m_mainLayout->addSpacerItem(m_dependingGemsSpacer); // Additional information QLabel* additionalInfoLabel = CreateStyledLabel(m_mainLayout, 14, s_headerColor); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h index c71d43eaac..96cbe23ee9 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h @@ -75,8 +75,9 @@ namespace O3DE::ProjectManager QLabel* m_requirementsTextLabel = nullptr; QSpacerItem* m_requirementsMainSpacer = nullptr; - // Depending and conflicting gems + // Depending gems GemsSubWidget* m_dependingGems = nullptr; + QSpacerItem* m_dependingGemsSpacer = nullptr; // Additional information QLabel* m_versionLabel = nullptr; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp index 1733257e3b..2b9c21bff7 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -25,6 +26,7 @@ #include #include #include +#include namespace O3DE::ProjectManager { @@ -117,18 +119,27 @@ namespace O3DE::ProjectManager painter->restore(); } + // Gem preview + QString previewPath = QDir(GemModel::GetPath(modelIndex)).filePath(ProjectPreviewImagePath); + QPixmap gemPreviewImage(previewPath); + QRect gemPreviewRect( + contentRect.left() + AdjustableHeaderWidget::s_headerTextIndent, + contentRect.center().y() - GemPreviewImageHeight / 2, + GemPreviewImageWidth, GemPreviewImageHeight); + painter->drawPixmap(gemPreviewRect, gemPreviewImage); + // Gem name QString gemName = GemModel::GetDisplayName(modelIndex); QFont gemNameFont(options.font); QPair nameXBounds = CalcColumnXBounds(HeaderOrder::Name); const int nameStartX = nameXBounds.first; - const int firstColumnTextStartX = s_itemMargins.left() + nameStartX + AdjustableHeaderWidget::s_headerTextIndent; - const int firstColumnMaxTextWidth = nameXBounds.second - nameStartX - AdjustableHeaderWidget::s_headerTextIndent; + const int nameColumnTextStartX = s_itemMargins.left() + nameStartX + AdjustableHeaderWidget::s_headerTextIndent; + const int nameColumnMaxTextWidth = nameXBounds.second - nameStartX - AdjustableHeaderWidget::s_headerTextIndent; gemNameFont.setPixelSize(static_cast(s_gemNameFontSize)); gemNameFont.setBold(true); - gemName = QFontMetrics(gemNameFont).elidedText(gemName, Qt::TextElideMode::ElideRight, firstColumnMaxTextWidth); + gemName = QFontMetrics(gemNameFont).elidedText(gemName, Qt::TextElideMode::ElideRight, nameColumnMaxTextWidth); QRect gemNameRect = GetTextRect(gemNameFont, gemName, s_gemNameFontSize); - gemNameRect.moveTo(firstColumnTextStartX, contentRect.top()); + gemNameRect.moveTo(nameColumnTextStartX, contentRect.top()); painter->setFont(gemNameFont); painter->setPen(m_textColor); gemNameRect = painter->boundingRect(gemNameRect, Qt::TextSingleLine, gemName); @@ -136,9 +147,9 @@ namespace O3DE::ProjectManager // Gem creator QString gemCreator = GemModel::GetCreator(modelIndex); - gemCreator = standardFontMetrics.elidedText(gemCreator, Qt::TextElideMode::ElideRight, firstColumnMaxTextWidth); + gemCreator = standardFontMetrics.elidedText(gemCreator, Qt::TextElideMode::ElideRight, nameColumnMaxTextWidth); QRect gemCreatorRect = GetTextRect(standardFont, gemCreator, s_fontSize); - gemCreatorRect.moveTo(firstColumnTextStartX, contentRect.top() + gemNameRect.height()); + gemCreatorRect.moveTo(nameColumnTextStartX, contentRect.top() + gemNameRect.height()); painter->setFont(standardFont); gemCreatorRect = painter->boundingRect(gemCreatorRect, Qt::TextSingleLine, gemCreator); @@ -161,7 +172,7 @@ namespace O3DE::ProjectManager QRect GemItemDelegate::CalcSummaryRect(const QRect& contentRect, bool hasTags) const { - const int featureTagAreaHeight = 30; + const int featureTagAreaHeight = 40; const int summaryHeight = contentRect.height() - (hasTags * featureTagAreaHeight); const auto [summaryStartX, summaryEndX] = CalcColumnXBounds(HeaderOrder::Summary); @@ -316,7 +327,7 @@ namespace O3DE::ProjectManager QRect GemItemDelegate::CalcButtonRect(const QRect& contentRect) const { - const QPoint topLeft = QPoint( + const QPoint topLeft = QPoint( s_itemMargins.left() + CalcColumnXBounds(HeaderOrder::Status).first + AdjustableHeaderWidget::s_headerTextIndent + s_statusIconSize + s_statusButtonSpacing, contentRect.center().y() - s_buttonHeight / 2); @@ -327,7 +338,7 @@ namespace O3DE::ProjectManager void GemItemDelegate::DrawPlatformIcons(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const { const GemInfo::Platforms platforms = GemModel::GetPlatforms(modelIndex); - int startX = 0; + int startX = s_itemMargins.left() + CalcColumnXBounds(HeaderOrder::Name).first + AdjustableHeaderWidget::s_headerTextIndent; // Iterate and draw the platforms in the order they are defined in the enum. for (int i = 0; i < GemInfo::NumPlatforms; ++i) @@ -453,7 +464,7 @@ namespace O3DE::ProjectManager } else { - circleCenter = buttonRect.center() + QPoint(-buttonRect.width() / 2 + s_buttonBorderRadius, 1); + circleCenter = buttonRect.center() + QPoint(-buttonRect.width() / 2 + s_buttonBorderRadius + 1, 1); } // Rounded rect diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h index a08fcb0a4b..f034ffce10 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h @@ -47,11 +47,11 @@ namespace O3DE::ProjectManager inline constexpr static int s_height = 105; // Gem item total height inline constexpr static qreal s_gemNameFontSize = 13.0; inline constexpr static qreal s_fontSize = 12.0; - inline constexpr static int s_defaultSummaryStartX = 190; + inline constexpr static int s_defaultSummaryStartX = 270; // Margin and borders - inline constexpr static QMargins s_itemMargins = QMargins(/*left=*/16, /*top=*/8, /*right=*/16, /*bottom=*/8); // Item border distances - inline constexpr static QMargins s_contentMargins = QMargins(/*left=*/20, /*top=*/12, /*right=*/20, /*bottom=*/12); // Distances of the elements within an item to the item borders + inline constexpr static QMargins s_itemMargins = QMargins(/*left=*/16, /*top=*/5, /*right=*/16, /*bottom=*/5); // Item border distances + inline constexpr static QMargins s_contentMargins = QMargins(/*left=*/10, /*top=*/12, /*right=*/20, /*bottom=*/12); // Distances of the elements within an item to the item borders inline constexpr static int s_borderWidth = 4; inline constexpr static int s_extraSummarySpacing = s_itemMargins.right(); @@ -68,8 +68,13 @@ namespace O3DE::ProjectManager inline constexpr static int s_featureTagBorderMarginY = 3; inline constexpr static int s_featureTagSpacing = 7; + // Status icon + inline constexpr static int s_statusIconSize = 16; + inline constexpr static int s_statusButtonSpacing = 5; + enum class HeaderOrder { + Preview, Name, Summary, Status @@ -109,8 +114,6 @@ namespace O3DE::ProjectManager // Status icons void SetStatusIcon(QPixmap& m_iconPixmap, const QString& iconPath); - inline constexpr static int s_statusIconSize = 16; - inline constexpr static int s_statusButtonSpacing = 5; QPixmap m_unknownStatusPixmap; QPixmap m_notDownloadedPixmap; diff --git a/Code/Tools/ProjectManager/Source/ProjectManagerDefs.h b/Code/Tools/ProjectManager/Source/ProjectManagerDefs.h index f184fd2e17..9b2cd73d77 100644 --- a/Code/Tools/ProjectManager/Source/ProjectManagerDefs.h +++ b/Code/Tools/ProjectManager/Source/ProjectManagerDefs.h @@ -15,6 +15,8 @@ namespace O3DE::ProjectManager inline constexpr static int ProjectPreviewImageWidth = 210; inline constexpr static int ProjectPreviewImageHeight = 280; inline constexpr static int ProjectTemplateImageWidth = 92; + inline constexpr static int GemPreviewImageWidth = 70; + inline constexpr static int GemPreviewImageHeight = 40; inline constexpr static int ProjectCommandLineTimeoutSeconds = 30; static const QString ProjectBuildDirectoryName = "build"; diff --git a/Gems/AssetValidation/preview.png b/Gems/AssetValidation/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/Gems/AssetValidation/preview.png +++ b/Gems/AssetValidation/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/Atom/Asset/ImageProcessingAtom/preview.png b/Gems/Atom/Asset/ImageProcessingAtom/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/Atom/Asset/ImageProcessingAtom/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/Atom/Asset/Shader/preview.png b/Gems/Atom/Asset/Shader/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/Atom/Asset/Shader/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/Atom/Bootstrap/preview.png b/Gems/Atom/Bootstrap/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/Atom/Bootstrap/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/Atom/Component/DebugCamera/preview.png b/Gems/Atom/Component/DebugCamera/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/Atom/Component/DebugCamera/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/Atom/Feature/Common/preview.png b/Gems/Atom/Feature/Common/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/Atom/Feature/Common/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/Atom/RHI/DX12/preview.png b/Gems/Atom/RHI/DX12/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/Atom/RHI/DX12/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/Atom/RHI/Metal/preview.png b/Gems/Atom/RHI/Metal/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/Atom/RHI/Metal/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/Atom/RHI/Null/preview.png b/Gems/Atom/RHI/Null/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/Atom/RHI/Null/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/Atom/RHI/Vulkan/preview.png b/Gems/Atom/RHI/Vulkan/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/Atom/RHI/Vulkan/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/Atom/RHI/preview.png b/Gems/Atom/RHI/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/Atom/RHI/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/Atom/RPI/preview.png b/Gems/Atom/RPI/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/Atom/RPI/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/Atom/Tools/AtomToolsFramework/preview.png b/Gems/Atom/Tools/AtomToolsFramework/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/Atom/Tools/AtomToolsFramework/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/Atom/Tools/MaterialEditor/preview.png b/Gems/Atom/Tools/MaterialEditor/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/Atom/Tools/MaterialEditor/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/Atom/preview.png b/Gems/Atom/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/Atom/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/AtomContent/ReferenceMaterials/preview.png b/Gems/AtomContent/ReferenceMaterials/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/AtomContent/ReferenceMaterials/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/AtomContent/Sponza/preview.png b/Gems/AtomContent/Sponza/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/AtomContent/Sponza/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/AtomContent/preview.png b/Gems/AtomContent/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/AtomContent/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/AtomLyIntegration/AtomBridge/preview.png b/Gems/AtomLyIntegration/AtomBridge/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/AtomLyIntegration/AtomBridge/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/AtomLyIntegration/AtomFont/preview.png b/Gems/AtomLyIntegration/AtomFont/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/AtomLyIntegration/AtomFont/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/AtomLyIntegration/AtomImGuiTools/preview.png b/Gems/AtomLyIntegration/AtomImGuiTools/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/AtomLyIntegration/AtomImGuiTools/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayIcons/preview.png b/Gems/AtomLyIntegration/AtomViewportDisplayIcons/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/AtomLyIntegration/AtomViewportDisplayIcons/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/preview.png b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/AtomLyIntegration/CommonFeatures/preview.png b/Gems/AtomLyIntegration/CommonFeatures/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/preview.png b/Gems/AtomLyIntegration/EMotionFXAtom/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/AtomLyIntegration/EMotionFXAtom/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/AtomLyIntegration/ImguiAtom/preview.png b/Gems/AtomLyIntegration/ImguiAtom/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/AtomLyIntegration/ImguiAtom/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/preview.png b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/AtomLyIntegration/preview.png b/Gems/AtomLyIntegration/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/AtomLyIntegration/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/AtomTressFX/preview.png b/Gems/AtomTressFX/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/AtomTressFX/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/AudioEngineWwise/preview.png b/Gems/AudioEngineWwise/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/Gems/AudioEngineWwise/preview.png +++ b/Gems/AudioEngineWwise/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/AudioSystem/preview.png b/Gems/AudioSystem/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/Gems/AudioSystem/preview.png +++ b/Gems/AudioSystem/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/CrashReporting/preview.png b/Gems/CrashReporting/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/Gems/CrashReporting/preview.png +++ b/Gems/CrashReporting/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/CustomAssetExample/preview.png b/Gems/CustomAssetExample/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/Gems/CustomAssetExample/preview.png +++ b/Gems/CustomAssetExample/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/DebugDraw/preview.png b/Gems/DebugDraw/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/Gems/DebugDraw/preview.png +++ b/Gems/DebugDraw/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/EMotionFX/preview.png b/Gems/EMotionFX/preview.png new file mode 100644 index 0000000000..b3b6192ad5 --- /dev/null +++ b/Gems/EMotionFX/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0f8ffb4980f6cfc34135f4a4b9967293ff34bcdb37019181cb22c6a07067ce8 +size 57461 diff --git a/Gems/EditorPythonBindings/preview.png b/Gems/EditorPythonBindings/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/Gems/EditorPythonBindings/preview.png +++ b/Gems/EditorPythonBindings/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/ExpressionEvaluation/preview.png b/Gems/ExpressionEvaluation/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/Gems/ExpressionEvaluation/preview.png +++ b/Gems/ExpressionEvaluation/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/GraphModel/preview.png b/Gems/GraphModel/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/GraphModel/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/LandscapeCanvas/preview.png b/Gems/LandscapeCanvas/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/LandscapeCanvas/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/LmbrCentral/preview.png b/Gems/LmbrCentral/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/Gems/LmbrCentral/preview.png +++ b/Gems/LmbrCentral/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/Maestro/preview.png b/Gems/Maestro/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/Gems/Maestro/preview.png +++ b/Gems/Maestro/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/MotionMatching/preview.png b/Gems/MotionMatching/preview.png index 0f393ac886..2979dbb6a4 100644 --- a/Gems/MotionMatching/preview.png +++ b/Gems/MotionMatching/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7ac9dd09bde78f389e3725ac49d61eff109857e004840bc0bc3881739df9618d -size 2217 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/MultiplayerCompression/preview.png b/Gems/MultiplayerCompression/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/Gems/MultiplayerCompression/preview.png +++ b/Gems/MultiplayerCompression/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/NvCloth/preview.png b/Gems/NvCloth/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/Gems/NvCloth/preview.png +++ b/Gems/NvCloth/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/Prefab/PrefabBuilder/preview.png b/Gems/Prefab/PrefabBuilder/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/Prefab/PrefabBuilder/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/Presence/preview.png b/Gems/Presence/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/Gems/Presence/preview.png +++ b/Gems/Presence/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/PrimitiveAssets/preview.png b/Gems/PrimitiveAssets/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/Gems/PrimitiveAssets/preview.png +++ b/Gems/PrimitiveAssets/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/Profiler/preview.png b/Gems/Profiler/preview.png index 0f393ac886..2979dbb6a4 100644 --- a/Gems/Profiler/preview.png +++ b/Gems/Profiler/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7ac9dd09bde78f389e3725ac49d61eff109857e004840bc0bc3881739df9618d -size 2217 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/PythonAssetBuilder/preview.png b/Gems/PythonAssetBuilder/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/Gems/PythonAssetBuilder/preview.png +++ b/Gems/PythonAssetBuilder/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/QtForPython/preview.png b/Gems/QtForPython/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/Gems/QtForPython/preview.png +++ b/Gems/QtForPython/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/ScriptedEntityTweener/preview.png b/Gems/ScriptedEntityTweener/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/Gems/ScriptedEntityTweener/preview.png +++ b/Gems/ScriptedEntityTweener/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/SliceFavorites/preview.png b/Gems/SliceFavorites/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/Gems/SliceFavorites/preview.png +++ b/Gems/SliceFavorites/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/StartingPointCamera/preview.png b/Gems/StartingPointCamera/preview.png index a8457c7f6e..2979dbb6a4 100644 --- a/Gems/StartingPointCamera/preview.png +++ b/Gems/StartingPointCamera/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7917fbf6e4e3a89e3432b8f48822b660bb245d2b84bb8efdf9f715593c0973df -size 38792 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/StartingPointInput/preview.png b/Gems/StartingPointInput/preview.png index a8457c7f6e..2979dbb6a4 100644 --- a/Gems/StartingPointInput/preview.png +++ b/Gems/StartingPointInput/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7917fbf6e4e3a89e3432b8f48822b660bb245d2b84bb8efdf9f715593c0973df -size 38792 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/StartingPointMovement/preview.png b/Gems/StartingPointMovement/preview.png index a8457c7f6e..2979dbb6a4 100644 --- a/Gems/StartingPointMovement/preview.png +++ b/Gems/StartingPointMovement/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7917fbf6e4e3a89e3432b8f48822b660bb245d2b84bb8efdf9f715593c0973df -size 38792 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/Terrain/preview.png b/Gems/Terrain/preview.png new file mode 100644 index 0000000000..2979dbb6a4 --- /dev/null +++ b/Gems/Terrain/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/TestAssetBuilder/preview.png b/Gems/TestAssetBuilder/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/Gems/TestAssetBuilder/preview.png +++ b/Gems/TestAssetBuilder/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/TextureAtlas/preview.png b/Gems/TextureAtlas/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/Gems/TextureAtlas/preview.png +++ b/Gems/TextureAtlas/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/VideoPlaybackFramework/preview.png b/Gems/VideoPlaybackFramework/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/Gems/VideoPlaybackFramework/preview.png +++ b/Gems/VideoPlaybackFramework/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 diff --git a/Gems/WhiteBox/preview.png b/Gems/WhiteBox/preview.png index 2f1ed47754..2979dbb6a4 100644 --- a/Gems/WhiteBox/preview.png +++ b/Gems/WhiteBox/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa -size 41127 +oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 +size 1232 From dd0f21b46067899a854122215b0ac69d7fd93862 Mon Sep 17 00:00:00 2001 From: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com> Date: Tue, 1 Feb 2022 09:36:42 +0000 Subject: [PATCH 47/53] Fix incorrect icon rendering (#6454) * fix incorrect icon rendering Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com> * add redundant parens Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com> * add tests for icon display fixes Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com> * update references to EditorVisibleEntityDataCache to EditorVisibleEntityDataCacheInterface Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com> * updates following review feedback and remaining updates for EditorVisibleEntityDataCacheInterface Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com> --- .../AzManipulatorTestFramework.h | 2 - .../ViewportInteraction.h | 23 +--- .../Source/ViewportInteraction.cpp | 55 --------- .../PropertyEditor/PropertyEntityIdCtrl.cpp | 2 +- .../UnitTest/AzToolsFrameworkTestHelpers.cpp | 55 +++++++++ .../UnitTest/AzToolsFrameworkTestHelpers.h | 39 +++++- .../MockEditorViewportIconDisplayInterface.h | 27 ++++ ...ockEditorVisibleEntityDataCacheInterface.h | 37 ++++++ .../UnitTest/Mocks/MockFocusModeInterface.h | 29 +++++ .../EditorDefaultSelection.cpp | 3 +- .../EditorDefaultSelection.h | 13 +- .../ViewportSelection/EditorHelpers.cpp | 30 +++-- .../ViewportSelection/EditorHelpers.h | 6 +- .../EditorInteractionSystemComponent.cpp | 2 +- ...ractionSystemViewportSelectionRequestBus.h | 4 +- .../EditorPickEntitySelection.cpp | 2 +- .../EditorPickEntitySelection.h | 6 +- .../EditorTransformComponentSelection.cpp | 6 +- .../EditorTransformComponentSelection.h | 10 +- .../EditorVisibleEntityDataCache.h | 56 ++++++--- .../aztoolsframeworktestcommon_files.cmake | 3 + ...EditorTransformComponentSelectionTests.cpp | 2 +- .../Tests/EditorViewportIconTests.cpp | 116 ++++++++++++++++++ .../Viewport/ViewportEditorModeTests.cpp | 6 +- .../Tests/aztoolsframeworktests_files.cmake | 1 + 25 files changed, 397 insertions(+), 138 deletions(-) create mode 100644 Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/Mocks/MockEditorViewportIconDisplayInterface.h create mode 100644 Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/Mocks/MockEditorVisibleEntityDataCacheInterface.h create mode 100644 Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/Mocks/MockFocusModeInterface.h create mode 100644 Code/Framework/AzToolsFramework/Tests/EditorViewportIconTests.cpp diff --git a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/AzManipulatorTestFramework.h b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/AzManipulatorTestFramework.h index 865a571d3b..b5251d6e97 100644 --- a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/AzManipulatorTestFramework.h +++ b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/AzManipulatorTestFramework.h @@ -47,8 +47,6 @@ namespace AzManipulatorTestFramework virtual void UpdateVisibility() = 0; //! Sets if sticky select is enabled or not. virtual void SetStickySelect(bool enabled) = 0; - //! Gets default Editor Camera Position. - virtual AZ::Vector3 DefaultEditorCameraPosition() const = 0; //! Sets if icons are visible in the viewport. virtual void SetIconsVisible(bool visible) = 0; //! Sets if helpers are visible in the viewport. diff --git a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ViewportInteraction.h b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ViewportInteraction.h index f29245bb34..3de54e9e6d 100644 --- a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ViewportInteraction.h +++ b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ViewportInteraction.h @@ -10,6 +10,7 @@ #include #include +#include namespace AzFramework { @@ -22,7 +23,7 @@ namespace AzManipulatorTestFramework class ViewportInteraction : public ViewportInteractionInterface , public AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler - , public AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::Handler + , public UnitTest::ViewportSettingsTestImpl , private AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler { public: @@ -50,19 +51,6 @@ namespace AzManipulatorTestFramework const AzFramework::ScreenPoint& screenPosition) override; float DeviceScalingFactor() override; - // ViewportSettingsRequestBus overrides ... - bool GridSnappingEnabled() const override; - float GridSize() const override; - bool ShowGrid() const override; - bool AngleSnappingEnabled() const override; - float AngleStep() const override; - float ManipulatorLineBoundWidth() const override; - float ManipulatorCircleBoundWidth() const override; - bool StickySelectEnabled() const override; - AZ::Vector3 DefaultEditorCameraPosition() const override; - bool IconsVisible() const override; - bool HelpersVisible() const override; - // EditorEntityViewportInteractionRequestBus overrides ... void FindVisibleEntities(AZStd::vector& visibleEntities) override; @@ -72,12 +60,5 @@ namespace AzManipulatorTestFramework AzFramework::EntityVisibilityQuery m_entityVisibilityQuery; AZStd::shared_ptr m_debugDisplayRequests; AzFramework::CameraState m_cameraState; - float m_gridSize = 1.0f; - float m_angularStep = 0.0f; - bool m_gridSnapping = false; - bool m_angularSnapping = false; - bool m_stickySelect = true; - bool m_iconsVisible = true; - bool m_helpersVisible = true; }; } // namespace AzManipulatorTestFramework diff --git a/Code/Framework/AzManipulatorTestFramework/Source/ViewportInteraction.cpp b/Code/Framework/AzManipulatorTestFramework/Source/ViewportInteraction.cpp index b7fd5e2b73..0eac957a0a 100644 --- a/Code/Framework/AzManipulatorTestFramework/Source/ViewportInteraction.cpp +++ b/Code/Framework/AzManipulatorTestFramework/Source/ViewportInteraction.cpp @@ -37,46 +37,6 @@ namespace AzManipulatorTestFramework return m_cameraState; } - bool ViewportInteraction::GridSnappingEnabled() const - { - return m_gridSnapping; - } - - float ViewportInteraction::GridSize() const - { - return m_gridSize; - } - - bool ViewportInteraction::ShowGrid() const - { - return false; - } - - bool ViewportInteraction::AngleSnappingEnabled() const - { - return m_angularSnapping; - } - - float ViewportInteraction::AngleStep() const - { - return m_angularStep; - } - - float ViewportInteraction::ManipulatorLineBoundWidth() const - { - return 0.1f; - } - - float ViewportInteraction::ManipulatorCircleBoundWidth() const - { - return 0.1f; - } - - bool ViewportInteraction::StickySelectEnabled() const - { - return m_stickySelect; - } - void ViewportInteraction::FindVisibleEntities(AZStd::vector& visibleEntitiesOut) { visibleEntitiesOut.assign(m_entityVisibilityQuery.Begin(), m_entityVisibilityQuery.End()); @@ -127,11 +87,6 @@ namespace AzManipulatorTestFramework m_helpersVisible = visible; } - AZ::Vector3 ViewportInteraction::DefaultEditorCameraPosition() const - { - return {}; - } - void ViewportInteraction::SetGridSize(float size) { m_gridSize = size; @@ -162,14 +117,4 @@ namespace AzManipulatorTestFramework { return 1.0f; } - - bool ViewportInteraction::IconsVisible() const - { - return m_iconsVisible; - } - - bool ViewportInteraction::HelpersVisible() const - { - return m_helpersVisible; - } } // namespace AzManipulatorTestFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyEntityIdCtrl.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyEntityIdCtrl.cpp index 7a9a5a4d7a..6d32145742 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyEntityIdCtrl.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyEntityIdCtrl.cpp @@ -122,7 +122,7 @@ namespace AzToolsFramework EditorInteractionSystemViewportSelectionRequestBus::Event( GetEntityContextId(), &EditorInteractionSystemViewportSelection::SetHandler, - [](const EditorVisibleEntityDataCache* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker) + [](const EditorVisibleEntityDataCacheInterface* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker) { return AZStd::make_unique(entityDataCache, viewportEditorModeTracker); }); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.cpp index 7f877facb6..dca1da64d8 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.cpp @@ -90,6 +90,61 @@ namespace UnitTest return AZStd::string(keyText.toUtf8().data()); } + bool ViewportSettingsTestImpl::GridSnappingEnabled() const + { + return m_gridSnapping; + } + + float ViewportSettingsTestImpl::GridSize() const + { + return m_gridSize; + } + + bool ViewportSettingsTestImpl::ShowGrid() const + { + return false; + } + + bool ViewportSettingsTestImpl::AngleSnappingEnabled() const + { + return m_angularSnapping; + } + + float ViewportSettingsTestImpl::AngleStep() const + { + return m_angularStep; + } + + float ViewportSettingsTestImpl::ManipulatorLineBoundWidth() const + { + return 0.1f; + } + + float ViewportSettingsTestImpl::ManipulatorCircleBoundWidth() const + { + return 0.1f; + } + + bool ViewportSettingsTestImpl::StickySelectEnabled() const + { + return m_stickySelect; + } + + bool ViewportSettingsTestImpl::IconsVisible() const + { + return m_iconsVisible; + } + + bool ViewportSettingsTestImpl::HelpersVisible() const + { + return m_helpersVisible; + } + + AZ::Vector3 ViewportSettingsTestImpl::DefaultEditorCameraPosition() const + { + return {}; + } + bool TestWidget::eventFilter(QObject* watched, QEvent* event) { AZ_UNUSED(watched); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h index 2c60ca914c..e5a655cfee 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h @@ -91,6 +91,43 @@ namespace UnitTest /// @param modifiers Optional keyboard modifiers to include during the wheel events, defaults to Qt::NoModifier AZStd::string QtKeyToAzString(Qt::Key key, Qt::KeyboardModifiers modifiers = Qt::NoModifier); + //! Test implementation of the ViewportSettingsRequestBus. + //! @note Can be used to customize viewport settings during test execution. + class ViewportSettingsTestImpl : public AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::Handler + { + public: + void Connect(const AzFramework::ViewportId viewportId) + { + AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::Handler::BusConnect(viewportId); + } + + void Disconnect() + { + AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::Handler::BusDisconnect(); + } + + // ViewportSettingsRequestBus overrides ... + bool GridSnappingEnabled() const override; + float GridSize() const override; + bool ShowGrid() const override; + bool AngleSnappingEnabled() const override; + float AngleStep() const override; + float ManipulatorLineBoundWidth() const override; + float ManipulatorCircleBoundWidth() const override; + bool StickySelectEnabled() const override; + AZ::Vector3 DefaultEditorCameraPosition() const override; + bool IconsVisible() const override; + bool HelpersVisible() const override; + + float m_gridSize = 1.0f; + float m_angularStep = 0.0f; + bool m_gridSnapping = false; + bool m_angularSnapping = false; + bool m_stickySelect = true; + bool m_iconsVisible = true; + bool m_helpersVisible = true; + }; + /// Test widget to store QActions generated by EditorTransformComponentSelection. class TestWidget : public QWidget { @@ -207,7 +244,7 @@ namespace UnitTest m_editorActions.Connect(); const auto viewportHandlerBuilder = - [this](const AzToolsFramework::EditorVisibleEntityDataCache* entityDataCache, + [this](const AzToolsFramework::EditorVisibleEntityDataCacheInterface* entityDataCache, [[maybe_unused]] AzToolsFramework::ViewportEditorModeTrackerInterface* viewportEditorModeTracker) { // create the default viewport (handles ComponentMode) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/Mocks/MockEditorViewportIconDisplayInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/Mocks/MockEditorViewportIconDisplayInterface.h new file mode 100644 index 0000000000..be29837b59 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/Mocks/MockEditorViewportIconDisplayInterface.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +#include + +namespace UnitTest +{ + class MockEditorViewportIconDisplayInterface : public AZ::Interface::Registrar + { + public: + virtual ~MockEditorViewportIconDisplayInterface() = default; + + //! AzToolsFramework::EditorViewportIconDisplayInterface overrides ... + MOCK_METHOD1(DrawIcon, void(const DrawParameters&)); + MOCK_METHOD1(GetOrLoadIconForPath, IconId(AZStd::string_view path)); + MOCK_METHOD1(GetIconLoadStatus, IconLoadStatus(IconId icon)); + }; +} // namespace UnitTest diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/Mocks/MockEditorVisibleEntityDataCacheInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/Mocks/MockEditorVisibleEntityDataCacheInterface.h new file mode 100644 index 0000000000..4fc35fa9aa --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/Mocks/MockEditorVisibleEntityDataCacheInterface.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +#include + +namespace UnitTest +{ + class MockEditorVisibleEntityDataCacheInterface : public AzToolsFramework::EditorVisibleEntityDataCacheInterface + { + using ComponentEntityAccentType = AzToolsFramework::Components::EditorSelectionAccentSystemComponent::ComponentEntityAccentType; + + public: + virtual ~MockEditorVisibleEntityDataCacheInterface() = default; + + // AzToolsFramework::EditorVisibleEntityDataCacheInterface overrides ... + MOCK_CONST_METHOD0(VisibleEntityDataCount, size_t()); + MOCK_CONST_METHOD1(GetVisibleEntityPosition, AZ::Vector3(size_t)); + MOCK_CONST_METHOD1(GetVisibleEntityTransform, const AZ::Transform&(size_t)); + MOCK_CONST_METHOD1(GetVisibleEntityId, AZ::EntityId(size_t)); + MOCK_CONST_METHOD1(GetVisibleEntityAccent, ComponentEntityAccentType(size_t)); + MOCK_CONST_METHOD1(IsVisibleEntityLocked, bool(size_t)); + MOCK_CONST_METHOD1(IsVisibleEntityVisible, bool(size_t)); + MOCK_CONST_METHOD1(IsVisibleEntitySelected, bool(size_t)); + MOCK_CONST_METHOD1(IsVisibleEntityIconHidden, bool(size_t)); + MOCK_CONST_METHOD1(IsVisibleEntityIndividuallySelectableInViewport, bool(size_t)); + MOCK_CONST_METHOD1(GetVisibleEntityIndexFromId, AZStd::optional(AZ::EntityId entityId)); + }; +} // namespace UnitTest diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/Mocks/MockFocusModeInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/Mocks/MockFocusModeInterface.h new file mode 100644 index 0000000000..7bb5db14c5 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/Mocks/MockFocusModeInterface.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +#include + +namespace UnitTest +{ + class MockFocusModeInterface : public AZ::Interface::Registrar + { + public: + virtual ~MockFocusModeInterface() = default; + + // AzToolsFramework::FocusModeInterface overrides ... + MOCK_METHOD1(SetFocusRoot, void(AZ::EntityId entityId)); + MOCK_METHOD1(ClearFocusRoot, void(AzFramework::EntityContextId entityContextId)); + MOCK_METHOD1(GetFocusRoot, AZ::EntityId(AzFramework::EntityContextId entityContextId)); + MOCK_METHOD1(GetFocusedEntities, AzToolsFramework::EntityIdList(AzFramework::EntityContextId entityContextId)); + MOCK_CONST_METHOD1(IsInFocusSubTree, bool(AZ::EntityId entityId)); + }; +} // namespace UnitTest diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorDefaultSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorDefaultSelection.cpp index 1541d4678e..4ae22ca899 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorDefaultSelection.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorDefaultSelection.cpp @@ -21,9 +21,8 @@ namespace AzToolsFramework AZ_CLASS_ALLOCATOR_IMPL(EditorDefaultSelection, AZ::SystemAllocator, 0) EditorDefaultSelection::EditorDefaultSelection( - const EditorVisibleEntityDataCache* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker) + const EditorVisibleEntityDataCacheInterface* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker) : m_phantomWidget(nullptr) - , m_entityDataCache(entityDataCache) , m_viewportEditorModeTracker(viewportEditorModeTracker) , m_componentModeCollection(viewportEditorModeTracker) { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorDefaultSelection.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorDefaultSelection.h index f44de0d6eb..f940a3e6b2 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorDefaultSelection.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorDefaultSelection.h @@ -27,7 +27,8 @@ namespace AzToolsFramework AZ_CLASS_ALLOCATOR_DECL //! @cond - EditorDefaultSelection(const EditorVisibleEntityDataCache* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker); + EditorDefaultSelection( + const EditorVisibleEntityDataCacheInterface* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker); EditorDefaultSelection(const EditorDefaultSelection&) = delete; EditorDefaultSelection& operator=(const EditorDefaultSelection&) = delete; virtual ~EditorDefaultSelection(); @@ -85,10 +86,8 @@ namespace AzToolsFramework QWidget m_phantomWidget; //!< The phantom widget responsible for holding QActions while in ComponentMode. QWidget* m_phantomOverrideWidget = nullptr; //!< It's possible to override the phantom widget in special circumstances (eg testing). ComponentModeFramework::ComponentModeCollection m_componentModeCollection; //!< Handles all active ComponentMode types. - AZStd::unique_ptr m_transformComponentSelection = - nullptr; //!< Viewport selection (responsible for - //!< manipulators and transform modifications). - const EditorVisibleEntityDataCache* m_entityDataCache = nullptr; //!< Reference to cached visible EntityData. + //! Viewport selection (responsible for manipulators and transform modifications). + AZStd::unique_ptr m_transformComponentSelection = nullptr; //! Mapping between passed ActionOverride (AddActionOverride) and allocated QAction. struct ActionOverrideMapping @@ -112,7 +111,7 @@ namespace AzToolsFramework AZStd::shared_ptr m_manipulatorManager; //!< The default manipulator manager. ViewportInteraction::MouseInteraction m_currentInteraction; //!< Current mouse interaction to be used for drawing manipulators. - ViewportEditorModeTrackerInterface* m_viewportEditorModeTracker = nullptr; //!< Tracker for activating/deactivating viewport editor modes. - + //! Tracker for activating/deactivating viewport editor modes. + ViewportEditorModeTrackerInterface* m_viewportEditorModeTracker = nullptr; }; } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp index ace2156d85..9b82129582 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp @@ -159,7 +159,7 @@ namespace AzToolsFramework return false; } - EditorHelpers::EditorHelpers(const EditorVisibleEntityDataCache* entityDataCache) + EditorHelpers::EditorHelpers(const EditorVisibleEntityDataCacheInterface* entityDataCache) : m_entityDataCache(entityDataCache) { m_focusModeInterface = AZ::Interface::Get(); @@ -344,9 +344,19 @@ namespace AzToolsFramework continue; } - int iconTextureId = 0; - EditorEntityIconComponentRequestBus::EventResult( - iconTextureId, entityId, &EditorEntityIconComponentRequests::GetEntityIconTextureId); + const AZ::Vector3& entityPosition = m_entityDataCache->GetVisibleEntityPosition(entityCacheIndex); + const AZ::Vector3 entityCameraVector = entityPosition - cameraState.m_position; + + if (const float directionFromCamera = entityCameraVector.Dot(cameraState.m_forward); directionFromCamera < 0.0f) + { + continue; + } + + const float distanceFromCamera = entityCameraVector.GetLength(); + if (distanceFromCamera < cameraState.m_nearClip) + { + continue; + } using ComponentEntityAccentType = Components::EditorSelectionAccentSystemComponent::ComponentEntityAccentType; const AZ::Color iconHighlight = [this, entityCacheIndex]() @@ -364,13 +374,13 @@ namespace AzToolsFramework return AZ::Color(1.0f, 1.0f, 1.0f, 1.0f); }(); - const AZ::Vector3& entityPosition = m_entityDataCache->GetVisibleEntityPosition(entityCacheIndex); - const float distanceFromCamera = cameraState.m_position.GetDistance(entityPosition); - const float iconSize = GetIconSize(distanceFromCamera); + int iconTextureId = 0; + EditorEntityIconComponentRequestBus::EventResult( + iconTextureId, entityId, &EditorEntityIconComponentRequestBus::Events::GetEntityIconTextureId); - editorViewportIconDisplay->DrawIcon({ viewportInfo.m_viewportId, iconTextureId, iconHighlight, entityPosition, - EditorViewportIconDisplayInterface::CoordinateSpace::WorldSpace, - AZ::Vector2{ iconSize, iconSize } }); + editorViewportIconDisplay->DrawIcon(EditorViewportIconDisplayInterface::DrawParameters{ + viewportInfo.m_viewportId, iconTextureId, iconHighlight, entityPosition, + EditorViewportIconDisplayInterface::CoordinateSpace::WorldSpace, AZ::Vector2(GetIconSize(distanceFromCamera)) }); } } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.h index 358e6d9117..b21e0b6737 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.h @@ -24,7 +24,7 @@ namespace AzFramework namespace AzToolsFramework { - class EditorVisibleEntityDataCache; + class EditorVisibleEntityDataCacheInterface; class FocusModeInterface; namespace ViewportInteraction @@ -64,7 +64,7 @@ namespace AzToolsFramework //! An EditorVisibleEntityDataCache must be passed to EditorHelpers to allow it to //! efficiently read entity data without resorting to EBus calls. - explicit EditorHelpers(const EditorVisibleEntityDataCache* entityDataCache); + explicit EditorHelpers(const EditorVisibleEntityDataCacheInterface* entityDataCache); EditorHelpers(const EditorHelpers&) = delete; EditorHelpers& operator=(const EditorHelpers&) = delete; ~EditorHelpers() = default; @@ -103,7 +103,7 @@ namespace AzToolsFramework AZStd::unique_ptr m_invalidClicks; //!< Display for invalid click behavior. - const EditorVisibleEntityDataCache* m_entityDataCache = nullptr; //!< Entity Data queried by the EditorHelpers. + const EditorVisibleEntityDataCacheInterface* m_entityDataCache = nullptr; //!< Entity Data queried by the EditorHelpers. const FocusModeInterface* m_focusModeInterface = nullptr; //!< API to interact with focus mode functionality. }; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorInteractionSystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorInteractionSystemComponent.cpp index 5d03231aab..17ffa2fdc1 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorInteractionSystemComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorInteractionSystemComponent.cpp @@ -84,7 +84,7 @@ namespace AzToolsFramework void EditorInteractionSystemComponent::SetDefaultHandler() { SetHandler( - [](const EditorVisibleEntityDataCache* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker) + [](const EditorVisibleEntityDataCacheInterface* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker) { return AZStd::make_unique(entityDataCache, viewportEditorModeTracker); }); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorInteractionSystemViewportSelectionRequestBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorInteractionSystemViewportSelectionRequestBus.h index 3579460ca0..78fd8d3429 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorInteractionSystemViewportSelectionRequestBus.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorInteractionSystemViewportSelectionRequestBus.h @@ -16,7 +16,7 @@ namespace AzToolsFramework { - class EditorVisibleEntityDataCache; + class EditorVisibleEntityDataCacheInterface; class ViewportEditorModeTrackerInterface; //! Bus to handle all mouse events originating from the viewport. @@ -34,7 +34,7 @@ namespace AzToolsFramework //! Alias for factory function to create a new type implementing the ViewportSelectionRequests interface. using ViewportSelectionRequestsBuilderFn = AZStd::function( - const EditorVisibleEntityDataCache*, ViewportEditorModeTrackerInterface*)>; + const EditorVisibleEntityDataCacheInterface*, ViewportEditorModeTrackerInterface*)>; //! Interface for system component implementing the ViewportSelectionRequests interface. //! This interface also includes a setter to set a custom handler also implementing diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorPickEntitySelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorPickEntitySelection.cpp index 059b265f51..a853a9182d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorPickEntitySelection.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorPickEntitySelection.cpp @@ -17,7 +17,7 @@ namespace AzToolsFramework AZ_CLASS_ALLOCATOR_IMPL(EditorPickEntitySelection, AZ::SystemAllocator, 0) EditorPickEntitySelection::EditorPickEntitySelection( - const EditorVisibleEntityDataCache* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker) + const EditorVisibleEntityDataCacheInterface* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker) : m_editorHelpers(AZStd::make_unique(entityDataCache)) , m_viewportEditorModeTracker(viewportEditorModeTracker) { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorPickEntitySelection.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorPickEntitySelection.h index 62fa4161b7..f941108ef8 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorPickEntitySelection.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorPickEntitySelection.h @@ -13,6 +13,7 @@ namespace AzToolsFramework { + class EditorVisibleEntityDataCacheInterface; class ViewportEditorModeTrackerInterface; //! Viewport interaction that will handle assigning an entity in the viewport to @@ -23,7 +24,7 @@ namespace AzToolsFramework AZ_CLASS_ALLOCATOR_DECL EditorPickEntitySelection( - const EditorVisibleEntityDataCache* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker); + const EditorVisibleEntityDataCacheInterface* entityDataCache, ViewportEditorModeTrackerInterface* viewportEditorModeTracker); ~EditorPickEntitySelection(); private: @@ -35,6 +36,7 @@ namespace AzToolsFramework AZStd::unique_ptr m_editorHelpers; //!< Editor visualization of entities (icons, shapes, debug visuals etc). AZ::EntityId m_hoveredEntityId; //!< What EntityId is the mouse currently hovering over (if any). AZ::EntityId m_cachedEntityIdUnderCursor; //!< Store the EntityId on each mouse move for use in Display. - ViewportEditorModeTrackerInterface* m_viewportEditorModeTracker = nullptr; //!< Tracker for activating/deactivating viewport editor modes. + //! Tracker for activating/deactivating viewport editor modes. + ViewportEditorModeTrackerInterface* m_viewportEditorModeTracker = nullptr; }; } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp index b14f58fafa..5c5cdcb4b4 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp @@ -381,7 +381,7 @@ namespace AzToolsFramework EntityIdContainer& selectedEntityIdsBeforeBoxSelect, EntityIdContainer& potentialSelectedEntityIds, EntityIdContainer& potentialDeselectedEntityIds, - const EditorVisibleEntityDataCache& entityDataCache, + const EditorVisibleEntityDataCacheInterface& entityDataCache, const int viewportId, const ViewportInteraction::KeyboardModifiers currentKeyboardModifiers, const ViewportInteraction::KeyboardModifiers& previousKeyboardModifiers) @@ -958,7 +958,7 @@ namespace AzToolsFramework // (useful in the context of drawing when we only care about entities we can see) // note: return the index if it is selectable, nullopt otherwise static AZStd::optional SelectableInVisibleViewportCache( - const EditorVisibleEntityDataCache& entityDataCache, const AZ::EntityId entityId) + const EditorVisibleEntityDataCacheInterface& entityDataCache, const AZ::EntityId entityId) { if (auto entityIndex = entityDataCache.GetVisibleEntityIndexFromId(entityId)) { @@ -1002,7 +1002,7 @@ namespace AzToolsFramework } } - EditorTransformComponentSelection::EditorTransformComponentSelection(const EditorVisibleEntityDataCache* entityDataCache) + EditorTransformComponentSelection::EditorTransformComponentSelection(const EditorVisibleEntityDataCacheInterface* entityDataCache) : m_entityDataCache(entityDataCache) { const AzFramework::EntityContextId entityContextId = GetEntityContextId(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h index 7b3ba08894..0465a7920f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h @@ -34,7 +34,7 @@ namespace AzToolsFramework { - class EditorVisibleEntityDataCache; + class EditorVisibleEntityDataCacheInterface; using EntityIdSet = AZStd::unordered_set; //!< Alias for unordered_set of EntityIds. @@ -167,7 +167,7 @@ namespace AzToolsFramework AZ_CLASS_ALLOCATOR_DECL EditorTransformComponentSelection() = default; - explicit EditorTransformComponentSelection(const EditorVisibleEntityDataCache* entityDataCache); + explicit EditorTransformComponentSelection(const EditorVisibleEntityDataCacheInterface* entityDataCache); EditorTransformComponentSelection(const EditorTransformComponentSelection&) = delete; EditorTransformComponentSelection& operator=(const EditorTransformComponentSelection&) = delete; virtual ~EditorTransformComponentSelection(); @@ -325,10 +325,8 @@ namespace AzToolsFramework AZ::EntityId m_currentEntityIdUnderCursor; //!< Store the EntityId on each mouse move for use in Display. AZ::EntityId m_editorCameraComponentEntityId; //!< The EditorCameraComponent EntityId if it is set. EntityIdSet m_selectedEntityIds; //!< Represents the current entities in the selection. - - const EditorVisibleEntityDataCache* m_entityDataCache = nullptr; //!< A cache of packed EntityData that can be - //!< iterated over efficiently without the need - //!< to make individual EBus calls. + //! A cache of packed EntityData that can be iterated over efficiently without the need to make individual EBus calls. + const EditorVisibleEntityDataCacheInterface* m_entityDataCache = nullptr; AZStd::unique_ptr m_editorHelpers; //!< Editor visualization of entities (icons, shapes, debug visuals etc). EntityIdManipulators m_entityIdManipulators; //!< Mapping from a Manipulator to potentially many EntityIds. diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorVisibleEntityDataCache.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorVisibleEntityDataCache.h index 4262d334df..44dc570d5a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorVisibleEntityDataCache.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorVisibleEntityDataCache.h @@ -20,10 +20,36 @@ namespace AzToolsFramework { + //! Read-only interface for EditorVisibleEntityDataCache to be used by systems that want to efficiently + //! query the state of visible entities in the viewport. + class EditorVisibleEntityDataCacheInterface + { + using ComponentEntityAccentType = Components::EditorSelectionAccentSystemComponent::ComponentEntityAccentType; + + public: + virtual ~EditorVisibleEntityDataCacheInterface() = default; + + virtual size_t VisibleEntityDataCount() const = 0; + virtual AZ::Vector3 GetVisibleEntityPosition(size_t index) const = 0; + virtual const AZ::Transform& GetVisibleEntityTransform(size_t index) const = 0; + virtual AZ::EntityId GetVisibleEntityId(size_t index) const = 0; + virtual ComponentEntityAccentType GetVisibleEntityAccent(size_t index) const = 0; + virtual bool IsVisibleEntityLocked(size_t index) const = 0; + virtual bool IsVisibleEntityVisible(size_t index) const = 0; + virtual bool IsVisibleEntitySelected(size_t index) const = 0; + virtual bool IsVisibleEntityIconHidden(size_t index) const = 0; + //! Returns true if the entity is individually selectable (none of its ancestors are a closed container entity). + //! @note It may still be desirable to be able to 'click' an entity that is a descendant of a closed container + //! to select the container itself, not the individual entity. + virtual bool IsVisibleEntityIndividuallySelectableInViewport(size_t index) const = 0; + virtual AZStd::optional GetVisibleEntityIndexFromId(AZ::EntityId entityId) const = 0; + }; + //! A cache of packed EntityData that can be iterated over efficiently without //! the need to make individual EBus calls class EditorVisibleEntityDataCache - : private EditorEntityVisibilityNotificationBus::Router + : public EditorVisibleEntityDataCacheInterface + , private EditorEntityVisibilityNotificationBus::Router , private EditorEntityLockComponentNotificationBus::Router , private AZ::TransformNotificationBus::Router , private EditorComponentSelectionNotificationsBus::Router @@ -45,22 +71,18 @@ namespace AzToolsFramework void CalculateVisibleEntityDatas(const AzFramework::ViewportInfo& viewportInfo); - //! EditorVisibleEntityDataCache interface - size_t VisibleEntityDataCount() const; - AZ::Vector3 GetVisibleEntityPosition(size_t index) const; - const AZ::Transform& GetVisibleEntityTransform(size_t index) const; - AZ::EntityId GetVisibleEntityId(size_t index) const; - ComponentEntityAccentType GetVisibleEntityAccent(size_t index) const; - bool IsVisibleEntityLocked(size_t index) const; - bool IsVisibleEntityVisible(size_t index) const; - bool IsVisibleEntitySelected(size_t index) const; - bool IsVisibleEntityIconHidden(size_t index) const; - //! Returns true if the entity is individually selectable (none of its ancestors are a closed container entity). - //! @note It may still be desirable to be able to 'click' an entity that is a descendant of a closed container - //! to select the container itself, not the individual entity. - bool IsVisibleEntityIndividuallySelectableInViewport(size_t index) const; - - AZStd::optional GetVisibleEntityIndexFromId(AZ::EntityId entityId) const; + //! EditorVisibleEntityDataCacheInterface overrides ... + size_t VisibleEntityDataCount() const override; + AZ::Vector3 GetVisibleEntityPosition(size_t index) const override; + const AZ::Transform& GetVisibleEntityTransform(size_t index) const override; + AZ::EntityId GetVisibleEntityId(size_t index) const override; + ComponentEntityAccentType GetVisibleEntityAccent(size_t index) const override; + bool IsVisibleEntityLocked(size_t index) const override; + bool IsVisibleEntityVisible(size_t index) const override; + bool IsVisibleEntitySelected(size_t index) const override; + bool IsVisibleEntityIconHidden(size_t index) const override; + bool IsVisibleEntityIndividuallySelectableInViewport(size_t index) const override; + AZStd::optional GetVisibleEntityIndexFromId(AZ::EntityId entityId) const override; void AddEntityIds(const EntityIdList& entityIds); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframeworktestcommon_files.cmake b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframeworktestcommon_files.cmake index b70407ac68..4259a6ee9c 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframeworktestcommon_files.cmake +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframeworktestcommon_files.cmake @@ -9,6 +9,9 @@ set(FILES UnitTest/AzToolsFrameworkTestHelpers.cpp UnitTest/AzToolsFrameworkTestHelpers.h + UnitTest/Mocks/MockFocusModeInterface.h + UnitTest/Mocks/MockEditorVisibleEntityDataCacheInterface.h + UnitTest/Mocks/MockEditorViewportIconDisplayInterface.h UnitTest/ToolsTestApplication.cpp UnitTest/ToolsTestApplication.h ) diff --git a/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp b/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp index dc9e1180b7..29d72a8270 100644 --- a/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp @@ -265,7 +265,7 @@ namespace UnitTest using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus; EditorInteractionSystemViewportSelectionRequestBus::Event( AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::SetHandler, - [](const AzToolsFramework::EditorVisibleEntityDataCache* entityDataCache, + [](const AzToolsFramework::EditorVisibleEntityDataCacheInterface* entityDataCache, [[maybe_unused]] AzToolsFramework::ViewportEditorModeTrackerInterface* viewportEditorModeTracker) { return AZStd::make_unique(entityDataCache, viewportEditorModeTracker); diff --git a/Code/Framework/AzToolsFramework/Tests/EditorViewportIconTests.cpp b/Code/Framework/AzToolsFramework/Tests/EditorViewportIconTests.cpp new file mode 100644 index 0000000000..46ce9804e6 --- /dev/null +++ b/Code/Framework/AzToolsFramework/Tests/EditorViewportIconTests.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace UnitTest +{ + class EditorViewportIconFixture : public AllocatorsTestFixture + { + public: + inline static constexpr AzFramework::ViewportId TestViewportId = 2468; + + void SetUp() override + { + AllocatorsTestFixture::SetUp(); + + m_focusModeMock = AZStd::make_unique<::testing::NiceMock>(); + m_editorViewportIconDisplayMock = AZStd::make_unique<::testing::NiceMock>(); + m_entityVisibleEntityDataCacheMock = AZStd::make_unique<::testing::NiceMock>(); + m_editorHelpers = AZStd::make_unique(m_entityVisibleEntityDataCacheMock.get()); + m_viewportSettings = AZStd::make_unique(); + + m_viewportSettings->Connect(TestViewportId); + m_viewportSettings->m_helpersVisible = false; + m_viewportSettings->m_iconsVisible = true; + + m_cameraState = AzFramework::CreateDefaultCamera(AZ::Transform::CreateIdentity(), AZ::Vector2(1024.0f, 768.0f)); + + using ::testing::_; + using ::testing::Return; + ON_CALL(*m_entityVisibleEntityDataCacheMock, VisibleEntityDataCount()).WillByDefault(Return(1)); + ON_CALL(*m_entityVisibleEntityDataCacheMock, GetVisibleEntityId(_)).WillByDefault(Return(AZ::EntityId())); + ON_CALL(*m_entityVisibleEntityDataCacheMock, IsVisibleEntityIconHidden(_)).WillByDefault(Return(false)); + ON_CALL(*m_entityVisibleEntityDataCacheMock, IsVisibleEntityVisible(_)).WillByDefault(Return(true)); + ON_CALL(*m_focusModeMock, IsInFocusSubTree(_)).WillByDefault(Return(true)); + } + + void TearDown() override + { + m_viewportSettings->Disconnect(); + m_viewportSettings.reset(); + m_editorHelpers.reset(); + m_entityVisibleEntityDataCacheMock.reset(); + m_editorViewportIconDisplayMock.reset(); + m_focusModeMock.reset(); + + AllocatorsTestFixture::TearDown(); + } + + AZStd::unique_ptr m_viewportSettings; + AZStd::unique_ptr m_editorHelpers; + AZStd::unique_ptr<::testing::NiceMock> m_focusModeMock; + AZStd::unique_ptr<::testing::NiceMock> m_entityVisibleEntityDataCacheMock; + AZStd::unique_ptr<::testing::NiceMock> m_editorViewportIconDisplayMock; + AzFramework::CameraState m_cameraState; + }; + + TEST_F(EditorViewportIconFixture, ViewportIconsAreNotDisplayedWhenInBetweenCameraAndNearClipPlane) + { + NullDebugDisplayRequests nullDebugDisplayRequests; + + const auto insideNearClip = m_cameraState.m_nearClip * 0.5f; + + using ::testing::_; + using ::testing::Return; + // given + // entity position (where icon will be drawn) is in between near clip plane and camera position + ON_CALL(*m_entityVisibleEntityDataCacheMock, GetVisibleEntityPosition(_)) + .WillByDefault(Return(AZ::Vector3(0.0f, insideNearClip, 0.0f))); + + EXPECT_CALL(*m_editorViewportIconDisplayMock, DrawIcon(_)).Times(0); + + // when + m_editorHelpers->DisplayHelpers( + AzFramework::ViewportInfo{ TestViewportId }, m_cameraState, nullDebugDisplayRequests, + [](AZ::EntityId) + { + return true; + }); + } + + TEST_F(EditorViewportIconFixture, ViewportIconsAreNotDisplayedWhenBehindCamera) + { + NullDebugDisplayRequests nullDebugDisplayRequests; + + using ::testing::_; + using ::testing::Return; + // given + // entity position (where icon will be drawn) behind the camera position + ON_CALL(*m_entityVisibleEntityDataCacheMock, GetVisibleEntityPosition(_)).WillByDefault(Return(AZ::Vector3(0.0f, -1.0f, 0.0f))); + + EXPECT_CALL(*m_editorViewportIconDisplayMock, DrawIcon(_)).Times(0); + + // when + m_editorHelpers->DisplayHelpers( + AzFramework::ViewportInfo{ TestViewportId }, m_cameraState, nullDebugDisplayRequests, + [](AZ::EntityId) + { + return true; + }); + } +} // namespace UnitTest diff --git a/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportEditorModeTests.cpp b/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportEditorModeTests.cpp index 85b65f3edf..96e56d0c6a 100644 --- a/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportEditorModeTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportEditorModeTests.cpp @@ -573,7 +573,7 @@ namespace UnitTest using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus; EditorInteractionSystemViewportSelectionRequestBus::Event( AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::SetHandler, - [](const AzToolsFramework::EditorVisibleEntityDataCache* entityDataCache, + [](const AzToolsFramework::EditorVisibleEntityDataCacheInterface* entityDataCache, [[maybe_unused]] AzToolsFramework::ViewportEditorModeTrackerInterface* viewportEditorModeTracker) { return AZStd::make_unique(entityDataCache, viewportEditorModeTracker); @@ -591,7 +591,7 @@ namespace UnitTest using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus; EditorInteractionSystemViewportSelectionRequestBus::Event( AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::SetHandler, - [](const AzToolsFramework::EditorVisibleEntityDataCache* entityDataCache, + [](const AzToolsFramework::EditorVisibleEntityDataCacheInterface* entityDataCache, [[maybe_unused]] AzToolsFramework::ViewportEditorModeTrackerInterface* viewportEditorModeTracker) { return AZStd::make_unique(entityDataCache, viewportEditorModeTracker); @@ -599,7 +599,7 @@ namespace UnitTest EditorInteractionSystemViewportSelectionRequestBus::Event( AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::SetHandler, - [](const AzToolsFramework::EditorVisibleEntityDataCache* entityDataCache, + [](const AzToolsFramework::EditorVisibleEntityDataCacheInterface* entityDataCache, [[maybe_unused]] AzToolsFramework::ViewportEditorModeTrackerInterface* viewportEditorModeTracker) { return AZStd::make_unique(entityDataCache, viewportEditorModeTracker); diff --git a/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake b/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake index 2631a84325..b23713f891 100644 --- a/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake +++ b/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake @@ -24,6 +24,7 @@ set(FILES ComponentModeTests.cpp EditorTransformComponentSelectionTests.cpp EditorVertexSelectionTests.cpp + EditorViewportIconTests.cpp Entity/EditorEntityContextComponentTests.cpp Entity/EditorEntityHelpersTests.cpp Entity/EditorEntitySearchComponentTests.cpp From 27b84761dea017667d9c64f3eab2e99a0c25aa38 Mon Sep 17 00:00:00 2001 From: Ignacio Martinez <82394219+AMZN-Igarri@users.noreply.github.com> Date: Tue, 1 Feb 2022 10:55:03 +0100 Subject: [PATCH 48/53] Adding Collapse All tooltip in the Asset Browser (#7236) * Added Collapse All Tooltip Signed-off-by: AMZN-Igarri <82394219+AMZN-Igarri@users.noreply.github.com> * Changed tooltip duration Signed-off-by: AMZN-Igarri <82394219+AMZN-Igarri@users.noreply.github.com> * Changed tooltip from .ui file Signed-off-by: AMZN-Igarri <82394219+AMZN-Igarri@users.noreply.github.com> * Removed custom tooltip duration Signed-off-by: AMZN-Igarri <82394219+AMZN-Igarri@users.noreply.github.com> --- Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.ui | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.ui b/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.ui index 2cc7c57ccd..e07a5cda95 100644 --- a/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.ui +++ b/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.ui @@ -78,10 +78,7 @@ Qt::ClickFocus - - - - 3 + Collapse All From 1dd4898713fbbcc99557dd0c611d6818151dfd33 Mon Sep 17 00:00:00 2001 From: jiaweig <51759646+jiaweig-amzn@users.noreply.github.com> Date: Tue, 1 Feb 2022 06:47:11 -0800 Subject: [PATCH 49/53] LYN-8551 Terrain: Renderer: Create compute pass for clipmaps (#7116) * Allocate a pass that will be used to generate clipmap Signed-off-by: jiaweig <51759646+jiaweig-amzn@users.noreply.github.com> * Fix some small issues Signed-off-by: jiaweig <51759646+jiaweig-amzn@users.noreply.github.com> * Rename the pass to avoid future conflict. Move assets to terrain gem. Signed-off-by: jiaweig <51759646+jiaweig-amzn@users.noreply.github.com> * Turn the pass off Signed-off-by: jiaweig <51759646+jiaweig-amzn@users.noreply.github.com> * Move pass templates to Terrain gem Signed-off-by: jiaweig <51759646+jiaweig-amzn@users.noreply.github.com> * move load template to private Signed-off-by: jiaweig <51759646+jiaweig-amzn@users.noreply.github.com> * Add macro texture compute pass Signed-off-by: jiaweig <51759646+jiaweig-amzn@users.noreply.github.com> * Fix uncleaned code from previous commit Signed-off-by: jiaweig <51759646+jiaweig-amzn@users.noreply.github.com> --- AutomatedTesting/Passes/MainPipeline.pass | 12 +++- .../TerrainDetailTextureComputePass.pass | 52 +++++++++++++++++ .../TerrainMacroTextureComputePass.pass | 52 +++++++++++++++++ .../Passes/TerrainPassTemplates.azasset | 17 ++++++ .../TerrainDetailTextureComputePass.azsl | 18 ++++++ .../TerrainDetailTextureComputePass.shader | 14 +++++ .../TerrainDetailTextureComputePassSrg.azsli | 16 +++++ .../TerrainMacroTextureComputePass.azsl | 18 ++++++ .../TerrainMacroTextureComputePass.shader | 14 +++++ .../TerrainMacroTextureComputePassSrg.azsli | 16 +++++ .../Passes/TerrainDetailTextureComputePass.h | 55 ++++++++++++++++++ .../Passes/TerrainMacroTextureComputePass.h | 55 ++++++++++++++++++ .../Components/TerrainSystemComponent.cpp | 24 ++++++++ .../Components/TerrainSystemComponent.h | 7 +++ .../TerrainDetailTextureComputePass.cpp | 58 +++++++++++++++++++ .../Passes/TerrainMacroTextureComputePass.cpp | 58 +++++++++++++++++++ .../TerrainFeatureProcessor.cpp | 11 ++-- Gems/Terrain/Code/terrain_files.cmake | 4 ++ 18 files changed, 495 insertions(+), 6 deletions(-) create mode 100644 Gems/Terrain/Assets/Passes/TerrainDetailTextureComputePass.pass create mode 100644 Gems/Terrain/Assets/Passes/TerrainMacroTextureComputePass.pass create mode 100644 Gems/Terrain/Assets/Passes/TerrainPassTemplates.azasset create mode 100644 Gems/Terrain/Assets/Shaders/Terrain/TerrainDetailTextureComputePass.azsl create mode 100644 Gems/Terrain/Assets/Shaders/Terrain/TerrainDetailTextureComputePass.shader create mode 100644 Gems/Terrain/Assets/Shaders/Terrain/TerrainDetailTextureComputePassSrg.azsli create mode 100644 Gems/Terrain/Assets/Shaders/Terrain/TerrainMacroTextureComputePass.azsl create mode 100644 Gems/Terrain/Assets/Shaders/Terrain/TerrainMacroTextureComputePass.shader create mode 100644 Gems/Terrain/Assets/Shaders/Terrain/TerrainMacroTextureComputePassSrg.azsli create mode 100644 Gems/Terrain/Code/Include/Terrain/Passes/TerrainDetailTextureComputePass.h create mode 100644 Gems/Terrain/Code/Include/Terrain/Passes/TerrainMacroTextureComputePass.h create mode 100644 Gems/Terrain/Code/Source/TerrainRenderer/Passes/TerrainDetailTextureComputePass.cpp create mode 100644 Gems/Terrain/Code/Source/TerrainRenderer/Passes/TerrainMacroTextureComputePass.cpp diff --git a/AutomatedTesting/Passes/MainPipeline.pass b/AutomatedTesting/Passes/MainPipeline.pass index c34c556983..39b992a11d 100644 --- a/AutomatedTesting/Passes/MainPipeline.pass +++ b/AutomatedTesting/Passes/MainPipeline.pass @@ -42,6 +42,16 @@ "RayTracingAccelerationStructurePass" ] }, + { + "Name": "TerrainDetailTextureComputePass", + "TemplateName": "TerrainDetailTextureComputePassTemplate", + "Enabled": false + }, + { + "Name": "TerrainMacroTextureComputePass", + "TemplateName": "TerrainMacroTextureComputePassTemplate", + "Enabled": false + }, { "Name": "DepthPrePass", "TemplateName": "DepthMSAAParentTemplate", @@ -215,7 +225,7 @@ // Note: The following two lines represent the choice of rendering pipeline for the hair. // You can either choose to use PPLL or ShortCut and accordingly change the flag // 'm_usePPLLRenderTechnique' in the class 'HairFeatureProcessor.cpp' -// "TemplateName": "HairParentPassTemplate", + // "TemplateName": "HairParentPassTemplate", "TemplateName": "HairParentShortCutPassTemplate", "Enabled": true, "Connections": [ diff --git a/Gems/Terrain/Assets/Passes/TerrainDetailTextureComputePass.pass b/Gems/Terrain/Assets/Passes/TerrainDetailTextureComputePass.pass new file mode 100644 index 0000000000..b880979615 --- /dev/null +++ b/Gems/Terrain/Assets/Passes/TerrainDetailTextureComputePass.pass @@ -0,0 +1,52 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + // Note: all the data here works as placeholders. + "PassTemplate": { + "Name": "TerrainDetailTextureComputePassTemplate", + "PassClass": "TerrainDetailTextureComputePass", + "Slots": [ + { + "Name": "DetailTextureClipmapOutput", + "ShaderInputName": "m_detailTexClipmap", + "SlotType": "Output", + "ScopeAttachmentUsage": "Shader" + } + ], + "ImageAttachments": [ + { + "Name": "DetailTextureClipmap", + "ImageDescriptor": { + "Format": "R32G32B32A32_FLOAT", + "BindFlags": "3", + "SharedQueueMask": "1", + "Size": { + "Width": 1024, + "Height": 1024 + } + } + } + ], + "Connections": [ + { + "LocalSlot": "DetailTextureClipmapOutput", + "AttachmentRef": { + "Pass": "This", + "Attachment": "DetailTextureClipmap" + } + } + ], + "PassData": { + "$type": "Terrain::TerrainDetailTextureComputePassData", + "ShaderAsset": { + "FilePath": "Shaders/Terrain/TerrainDetailTextureComputePass.shader" + }, + "Target Thread Count X": 1024, + "Target Thread Count Y": 1024, + "Target Thread Count Z": 1 + } + } + } +} diff --git a/Gems/Terrain/Assets/Passes/TerrainMacroTextureComputePass.pass b/Gems/Terrain/Assets/Passes/TerrainMacroTextureComputePass.pass new file mode 100644 index 0000000000..ecb31acf69 --- /dev/null +++ b/Gems/Terrain/Assets/Passes/TerrainMacroTextureComputePass.pass @@ -0,0 +1,52 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + // Note: all the data here works as placeholders. + "PassTemplate": { + "Name": "TerrainMacroTextureComputePassTemplate", + "PassClass": "TerrainMacroTextureComputePass", + "Slots": [ + { + "Name": "MacroTextureClipmapOutput", + "ShaderInputName": "m_macroTexClipmap", + "SlotType": "Output", + "ScopeAttachmentUsage": "Shader" + } + ], + "ImageAttachments": [ + { + "Name": "MacroTextureClipmap", + "ImageDescriptor": { + "Format": "R32G32B32A32_FLOAT", + "BindFlags": "3", + "SharedQueueMask": "1", + "Size": { + "Width": 1024, + "Height": 1024 + } + } + } + ], + "Connections": [ + { + "LocalSlot": "MacroTextureClipmapOutput", + "AttachmentRef": { + "Pass": "This", + "Attachment": "MacroTextureClipmap" + } + } + ], + "PassData": { + "$type": "Terrain::TerrainMacroTextureComputePassData", + "ShaderAsset": { + "FilePath": "Shaders/Terrain/TerrainMacroTextureComputePass.shader" + }, + "Target Thread Count X": 1024, + "Target Thread Count Y": 1024, + "Target Thread Count Z": 1 + } + } + } +} diff --git a/Gems/Terrain/Assets/Passes/TerrainPassTemplates.azasset b/Gems/Terrain/Assets/Passes/TerrainPassTemplates.azasset new file mode 100644 index 0000000000..8b20edb6b9 --- /dev/null +++ b/Gems/Terrain/Assets/Passes/TerrainPassTemplates.azasset @@ -0,0 +1,17 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "AssetAliasesSourceData", + "ClassData": { + "AssetPaths": [ + { + "Name": "TerrainDetailTextureComputePassTemplate", + "Path": "Passes/TerrainDetailTextureComputePass.pass" + }, + { + "Name": "TerrainMacroTextureComputePassTemplate", + "Path": "Passes/TerrainMacroTextureComputePass.pass" + } + ] + } +} diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainDetailTextureComputePass.azsl b/Gems/Terrain/Assets/Shaders/Terrain/TerrainDetailTextureComputePass.azsl new file mode 100644 index 0000000000..a982e1aa49 --- /dev/null +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainDetailTextureComputePass.azsl @@ -0,0 +1,18 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#include "TerrainDetailTextureComputePassSrg.azsli" + +[numthreads(32,32,1)] +void MainCS( + uint3 dispatchThreadID : SV_DispatchThreadID, + uint3 groupID : SV_GroupID, + uint groupIndex : SV_GroupIndex) +{ + // Simple code to paint the whole image yellow for debug purpose before we actually write clipmap generation code + PassSrg::m_detailTexClipmap[dispatchThreadID.xy].rgba = float4(1.0, 1.0, 0.0, 1.0); +} diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainDetailTextureComputePass.shader b/Gems/Terrain/Assets/Shaders/Terrain/TerrainDetailTextureComputePass.shader new file mode 100644 index 0000000000..6b0488aaaa --- /dev/null +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainDetailTextureComputePass.shader @@ -0,0 +1,14 @@ +{ + "Source": "TerrainDetailTextureComputePass.azsl", + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "MainCS", + "type": "Compute" + } + ] + } +} diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainDetailTextureComputePassSrg.azsli b/Gems/Terrain/Assets/Shaders/Terrain/TerrainDetailTextureComputePassSrg.azsli new file mode 100644 index 0000000000..aaa67b52da --- /dev/null +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainDetailTextureComputePassSrg.azsli @@ -0,0 +1,16 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +ShaderResourceGroup PassSrg : SRG_PerPass +{ + RWTexture2D m_detailTexClipmap; +} diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainMacroTextureComputePass.azsl b/Gems/Terrain/Assets/Shaders/Terrain/TerrainMacroTextureComputePass.azsl new file mode 100644 index 0000000000..287002cf09 --- /dev/null +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainMacroTextureComputePass.azsl @@ -0,0 +1,18 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#include "TerrainMacroTextureComputePassSrg.azsli" + +[numthreads(32,32,1)] +void MainCS( + uint3 dispatchThreadID : SV_DispatchThreadID, + uint3 groupID : SV_GroupID, + uint groupIndex : SV_GroupIndex) +{ + // Simple code to paint the whole image magenta for debug purpose before we actually write clipmap generation code + PassSrg::m_macroTexClipmap[dispatchThreadID.xy].rgba = float4(1.0, 0.0, 1.0, 1.0); +} diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainMacroTextureComputePass.shader b/Gems/Terrain/Assets/Shaders/Terrain/TerrainMacroTextureComputePass.shader new file mode 100644 index 0000000000..5e043dea12 --- /dev/null +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainMacroTextureComputePass.shader @@ -0,0 +1,14 @@ +{ + "Source": "TerrainMacroTextureComputePass.azsl", + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "MainCS", + "type": "Compute" + } + ] + } +} diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainMacroTextureComputePassSrg.azsli b/Gems/Terrain/Assets/Shaders/Terrain/TerrainMacroTextureComputePassSrg.azsli new file mode 100644 index 0000000000..462e5cee34 --- /dev/null +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainMacroTextureComputePassSrg.azsli @@ -0,0 +1,16 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +ShaderResourceGroup PassSrg : SRG_PerPass +{ + RWTexture2D m_macroTexClipmap; +} diff --git a/Gems/Terrain/Code/Include/Terrain/Passes/TerrainDetailTextureComputePass.h b/Gems/Terrain/Code/Include/Terrain/Passes/TerrainDetailTextureComputePass.h new file mode 100644 index 0000000000..b7d5138d7f --- /dev/null +++ b/Gems/Terrain/Code/Include/Terrain/Passes/TerrainDetailTextureComputePass.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include + +#include +#include +#include +#include + +namespace Terrain +{ + class TerrainFeatureProcessor; + + struct TerrainDetailTextureComputePassData + : public AZ::RPI::ComputePassData + { + AZ_RTTI(Terrain::TerrainDetailTextureComputePassData, "{387F7457-16E5-4AA6-8D96-56ED4532CA8D}", AZ::RPI::ComputePassData); + AZ_CLASS_ALLOCATOR(Terrain::TerrainDetailTextureComputePassData, AZ::SystemAllocator, 0); + + TerrainDetailTextureComputePassData() = default; + virtual ~TerrainDetailTextureComputePassData() = default; + + static void Reflect(AZ::ReflectContext* context); + }; + + class TerrainDetailTextureComputePass + : public AZ::RPI::ComputePass + { + AZ_RPI_PASS(TerrainDetailTextureComputePass); + + public: + AZ_RTTI(Terrain::TerrainDetailTextureComputePass, "{69A8207B-3311-4BB1-BD4E-A08B5E0424B5}", AZ::RPI::ComputePass); + AZ_CLASS_ALLOCATOR(Terrain::TerrainDetailTextureComputePass, AZ::SystemAllocator, 0); + virtual ~TerrainDetailTextureComputePass() = default; + + static AZ::RPI::Ptr Create(const AZ::RPI::PassDescriptor& descriptor); + + void SetFeatureProcessor(); + + void CompileResources(const AZ::RHI::FrameGraphCompileContext& context) override; + private: + TerrainDetailTextureComputePass(const AZ::RPI::PassDescriptor& descriptor); + + void BuildCommandListInternal(const AZ::RHI::FrameGraphExecuteContext& context) override; + + TerrainFeatureProcessor* m_terrainFeatureProcessor; + }; +} // namespace AZ::Render diff --git a/Gems/Terrain/Code/Include/Terrain/Passes/TerrainMacroTextureComputePass.h b/Gems/Terrain/Code/Include/Terrain/Passes/TerrainMacroTextureComputePass.h new file mode 100644 index 0000000000..5cadea0706 --- /dev/null +++ b/Gems/Terrain/Code/Include/Terrain/Passes/TerrainMacroTextureComputePass.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include + +#include +#include +#include +#include + +namespace Terrain +{ + class TerrainFeatureProcessor; + + struct TerrainMacroTextureComputePassData + : public AZ::RPI::ComputePassData + { + AZ_RTTI(Terrain::TerrainMacroTextureComputePassData, "{BB11DACF-AF47-402D-92C6-33C644F6F530}", AZ::RPI::ComputePassData); + AZ_CLASS_ALLOCATOR(Terrain::TerrainMacroTextureComputePassData, AZ::SystemAllocator, 0); + + TerrainMacroTextureComputePassData() = default; + virtual ~TerrainMacroTextureComputePassData() = default; + + static void Reflect(AZ::ReflectContext* context); + }; + + class TerrainMacroTextureComputePass + : public AZ::RPI::ComputePass + { + AZ_RPI_PASS(TerrainMacroTextureComputePass); + + public: + AZ_RTTI(Terrain::TerrainMacroTextureComputePass, "{E493C3D2-D657-49ED-A5B1-A29B2995F6A8}", AZ::RPI::ComputePass); + AZ_CLASS_ALLOCATOR(Terrain::TerrainMacroTextureComputePass, AZ::SystemAllocator, 0); + virtual ~TerrainMacroTextureComputePass() = default; + + static AZ::RPI::Ptr Create(const AZ::RPI::PassDescriptor& descriptor); + + void SetFeatureProcessor(); + + void CompileResources(const AZ::RHI::FrameGraphCompileContext& context) override; + private: + TerrainMacroTextureComputePass(const AZ::RPI::PassDescriptor& descriptor); + + void BuildCommandListInternal(const AZ::RHI::FrameGraphExecuteContext& context) override; + + TerrainFeatureProcessor* m_terrainFeatureProcessor; + }; +} // namespace AZ::Render diff --git a/Gems/Terrain/Code/Source/Components/TerrainSystemComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainSystemComponent.cpp index 5357ccce32..ae321b37ff 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainSystemComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainSystemComponent.cpp @@ -14,6 +14,8 @@ #include #include +#include +#include namespace Terrain { @@ -63,11 +65,33 @@ namespace Terrain // every time an entity is added or removed to a level. If this ever changes, the Terrain System ownership could move into // the level component. m_terrainSystem = new TerrainSystem(); + + auto* passSystem = AZ::RPI::PassSystemInterface::Get(); + AZ_Assert(passSystem, "Cannot get the pass system."); + + // Setup handler for load pass templates mappings + m_loadTemplatesHandler = AZ::RPI::PassSystemInterface::OnReadyLoadTemplatesEvent::Handler([this]() { this->LoadPassTemplateMappings(); }); + passSystem->ConnectEvent(m_loadTemplatesHandler); + + // Register terrain system related passes + passSystem->AddPassCreator(AZ::Name("TerrainDetailTextureComputePass"), &TerrainDetailTextureComputePass::Create); + passSystem->AddPassCreator(AZ::Name("TerrainMacroTextureComputePass"), &TerrainDetailTextureComputePass::Create); } void TerrainSystemComponent::Deactivate() { + m_loadTemplatesHandler.Disconnect(); + delete m_terrainSystem; m_terrainSystem = nullptr; } + + void TerrainSystemComponent::LoadPassTemplateMappings() + { + auto* passSystem = AZ::RPI::PassSystemInterface::Get(); + AZ_Assert(passSystem, "Cannot get the pass system."); + + const char* passTemplatesFile = "Passes/TerrainPassTemplates.azasset"; + passSystem->LoadPassTemplateMappings(passTemplatesFile); + } } diff --git a/Gems/Terrain/Code/Source/Components/TerrainSystemComponent.h b/Gems/Terrain/Code/Source/Components/TerrainSystemComponent.h index b294c0473a..586c9c6be3 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainSystemComponent.h +++ b/Gems/Terrain/Code/Source/Components/TerrainSystemComponent.h @@ -9,6 +9,7 @@ #pragma once #include +#include namespace Terrain { @@ -36,5 +37,11 @@ namespace Terrain //////////////////////////////////////////////////////////////////////// TerrainSystem* m_terrainSystem{ nullptr }; + + private: + //! Used for loading the pass templates of the terrain gem. + AZ::RPI::PassSystemInterface::OnReadyLoadTemplatesEvent::Handler m_loadTemplatesHandler; + + void LoadPassTemplateMappings(); }; } diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/Passes/TerrainDetailTextureComputePass.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/Passes/TerrainDetailTextureComputePass.cpp new file mode 100644 index 0000000000..0bfbfa134d --- /dev/null +++ b/Gems/Terrain/Code/Source/TerrainRenderer/Passes/TerrainDetailTextureComputePass.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include + +namespace Terrain +{ + void TerrainDetailTextureComputePassData::Reflect(AZ::ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(1); + } + } + + AZ::RPI::Ptr TerrainDetailTextureComputePass::Create(const AZ::RPI::PassDescriptor& descriptor) + { + AZ::RPI::Ptr pass = aznew TerrainDetailTextureComputePass(descriptor); + return pass; + } + + TerrainDetailTextureComputePass::TerrainDetailTextureComputePass(const AZ::RPI::PassDescriptor& descriptor) + : AZ::RPI::ComputePass(descriptor) + { + const TerrainDetailTextureComputePass* passData = AZ::RPI::PassUtils::GetPassData(descriptor); + if (passData) + { + // Copy data to pass + + } + } + + void TerrainDetailTextureComputePass::BuildCommandListInternal(const AZ::RHI::FrameGraphExecuteContext& context) + { + ComputePass::BuildCommandListInternal(context); + } + + void TerrainDetailTextureComputePass::SetFeatureProcessor() + { + m_terrainFeatureProcessor = GetRenderPipeline()->GetScene()->GetFeatureProcessor(); + } + + void TerrainDetailTextureComputePass::CompileResources(const AZ::RHI::FrameGraphCompileContext& context) + { + ComputePass::CompileResources(context); + } + +} // namespace Terrain diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/Passes/TerrainMacroTextureComputePass.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/Passes/TerrainMacroTextureComputePass.cpp new file mode 100644 index 0000000000..32eb849db3 --- /dev/null +++ b/Gems/Terrain/Code/Source/TerrainRenderer/Passes/TerrainMacroTextureComputePass.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include + +namespace Terrain +{ + void TerrainMacroTextureComputePassData::Reflect(AZ::ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(1); + } + } + + AZ::RPI::Ptr TerrainMacroTextureComputePass::Create(const AZ::RPI::PassDescriptor& descriptor) + { + AZ::RPI::Ptr pass = aznew TerrainMacroTextureComputePass(descriptor); + return pass; + } + + TerrainMacroTextureComputePass::TerrainMacroTextureComputePass(const AZ::RPI::PassDescriptor& descriptor) + : AZ::RPI::ComputePass(descriptor) + { + const TerrainMacroTextureComputePass* passData = AZ::RPI::PassUtils::GetPassData(descriptor); + if (passData) + { + // Copy data to pass + + } + } + + void TerrainMacroTextureComputePass::BuildCommandListInternal(const AZ::RHI::FrameGraphExecuteContext& context) + { + ComputePass::BuildCommandListInternal(context); + } + + void TerrainMacroTextureComputePass::SetFeatureProcessor() + { + m_terrainFeatureProcessor = GetRenderPipeline()->GetScene()->GetFeatureProcessor(); + } + + void TerrainMacroTextureComputePass::CompileResources(const AZ::RHI::FrameGraphCompileContext& context) + { + ComputePass::CompileResources(context); + } + +} // namespace Terrain diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp index 17cded8c97..e33f951bb1 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp @@ -6,12 +6,13 @@ * */ +#include +#include #include #include - #include - +#include #include #include #include @@ -21,9 +22,6 @@ #include #include #include - -#include - #include #include @@ -55,6 +53,9 @@ namespace Terrain ->Version(0) ; } + + TerrainDetailTextureComputePassData::Reflect(context); + TerrainMacroTextureComputePassData::Reflect(context); } void TerrainFeatureProcessor::Activate() diff --git a/Gems/Terrain/Code/terrain_files.cmake b/Gems/Terrain/Code/terrain_files.cmake index 53a7c6ac6d..cfc7d40cd5 100644 --- a/Gems/Terrain/Code/terrain_files.cmake +++ b/Gems/Terrain/Code/terrain_files.cmake @@ -9,6 +9,8 @@ set(FILES Include/Terrain/Ebuses/TerrainAreaSurfaceRequestBus.h Include/Terrain/TerrainDataConstants.h + Include/Terrain/Passes/TerrainDetailTextureComputePass.h + Include/Terrain/Passes/TerrainMacroTextureComputePass.h Source/Components/TerrainHeightGradientListComponent.cpp Source/Components/TerrainHeightGradientListComponent.h Source/Components/TerrainLayerSpawnerComponent.cpp @@ -39,6 +41,8 @@ set(FILES Source/TerrainRenderer/BindlessImageArrayHandler.h Source/TerrainRenderer/ClipmapBounds.cpp Source/TerrainRenderer/ClipmapBounds.h + Source/TerrainRenderer/Passes/TerrainDetailTextureComputePass.cpp + Source/TerrainRenderer/Passes/TerrainMacroTextureComputePass.cpp Source/TerrainRenderer/TerrainFeatureProcessor.cpp Source/TerrainRenderer/TerrainFeatureProcessor.h Source/TerrainRenderer/TerrainDetailMaterialManager.cpp From ff4529fc60b9ec8f481db488ab271aced97e0229 Mon Sep 17 00:00:00 2001 From: bosnichd Date: Tue, 1 Feb 2022 09:36:44 -0700 Subject: [PATCH 50/53] Terrain ray cast benchmarks and optimization. (#7303) * Terrain ray cast benchmarks and optimization: - Added some benchmarks that exercise terrain ray casting. - Optimized terrain ray casting by removing an unnecessary AABB intersection check (this was suggested by @invertednormal in the original review, but I forgot to actually remove it until now). - Fixed a bug where we were not normalizing the ray direction before performing the Moller-Trumbore ray<->triangle intersection calculations. Signed-off-by: bosnichd * Update to make work with changes pulled down from mainline. Signed-off-by: bosnichd --- .../TerrainRaycast/TerrainRaycastContext.cpp | 29 +------ .../Code/Tests/TerrainSystemBenchmarks.cpp | 84 +++++++++++++++++++ 2 files changed, 86 insertions(+), 27 deletions(-) diff --git a/Gems/Terrain/Code/Source/TerrainRaycast/TerrainRaycastContext.cpp b/Gems/Terrain/Code/Source/TerrainRaycast/TerrainRaycastContext.cpp index 1f05c44314..1cc95d4efc 100644 --- a/Gems/Terrain/Code/Source/TerrainRaycast/TerrainRaycastContext.cpp +++ b/Gems/Terrain/Code/Source/TerrainRaycast/TerrainRaycastContext.cpp @@ -35,7 +35,6 @@ namespace inline void FindNearestIntersection(const AZ::Aabb& aabb, const AZ::Vector3& rayStart, const AZ::Vector3& rayDirection, - const AZ::Vector3& rayDirectionReciprocal, AzFramework::RenderGeometry::RayResult& result) { float intersectionT; @@ -43,7 +42,7 @@ namespace AZ::Vector3 intersectionNormal; const int intersectionResult = AZ::Intersect::IntersectRayAABB(rayStart, rayDirection, - rayDirectionReciprocal, + rayDirection.GetReciprocal(), aabb, intersectionT, intersectionEndT, @@ -117,7 +116,6 @@ namespace const AZ::Aabb& aabb, const AZ::Vector3& rayStart, const AZ::Vector3& rayDirection, - const AZ::Vector3& rayDirectionReciprocal, AzFramework::RenderGeometry::RayResult& result) { // Obtain the height values at each corner of the AABB. @@ -132,26 +130,6 @@ namespace point2.SetZ(terrainSystem.GetHeight(point2, AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT)); point3.SetZ(terrainSystem.GetHeight(point3, AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT)); - // Construct a smaller AABB that tightly encloses the four terrain points. - const float refinedMinZ = AZStd::GetMin(AZStd::GetMin(AZStd::GetMin(point0.GetZ(), point1.GetZ()), point2.GetZ()), point3.GetZ()); - const float refinedMaxZ = AZStd::GetMax(AZStd::GetMax(AZStd::GetMax(point0.GetZ(), point1.GetZ()), point2.GetZ()), point3.GetZ()); - const AZ::Vector3 refinedMin(aabbMin.GetX(), aabbMin.GetY(), refinedMinZ); - const AZ::Vector3 refinedMax(aabbMax.GetX(), aabbMax.GetY(), refinedMaxZ); - const AZ::Aabb refinedAABB = AZ::Aabb::CreateFromMinMax(refinedMin, refinedMax); - - // Check for a hit against the refined AABB. - float intersectionT; - float intersectionEndT; - const int intersectionResult = AZ::Intersect::IntersectRayAABB2(rayStart, - rayDirectionReciprocal, - refinedAABB, - intersectionT, - intersectionEndT); - if (intersectionResult == AZ::Intersect::ISECT_RAY_AABB_NONE) - { - return; - } - // Finally, triangulate the four terrain points and check for a hit, // splitting using the top-left -> bottom-right diagonal so to match // the current behavior of the terrain physics and rendering systems. @@ -218,12 +196,10 @@ namespace { // Find the nearest intersection (if any) between the ray and terrain world bounds. // Note that the ray might (and often will) start inside the terrain world bounds. - const AZ::Vector3 rayDirection = rayEnd - rayStart; - const AZ::Vector3 rayDirectionReciprocal = rayDirection.GetReciprocal(); + const AZ::Vector3 rayDirection = (rayEnd - rayStart).GetNormalized(); FindNearestIntersection(terrainWorldBounds, rayStart, rayDirection, - rayDirectionReciprocal, result); if (!result) { @@ -327,7 +303,6 @@ namespace currentVoxel, rayStart, rayDirection, - rayDirectionReciprocal, result); if (result) { diff --git a/Gems/Terrain/Code/Tests/TerrainSystemBenchmarks.cpp b/Gems/Terrain/Code/Tests/TerrainSystemBenchmarks.cpp index e54747abcc..6aad3f9568 100644 --- a/Gems/Terrain/Code/Tests/TerrainSystemBenchmarks.cpp +++ b/Gems/Terrain/Code/Tests/TerrainSystemBenchmarks.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -682,6 +683,89 @@ namespace UnitTest ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) ->Unit(::benchmark::kMillisecond); + + BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_GetClosestIntersectionRandom)(benchmark::State& state) + { + // Run the benchmark + const uint32_t numRays = aznumeric_cast(state.range(1)); + RunTerrainApiBenchmark( + state, + [numRays]([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, + [[maybe_unused]] AzFramework::Terrain::TerrainDataRequests::Sampler sampler) + { + // Cast rays starting at random positions above the terrain, + // and ending at a random positions below the terrain. + AZ::SimpleLcgRandom random; + AzFramework::RenderGeometry::RayRequest ray; + AzFramework::RenderGeometry::RayResult result; + for (uint32_t i = 0; i < numRays; ++i) + { + ray.m_startWorldPosition.SetX(worldBounds.GetMin().GetX() + (random.GetRandomFloat() * worldBounds.GetXExtent())); + ray.m_startWorldPosition.SetY(worldBounds.GetMin().GetY() + (random.GetRandomFloat() * worldBounds.GetYExtent())); + ray.m_startWorldPosition.SetZ(worldBounds.GetMax().GetZ()); + ray.m_endWorldPosition.SetX(worldBounds.GetMin().GetX() + (random.GetRandomFloat() * worldBounds.GetXExtent())); + ray.m_endWorldPosition.SetY(worldBounds.GetMin().GetY() + (random.GetRandomFloat() * worldBounds.GetYExtent())); + ray.m_endWorldPosition.SetZ(worldBounds.GetMin().GetZ()); + AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( + result, &AzFramework::Terrain::TerrainDataRequests::GetClosestIntersection, ray); + } + }); + } + + BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_GetClosestIntersectionRandom) + ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Args({ 4096, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Args({ 1024, 10, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Args({ 2048, 10, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Args({ 4096, 10, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Args({ 1024, 100, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Args({ 2048, 100, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Args({ 4096, 100, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Args({ 1024, 1000, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Args({ 2048, 1000, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Args({ 4096, 1000, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Unit(::benchmark::kMillisecond); + + BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_GetClosestIntersectionWorstCase)(benchmark::State& state) + { + // Run the benchmark + const uint32_t numRays = aznumeric_cast(state.range(1)); + RunTerrainApiBenchmark( + state, + [numRays]([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds, + [[maybe_unused]] AzFramework::Terrain::TerrainDataRequests::Sampler sampler) + { + // Cast rays starting at an upper corner of the terrain world, + // and ending at the opposite top corner of the terrain world, + // traversing the entire grid without finding an intersection. + AzFramework::RenderGeometry::RayRequest ray; + AzFramework::RenderGeometry::RayResult result; + ray.m_startWorldPosition = worldBounds.GetMax(); + ray.m_endWorldPosition = worldBounds.GetMin(); + ray.m_endWorldPosition.SetZ(ray.m_startWorldPosition.GetZ()); + for (uint32_t i = 0; i < numRays; ++i) + { + AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( + result, &AzFramework::Terrain::TerrainDataRequests::GetClosestIntersection, ray); + } + }); + } + + BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_GetClosestIntersectionWorstCase) + ->Args({ 1024, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Args({ 2048, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Args({ 4096, 1, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Args({ 1024, 10, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Args({ 2048, 10, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Args({ 4096, 10, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Args({ 1024, 100, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Args({ 2048, 100, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Args({ 4096, 100, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Args({ 1024, 1000, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Args({ 2048, 1000, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Args({ 4096, 1000, static_cast(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) }) + ->Unit(::benchmark::kMillisecond); #endif } From 3f63cf3546b26fd5653076c43bebd97ea6bb08db Mon Sep 17 00:00:00 2001 From: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> Date: Tue, 1 Feb 2022 10:44:14 -0600 Subject: [PATCH 51/53] Misc SurfaceData Optimizations (#7299) * Misc SurfaceData Optimizations. This includes a few different optimizations found while trying to make the bulk query APIs faster: * Switches mutexes over to shared_lock to optimize for the multi-reader-single-writer pattern * Surface provider point creation now uses a pre-created set of masks to initialize with, and uses std::move() to move the created point into the output list instead of copying it. * Splits CombineSortAndFilterNeightboringPoints so that the FilterPoints() can occur separately and efficiently with erase/remove_if, and avoids making a copy of the output points. * Optimized SurfaceDataShapeComponent::ModifySurfacePoints * Fixed potential bug where the sort wasn't stable since it only compared the Z value, and could have produced unexpected results for differing points with the exact same Z value. * Fixed up a couple small bugs and missing checks in the unit tests Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Fixed syntax on unit tests. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> --- .../SurfaceData/SurfaceDataMeshComponent.cpp | 11 +- .../SurfaceData/SurfaceDataMeshComponent.h | 4 +- .../SurfaceData/Utility/SurfaceDataUtility.h | 13 +- .../SurfaceDataColliderComponent.cpp | 16 +- .../Components/SurfaceDataColliderComponent.h | 4 +- .../Components/SurfaceDataShapeComponent.cpp | 40 ++--- .../Components/SurfaceDataShapeComponent.h | 4 +- .../Source/SurfaceDataSystemComponent.cpp | 162 +++++++++--------- .../Code/Source/SurfaceDataSystemComponent.h | 6 +- .../SurfaceDataColliderComponentTest.cpp | 26 ++- .../Code/Tests/SurfaceDataTest.cpp | 104 +++++------ .../TerrainSurfaceDataSystemComponent.cpp | 2 +- 12 files changed, 216 insertions(+), 176 deletions(-) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SurfaceData/SurfaceDataMeshComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SurfaceData/SurfaceDataMeshComponent.cpp index 862e3599a3..71e880fdef 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SurfaceData/SurfaceDataMeshComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SurfaceData/SurfaceDataMeshComponent.cpp @@ -95,6 +95,7 @@ namespace SurfaceData m_refresh = false; // Update the cached mesh data and bounds, then register the surface data provider + AssignSurfaceTagWeights(m_configuration.m_tags, 1.0f, m_newPointWeights); UpdateMeshData(); } @@ -115,7 +116,7 @@ namespace SurfaceData // Clear the cached mesh data { - AZStd::lock_guard lock(m_cacheMutex); + AZStd::unique_lock lock(m_cacheMutex); m_meshAssetData = {}; m_meshBounds = AZ::Aabb::CreateNull(); m_meshWorldTM = AZ::Transform::CreateIdentity(); @@ -145,7 +146,7 @@ namespace SurfaceData bool SurfaceDataMeshComponent::DoRayTrace(const AZ::Vector3& inPosition, AZ::Vector3& outPosition, AZ::Vector3& outNormal) const { - AZStd::lock_guard lock(m_cacheMutex); + AZStd::shared_lock lock(m_cacheMutex); // test AABB as first pass to claim the point const AZ::Vector3 testPosition = AZ::Vector3( @@ -181,8 +182,8 @@ namespace SurfaceData point.m_entityId = GetEntityId(); point.m_position = hitPosition; point.m_normal = hitNormal; - AddMaxValueForMasks(point.m_masks, m_configuration.m_tags, 1.0f); - surfacePointList.push_back(point); + point.m_masks = m_newPointWeights; + surfacePointList.push_back(AZStd::move(point)); } } @@ -235,7 +236,7 @@ namespace SurfaceData bool meshValidAfterUpdate = false; { - AZStd::lock_guard lock(m_cacheMutex); + AZStd::unique_lock lock(m_cacheMutex); meshValidBeforeUpdate = (m_meshAssetData.GetAs() != nullptr) && (m_meshBounds.IsValid()); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SurfaceData/SurfaceDataMeshComponent.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SurfaceData/SurfaceDataMeshComponent.h index 92536f9523..758bb1ee99 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SurfaceData/SurfaceDataMeshComponent.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SurfaceData/SurfaceDataMeshComponent.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -96,11 +97,12 @@ namespace SurfaceData // cached data AZStd::atomic_bool m_refresh{ false }; - mutable AZStd::recursive_mutex m_cacheMutex; + mutable AZStd::shared_mutex m_cacheMutex; AZ::Data::Asset m_meshAssetData; AZ::Transform m_meshWorldTM = AZ::Transform::CreateIdentity(); AZ::Transform m_meshWorldTMInverse = AZ::Transform::CreateIdentity(); AZ::Vector3 m_meshNonUniformScale = AZ::Vector3::CreateOne(); AZ::Aabb m_meshBounds = AZ::Aabb::CreateNull(); + SurfaceTagWeightMap m_newPointWeights; }; } diff --git a/Gems/SurfaceData/Code/Include/SurfaceData/Utility/SurfaceDataUtility.h b/Gems/SurfaceData/Code/Include/SurfaceData/Utility/SurfaceDataUtility.h index 29b3fac8c7..2a4a39ee3b 100644 --- a/Gems/SurfaceData/Code/Include/SurfaceData/Utility/SurfaceDataUtility.h +++ b/Gems/SurfaceData/Code/Include/SurfaceData/Utility/SurfaceDataUtility.h @@ -88,6 +88,16 @@ namespace SurfaceData const AZ::Vector3& rayStart, const AZ::Vector3& rayEnd, AZ::Vector3& outPosition, AZ::Vector3& outNormal); + AZ_INLINE void AssignSurfaceTagWeights(const SurfaceTagVector& tags, float weight, SurfaceTagWeightMap& weights) + { + weights.clear(); + weights.reserve(tags.size()); + for (auto& tag : tags) + { + weights[tag] = weight; + } + } + AZ_INLINE void AddMaxValueForMasks(SurfaceTagWeightMap& masks, const AZ::Crc32 tag, const float value) { const auto maskItr = masks.find(tag); @@ -166,7 +176,8 @@ namespace SurfaceData } template - AZ_INLINE bool HasMatchingTags(const SurfaceTagWeightMap& sourceTags, const SampleContainer& sampleTags, float valueMin, float valueMax) + AZ_INLINE bool HasMatchingTags( + const SurfaceTagWeightMap& sourceTags, const SampleContainer& sampleTags, float valueMin, float valueMax) { for (const auto& sampleTag : sampleTags) { diff --git a/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.cpp b/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.cpp index d7de299829..dc4e5e6e74 100644 --- a/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.cpp +++ b/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.cpp @@ -132,6 +132,7 @@ namespace SurfaceData Physics::ColliderComponentEventBus::Handler::BusConnect(GetEntityId()); // Update the cached collider data and bounds, then register the surface data provider / modifier + AssignSurfaceTagWeights(m_configuration.m_providerTags, 1.0f, m_newPointWeights); UpdateColliderData(); } @@ -157,7 +158,7 @@ namespace SurfaceData // Clear the cached mesh data { - AZStd::lock_guard lock(m_cacheMutex); + AZStd::unique_lock lock(m_cacheMutex); m_colliderBounds = AZ::Aabb::CreateNull(); } } @@ -184,7 +185,7 @@ namespace SurfaceData bool SurfaceDataColliderComponent::DoRayTrace(const AZ::Vector3& inPosition, bool queryPointOnly, AZ::Vector3& outPosition, AZ::Vector3& outNormal) const { - AZStd::lock_guard lock(m_cacheMutex); + AZStd::shared_lock lock(m_cacheMutex); // test AABB as first pass to claim the point const AZ::Vector3 testPosition = AZ::Vector3( @@ -240,17 +241,14 @@ namespace SurfaceData point.m_entityId = GetEntityId(); point.m_position = hitPosition; point.m_normal = hitNormal; - for (auto& tag : m_configuration.m_providerTags) - { - point.m_masks[tag] = 1.0f; - } - surfacePointList.push_back(point); + point.m_masks = m_newPointWeights; + surfacePointList.push_back(AZStd::move(point)); } } void SurfaceDataColliderComponent::ModifySurfacePoints(SurfacePointList& surfacePointList) const { - AZStd::lock_guard lock(m_cacheMutex); + AZStd::shared_lock lock(m_cacheMutex); if (m_colliderBounds.IsValid() && !m_configuration.m_modifierTags.empty()) { @@ -308,7 +306,7 @@ namespace SurfaceData bool colliderValidAfterUpdate = false; { - AZStd::lock_guard lock(m_cacheMutex); + AZStd::unique_lock lock(m_cacheMutex); colliderValidBeforeUpdate = m_colliderBounds.IsValid(); diff --git a/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.h b/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.h index 43528ecae3..50c84f9519 100644 --- a/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.h +++ b/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -98,7 +99,8 @@ namespace SurfaceData // cached data AZStd::atomic_bool m_refresh{ false }; - mutable AZStd::recursive_mutex m_cacheMutex; + mutable AZStd::shared_mutex m_cacheMutex; AZ::Aabb m_colliderBounds = AZ::Aabb::CreateNull(); + SurfaceTagWeightMap m_newPointWeights; }; } diff --git a/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.cpp b/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.cpp index a45903498e..682bac150a 100644 --- a/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.cpp +++ b/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.cpp @@ -89,6 +89,7 @@ namespace SurfaceData LmbrCentral::ShapeComponentNotificationsBus::Handler::BusConnect(GetEntityId()); // Update the cached shape data and bounds, then register the surface data provider / modifier + AssignSurfaceTagWeights(m_configuration.m_providerTags, 1.0f, m_newPointWeights); UpdateShapeData(); } @@ -115,7 +116,7 @@ namespace SurfaceData // Clear the cached shape data { - AZStd::lock_guard lock(m_cacheMutex); + AZStd::unique_lock lock(m_cacheMutex); m_shapeBounds = AZ::Aabb::CreateNull(); m_shapeBoundsIsValid = false; } @@ -143,7 +144,7 @@ namespace SurfaceData void SurfaceDataShapeComponent::GetSurfacePoints(const AZ::Vector3& inPosition, SurfacePointList& surfacePointList) const { - AZStd::lock_guard lock(m_cacheMutex); + AZStd::shared_lock lock(m_cacheMutex); if (m_shapeBoundsIsValid) { @@ -158,36 +159,35 @@ namespace SurfaceData point.m_entityId = GetEntityId(); point.m_position = rayOrigin + intersectionDistance * rayDirection; point.m_normal = AZ::Vector3::CreateAxisZ(); - for (auto& tag : m_configuration.m_providerTags) - { - point.m_masks[tag] = 1.0f; - } - surfacePointList.push_back(point); + point.m_masks = m_newPointWeights; + surfacePointList.push_back(AZStd::move(point)); } } } void SurfaceDataShapeComponent::ModifySurfacePoints(SurfacePointList& surfacePointList) const { - AZ_PROFILE_FUNCTION(Entity); - - AZStd::lock_guard lock(m_cacheMutex); + AZStd::shared_lock lock(m_cacheMutex); if (m_shapeBoundsIsValid && !m_configuration.m_modifierTags.empty()) { const AZ::EntityId entityId = GetEntityId(); - for (auto& point : surfacePointList) - { - if (point.m_entityId != entityId && m_shapeBounds.Contains(point.m_position)) + LmbrCentral::ShapeComponentRequestsBus::Event( + GetEntityId(), + [entityId, this, &surfacePointList](LmbrCentral::ShapeComponentRequestsBus::Events* shape) { - bool inside = false; - LmbrCentral::ShapeComponentRequestsBus::EventResult(inside, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::IsPointInside, point.m_position); - if (inside) + for (auto& point : surfacePointList) { - AddMaxValueForMasks(point.m_masks, m_configuration.m_modifierTags, 1.0f); + if (point.m_entityId != entityId && m_shapeBounds.Contains(point.m_position)) + { + bool inside = shape->IsPointInside(point.m_position); + if (inside) + { + AddMaxValueForMasks(point.m_masks, m_configuration.m_modifierTags, 1.0f); + } + } } - } - } + }); } } @@ -228,7 +228,7 @@ namespace SurfaceData bool shapeValidAfterUpdate = false; { - AZStd::lock_guard lock(m_cacheMutex); + AZStd::unique_lock lock(m_cacheMutex); shapeValidBeforeUpdate = m_shapeBoundsIsValid; diff --git a/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.h b/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.h index f2c478ed27..59b7950412 100644 --- a/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.h +++ b/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -92,9 +93,10 @@ namespace SurfaceData // cached data AZStd::atomic_bool m_refresh{ false }; - mutable AZStd::recursive_mutex m_cacheMutex; + mutable AZStd::shared_mutex m_cacheMutex; AZ::Aabb m_shapeBounds = AZ::Aabb::CreateNull(); bool m_shapeBoundsIsValid = false; static const float s_rayAABBHeightPadding; + SurfaceTagWeightMap m_newPointWeights; }; } diff --git a/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.cpp b/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.cpp index a2a3bc352e..66f282d12b 100644 --- a/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.cpp +++ b/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.cpp @@ -181,10 +181,10 @@ namespace SurfaceData void SurfaceDataSystemComponent::GetSurfacePoints(const AZ::Vector3& inPosition, const SurfaceTagVector& desiredTags, SurfacePointList& surfacePointList) const { - const bool hasDesiredTags = HasValidTags(desiredTags); - const bool hasModifierTags = hasDesiredTags && HasMatchingTags(desiredTags, m_registeredModifierTags); + const bool useTagFilters = HasValidTags(desiredTags); + const bool hasModifierTags = useTagFilters && HasMatchingTags(desiredTags, m_registeredModifierTags); - AZStd::lock_guard registrationLock(m_registrationMutex); + AZStd::shared_lock registrationLock(m_registrationMutex); surfacePointList.clear(); @@ -195,7 +195,7 @@ namespace SurfaceData const SurfaceDataRegistryEntry& entry = entryPair.second; if (!entry.m_bounds.IsValid() || AabbContains2D(entry.m_bounds, inPosition)) { - if (!hasDesiredTags || hasModifierTags || HasMatchingTags(desiredTags, entry.m_tags)) + if (!useTagFilters || hasModifierTags || HasMatchingTags(desiredTags, entry.m_tags)) { SurfaceDataProviderRequestBus::Event(entryAddress, &SurfaceDataProviderRequestBus::Events::GetSurfacePoints, inPosition, surfacePointList); } @@ -219,7 +219,12 @@ namespace SurfaceData // same XY coordinates and extremely similar Z values. This produces results that are sorted in decreasing Z order. // Also, this filters out any remaining points that don't match the desired tag list. This can happen when a surface provider // doesn't add a desired tag, and a surface modifier has the *potential* to add it, but then doesn't. - CombineSortAndFilterNeighboringPoints(surfacePointList, hasDesiredTags, desiredTags); + if (useTagFilters) + { + FilterPoints(surfacePointList, desiredTags); + } + + CombineAndSortNeighboringPoints(surfacePointList); } } @@ -248,34 +253,33 @@ namespace SurfaceData void SurfaceDataSystemComponent::GetSurfacePointsFromList( AZStd::span inPositions, const SurfaceTagVector& desiredTags, SurfacePointLists& surfacePointLists) const { - AZStd::lock_guard registrationLock(m_registrationMutex); + AZStd::shared_lock registrationLock(m_registrationMutex); const size_t totalQueryPositions = inPositions.size(); surfacePointLists.clear(); surfacePointLists.resize(totalQueryPositions); - const bool hasDesiredTags = HasValidTags(desiredTags); - const bool hasModifierTags = hasDesiredTags && HasMatchingTags(desiredTags, m_registeredModifierTags); + const bool useTagFilters = HasValidTags(desiredTags); + const bool hasModifierTags = useTagFilters && HasMatchingTags(desiredTags, m_registeredModifierTags); // Loop through each data provider, and query all the points for each one. This allows us to check the tags and the overall // AABB bounds just once per provider, instead of once per point. It also allows for an eventual optimization in which we could // send the list of points directly into each SurfaceDataProvider. - for (const auto& entryPair : m_registeredSurfaceDataProviders) + for (const auto& [providerHandle, provider] : m_registeredSurfaceDataProviders) { - const SurfaceDataRegistryEntry& entry = entryPair.second; - bool alwaysApplies = !entry.m_bounds.IsValid(); + bool hasInfiniteBounds = !provider.m_bounds.IsValid(); - if (!hasDesiredTags || hasModifierTags || HasMatchingTags(desiredTags, entry.m_tags)) + if (!useTagFilters || hasModifierTags || HasMatchingTags(desiredTags, provider.m_tags)) { for (size_t index = 0; index < totalQueryPositions; index++) { - const auto& inPosition = inPositions[index]; - SurfacePointList& surfacePointList = surfacePointLists[index]; - if (alwaysApplies || AabbContains2D(entry.m_bounds, inPosition)) + bool inBounds = hasInfiniteBounds || AabbContains2D(provider.m_bounds, inPositions[index]); + if (inBounds) { SurfaceDataProviderRequestBus::Event( - entryPair.first, &SurfaceDataProviderRequestBus::Events::GetSurfacePoints, inPosition, surfacePointList); + providerHandle, &SurfaceDataProviderRequestBus::Events::GetSurfacePoints, + inPositions[index], surfacePointLists[index]); } } } @@ -289,7 +293,7 @@ namespace SurfaceData for (const auto& entryPair : m_registeredSurfaceDataModifiers) { const SurfaceDataRegistryEntry& entry = entryPair.second; - bool alwaysApplies = !entry.m_bounds.IsValid(); + bool hasInfiniteBounds = !entry.m_bounds.IsValid(); for (size_t index = 0; index < totalQueryPositions; index++) { @@ -297,10 +301,11 @@ namespace SurfaceData SurfacePointList& surfacePointList = surfacePointLists[index]; if (!surfacePointList.empty()) { - if (alwaysApplies || AabbContains2D(entry.m_bounds, inPosition)) + if (hasInfiniteBounds || AabbContains2D(entry.m_bounds, inPosition)) { SurfaceDataModifierRequestBus::Event( - entryPair.first, &SurfaceDataModifierRequestBus::Events::ModifySurfacePoints, surfacePointList); + entryPair.first, &SurfaceDataModifierRequestBus::Events::ModifySurfacePoints, + surfacePointList); } } } @@ -312,88 +317,85 @@ namespace SurfaceData // doesn't add a desired tag, and a surface modifier has the *potential* to add it, but then doesn't. for (auto& surfacePointList : surfacePointLists) { - if (!surfacePointList.empty()) + if (useTagFilters) { - CombineSortAndFilterNeighboringPoints(surfacePointList, hasDesiredTags, desiredTags); + FilterPoints(surfacePointList, desiredTags); } + CombineAndSortNeighboringPoints(surfacePointList); } - } - + } - void SurfaceDataSystemComponent::CombineSortAndFilterNeighboringPoints(SurfacePointList& sourcePointList, bool hasDesiredTags, const SurfaceTagVector& desiredTags) const + void SurfaceDataSystemComponent::FilterPoints(SurfacePointList& sourcePointList, const SurfaceTagVector& desiredTags) const { - AZ_PROFILE_FUNCTION(Entity); + // Before sorting and combining, filter out any points that don't match our search tags. + sourcePointList.erase( + AZStd::remove_if( + sourcePointList.begin(), sourcePointList.end(), + [desiredTags](SurfacePoint& point) -> bool + { + return !HasMatchingTags(point.m_masks, desiredTags); + }), + sourcePointList.end()); + } - if (sourcePointList.empty()) + void SurfaceDataSystemComponent::CombineAndSortNeighboringPoints(SurfacePointList& sourcePointList) const + { + // If there's only 0 or 1 point, there is no sorting or combining that needs to happen, so just return. + if (sourcePointList.size() <= 1) { return; } - // Sorting only makes sense if we have two or more points - if (sourcePointList.size() > 1) + // Efficient point consolidation requires the points to be pre-sorted so we are only comparing/combining neighbors. + // Sort XY points together, with decreasing Z. + AZStd::sort(sourcePointList.begin(), sourcePointList.end(), [](const SurfacePoint& a, const SurfacePoint& b) { - //sort by depth/distance before combining points - AZStd::sort(sourcePointList.begin(), sourcePointList.end(), [](const SurfacePoint& a, const SurfacePoint& b) + // Our goal is to have identical XY values sorted adjacent to each other with decreasing Z. + // We sort increasing Y, then increasing X, then decreasing Z, because we need to compare all 3 values for a + // stable sort. The choice of increasing Y first is because we'll often generate the points as ranges of X values within + // ranges of Y values, so this will produce the most usable and expected output sort. + if (a.m_position.GetY() != b.m_position.GetY()) { - return a.m_position.GetZ() > b.m_position.GetZ(); - }); - } - - - //efficient point consolidation requires the points to be pre-sorted so we are only comparing/combining neighbors - const size_t sourcePointCount = sourcePointList.size(); - size_t targetPointIndex = 0; - size_t sourcePointIndex = 0; - - m_targetPointList.clear(); - m_targetPointList.reserve(sourcePointCount); - - // Locate the first point that matches our desired tags, if one exists. - for (sourcePointIndex = 0; sourcePointIndex < sourcePointCount; sourcePointIndex++) - { - if (!hasDesiredTags || (HasMatchingTags(sourcePointList[sourcePointIndex].m_masks, desiredTags))) + return a.m_position.GetY() < b.m_position.GetY(); + } + if (a.m_position.GetX() != b.m_position.GetX()) { - break; + return a.m_position.GetX() < b.m_position.GetX(); + } + if (a.m_position.GetZ() != b.m_position.GetZ()) + { + return a.m_position.GetZ() > b.m_position.GetZ(); } - } - if (sourcePointIndex < sourcePointCount) + // If we somehow ended up with two points with identical positions getting generated, use the entity ID as the tiebreaker + // to guarantee a stable sort. We should never have two identical positions generated from the same entity. + return a.m_entityId < b.m_entityId; + }); + + // iterate over subsequent source points for comparison and consolidation with the last added target/unique point + for (auto pointItr = sourcePointList.begin() + 1; pointItr < sourcePointList.end();) { - // We found a point that matches our tags, so add it to our target list as the first point. - m_targetPointList.push_back(sourcePointList[sourcePointIndex++]); + auto prevPointItr = pointItr - 1; - //iterate over subsequent source points for comparison and consolidation with the last added target/unique point - for (; sourcePointIndex < sourcePointCount; ++sourcePointIndex) + // (Someday we should add a configurable tolerance for comparison) + if (pointItr->m_position.IsClose(prevPointItr->m_position) && pointItr->m_normal.IsClose(prevPointItr->m_normal)) { - const auto& sourcePoint = sourcePointList[sourcePointIndex]; - - if (!hasDesiredTags || (HasMatchingTags(sourcePoint.m_masks, desiredTags))) - { - auto& targetPoint = m_targetPointList[targetPointIndex]; - - // [LY-90907] need to add a configurable tolerance for comparison - if (targetPoint.m_position.IsClose(sourcePoint.m_position) && - targetPoint.m_normal.IsClose(sourcePoint.m_normal)) - { - //consolidate points with similar attributes by adding masks to the target point and ignoring the source - AddMaxValueForMasks(targetPoint.m_masks, sourcePoint.m_masks); - continue; - } + // consolidate points with similar attributes by adding masks/weights to the previous point and deleting this point. + AddMaxValueForMasks(prevPointItr->m_masks, pointItr->m_masks); - //if the points were too different, we have to add a new target point to compare against - m_targetPointList.push_back(sourcePoint); - ++targetPointIndex; - } + pointItr = sourcePointList.erase(pointItr); + } + else + { + pointItr++; } - - AZStd::swap(sourcePointList, m_targetPointList); } } SurfaceDataRegistryHandle SurfaceDataSystemComponent::RegisterSurfaceDataProviderInternal(const SurfaceDataRegistryEntry& entry) { - AZStd::lock_guard registrationLock(m_registrationMutex); + AZStd::unique_lock registrationLock(m_registrationMutex); SurfaceDataRegistryHandle handle = ++m_registeredSurfaceDataProviderHandleCounter; m_registeredSurfaceDataProviders[handle] = entry; return handle; @@ -401,7 +403,7 @@ namespace SurfaceData SurfaceDataRegistryEntry SurfaceDataSystemComponent::UnregisterSurfaceDataProviderInternal(const SurfaceDataRegistryHandle& handle) { - AZStd::lock_guard registrationLock(m_registrationMutex); + AZStd::unique_lock registrationLock(m_registrationMutex); SurfaceDataRegistryEntry entry; auto entryItr = m_registeredSurfaceDataProviders.find(handle); if (entryItr != m_registeredSurfaceDataProviders.end()) @@ -414,7 +416,7 @@ namespace SurfaceData bool SurfaceDataSystemComponent::UpdateSurfaceDataProviderInternal(const SurfaceDataRegistryHandle& handle, const SurfaceDataRegistryEntry& entry, AZ::Aabb& oldBounds) { - AZStd::lock_guard registrationLock(m_registrationMutex); + AZStd::unique_lock registrationLock(m_registrationMutex); auto entryItr = m_registeredSurfaceDataProviders.find(handle); if (entryItr != m_registeredSurfaceDataProviders.end()) { @@ -427,7 +429,7 @@ namespace SurfaceData SurfaceDataRegistryHandle SurfaceDataSystemComponent::RegisterSurfaceDataModifierInternal(const SurfaceDataRegistryEntry& entry) { - AZStd::lock_guard registrationLock(m_registrationMutex); + AZStd::unique_lock registrationLock(m_registrationMutex); SurfaceDataRegistryHandle handle = ++m_registeredSurfaceDataModifierHandleCounter; m_registeredSurfaceDataModifiers[handle] = entry; m_registeredModifierTags.insert(entry.m_tags.begin(), entry.m_tags.end()); @@ -436,7 +438,7 @@ namespace SurfaceData SurfaceDataRegistryEntry SurfaceDataSystemComponent::UnregisterSurfaceDataModifierInternal(const SurfaceDataRegistryHandle& handle) { - AZStd::lock_guard registrationLock(m_registrationMutex); + AZStd::unique_lock registrationLock(m_registrationMutex); SurfaceDataRegistryEntry entry; auto entryItr = m_registeredSurfaceDataModifiers.find(handle); if (entryItr != m_registeredSurfaceDataModifiers.end()) @@ -449,7 +451,7 @@ namespace SurfaceData bool SurfaceDataSystemComponent::UpdateSurfaceDataModifierInternal(const SurfaceDataRegistryHandle& handle, const SurfaceDataRegistryEntry& entry, AZ::Aabb& oldBounds) { - AZStd::lock_guard registrationLock(m_registrationMutex); + AZStd::unique_lock registrationLock(m_registrationMutex); auto entryItr = m_registeredSurfaceDataModifiers.find(handle); if (entryItr != m_registeredSurfaceDataModifiers.end()) { diff --git a/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.h b/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.h index 8914ee7972..6ec2cab4eb 100644 --- a/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.h +++ b/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.h @@ -10,6 +10,7 @@ #include #include +#include #include namespace SurfaceData @@ -57,7 +58,8 @@ namespace SurfaceData void RefreshSurfaceData(const AZ::Aabb& dirtyArea) override; private: - void CombineSortAndFilterNeighboringPoints(SurfacePointList& sourcePointList, bool hasDesiredTags, const SurfaceTagVector& desiredTags) const; + void FilterPoints(SurfacePointList& sourcePointList, const SurfaceTagVector& desiredTags) const; + void CombineAndSortNeighboringPoints(SurfacePointList& sourcePointList) const; SurfaceDataRegistryHandle RegisterSurfaceDataProviderInternal(const SurfaceDataRegistryEntry& entry); SurfaceDataRegistryEntry UnregisterSurfaceDataProviderInternal(const SurfaceDataRegistryHandle& handle); @@ -67,7 +69,7 @@ namespace SurfaceData SurfaceDataRegistryEntry UnregisterSurfaceDataModifierInternal(const SurfaceDataRegistryHandle& handle); bool UpdateSurfaceDataModifierInternal(const SurfaceDataRegistryHandle& handle, const SurfaceDataRegistryEntry& entry, AZ::Aabb& oldBounds); - mutable AZStd::recursive_mutex m_registrationMutex; + mutable AZStd::shared_mutex m_registrationMutex; AZStd::unordered_map m_registeredSurfaceDataProviders; AZStd::unordered_map m_registeredSurfaceDataModifiers; SurfaceDataRegistryHandle m_registeredSurfaceDataProviderHandleCounter = InvalidSurfaceDataRegistryHandle; diff --git a/Gems/SurfaceData/Code/Tests/SurfaceDataColliderComponentTest.cpp b/Gems/SurfaceData/Code/Tests/SurfaceDataColliderComponentTest.cpp index b0aa5dd38f..c51ea49f14 100644 --- a/Gems/SurfaceData/Code/Tests/SurfaceDataColliderComponentTest.cpp +++ b/Gems/SurfaceData/Code/Tests/SurfaceDataColliderComponentTest.cpp @@ -93,10 +93,28 @@ namespace UnitTest // Compare two surface points. bool SurfacePointsAreEqual(const SurfaceData::SurfacePoint& lhs, const SurfaceData::SurfacePoint& rhs) { - return (lhs.m_entityId == rhs.m_entityId) - && (lhs.m_position == rhs.m_position) - && (lhs.m_normal == rhs.m_normal) - && (lhs.m_masks == rhs.m_masks); + if ((lhs.m_entityId != rhs.m_entityId) + || (lhs.m_position != rhs.m_position) + || (lhs.m_normal != rhs.m_normal) + || (lhs.m_masks.size() != rhs.m_masks.size())) + { + return false; + } + + for (auto& mask : lhs.m_masks) + { + auto maskEntry = rhs.m_masks.find(mask.first); + if (maskEntry == rhs.m_masks.end()) + { + return false; + } + if (maskEntry->second != mask.second) + { + return false; + } + } + + return true; } // Common test function for testing the "Provider" functionality of the component. diff --git a/Gems/SurfaceData/Code/Tests/SurfaceDataTest.cpp b/Gems/SurfaceData/Code/Tests/SurfaceDataTest.cpp index 1d339edc71..2d6d64f65d 100644 --- a/Gems/SurfaceData/Code/Tests/SurfaceDataTest.cpp +++ b/Gems/SurfaceData/Code/Tests/SurfaceDataTest.cpp @@ -204,30 +204,31 @@ public: m_surfaceDataSystemEntity.reset(); } - bool ValidateRegionListSize(AZ::Aabb bounds, AZ::Vector2 stepSize, const SurfaceData::SurfacePointLists& outputLists) - { - // We expect the output list to contain width * height output entries. - // The right edge of the AABB should be treated as exclusive, so a 4x4 box with 1 step size will produce 16 entries (0, 1, 2, 3 on each dimension), - // but a 4.1 x 4.1 box with 1 step size will produce 25 entries (0, 1, 2, 3, 4 on each dimension). - return (outputLists.size() == aznumeric_cast(ceil(bounds.GetXExtent() * stepSize.GetX()) * ceil(bounds.GetYExtent() * stepSize.GetY()))); - } - void CompareSurfacePointListWithGetSurfacePoints( - SurfaceData::SurfacePointLists surfacePointLists, const SurfaceData::SurfaceTagVector& testTags) + const AZStd::vector& queryPositions, SurfaceData::SurfacePointLists surfacePointLists, + const SurfaceData::SurfaceTagVector& testTags) { - for (auto& pointList : surfacePointLists) - { - AZ::Vector3 queryPosition(pointList[0].m_position.GetX(), pointList[0].m_position.GetY(), 16.0f); - SurfaceData::SurfacePointList singleQueryPointList; + SurfaceData::SurfacePointLists singleQueryPointLists; + for (auto& queryPosition : queryPositions) + { + SurfaceData::SurfacePointList tempSingleQueryPointList; SurfaceData::SurfaceDataSystemRequestBus::Broadcast( - &SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, queryPosition, testTags, singleQueryPointList); + &SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, queryPosition, testTags, tempSingleQueryPointList); + singleQueryPointLists.push_back(tempSingleQueryPointList); + } - // Verify the two point lists are the same size, then verify that each point in each list is equal. - ASSERT_EQ(pointList.size(), singleQueryPointList.size()); - for (size_t index = 0; index < pointList.size(); index++) + // Verify the two point lists are the same size, then verify that each point in each list is equal. + ASSERT_EQ(singleQueryPointLists.size(), surfacePointLists.size()); + for (size_t listIndex = 0; listIndex < surfacePointLists.size(); listIndex++) + { + auto& surfacePointList = surfacePointLists[listIndex]; + auto& singleQueryPointList = singleQueryPointLists[listIndex]; + + ASSERT_EQ(singleQueryPointList.size(), surfacePointList.size()); + for (size_t index = 0; index < surfacePointList.size(); index++) { - SurfaceData::SurfacePoint& point1 = pointList[index]; + SurfaceData::SurfacePoint& point1 = surfacePointList[index]; SurfaceData::SurfacePoint& point2 = singleQueryPointList[index]; EXPECT_EQ(point1.m_entityId, point2.m_entityId); @@ -399,8 +400,8 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestAabbOverlaps2D) // Make sure the test produces the correct result. // Also make sure it's correct regardless of which order the boxes are passed in. - EXPECT_TRUE(SurfaceData::AabbOverlaps2D(box1, box2) == testCase.m_overlaps); - EXPECT_TRUE(SurfaceData::AabbOverlaps2D(box2, box1) == testCase.m_overlaps); + EXPECT_EQ(SurfaceData::AabbOverlaps2D(box1, box2), testCase.m_overlaps); + EXPECT_EQ(SurfaceData::AabbOverlaps2D(box2, box1), testCase.m_overlaps); } } @@ -448,9 +449,9 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestAabbContains2D) AZ::Vector3& point = testCase.m_testData[TestCase::POINT]; // Make sure the test produces the correct result. - EXPECT_TRUE(SurfaceData::AabbContains2D(box, point) == testCase.m_contains); + EXPECT_EQ(SurfaceData::AabbContains2D(box, point), testCase.m_contains); // Test the Vector2 version as well. - EXPECT_TRUE(SurfaceData::AabbContains2D(box, AZ::Vector2(point.GetX(), point.GetY())) == testCase.m_contains); + EXPECT_EQ(SurfaceData::AabbContains2D(box, AZ::Vector2(point.GetX(), point.GetY())), testCase.m_contains); } } @@ -482,19 +483,17 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion) &SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromRegion, regionBounds, stepSize, testTags, availablePointsPerPosition); - EXPECT_TRUE(ValidateRegionListSize(regionBounds, stepSize, availablePointsPerPosition)); - // We expect every entry in the output list to have two surface points, at heights 0 and 4, sorted in // decreasing height order. The masks list should be the same size as the set of masks the provider owns. // We *could* check every mask as well for completeness, but that seems like overkill. for (auto& pointList : availablePointsPerPosition) { - EXPECT_TRUE(pointList.size() == 2); - EXPECT_TRUE(pointList[0].m_position.GetZ() == 4.0f); - EXPECT_TRUE(pointList[1].m_position.GetZ() == 0.0f); + EXPECT_EQ(pointList.size(), 2); + EXPECT_EQ(pointList[0].m_position.GetZ(), 4.0f); + EXPECT_EQ(pointList[1].m_position.GetZ(), 0.0f); for (auto& point : pointList) { - EXPECT_TRUE(point.m_masks.size() == providerTags.size()); + EXPECT_EQ(point.m_masks.size(), providerTags.size()); } } } @@ -520,13 +519,11 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_NoMatchingMas &SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromRegion, regionBounds, stepSize, testTags, availablePointsPerPosition); - EXPECT_TRUE(ValidateRegionListSize(regionBounds, stepSize, availablePointsPerPosition)); - // We expect every entry in the output list to have no surface points, since the requested mask doesn't match // any of the masks from our mock surface provider. for (auto& queryPosition : availablePointsPerPosition) { - EXPECT_TRUE(queryPosition.size() == 0); + EXPECT_TRUE(queryPosition.empty()); } } @@ -550,13 +547,11 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_NoMatchingReg &SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromRegion, regionBounds, stepSize, testTags, availablePointsPerPosition); - EXPECT_TRUE(ValidateRegionListSize(regionBounds, stepSize, availablePointsPerPosition)); - // We expect every entry in the output list to have no surface points, since the input points don't overlap with // our surface provider. for (auto& pointList : availablePointsPerPosition) { - EXPECT_TRUE(pointList.size() == 0); + EXPECT_TRUE(pointList.empty()); } } @@ -602,16 +597,17 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_ProviderModif &SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromRegion, regionBounds, stepSize, testTags, availablePointsPerPosition); - EXPECT_TRUE(ValidateRegionListSize(regionBounds, stepSize, availablePointsPerPosition)); - // We expect every entry in the output list to have two surface points (with heights 0 and 4), // and each point should have both the "test_surface1" and "test_surface2" tag. for (auto& pointList : availablePointsPerPosition) { - EXPECT_TRUE(pointList.size() == 2); + EXPECT_EQ(pointList.size(), 2); + float expectedZ = 4.0f; for (auto& point : pointList) { - EXPECT_TRUE(point.m_masks.size() == 2); + EXPECT_EQ(point.m_position.GetZ(), expectedZ); + EXPECT_EQ(point.m_masks.size(), 2); + expectedZ = (expectedZ == 4.0f) ? 0.0f : 4.0f; } } } @@ -624,7 +620,7 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_SimilarPoints // sets of tags. // Create two mock Surface Providers that covers from (0, 0) - (8, 8) in space, with points spaced 0.25 apart. - // The first has heights 0 and 4, with the tag "surfaceTag1". The second has heights 0.005 and 4.005, with the tag "surfaceTag2". + // The first has heights 0 and 4, with the tag "surfaceTag1". The second has heights 0.0005 and 4.0005, with the tag "surfaceTag2". SurfaceData::SurfaceTagVector provider1Tags = { SurfaceData::SurfaceTag(m_testSurface1Crc) }; MockSurfaceProvider mockProvider1(MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, provider1Tags, AZ::Vector3(0.0f), AZ::Vector3(8.0f), AZ::Vector3(0.25f, 0.25f, 4.0f), @@ -648,16 +644,17 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_SimilarPoints &SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromRegion, regionBounds, stepSize, testTags, availablePointsPerPosition); - EXPECT_TRUE(ValidateRegionListSize(regionBounds, stepSize, availablePointsPerPosition)); - // We expect every entry in the output list to have two surface points, not four. The two points // should have both surface tags on them. for (auto& pointList : availablePointsPerPosition) { - EXPECT_TRUE(pointList.size() == 2); + EXPECT_EQ(pointList.size(), 2); + float expectedZ = 4.0005f; for (auto& point : pointList) { - EXPECT_TRUE(point.m_masks.size() == 2); + EXPECT_EQ(point.m_position.GetZ(), expectedZ); + EXPECT_EQ(point.m_masks.size(), 2); + expectedZ = (expectedZ == 4.0005f) ? 0.0005f : 4.0005f; } } } @@ -692,16 +689,14 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_DissimilarPoi &SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromRegion, regionBounds, stepSize, testTags, availablePointsPerPosition); - EXPECT_TRUE(ValidateRegionListSize(regionBounds, stepSize, availablePointsPerPosition)); - // We expect every entry in the output list to have four surface points with one tag each, // because the points are far enough apart that they won't merge. for (auto& pointList : availablePointsPerPosition) { - EXPECT_TRUE(pointList.size() == 4); + EXPECT_EQ(pointList.size(), 4); for (auto& point : pointList) { - EXPECT_TRUE(point.m_masks.size() == 1); + EXPECT_EQ(point.m_masks.size(), 1); } } } @@ -727,10 +722,17 @@ TEST_F(SurfaceDataTestApp, SurfaceData_VerifyGetSurfacePointsFromRegionAndGetSur &SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromRegion, regionBounds, stepSize, providerTags, availablePointsPerPosition); - EXPECT_TRUE(ValidateRegionListSize(regionBounds, stepSize, availablePointsPerPosition)); - // For each point entry returned from GetSurfacePointsFromRegion, call GetSurfacePoints and verify the results match. - CompareSurfacePointListWithGetSurfacePoints(availablePointsPerPosition, providerTags); + AZStd::vector queryPositions; + for (float y = 0.0f; y < 4.0f; y += 1.0f) + { + for (float x = 0.0f; x < 4.0f; x += 1.0f) + { + queryPositions.push_back(AZ::Vector3(x, y, 16.0f)); + } + } + + CompareSurfacePointListWithGetSurfacePoints(queryPositions, availablePointsPerPosition, providerTags); } TEST_F(SurfaceDataTestApp, SurfaceData_VerifyGetSurfacePointsFromListAndGetSurfacePointsMatch) @@ -763,7 +765,7 @@ TEST_F(SurfaceDataTestApp, SurfaceData_VerifyGetSurfacePointsFromListAndGetSurfa EXPECT_EQ(availablePointsPerPosition.size(), 16); // For each point entry returned from GetSurfacePointsFromList, call GetSurfacePoints and verify the results match. - CompareSurfacePointListWithGetSurfacePoints(availablePointsPerPosition, providerTags); + CompareSurfacePointListWithGetSurfacePoints(queryPositions, availablePointsPerPosition, providerTags); } // This uses custom test / benchmark hooks so that we can load LmbrCentral and use Shape components in our unit tests and benchmarks. AZ_UNIT_TEST_HOOK(new UnitTest::SurfaceDataTestEnvironment, UnitTest::SurfaceDataBenchmarkEnvironment); diff --git a/Gems/Terrain/Code/Source/Components/TerrainSurfaceDataSystemComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainSurfaceDataSystemComponent.cpp index 03011db2f3..9a9cb24e4c 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainSurfaceDataSystemComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainSurfaceDataSystemComponent.cpp @@ -177,7 +177,7 @@ namespace Terrain const AZ::Crc32 terrainTag = isHole ? Constants::s_terrainHoleTagCrc : Constants::s_terrainTagCrc; point.m_masks[terrainTag] = 1.0f; - surfacePointList.push_back(point); + surfacePointList.push_back(AZStd::move(point)); } AZ::Aabb TerrainSurfaceDataSystemComponent::GetSurfaceAabb() const From 71cc3a256845586f569a46cd0406bbf9c433ba35 Mon Sep 17 00:00:00 2001 From: Steve Pham <82231385+spham-amzn@users.noreply.github.com> Date: Tue, 1 Feb 2022 09:04:32 -0800 Subject: [PATCH 52/53] Remove -Wno-comment warning suppression Signed-off-by: Steve Pham <82231385+spham-amzn@users.noreply.github.com> --- Code/Framework/AzCore/Tests/TaskTests.cpp | 64 +-- .../GridMate/Carrier/SecureSocketDriver.h | 16 +- .../Code/Source/Converters/FIR-Filter.cpp | 148 +++--- .../Feature/ParamMacros/ParamMacrosHowTo.inl | 474 +++++++++--------- Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp | 52 +- .../RPI/Code/Tests/Shader/ShaderTests.cpp | 29 +- .../EMotionFX/Rendering/Common/RenderUtil.cpp | 52 +- .../Code/Tests/AutoSkeletonLODTests.cpp | 36 +- Gems/EMotionFX/Code/Tests/BlendSpaceTests.cpp | 28 +- .../Code/Tests/NonUniformMotionDataTests.cpp | 78 +-- .../Code/Source/Shape/TubeShape.cpp | 25 +- .../Common/GCC/Configurations_gcc.cmake | 2 - 12 files changed, 514 insertions(+), 490 deletions(-) diff --git a/Code/Framework/AzCore/Tests/TaskTests.cpp b/Code/Framework/AzCore/Tests/TaskTests.cpp index 4f53772d51..53fa89900e 100644 --- a/Code/Framework/AzCore/Tests/TaskTests.cpp +++ b/Code/Framework/AzCore/Tests/TaskTests.cpp @@ -447,13 +447,13 @@ namespace UnitTest { x -= 1; }); - - // a <-- Root - // / \ - // b c - // \ / - // d - + /* + a <-- Root + / \ + b c + \ / + d + */ a.Precedes(b, c); d.Follows(b, c); @@ -522,20 +522,20 @@ namespace UnitTest { x -= 1; }); - - // NOTE: The ideal way to express this topology is without the wait on the subgraph - // at task g, but this is more an illustrative test. Better is to express the entire - // graph in a single larger graph. - // a <-- Root - // / \ - // b c - f - // \ \ \ - // \ e - g - // \ / - // \ / - // \ / - // d - + /* + NOTE: The ideal way to express this topology is without the wait on the subgraph + at task g, but this is more an illustrative test. Better is to express the entire + graph in a single larger graph. + a <-- Root + / \ + b c - f + \ \ \ + \ e - g + \ / + \ / + \ / + d + */ a.Precedes(b); a.Precedes(c); b.Precedes(d); @@ -593,17 +593,17 @@ namespace UnitTest { x += 0b1000; }); - - // a <-- Root - // / \ - // b c - f - // \ \ \ - // \ e - g - // \ / - // \ / - // \ / - // d - + /* + a <-- Root + / \ + b c - f + \ \ \ + \ e - g + \ / + \ / + \ / + d + */ a.Precedes(b, c); b.Precedes(d); c.Precedes(e, f); diff --git a/Code/Framework/GridMate/GridMate/Carrier/SecureSocketDriver.h b/Code/Framework/GridMate/GridMate/Carrier/SecureSocketDriver.h index 76b7958085..30a9f99436 100644 --- a/Code/Framework/GridMate/GridMate/Carrier/SecureSocketDriver.h +++ b/Code/Framework/GridMate/GridMate/Carrier/SecureSocketDriver.h @@ -19,13 +19,15 @@ #define AZ_DebugSecureSocket(...) #define AZ_DebugSecureSocketConnection(window, fmt, ...) -//#define AZ_DebugUseSocketDebugLog -//#define AZ_DebugSecureSocket AZ_TracePrintf -//#define AZ_DebugSecureSocketConnection(window, fmt, ...) \ -//{\ -// AZStd::string line = AZStd::string::format(fmt, __VA_ARGS__);\ -// this->m_dbgLog += line;\ -//} +/* + #define AZ_DebugUseSocketDebugLog + #define AZ_DebugSecureSocket AZ_TracePrintf + #define AZ_DebugSecureSocketConnection(window, fmt, ...) \ + {\ + AZStd::string line = AZStd::string::format(fmt, __VA_ARGS__);\ + this->m_dbgLog += line;\ + } +*/ #if AZ_TRAIT_GRIDMATE_SECURE_SOCKET_DRIVER_HOOK_ENABLED struct ssl_st; diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Converters/FIR-Filter.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Converters/FIR-Filter.cpp index 0387ec5c7b..2b1e822db4 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Converters/FIR-Filter.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Converters/FIR-Filter.cpp @@ -999,79 +999,81 @@ namespace ImageProcessingAtom } // TODO: not working yet, debug and enable - //static void SplitAlgorithm(const void* i, void* o, struct prcparm* templ, int threads = 8) - //{ - // struct prcparm fraction[32]; - // int t, istart = 0, sstart = 0, ostart = 0; - // const bool scaler = true; - - // int theight = 0; - - // /* prepare data to be emitted to the threads */ - // for (t = 0; t < threads; t++) - // { - // fraction[t] = *templ; - - // /* adjust the processing-region according to the available threads */ - // { - //#undef split /* only prefix-threads need aligned transpose (for not trashing suffix-thread data) */ - //#define split(rows) !scaler \ - // ? ((rows * (t + 1)) / threads) & (~(t != threads - 1 ? 15 : 0)) \ - // : ((rows * (t + 1)) / threads) & (~0) - - // /* area covered */ - // const int inrows = (fraction[t].regional ? fraction[t].region.inrows : fraction[t].inrows); - // const int incols = (fraction[t].regional ? fraction[t].region.incols : fraction[t].incols); - // const int subrows = (fraction[t].regional ? fraction[t].region.subrows : fraction[t].subrows); - // const int subcols = (fraction[t].regional ? fraction[t].region.subcols : fraction[t].subcols); - // const int outrows = (fraction[t].regional ? fraction[t].region.outrows : fraction[t].outrows); - // const int outcols = (fraction[t].regional ? fraction[t].region.outcols : fraction[t].outcols); - - // /* splitting blocks */ - // const int istop = split(inrows), sstop = split(subrows), ostop = split(outrows); - // const int irows = istop - istart, srows = sstop - sstart, orows = ostop - ostart; - // const int icols = incols, scols = subcols, ocols = outcols; - - // AZ_Assert(irows > 0, "%s: Expect row count to be above zero!", __FUNCTION__); - // AZ_Assert(orows > 0, "%s: Expect row count to be above zero!", __FUNCTION__); - // AZ_Assert(icols > 0, "%s: Expect column count to be above zero!", __FUNCTION__); - // AZ_Assert(ocols > 0, "%s: Expect column count to be above zero!", __FUNCTION__); - - // /* now we are regional */ - // fraction[t].regional = true; - - // /* take previous regionality into account */ - // fraction[t].region.intop += istart; - // fraction[t].region.subtop += sstart; - // fraction[t].region.outtop += ostart; - // fraction[t].region.inrows = irows; - // fraction[t].region.subrows = srows; - // fraction[t].region.outrows = orows; - - // /* take previous regionality into account */ - // fraction[t].region.inleft += 0; - // fraction[t].region.subleft += 0; - // fraction[t].region.outleft += 0; - // fraction[t].region.incols = icols; - // fraction[t].region.subcols = scols; - // fraction[t].region.outcols = ocols; - - // /* advance block */ - // istart = istop; - // sstart = sstop; - // ostart = ostop; - - // /* check */ - // theight += irows; - // } - - // // the algorithm supports "i" and "o" pointing to the same memory - // CheckBoundaries((float*)i, (float*)o, &fraction[t]); - // RunAlgorithm((float*)i, (float*)o, &fraction[t]); - // } - - // AZ_Assert(theight >= (templ->regional ? templ->region.inrows : templ->inrows), "%s: Invalid height!", __FUNCTION__); - //} + /* + static void SplitAlgorithm(const void* i, void* o, struct prcparm* templ, int threads = 8) + { + struct prcparm fraction[32]; + int t, istart = 0, sstart = 0, ostart = 0; + const bool scaler = true; + + int theight = 0; + + // prepare data to be emitted to the threads + for (t = 0; t < threads; t++) + { + fraction[t] = *templ; + + // adjust the processing-region according to the available threads + { + #undef split // only prefix-threads need aligned transpose (for not trashing suffix-thread data) + #define split(rows) !scaler \ + ? ((rows * (t + 1)) / threads) & (~(t != threads - 1 ? 15 : 0)) \ + : ((rows * (t + 1)) / threads) & (~0) + + // area covered + const int inrows = (fraction[t].regional ? fraction[t].region.inrows : fraction[t].inrows); + const int incols = (fraction[t].regional ? fraction[t].region.incols : fraction[t].incols); + const int subrows = (fraction[t].regional ? fraction[t].region.subrows : fraction[t].subrows); + const int subcols = (fraction[t].regional ? fraction[t].region.subcols : fraction[t].subcols); + const int outrows = (fraction[t].regional ? fraction[t].region.outrows : fraction[t].outrows); + const int outcols = (fraction[t].regional ? fraction[t].region.outcols : fraction[t].outcols); + + // splitting blocks + const int istop = split(inrows), sstop = split(subrows), ostop = split(outrows); + const int irows = istop - istart, srows = sstop - sstart, orows = ostop - ostart; + const int icols = incols, scols = subcols, ocols = outcols; + + AZ_Assert(irows > 0, "%s: Expect row count to be above zero!", __FUNCTION__); + AZ_Assert(orows > 0, "%s: Expect row count to be above zero!", __FUNCTION__); + AZ_Assert(icols > 0, "%s: Expect column count to be above zero!", __FUNCTION__); + AZ_Assert(ocols > 0, "%s: Expect column count to be above zero!", __FUNCTION__); + + // now we are regional + fraction[t].regional = true; + + // take previous regionality into account + fraction[t].region.intop += istart; + fraction[t].region.subtop += sstart; + fraction[t].region.outtop += ostart; + fraction[t].region.inrows = irows; + fraction[t].region.subrows = srows; + fraction[t].region.outrows = orows; + + // take previous regionality into account + fraction[t].region.inleft += 0; + fraction[t].region.subleft += 0; + fraction[t].region.outleft += 0; + fraction[t].region.incols = icols; + fraction[t].region.subcols = scols; + fraction[t].region.outcols = ocols; + + // advance block + istart = istop; + sstart = sstop; + ostart = ostop; + + // check + theight += irows; + } + + // the algorithm supports "i" and "o" pointing to the same memory + CheckBoundaries((float*)i, (float*)o, &fraction[t]); + RunAlgorithm((float*)i, (float*)o, &fraction[t]); + } + + AZ_Assert(theight >= (templ->regional ? templ->region.inrows : templ->inrows), "%s: Invalid height!", __FUNCTION__); + } + */ /* #################################################################################################################### \ */ diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ParamMacros/ParamMacrosHowTo.inl b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ParamMacros/ParamMacrosHowTo.inl index e6fd6c10ba..ecf63ff5f6 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ParamMacros/ParamMacrosHowTo.inl +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ParamMacros/ParamMacrosHowTo.inl @@ -6,239 +6,241 @@ * */ -// -// This is a quick guide on how to use the parameter macros included in this folder -// -// Part I: The Pattern -// -// The aim of this macro system is to allow users to define parameters once and have the macros generate a bunch of boilerplate code for each defined parameter. -// While these macros makes things a little more complex upfront, the ability to add and remove variables in just one place without having to dig -// through multiple files every time is great for iteration speed and maintenance. To accomplish this, we use a pattern similar to the following example: -// -// Let's say we want to specify class members in one place and have the members, getters and setters be auto generated. -// First, we define a macro MY_CLASS_PARAMS that uses a yet-undefined macro MAKE_PARAM(TYPE, NAME): -// -// #define MY_CLASS_PARAMS \ -// MAKE_PARAM(float, Width) \ -// MAKE_PARAM(float, Height) \ -// MAKE_PARAM(float, Depth) \ -// -// Now we need only specify what MAKE_PARAM needs to do and then we can call the MY_CLASS_PARAMS macro to apply the logic to all defined params. For instance: -// -// #define MAKE_PARAM(TYPE, NAME) TYPE m_##NAME; -// MY_CLASS_PARAMS -// #undef MAKE_PARAM -// -// This will generate the members as follows: -// -// float m_Width; -// float m_Height; -// float m_Depth; -// -// Now elsewhere in the class definition we can generate getters and setters: -// -// #define MAKE_PARAM(TYPE, NAME) \ -// TYPE Get##NAME() { return m_##NAME; } \ -// void Set##NAME(TYPE NAME) { m_##NAME = NAME; } \ -// -// MY_CLASS_PARAMS -// -// #undef MAKE_PARAM -// -// This will generate the following: -// -// float GetWidth() { return m_Width; } -// void SetWidth(float Width) { m_Width = Width; } -// float GetHeight() { return m_Height; } -// void SetHeight(float Width) { m_Height = Height; } -// float GetDepth() { return m_Width; } -// void SetDepth(float Depth) { m_Depth = Depth; } -// -// If we wanted to generate further code for each variable, we need only redefine the MAKE_PARAM macro and invoke MY_CLASS_PARAMS -// -// ___________________________________________________________________________________________________________________________________________________ -// -// Part II: Using .inl files -// -// A key difference between the above example and our macro system is that we put macro definitions in .inl files so they can be easily reused -// If we were to reuse the above example, it would look something like this: -// -// GenerateMembers.inl -// -// #define MAKE_PARAM(TYPE, NAME) TYPE m_##NAME; -// -// GenerateGettersAndSetters.inl -// -// #define MAKE_PARAM(TYPE, NAME) \ -// TYPE Get##NAME() { return m_##NAME; } \ -// void Set##NAME(TYPE NAME) { m_##NAME = NAME; } \ -// -// BoxParams.inl -// -// MAKE_PARAM(float, Width) \ -// MAKE_PARAM(float, Height) \ -// MAKE_PARAM(float, Depth) \ -// -// CylinderParams.inl -// -// MAKE_PARAM(float, Radius) \ -// MAKE_PARAM(float, Height) \ -// -// Now we can use these .inl files to generate two classes, each with members, getters and setters -// -// class Box -// { -// // Auto-gen members from BoxParams.inl -// #include -// #include -// #undef MAKE_PARAM -// -// // Auto-gen getters and setters from BoxParams.inl -// #include -// #include -// #undef MAKE_PARAM -// } -// -// class Cylinder -// { -// // Auto-gen members from CylinderParams.inl -// #include -// #include -// #undef MAKE_PARAM -// -// // Auto-gen getters and setters from CylinderParams.inl -// #include -// #include -// #undef MAKE_PARAM -// } -// -// This will result in the following code: -// -// class Box -// { -// // Auto-gen members from BoxParams.inl -// float m_Width; -// float m_Height; -// float m_Depth; -// -// // Auto-gen getters and setters from BoxParams.inl -// float GetWidth() { return m_Width; } -// void SetWidth(float Width) { m_Width = Width; } -// float GetHeight() { return m_Height; } -// void SetHeight(float Width) { m_Height = Height; } -// float GetDepth() { return m_Width; } -// void SetDepth(float Depth) { m_Depth = Depth; } -// } -// -// class Cylinder -// { -// // Auto-gen members from CylinderParams.inl -// float m_Radius; -// float m_Height; -// -// // Auto-gen getters and setters from CylinderParams.inl -// float GetRadius() { return m_Radius; } -// void SetRadius(float Radius) { m_Radius = Raidus; } -// float GetHeight() { return m_Height; } -// void SetHeight(float Width) { m_Height = Height; } -// } -// -// As you can see, this macro pattern allows us to add a member to Box or Cylinder by adding a single line to BoxParams.inl or CylinderParams.inl -// Because of the number of classes an boiler plate code involved in creating Open 3D Engine Component, this macro system allows us to change one line -// in one file instead of changing over a dozens of lines in half a dozen files. -// -// ___________________________________________________________________________________________________________________________________________________ -// -// Part III: Using the Macros for Post Process members -// -// If you want to create a new post process, you can create a .inl file (see DepthOfFieldParams.inl for an example) and declare members using the macros below: -// -// #define AZ_GFX_BOOL_PARAM(Name, MemberName, DefaultValue) -// #define AZ_GFX_FLOAT_PARAM(Name, MemberName, DefaultValue) -// #define AZ_GFX_UINT32_PARAM(Name, MemberName, DefaultValue) -// #define AZ_GFX_VEC2_PARAM(Name, MemberName, DefaultValue) -// #define AZ_GFX_VEC3_PARAM(Name, MemberName, DefaultValue) -// #define AZ_GFX_VEC4_PARAM(Name, MemberName, DefaultValue) -// -// Where: -// Name - The name of the param that will be used for reflection and appended to setters and getters, for example Width -// MemberName - The name of the member defined inside your class, for example m_width -// DefaultValue - The default value that the member will be statically initialized to, for example 1.0f -// BOOL, FLOAT, UINT32, VEC2 etc. all designate the type of the param you are defining -// -// If you have a custom type for your parameter, you can use the AZ_GFX_COMMON_PARAM macro: -// AZ_GFX_COMMON_PARAM(Name, MemberName, DefaultValue, ValueType) -// The keywords here are the same as above, with the addition of ValueType: the custom type you want your param to be -// -// Example usages: -// -// #define AZ_GFX_VEC3_PARAM(Position, m_position, Vector3(0.0f, 0.0f, 0.0f)) -// -// #define AZ_GFX_COMMON_PARAM(Format, m_format, Format::Unknown, FormatEnum) -// -// ___________________________________________________________________________________________________________________________________________________ -// -// Part IV: Using the Macros for Post Process overrides -// -// The Post Process System allows users to specify whether settings should be overridden or not on a per-member basis. -// To enable this, when you declare a member that can be overridden by higher priority Post Process Settings, in addition -// to using the above macros to define the member, you should also use one of the following to specify the override: -// -// #define AZ_GFX_ANY_PARAM_BOOL_OVERRIDE(Name, MemberName, ValueType) -// #define AZ_GFX_INTEGER_PARAM_FLOAT_OVERRIDE(Name, MemberName, ValueType) -// #define AZ_GFX_FLOAT_PARAM_FLOAT_OVERRIDE(Name, MemberName, ValueType) -// -// Where: -// Name - The name of the param that will be used for reflection and appended to setters and getters, for example Width -// MemberName - The name of the member defined inside your class, for example m_width -// ValueType - The type of the parameter you defined (bool, float, uint32_t, Vector2 etc.) -// -// A bit more details on each of the override macros: -// -// AZ_GFX_ANY_PARAM_BOOL_OVERRIDE can be used for params of any type. The override variable will be a bool (checkbox in the UI). -// The override application is a simple binary operation: take all of the source or the target (no lerp) depending on the bool. -// -// AZ_GFX_INTEGER_PARAM_FLOAT_OVERRIDE should be used for params of integer types (int, uint, integer vectors...) The override variable -// will be a float from 0.0 to 1.0 (slider in the UI). The override will lerp between target and source using the override float -// variable. Note that for this reason the param integer type must support multiplication by a float value. -// -// AZ_GFX_FLOAT_PARAM_FLOAT_OVERRIDE should be used for params of floating point types (float, double, Vector4...) The override variable -// will be a float from 0.0 to 1.0 (slider in the UI). The override will lerp between target and source using the override float. -// -// Example usage: -// -// #define AZ_GFX_VEC3_PARAM(Position, m_position, Vector3(0.0f, 0.0f, 0.0f)) -// #define AZ_GFX_FLOAT_PARAM_FLOAT_OVERRIDE(Position, m_position, Vector3) -// -// ___________________________________________________________________________________________________________________________________________________ -// -// Part V: Defining functionality with .inl files -// -// There are many .inl fies in this folder that provide pre-defined behaviors for params declared with the above macros -// To use these files, start by including the .inl file that specifies the behavior, then include your own .inl that defines your params -// then include EndParams.inl (this will #undef all used macros so as to avoid collisions with subsequent macro usage further in the file) -// -// Here is an example of how that looks: -// -// #include <- The behavior you want (behavior is described in each file) -// #include <- Your file in which you declare your params -// #include <- This #undef a bunch of macros to avoid conflicts -// -// You may of course use your own custom behaviors by specifying your definition for the param and override macros. -// You can specify each macro individually (AZ_GFX_BOOL_PARAM, AZ_GFX_FLOAT_PARAM, AZ_GFX_UINT32_PARAM, AZ_GFX_VEC2_PARAM, etc.) -// or you can specify the AZ_GFX_COMMON_PARAM and AZ_GFX_COMMON_OVERRIDE macros if there's no difference between variable types. -// -// AZ_GFX_COMMON_PARAM and AZ_GFX_COMMON_OVERRIDE are helper macros that the other _PARAM and _OVERRIDE macros can be mapped to -// using MapAllCommon.inl, allowing you to specify one definition for all types rather than each type individually. -// Here is an example of how we can use AZ_GFX_COMMON_PARAM and AZ_GFX_COMMON_OVERRIDE to auto-generate getters for our member parameters: -// -// #define AZ_GFX_COMMON_PARAM(Name, MemberName, DefaultValue, ValueType) \ -// ValueType Get##Name() const override { return MemberName; } \ -// -// #define AZ_GFX_COMMON_OVERRIDE(Name, MemberName, ValueType, OverrideValueType) \ -// OverrideValueType Get##Name##Override() const override { return MemberName##Override; } \ -// -// #include -// #include -// #include -// +/* + + This is a quick guide on how to use the parameter macros included in this folder + + Part I: The Pattern + + The aim of this macro system is to allow users to define parameters once and have the macros generate a bunch of boilerplate code for each defined parameter. + While these macros makes things a little more complex upfront, the ability to add and remove variables in just one place without having to dig + through multiple files every time is great for iteration speed and maintenance. To accomplish this, we use a pattern similar to the following example: + + Let's say we want to specify class members in one place and have the members, getters and setters be auto generated. + First, we define a macro MY_CLASS_PARAMS that uses a yet-undefined macro MAKE_PARAM(TYPE, NAME): + + #define MY_CLASS_PARAMS \ + MAKE_PARAM(float, Width) \ + MAKE_PARAM(float, Height) \ + MAKE_PARAM(float, Depth) \ + + Now we need only specify what MAKE_PARAM needs to do and then we can call the MY_CLASS_PARAMS macro to apply the logic to all defined params. For instance: + + #define MAKE_PARAM(TYPE, NAME) TYPE m_##NAME; + MY_CLASS_PARAMS + #undef MAKE_PARAM + + This will generate the members as follows: + + float m_Width; + float m_Height; + float m_Depth; + + Now elsewhere in the class definition we can generate getters and setters: + + #define MAKE_PARAM(TYPE, NAME) \ + TYPE Get##NAME() { return m_##NAME; } \ + void Set##NAME(TYPE NAME) { m_##NAME = NAME; } \ + + MY_CLASS_PARAMS + + #undef MAKE_PARAM + + This will generate the following: + + float GetWidth() { return m_Width; } + void SetWidth(float Width) { m_Width = Width; } + float GetHeight() { return m_Height; } + void SetHeight(float Width) { m_Height = Height; } + float GetDepth() { return m_Width; } + void SetDepth(float Depth) { m_Depth = Depth; } + + If we wanted to generate further code for each variable, we need only redefine the MAKE_PARAM macro and invoke MY_CLASS_PARAMS + + ___________________________________________________________________________________________________________________________________________________ + + Part II: Using .inl files + + A key difference between the above example and our macro system is that we put macro definitions in .inl files so they can be easily reused + If we were to reuse the above example, it would look something like this: + + GenerateMembers.inl + + #define MAKE_PARAM(TYPE, NAME) TYPE m_##NAME; + + GenerateGettersAndSetters.inl + + #define MAKE_PARAM(TYPE, NAME) \ + TYPE Get##NAME() { return m_##NAME; } \ + void Set##NAME(TYPE NAME) { m_##NAME = NAME; } \ + + BoxParams.inl + + MAKE_PARAM(float, Width) \ + MAKE_PARAM(float, Height) \ + MAKE_PARAM(float, Depth) \ + + CylinderParams.inl + + MAKE_PARAM(float, Radius) \ + MAKE_PARAM(float, Height) \ + + Now we can use these .inl files to generate two classes, each with members, getters and setters + + class Box + { + // Auto-gen members from BoxParams.inl + #include + #include + #undef MAKE_PARAM + + // Auto-gen getters and setters from BoxParams.inl + #include + #include + #undef MAKE_PARAM + } + + class Cylinder + { + // Auto-gen members from CylinderParams.inl + #include + #include + #undef MAKE_PARAM + + // Auto-gen getters and setters from CylinderParams.inl + #include + #include + #undef MAKE_PARAM + } + + This will result in the following code: + + class Box + { + // Auto-gen members from BoxParams.inl + float m_Width; + float m_Height; + float m_Depth; + + // Auto-gen getters and setters from BoxParams.inl + float GetWidth() { return m_Width; } + void SetWidth(float Width) { m_Width = Width; } + float GetHeight() { return m_Height; } + void SetHeight(float Width) { m_Height = Height; } + float GetDepth() { return m_Width; } + void SetDepth(float Depth) { m_Depth = Depth; } + } + + class Cylinder + { + // Auto-gen members from CylinderParams.inl + float m_Radius; + float m_Height; + + // Auto-gen getters and setters from CylinderParams.inl + float GetRadius() { return m_Radius; } + void SetRadius(float Radius) { m_Radius = Raidus; } + float GetHeight() { return m_Height; } + void SetHeight(float Width) { m_Height = Height; } + } + + As you can see, this macro pattern allows us to add a member to Box or Cylinder by adding a single line to BoxParams.inl or CylinderParams.inl + Because of the number of classes an boiler plate code involved in creating Open 3D Engine Component, this macro system allows us to change one line + in one file instead of changing over a dozens of lines in half a dozen files. + + ___________________________________________________________________________________________________________________________________________________ + + Part III: Using the Macros for Post Process members + + If you want to create a new post process, you can create a .inl file (see DepthOfFieldParams.inl for an example) and declare members using the macros below: + + #define AZ_GFX_BOOL_PARAM(Name, MemberName, DefaultValue) + #define AZ_GFX_FLOAT_PARAM(Name, MemberName, DefaultValue) + #define AZ_GFX_UINT32_PARAM(Name, MemberName, DefaultValue) + #define AZ_GFX_VEC2_PARAM(Name, MemberName, DefaultValue) + #define AZ_GFX_VEC3_PARAM(Name, MemberName, DefaultValue) + #define AZ_GFX_VEC4_PARAM(Name, MemberName, DefaultValue) + + Where: + Name - The name of the param that will be used for reflection and appended to setters and getters, for example Width + MemberName - The name of the member defined inside your class, for example m_width + DefaultValue - The default value that the member will be statically initialized to, for example 1.0f + BOOL, FLOAT, UINT32, VEC2 etc. all designate the type of the param you are defining + + If you have a custom type for your parameter, you can use the AZ_GFX_COMMON_PARAM macro: + AZ_GFX_COMMON_PARAM(Name, MemberName, DefaultValue, ValueType) + The keywords here are the same as above, with the addition of ValueType: the custom type you want your param to be + + Example usages: + + #define AZ_GFX_VEC3_PARAM(Position, m_position, Vector3(0.0f, 0.0f, 0.0f)) + + #define AZ_GFX_COMMON_PARAM(Format, m_format, Format::Unknown, FormatEnum) + + ___________________________________________________________________________________________________________________________________________________ + + Part IV: Using the Macros for Post Process overrides + + The Post Process System allows users to specify whether settings should be overridden or not on a per-member basis. + To enable this, when you declare a member that can be overridden by higher priority Post Process Settings, in addition + to using the above macros to define the member, you should also use one of the following to specify the override: + + #define AZ_GFX_ANY_PARAM_BOOL_OVERRIDE(Name, MemberName, ValueType) + #define AZ_GFX_INTEGER_PARAM_FLOAT_OVERRIDE(Name, MemberName, ValueType) + #define AZ_GFX_FLOAT_PARAM_FLOAT_OVERRIDE(Name, MemberName, ValueType) + + Where: + Name - The name of the param that will be used for reflection and appended to setters and getters, for example Width + MemberName - The name of the member defined inside your class, for example m_width + ValueType - The type of the parameter you defined (bool, float, uint32_t, Vector2 etc.) + + A bit more details on each of the override macros: + + AZ_GFX_ANY_PARAM_BOOL_OVERRIDE can be used for params of any type. The override variable will be a bool (checkbox in the UI). + The override application is a simple binary operation: take all of the source or the target (no lerp) depending on the bool. + + AZ_GFX_INTEGER_PARAM_FLOAT_OVERRIDE should be used for params of integer types (int, uint, integer vectors...) The override variable + will be a float from 0.0 to 1.0 (slider in the UI). The override will lerp between target and source using the override float + variable. Note that for this reason the param integer type must support multiplication by a float value. + + AZ_GFX_FLOAT_PARAM_FLOAT_OVERRIDE should be used for params of floating point types (float, double, Vector4...) The override variable + will be a float from 0.0 to 1.0 (slider in the UI). The override will lerp between target and source using the override float. + + Example usage: + + #define AZ_GFX_VEC3_PARAM(Position, m_position, Vector3(0.0f, 0.0f, 0.0f)) + #define AZ_GFX_FLOAT_PARAM_FLOAT_OVERRIDE(Position, m_position, Vector3) + + ___________________________________________________________________________________________________________________________________________________ + + Part V: Defining functionality with .inl files + + There are many .inl fies in this folder that provide pre-defined behaviors for params declared with the above macros + To use these files, start by including the .inl file that specifies the behavior, then include your own .inl that defines your params + then include EndParams.inl (this will #undef all used macros so as to avoid collisions with subsequent macro usage further in the file) + + Here is an example of how that looks: + + #include <- The behavior you want (behavior is described in each file) + #include <- Your file in which you declare your params + #include <- This #undef a bunch of macros to avoid conflicts + + You may of course use your own custom behaviors by specifying your definition for the param and override macros. + You can specify each macro individually (AZ_GFX_BOOL_PARAM, AZ_GFX_FLOAT_PARAM, AZ_GFX_UINT32_PARAM, AZ_GFX_VEC2_PARAM, etc.) + or you can specify the AZ_GFX_COMMON_PARAM and AZ_GFX_COMMON_OVERRIDE macros if there's no difference between variable types. + + AZ_GFX_COMMON_PARAM and AZ_GFX_COMMON_OVERRIDE are helper macros that the other _PARAM and _OVERRIDE macros can be mapped to + using MapAllCommon.inl, allowing you to specify one definition for all types rather than each type individually. + Here is an example of how we can use AZ_GFX_COMMON_PARAM and AZ_GFX_COMMON_OVERRIDE to auto-generate getters for our member parameters: + + #define AZ_GFX_COMMON_PARAM(Name, MemberName, DefaultValue, ValueType) \ + ValueType Get##Name() const override { return MemberName; } \ + + #define AZ_GFX_COMMON_OVERRIDE(Name, MemberName, ValueType, OverrideValueType) \ + OverrideValueType Get##Name##Override() const override { return MemberName##Override; } \ + + #include + #include + #include + +*/ diff --git a/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp b/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp index 81d773d8c0..cbdb99a008 100644 --- a/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp @@ -976,14 +976,14 @@ namespace UnitTest EXPECT_EQ(uvStreamTangentBitmask.GetFullTangentBitmask(), 0x70000F51); } - // - // +----+ - // / /| - // +----+ | - // | | + - // | |/ - // +----+ - // + /* + +----+ + / /| + +----+ | + | | + + | |/ + +----+ + */ static constexpr AZStd::array CubePositions = { -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f }; static constexpr AZStd::array CubeIndices = { @@ -993,23 +993,25 @@ namespace UnitTest static constexpr AZStd::array QuadPositions = { -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, -1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f }; static constexpr AZStd::array QuadIndices = { uint32_t{ 0 }, 2, 1, 1, 2, 3 }; - // This class creates a Model with one LOD, whose mesh contains 2 planes. Plane 1 is in the XY plane at Z=-0.5, and - // plane 2 is in the XY plane at Z=0.5. The two planes each have 9 quads which have been triangulated. It only has - // a position and index buffer. - // - // -0.33 - // -1 0.33 1 - // 0.5 *---*---*---* - // \ / \ / \ / \ - // *---*---*---* - // \ / \ / \ / \ - // -0.5 *- *---*---*---* - // \ \ / \ / \ / \ - // *- *---*---*---* - // \ \ \ \ - // *---*---*---* - // \ / \ / \ / \ - // *---*---*---* + /* + This class creates a Model with one LOD, whose mesh contains 2 planes. Plane 1 is in the XY plane at Z=-0.5, and + plane 2 is in the XY plane at Z=0.5. The two planes each have 9 quads which have been triangulated. It only has + a position and index buffer. + + -0.33 + -1 0.33 1 + 0.5 *---*---*---* + \ / \ / \ / \ + *---*---*---* + \ / \ / \ / \ + -0.5 *- *---*---*---* + \ \ / \ / \ / \ + *- *---*---*---* + \ \ \ \ + *---*---*---* + \ / \ / \ / \ + *---*---*---* + */ static constexpr AZStd::array TwoSeparatedPlanesPositions{ -1.0f, -0.333f, -0.5f, -0.333f, -1.0f, -0.5f, -0.333f, -0.333f, -0.5f, 0.333f, -0.333f, -0.5f, 1.0f, -1.0f, -0.5f, 1.0f, -0.333f, -0.5f, 0.333f, -1.0f, -0.5f, 0.333f, 1.0f, -0.5f, 1.0f, 0.333f, -0.5f, 1.0f, 1.0f, -0.5f, diff --git a/Gems/Atom/RPI/Code/Tests/Shader/ShaderTests.cpp b/Gems/Atom/RPI/Code/Tests/Shader/ShaderTests.cpp index 74f79ad80a..b30fd0ec94 100644 --- a/Gems/Atom/RPI/Code/Tests/Shader/ShaderTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Shader/ShaderTests.cpp @@ -1666,20 +1666,21 @@ namespace UnitTest EXPECT_FALSE(result7.IsFullyBaked()); EXPECT_EQ(result7.GetStableId().GetIndex(), stableId7); - // All searches so far found exactly the node we were looking for - // The next couple of searches will not find the requested node - // and will instead default to its parent, up the tree to the root - // - // [] [Root] - // / \ - // [Color] [Teal] [Fuchsia] - // / \ - // [Quality] [Sublime] [Auto] - // / - // [NumberSamples] [50] - // / \ - // [Raytracing] [On] [Off] - + /* + All searches so far found exactly the node we were looking for + The next couple of searches will not find the requested node + and will instead default to its parent, up the tree to the root + + [] [Root] + / \ + [Color] [Teal] [Fuchsia] + / \ + [Quality] [Sublime] [Auto] + / + [NumberSamples] [50] + / \ + [Raytracing] [On] [Off] + */ // ---------------------------------------- // [Quality::Poor] diff --git a/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/RenderUtil.cpp b/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/RenderUtil.cpp index 23e5d25824..7f909e0e5f 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/RenderUtil.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/RenderUtil.cpp @@ -1634,19 +1634,19 @@ namespace MCommon AZ::Vector3 vertices[7]; /* - // 4 - // / \ - // / \ - // / \ - // / \ - // / \ - // 5-----6 2-----3 - // | | - // | | - // | | - // | | - // | | - // 0---------1 + 4 + / \ + / \ + / \ + / \ + / \ + 5-----6 2-----3 + | | + | | + | | + | | + | | + 0---------1 */ // construct the arrow vertices vertices[0] = center + AZ::Vector3(-right * trailWidthHalf - forward * trailLengh) * scale; @@ -1744,19 +1744,19 @@ namespace MCommon AZ::Vector3 oldLeft, oldRight; /* - // 4 - // / \ - // / \ - // / \ - // / \ - // / \ - // 5-----6 2-----3 - // | | - // | | - // | | - // | | - // | | - // 0-------1 + 4 + / \ + / \ + / \ + / \ + / \ + 5-----6 2-----3 + | | + | | + | | + | | + | | + 0-------1 */ // construct the arrow vertices vertices[0] = center + (-right * trailWidthHalf - forward * trailLength) * scale; diff --git a/Gems/EMotionFX/Code/Tests/AutoSkeletonLODTests.cpp b/Gems/EMotionFX/Code/Tests/AutoSkeletonLODTests.cpp index 726aa664f0..d445bc62e0 100644 --- a/Gems/EMotionFX/Code/Tests/AutoSkeletonLODTests.cpp +++ b/Gems/EMotionFX/Code/Tests/AutoSkeletonLODTests.cpp @@ -31,23 +31,25 @@ namespace EMotionFX class AutoSkeletonLODActor : public SimpleJointChainActor { - // This creates an Actor with following hierarchy. - // The numbers are the joint indices. - // - // 5 - // / - // / - // 0-----1-----2-----3-----4 - // \ - // \ - // 6 - // - // 7 (a node with skinned mesh) - // - // The mesh is on node 7, which is also a root node, just like joint number 0. - // We (fake) skin the first six joints to the mesh of node 7. - // Our test will actually skin to only a selection of these first seven joints. - // We then test which joints get disabled and which not. + /* + This creates an Actor with following hierarchy. + The numbers are the joint indices. + + 5 + / + / + 0-----1-----2-----3-----4 + \ + \ + 6 + + 7 (a node with skinned mesh) + + The mesh is on node 7, which is also a root node, just like joint number 0. + We (fake) skin the first six joints to the mesh of node 7. + Our test will actually skin to only a selection of these first seven joints. + We then test which joints get disabled and which not. + */ public: explicit AutoSkeletonLODActor(AZ::u32 numSubMeshJoints) : SimpleJointChainActor(5) diff --git a/Gems/EMotionFX/Code/Tests/BlendSpaceTests.cpp b/Gems/EMotionFX/Code/Tests/BlendSpaceTests.cpp index b2f6f49d8f..ce8387f150 100644 --- a/Gems/EMotionFX/Code/Tests/BlendSpaceTests.cpp +++ b/Gems/EMotionFX/Code/Tests/BlendSpaceTests.cpp @@ -254,19 +254,21 @@ namespace EMotionFX EXPECT_EQ(motions.size(), 4); EXPECT_EQ(uniqueData->m_triangles.size(), 2); - // run 2 * - // |\ - // | \ - // | \ - // | \ - // | \ - // forward 1 * * 3 Strafe - // | / - // | / - // | / - // | / - // |/ - // idle 0 * + /* + run 2 * + |\ + | \ + | \ + | \ + | \ + forward 1 * * 3 Strafe + | / + | / + | / + | / + |/ + idle 0 * + */ EXPECT_EQ(uniqueData->m_triangles[0], BlendSpace2DNode::Triangle(1, 0, 3)); EXPECT_EQ(uniqueData->m_triangles[1], BlendSpace2DNode::Triangle(2, 1, 3)); } diff --git a/Gems/EMotionFX/Code/Tests/NonUniformMotionDataTests.cpp b/Gems/EMotionFX/Code/Tests/NonUniformMotionDataTests.cpp index 09a4e46b84..cc3e3c344a 100644 --- a/Gems/EMotionFX/Code/Tests/NonUniformMotionDataTests.cpp +++ b/Gems/EMotionFX/Code/Tests/NonUniformMotionDataTests.cpp @@ -285,12 +285,14 @@ namespace EMotionFX numRemoved = motionData.ReduceSamples(reduceSettings); EXPECT_EQ(motionData.GetNumFloatSamples(0), 2); EXPECT_EQ(numRemoved, 9); - - // Set the sample in the middle to 1.0 and the rest to 0. - // - // /\ - // / \ - // ---------------------/ \--------------------- + + /* + Set the sample in the middle to 1.0 and the rest to 0. + + /\ + / \ + ---------------------/ \--------------------- + */ motionData.AllocateFloatSamples(0, 11); for (size_t i = 0; i < motionData.GetNumFloatSamples(0); ++i) { @@ -301,12 +303,14 @@ namespace EMotionFX numRemoved = motionData.ReduceSamples(reduceSettings); EXPECT_EQ(motionData.GetNumFloatSamples(0), 5); EXPECT_EQ(numRemoved, 6); - - // Make a bump of 2 frames. - // - // /------\ - // / \ - // ---------------------/ \--------------- + + /* + Make a bump of 2 frames. + + /------\ + / \ + ---------------------/ \--------------- + */ motionData.AllocateFloatSamples(0, 11); for (size_t i = 0; i < motionData.GetNumFloatSamples(0); ++i) { @@ -398,11 +402,13 @@ namespace EMotionFX EXPECT_EQ(motionData.GetNumJointRotationSamples(0), 0); EXPECT_EQ(numRemoved, 11); - // Set the sample in the middle to 1.0 and the rest to 0. - // - // /\ - // / \ - // ---------------------/ \--------------------- + /* + Set the sample in the middle to 1.0 and the rest to 0. + + /\ + / \ + ---------------------/ \--------------------- + */ motionData.AllocateJointRotationSamples(0, 11); for (size_t i = 0; i < motionData.GetNumJointRotationSamples(0); ++i) { @@ -413,12 +419,14 @@ namespace EMotionFX numRemoved = motionData.ReduceSamples(reduceSettings); EXPECT_EQ(motionData.GetNumJointRotationSamples(0), 5); EXPECT_EQ(numRemoved, 6); - - // Make a bump of 2 frames. - // - // /------\ - // / \ - // ---------------------/ \--------------- + + /* + Make a bump of 2 frames. + + /------\ + / \ + ---------------------/ \--------------- + */ motionData.AllocateJointRotationSamples(0, 11); for (size_t i = 0; i < motionData.GetNumJointRotationSamples(0); ++i) { @@ -509,11 +517,13 @@ namespace EMotionFX EXPECT_EQ(motionData.GetNumJointPositionSamples(0), 0); EXPECT_EQ(numRemoved, 11); - // Set the sample in the middle to 1.0 and the rest to 0. - // - // /\ - // / \ - // ---------------------/ \--------------------- + /* + Set the sample in the middle to 1.0 and the rest to 0. + + /\ + / \ + ---------------------/ \--------------------- + */ motionData.AllocateJointPositionSamples(0, 11); for (size_t i = 0; i < motionData.GetNumJointPositionSamples(0); ++i) { @@ -525,11 +535,13 @@ namespace EMotionFX EXPECT_EQ(motionData.GetNumJointPositionSamples(0), 5); EXPECT_EQ(numRemoved, 6); - // Make a bump of 2 frames. - // - // /------\ - // / \ - // ---------------------/ \--------------- + /* + Make a bump of 2 frames. + + /------\ + / \ + ---------------------/ \--------------- + */ motionData.AllocateJointPositionSamples(0, 11); for (size_t i = 0; i < motionData.GetNumJointPositionSamples(0); ++i) { diff --git a/Gems/LmbrCentral/Code/Source/Shape/TubeShape.cpp b/Gems/LmbrCentral/Code/Source/Shape/TubeShape.cpp index 68198c491a..6dc6bf3f18 100644 --- a/Gems/LmbrCentral/Code/Source/Shape/TubeShape.cpp +++ b/Gems/LmbrCentral/Code/Source/Shape/TubeShape.cpp @@ -328,18 +328,19 @@ namespace LmbrCentral sides, capSegments, vertices); } } - - /// Generates vertices and indices for a tube shape - /// Split into two stages: - /// - Generate vertex positions - /// - Generate indices (faces) - /// Heres a rough diagram of how it is built: - /// ____________ - /// /_|__|__|__|_\ - /// \_|__|__|__|_/ - /// - A single vertex at each end of the tube - /// - Angled end cap segments - /// - Middle segments + /* + Generates vertices and indices for a tube shape + Split into two stages: + - Generate vertex positions + - Generate indices (faces) + Heres a rough diagram of how it is built: + ____________ + /_|__|__|__|_\ + \_|__|__|__|_/ + - A single vertex at each end of the tube + - Angled end cap segments + - Middle segments + */ void GenerateSolidTubeMesh( const AZ::SplinePtr& spline, const SplineAttribute& variableRadius, const float radius, const AZ::u32 capSegments, const AZ::u32 sides, diff --git a/cmake/Platform/Common/GCC/Configurations_gcc.cmake b/cmake/Platform/Common/GCC/Configurations_gcc.cmake index 9963c62d39..12dd4d7bdd 100644 --- a/cmake/Platform/Common/GCC/Configurations_gcc.cmake +++ b/cmake/Platform/Common/GCC/Configurations_gcc.cmake @@ -34,7 +34,6 @@ ly_append_configurations_options( -fno-exceptions -fvisibility=hidden -fvisibility-inlines-hidden - -Wall -Werror @@ -45,7 +44,6 @@ ly_append_configurations_options( -Wno-array-bounds -Wno-attributes -Wno-class-memaccess - -Wno-comment -Wno-delete-non-virtual-dtor -Wno-enum-compare -Wno-format-overflow From 30de4e92e03f04f1b1229af2eaf52e4d4c8e8c04 Mon Sep 17 00:00:00 2001 From: Benjamin Jillich <43751992+amzn-jillich@users.noreply.github.com> Date: Tue, 1 Feb 2022 18:05:58 +0100 Subject: [PATCH 53/53] Motion Matching: Added example level and assets for an automatic demo #7317 * Added camera controller script canvas graph that follows the character and rotates slowly around it. * Added simple motion matching anim graph using the automatic target mode that doesn't need user input and just makes the character run around in the level. * Added example level. Note: As O3DE can't locate level files from within the gems asset folder, the level file needs to manually be copy & pasted to the ${YourProject}\Levels\ folder. Signed-off-by: Benjamin Jillich jillich@amazon.com --- ...ameraController_AutomaticDemo.scriptcanvas | 4255 +++++++++++ ...acterController_AutomaticDemo.scriptcanvas | 6387 +++++++++++++++++ .../MotionMatching_AutomaticDemo.animgraph | 3 + .../MotionMatching_AutomaticDemo.ly | 3 + .../Assets/MotionMatching.animgraph | 4 +- .../Assets/MotionMatching.motionset | 4 +- Gems/MotionMatching/preview.png | 4 +- 7 files changed, 10654 insertions(+), 6 deletions(-) create mode 100644 Gems/MotionMatching/Assets/Levels/MotionMatching_AutomaticDemo/CameraController_AutomaticDemo.scriptcanvas create mode 100644 Gems/MotionMatching/Assets/Levels/MotionMatching_AutomaticDemo/CharacterController_AutomaticDemo.scriptcanvas create mode 100644 Gems/MotionMatching/Assets/Levels/MotionMatching_AutomaticDemo/MotionMatching_AutomaticDemo.animgraph create mode 100644 Gems/MotionMatching/Assets/Levels/MotionMatching_AutomaticDemo/MotionMatching_AutomaticDemo.ly diff --git a/Gems/MotionMatching/Assets/Levels/MotionMatching_AutomaticDemo/CameraController_AutomaticDemo.scriptcanvas b/Gems/MotionMatching/Assets/Levels/MotionMatching_AutomaticDemo/CameraController_AutomaticDemo.scriptcanvas new file mode 100644 index 0000000000..8569d7686b --- /dev/null +++ b/Gems/MotionMatching/Assets/Levels/MotionMatching_AutomaticDemo/CameraController_AutomaticDemo.scriptcanvas @@ -0,0 +1,4255 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "ScriptCanvasData", + "ClassData": { + "m_scriptCanvas": { + "Id": { + "id": 1874297699023155003 + }, + "Name": "CameraController", + "Components": { + "Component_[18372998151874304809]": { + "$type": "EditorGraph", + "Id": 18372998151874304809, + "m_graphData": { + "m_nodes": [ + { + "Id": { + "id": 58688970296996 + }, + "Name": "SC-Node((NodeFunctionGenericMultiReturn)<{Transform(double )}* RotationXDegreesTraits >)", + "Components": { + "Component_[11367256627712525407]": { + "$type": "(NodeFunctionGenericMultiReturn)<{Transform(double )}* RotationXDegreesTraits >", + "Id": 11367256627712525407, + "Slots": [ + { + "id": { + "m_id": "{DC131C25-5C2C-482D-96A1-88542026DF75}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{A4D7609D-9560-43D5-9FDE-36242CE35B48}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{22458849-F990-4D8A-94B2-03D8F3878E19}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Number: Degrees", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{24EDA641-DBF3-41A8-8D47-60A3D83E3F23}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result: Transform", + "DisplayDataType": { + "m_type": 7 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": -15.0, + "label": "Number: Degrees" + } + ], + "Initialized": true + } + } + }, + { + "Id": { + "id": 58624545787556 + }, + "Name": "SC-Node(OperatorMul)", + "Components": { + "Component_[11451129070958096728]": { + "$type": "OperatorMul", + "Id": 11451129070958096728, + "Slots": [ + { + "id": { + "m_id": "{3180019C-C78A-456E-B3E2-F1601B4A629A}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{A4F4074B-5E3B-4A5B-A7F7-AC2E4AAA0939}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{B2F820AB-9377-4A47-AD2C-03D76C6E4024}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "OperatorType": "Multiply", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 7 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Transform", + "toolTip": "An operand to use in performing the specified Operation", + "DisplayDataType": { + "m_type": 7 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{5509933D-93A5-483E-B7B5-FDD637C997DA}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "OperatorType": "Multiply", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 7 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Transform", + "toolTip": "An operand to use in performing the specified Operation", + "DisplayDataType": { + "m_type": 7 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{96271AD4-A2BB-4597-B78F-8053F0200C3D}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "OperatorType": "Multiply", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 7 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Result", + "toolTip": "The result of the specified operation", + "DisplayDataType": { + "m_type": 7 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 7 + }, + "isNullPointer": false, + "$type": "Transform", + "value": { + "Translation": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": 1.0 + }, + "label": "Transform" + }, + { + "scriptCanvasType": { + "m_type": 7 + }, + "isNullPointer": false, + "$type": "Transform", + "value": { + "Translation": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": 1.0 + }, + "label": "Transform" + } + ] + } + } + }, + { + "Id": { + "id": 58667495460516 + }, + "Name": "EBusEventHandler", + "Components": { + "Component_[12391893846605096594]": { + "$type": "EBusEventHandler", + "Id": 12391893846605096594, + "Slots": [ + { + "id": { + "m_id": "{47B8793A-68B3-4DF7-B485-98CF8625EE94}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Connect", + "toolTip": "Connect this event handler to the specified entity.", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{75D0B671-F876-4383-B3AE-414B37AD0638}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Disconnect", + "toolTip": "Disconnect this event handler.", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{03259091-B08D-4118-9AA3-053FD63AD6F7}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "OnConnected", + "toolTip": "Signaled when a connection has taken place.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{7C2E17C2-C3A1-4CB1-9AE6-521C4DD0D780}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "OnDisconnected", + "toolTip": "Signaled when this event handler is disconnected.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{9EB40EA9-0799-4FFF-9816-D6BEE9302D56}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "OnFailure", + "toolTip": "Signaled when it is not possible to connect this handler.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{02148868-388F-44F0-9E3A-C31601701F3B}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Number", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{1D2C5717-17A6-401F-BBF6-08EF84870CD3}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "ScriptTimePoint", + "DisplayDataType": { + "m_type": 4, + "m_azType": "{4C0F6AD4-0D4F-4354-AD4A-0C01E948245C}" + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{8965578E-A29D-4468-B12B-9D4E4F814641}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "ExecutionSlot:OnTick", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + }, + "IsLatent": true + }, + { + "id": { + "m_id": "{53E6F39B-30C2-4828-83A4-A286E22DD18D}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result: Number", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{909B60CC-693B-4DC9-9BC2-65B1D9373B5A}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "ExecutionSlot:GetTickOrder", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + }, + "IsLatent": true + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Result: Number" + } + ], + "m_eventMap": [ + { + "Key": { + "Value": 1502188240 + }, + "Value": { + "m_eventName": "OnTick", + "m_eventId": { + "Value": 1502188240 + }, + "m_eventSlotId": { + "m_id": "{8965578E-A29D-4468-B12B-9D4E4F814641}" + }, + "m_parameterSlotIds": [ + { + "m_id": "{02148868-388F-44F0-9E3A-C31601701F3B}" + }, + { + "m_id": "{1D2C5717-17A6-401F-BBF6-08EF84870CD3}" + } + ], + "m_numExpectedArguments": 2 + } + }, + { + "Key": { + "Value": 1890826333 + }, + "Value": { + "m_eventName": "GetTickOrder", + "m_eventId": { + "Value": 1890826333 + }, + "m_eventSlotId": { + "m_id": "{909B60CC-693B-4DC9-9BC2-65B1D9373B5A}" + }, + "m_resultSlotId": { + "m_id": "{53E6F39B-30C2-4828-83A4-A286E22DD18D}" + } + } + } + ], + "m_ebusName": "TickBus", + "m_busId": { + "Value": 1209186864 + } + } + } + }, + { + "Id": { + "id": 58633135722148 + }, + "Name": "SC-Node(OperatorMul)", + "Components": { + "Component_[13427352953117170385]": { + "$type": "OperatorMul", + "Id": 13427352953117170385, + "Slots": [ + { + "id": { + "m_id": "{B6D38C30-7BB4-4549-AFFA-C5A52AE9796E}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{86D20026-0D06-4DFB-B3CD-9BE5038B8121}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{F9E6233F-EAFB-43F5-A10D-96A3A41763A7}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "OperatorType": "Multiply", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 7 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Number", + "toolTip": "An operand to use in performing the specified Operation", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{9D3D613C-ABE4-4028-A1CC-22E6E036376C}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "OperatorType": "Multiply", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 7 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Number", + "toolTip": "An operand to use in performing the specified Operation", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{C0EA31C9-71C6-4CCF-A31B-33E7385C78D0}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "OperatorType": "Multiply", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 7 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Result", + "toolTip": "The result of the specified operation", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 1.0, + "label": "Number" + }, + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": -90.0, + "label": "Number" + } + ] + } + } + }, + { + "Id": { + "id": 214400860662419 + }, + "Name": "SC Node(GetVariable)", + "Components": { + "Component_[1359072869693534631]": { + "$type": "GetVariableNode", + "Id": 1359072869693534631, + "Slots": [ + { + "id": { + "m_id": "{7500DE86-7349-4213-B3D6-F2872EDF15A6}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "When signaled sends the property referenced by this node to a Data Output slot", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{8C8D3DD7-F821-42F4-AF51-5D976133DAE4}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "toolTip": "Signaled after the referenced property has been pushed to the Data Output slot", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{5E4734A2-3CAF-4E29-AC72-5689FF1FD619}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Number", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "m_variableId": { + "m_id": "{0620A309-A152-4CF3-BF76-284115B30780}" + }, + "m_variableDataOutSlotId": { + "m_id": "{5E4734A2-3CAF-4E29-AC72-5689FF1FD619}" + } + } + } + }, + { + "Id": { + "id": 58577301147300 + }, + "Name": "SC-Node(OperatorMul)", + "Components": { + "Component_[14055904483179664364]": { + "$type": "OperatorMul", + "Id": 14055904483179664364, + "Slots": [ + { + "id": { + "m_id": "{F5BE2CBC-3CA3-44BD-B7D4-71D9CAC023B3}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{4CD5D25E-73DA-4936-89E9-8F0A7946DED1}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{2F0B4FC7-0B48-42A9-8073-8493E9F1D2E5}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "OperatorType": "Multiply", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 7 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Number", + "toolTip": "An operand to use in performing the specified Operation", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{67B729E8-73F9-4B97-A67C-C986E874BD01}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "OperatorType": "Multiply", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 7 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Number", + "toolTip": "An operand to use in performing the specified Operation", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{785A9BC8-8F65-4DFE-8809-7C8E937A8D8B}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "OperatorType": "Multiply", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 7 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Result", + "toolTip": "The result of the specified operation", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 1.0, + "label": "Number" + }, + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 1.0, + "label": "Number" + } + ] + } + } + }, + { + "Id": { + "id": 58581596114596 + }, + "Name": "SC-Node(OperatorMul)", + "Components": { + "Component_[14210959117790557692]": { + "$type": "OperatorMul", + "Id": 14210959117790557692, + "Slots": [ + { + "id": { + "m_id": "{374AA17F-7CC0-4808-8269-6CC4F64579C3}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{1E111259-19D2-4180-81A1-F648F79B004D}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{E7D6A002-7522-4915-BCCB-89A29E3D5582}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "OperatorType": "Multiply", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 7 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Transform", + "toolTip": "An operand to use in performing the specified Operation", + "DisplayDataType": { + "m_type": 7 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{0ABC384E-FBE6-40F8-B5C3-7652B814102C}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "OperatorType": "Multiply", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 7 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Transform", + "toolTip": "An operand to use in performing the specified Operation", + "DisplayDataType": { + "m_type": 7 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{7DA1EC3E-277C-4DDA-94FD-EF2EC66CD272}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "OperatorType": "Multiply", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 7 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Result", + "toolTip": "The result of the specified operation", + "DisplayDataType": { + "m_type": 7 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 7 + }, + "isNullPointer": false, + "$type": "Transform", + "value": { + "Translation": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": 1.0 + }, + "label": "Transform" + }, + { + "scriptCanvasType": { + "m_type": 7 + }, + "isNullPointer": false, + "$type": "Transform", + "value": { + "Translation": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": 1.0 + }, + "label": "Transform" + } + ] + } + } + }, + { + "Id": { + "id": 213241219492499 + }, + "Name": "SC-Node(OperatorAdd)", + "Components": { + "Component_[14948826965328970882]": { + "$type": "OperatorAdd", + "Id": 14948826965328970882, + "Slots": [ + { + "id": { + "m_id": "{56805D2B-0C98-4145-80FB-AA1DCB16CF1A}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{008FCEC7-3E86-4249-A736-F158EFDB0EFA}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{DB849F77-66D0-4D64-A45F-122F93E1E80C}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 8 + }, + { + "m_type": 9 + }, + { + "m_type": 10 + }, + { + "m_type": 11 + }, + { + "m_type": 12 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Number", + "toolTip": "An operand to use in performing the specified Operation", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{CA8ABF3F-BF80-4A8B-A2B2-A1D2C16278C1}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 8 + }, + { + "m_type": 9 + }, + { + "m_type": 10 + }, + { + "m_type": 11 + }, + { + "m_type": 12 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Number", + "toolTip": "An operand to use in performing the specified Operation", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{53895246-F759-4328-AABB-D26E49E5208D}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 8 + }, + { + "m_type": 9 + }, + { + "m_type": 10 + }, + { + "m_type": 11 + }, + { + "m_type": 12 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Result", + "toolTip": "The result of the specified operation", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Number" + }, + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Number" + } + ] + } + } + }, + { + "Id": { + "id": 58628840754852 + }, + "Name": "SC-Node(SetWorldTM)", + "Components": { + "Component_[15733030521963116718]": { + "$type": "{E42861BD-1956-45AE-8DD7-CCFC1E3E5ACF} Method", + "Id": 15733030521963116718, + "Slots": [ + { + "id": { + "m_id": "{785CBBEE-E704-4049-A180-8E99E3E1E1F2}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "EntityID: 0", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{A7826FF9-C3B2-4E28-815D-9B07A6EE0949}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Transform: 1", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{6BB59597-64B4-458F-9C96-B3E0DA73279A}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{44A7845D-724A-4BB6-99F1-12B2E7979D93}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 1 + }, + "isNullPointer": false, + "$type": "EntityId", + "value": { + "id": 2901262558 + }, + "label": "Transform" + }, + { + "scriptCanvasType": { + "m_type": 7 + }, + "isNullPointer": false, + "$type": "Transform", + "value": { + "Translation": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": 1.0 + }, + "label": "Transform: 1" + } + ], + "methodType": 0, + "methodName": "SetWorldTM", + "className": "TransformBus", + "resultSlotIDs": [ + {} + ], + "inputSlots": [ + { + "m_id": "{785CBBEE-E704-4049-A180-8E99E3E1E1F2}" + }, + { + "m_id": "{A7826FF9-C3B2-4E28-815D-9B07A6EE0949}" + } + ], + "prettyClassName": "TransformBus" + } + } + }, + { + "Id": { + "id": 239870016727699 + }, + "Name": "SC-Node(OperatorMul)", + "Components": { + "Component_[3092852928531574536]": { + "$type": "OperatorMul", + "Id": 3092852928531574536, + "Slots": [ + { + "id": { + "m_id": "{FEFF67A1-C89B-447E-80D4-CD94CB0C8AD7}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{7E5D8673-F03A-4134-87DE-88AD13A74C29}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{8A243C9C-0D69-4753-B28B-A8D2FAC5D508}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "OperatorType": "Multiply", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 7 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Number", + "toolTip": "An operand to use in performing the specified Operation", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{3F3F3DD6-124E-48A8-8373-6666900D8177}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "OperatorType": "Multiply", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 7 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Number", + "toolTip": "An operand to use in performing the specified Operation", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{DFD1DE7E-B470-43F6-A6CF-E9E572D0C075}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "OperatorType": "Multiply", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 7 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Result", + "toolTip": "The result of the specified operation", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "isOverloadedStorage": false, + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 1.0, + "label": "Number" + }, + { + "isOverloadedStorage": false, + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 10.0, + "label": "Number" + } + ] + } + } + }, + { + "Id": { + "id": 58654610558628 + }, + "Name": "SC Node(SetVariable)", + "Components": { + "Component_[5081311661452231726]": { + "$type": "SetVariableNode", + "Id": 5081311661452231726, + "Slots": [ + { + "id": { + "m_id": "{F32F9B01-14B4-4DC3-AE0E-9E233DE5E941}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "When signaled sends the variable referenced by this node to a Data Output slot", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{47175A2C-5EE4-4C48-8CC1-8F2C84D36FE5}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "toolTip": "Signaled after the referenced variable has been pushed to the Data Output slot", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{B22A0453-9715-46C7-A766-9536020AB59F}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Number", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{1B58C71B-3AB9-4D90-A8D5-164A20B083DA}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Number", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Number" + } + ], + "m_variableId": { + "m_id": "{0620A309-A152-4CF3-BF76-284115B30780}" + }, + "m_variableDataInSlotId": { + "m_id": "{B22A0453-9715-46C7-A766-9536020AB59F}" + }, + "m_variableDataOutSlotId": { + "m_id": "{1B58C71B-3AB9-4D90-A8D5-164A20B083DA}" + } + } + } + }, + { + "Id": { + "id": 58671790427812 + }, + "Name": "SC-Node((NodeFunctionGenericMultiReturn)<{Transform(Vector3 )}* FromTranslationTraits >)", + "Components": { + "Component_[5185507574364590308]": { + "$type": "(NodeFunctionGenericMultiReturn)<{Transform(Vector3 )}* FromTranslationTraits >", + "Id": 5185507574364590308, + "Slots": [ + { + "id": { + "m_id": "{991F134E-88CC-423B-93C9-FFE9A9406739}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{FACA9011-E0FC-413E-B8AE-136D7DD2BD14}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{210001FC-BDC8-4E41-9990-6F42A0368833}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Vector3: Translation", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{49236CAF-226C-4F65-9C1D-92436010232D}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result: Transform", + "DisplayDataType": { + "m_type": 7 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 8 + }, + "isNullPointer": false, + "$type": "Vector3", + "value": [ + -0.6499999761581421, + -4.699999809265137, + 0.699999988079071 + ], + "label": "Vector3: Translation" + } + ], + "Initialized": true + } + } + }, + { + "Id": { + "id": 58590186049188 + }, + "Name": "SC-Node((NodeFunctionGenericMultiReturn)<{Transform(double )}* RotationZDegreesTraits >)", + "Components": { + "Component_[5714854897173958925]": { + "$type": "(NodeFunctionGenericMultiReturn)<{Transform(double )}* RotationZDegreesTraits >", + "Id": 5714854897173958925, + "Slots": [ + { + "id": { + "m_id": "{4B28D8A2-7F6C-4EB1-A7D5-EA68FB783B52}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{A6D81A02-E3CB-44FC-80E6-61D8AB732ACC}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{694A218A-8047-4981-BF62-726AF9BCB3C6}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Number: Degrees", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{065D3BDE-D0B8-4F84-82EB-8B48779A80CA}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result: Transform", + "DisplayDataType": { + "m_type": 7 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Number: Degrees" + } + ], + "Initialized": true + } + } + }, + { + "Id": { + "id": 58607365918372 + }, + "Name": "SC-Node((NodeFunctionGenericMultiReturn)<{Transform(Vector3 )}* FromTranslationTraits >)", + "Components": { + "Component_[6151602155863937217]": { + "$type": "(NodeFunctionGenericMultiReturn)<{Transform(Vector3 )}* FromTranslationTraits >", + "Id": 6151602155863937217, + "Slots": [ + { + "id": { + "m_id": "{AC5F0607-97E7-4EF4-A4A1-7CE21E42A484}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{5F03F883-BFAC-424C-BB04-334B68B845FC}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{E00A1E12-1D89-42EC-939B-284B1D6B4EAE}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Vector3: Translation", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{F84430FF-A436-459B-B849-6456573B85E7}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result: Transform", + "DisplayDataType": { + "m_type": 7 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 8 + }, + "isNullPointer": false, + "$type": "Vector3", + "value": [ + 0.0, + 0.0, + 0.0 + ], + "label": "Vector3: Translation" + } + ], + "Initialized": true + } + } + }, + { + "Id": { + "id": 58663200493220 + }, + "Name": "SC-Node(GetWorldTranslation)", + "Components": { + "Component_[7931832476506096485]": { + "$type": "{E42861BD-1956-45AE-8DD7-CCFC1E3E5ACF} Method", + "Id": 7931832476506096485, + "Slots": [ + { + "id": { + "m_id": "{D2B68C1A-14D8-45DE-8AED-DDB02B925B24}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "EntityID: 0", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{94D7F295-34CD-42AC-AD7B-65B8407F2CF4}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{0A0BFDE6-2029-414E-8A8F-41DF60F9C348}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{2FAA43A0-BC0B-428B-B0BF-AFDDFDEA474C}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result: Vector3", + "DisplayDataType": { + "m_type": 8 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 1 + }, + "isNullPointer": false, + "$type": "EntityId", + "value": { + "id": 653853330958 + }, + "label": "EntityID: 0" + } + ], + "methodType": 0, + "methodName": "GetWorldTranslation", + "className": "TransformBus", + "resultSlotIDs": [ + {} + ], + "inputSlots": [ + { + "m_id": "{D2B68C1A-14D8-45DE-8AED-DDB02B925B24}" + } + ], + "prettyClassName": "TransformBus" + } + } + }, + { + "Id": { + "id": 58594481016484 + }, + "Name": "SC Node(GetVariable)", + "Components": { + "Component_[8136131668650391163]": { + "$type": "GetVariableNode", + "Id": 8136131668650391163, + "Slots": [ + { + "id": { + "m_id": "{09256706-E7A2-47C3-ACE0-3BE7D80E34FD}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "When signaled sends the property referenced by this node to a Data Output slot", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{1ACC7F53-E81A-4721-9AC8-5C1A33C8BE96}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "toolTip": "Signaled after the referenced property has been pushed to the Data Output slot", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{4972DD3B-D554-47B7-816B-CBCF67697ABA}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Number", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "m_variableId": { + "m_id": "{0620A309-A152-4CF3-BF76-284115B30780}" + }, + "m_variableDataOutSlotId": { + "m_id": "{4972DD3B-D554-47B7-816B-CBCF67697ABA}" + } + } + } + }, + { + "Id": { + "id": 58658905525924 + }, + "Name": "SC-Node(OperatorMul)", + "Components": { + "Component_[8240456843039210842]": { + "$type": "OperatorMul", + "Id": 8240456843039210842, + "Slots": [ + { + "id": { + "m_id": "{472B1FB5-766E-4F47-9955-B655857BE135}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{1ACBC12A-7FB1-4ADF-92BC-56B0DCE8CFAD}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{73F9A12E-8255-4C5E-89A8-B2F837C29AF6}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "OperatorType": "Multiply", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 7 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Transform", + "toolTip": "An operand to use in performing the specified Operation", + "DisplayDataType": { + "m_type": 7 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{89EA50A0-3CD4-46D6-8404-534EDB8C94EE}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "OperatorType": "Multiply", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 7 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Transform", + "toolTip": "An operand to use in performing the specified Operation", + "DisplayDataType": { + "m_type": 7 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{A56467EC-4D3B-4CBE-847D-E5367BEFF26C}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "OperatorType": "Multiply", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 7 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Result", + "toolTip": "The result of the specified operation", + "DisplayDataType": { + "m_type": 7 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 7 + }, + "isNullPointer": false, + "$type": "Transform", + "value": { + "Translation": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": 1.0 + }, + "label": "Transform" + }, + { + "scriptCanvasType": { + "m_type": 7 + }, + "isNullPointer": false, + "$type": "Transform", + "value": { + "Translation": [ + 0.0, + 0.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "Scale": 1.0 + }, + "label": "Transform" + } + ] + } + } + } + ], + "m_connections": [ + { + "Id": { + "id": 58706150166180 + }, + "Name": "srcEndpoint=(Get Variable: Out), destEndpoint=(RotationZDegrees: In)", + "Components": { + "Component_[553961487450147653]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 553961487450147653, + "sourceEndpoint": { + "nodeId": { + "id": 58594481016484 + }, + "slotId": { + "m_id": "{1ACC7F53-E81A-4721-9AC8-5C1A33C8BE96}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58590186049188 + }, + "slotId": { + "m_id": "{4B28D8A2-7F6C-4EB1-A7D5-EA68FB783B52}" + } + } + } + } + }, + { + "Id": { + "id": 58710445133476 + }, + "Name": "srcEndpoint=(Get Variable: Number), destEndpoint=(RotationZDegrees: Number: Degrees)", + "Components": { + "Component_[8946072267986857913]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 8946072267986857913, + "sourceEndpoint": { + "nodeId": { + "id": 58594481016484 + }, + "slotId": { + "m_id": "{4972DD3B-D554-47B7-816B-CBCF67697ABA}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58590186049188 + }, + "slotId": { + "m_id": "{694A218A-8047-4981-BF62-726AF9BCB3C6}" + } + } + } + } + }, + { + "Id": { + "id": 58714740100772 + }, + "Name": "srcEndpoint=(Multiply (*): Out), destEndpoint=(SetWorldTM: In)", + "Components": { + "Component_[3188243497734984899]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 3188243497734984899, + "sourceEndpoint": { + "nodeId": { + "id": 58581596114596 + }, + "slotId": { + "m_id": "{1E111259-19D2-4180-81A1-F648F79B004D}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58628840754852 + }, + "slotId": { + "m_id": "{6BB59597-64B4-458F-9C96-B3E0DA73279A}" + } + } + } + } + }, + { + "Id": { + "id": 58719035068068 + }, + "Name": "srcEndpoint=(Multiply (*): Result), destEndpoint=(SetWorldTM: Transform: 1)", + "Components": { + "Component_[16154630012362343924]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 16154630012362343924, + "sourceEndpoint": { + "nodeId": { + "id": 58581596114596 + }, + "slotId": { + "m_id": "{7DA1EC3E-277C-4DDA-94FD-EF2EC66CD272}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58628840754852 + }, + "slotId": { + "m_id": "{A7826FF9-C3B2-4E28-815D-9B07A6EE0949}" + } + } + } + } + }, + { + "Id": { + "id": 58723330035364 + }, + "Name": "srcEndpoint=(GetWorldTranslation: Result: Vector3), destEndpoint=(FromTranslation: Vector3: Translation)", + "Components": { + "Component_[12394822413729075366]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 12394822413729075366, + "sourceEndpoint": { + "nodeId": { + "id": 58663200493220 + }, + "slotId": { + "m_id": "{2FAA43A0-BC0B-428B-B0BF-AFDDFDEA474C}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58607365918372 + }, + "slotId": { + "m_id": "{E00A1E12-1D89-42EC-939B-284B1D6B4EAE}" + } + } + } + } + }, + { + "Id": { + "id": 58727625002660 + }, + "Name": "srcEndpoint=(GetWorldTranslation: Out), destEndpoint=(FromTranslation: In)", + "Components": { + "Component_[4243880791484280857]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 4243880791484280857, + "sourceEndpoint": { + "nodeId": { + "id": 58663200493220 + }, + "slotId": { + "m_id": "{0A0BFDE6-2029-414E-8A8F-41DF60F9C348}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58607365918372 + }, + "slotId": { + "m_id": "{AC5F0607-97E7-4EF4-A4A1-7CE21E42A484}" + } + } + } + } + }, + { + "Id": { + "id": 58731919969956 + }, + "Name": "srcEndpoint=(FromTranslation: Out), destEndpoint=(GetWorldTranslation: In)", + "Components": { + "Component_[1519056228570549771]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 1519056228570549771, + "sourceEndpoint": { + "nodeId": { + "id": 58671790427812 + }, + "slotId": { + "m_id": "{FACA9011-E0FC-413E-B8AE-136D7DD2BD14}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58663200493220 + }, + "slotId": { + "m_id": "{94D7F295-34CD-42AC-AD7B-65B8407F2CF4}" + } + } + } + } + }, + { + "Id": { + "id": 58736214937252 + }, + "Name": "srcEndpoint=(Multiply (*): Out), destEndpoint=(Multiply (*): In)", + "Components": { + "Component_[6232453632040220382]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 6232453632040220382, + "sourceEndpoint": { + "nodeId": { + "id": 58624545787556 + }, + "slotId": { + "m_id": "{A4F4074B-5E3B-4A5B-A7F7-AC2E4AAA0939}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58581596114596 + }, + "slotId": { + "m_id": "{374AA17F-7CC0-4808-8269-6CC4F64579C3}" + } + } + } + } + }, + { + "Id": { + "id": 58740509904548 + }, + "Name": "srcEndpoint=(Multiply (*): Result), destEndpoint=(Multiply (*): Transform)", + "Components": { + "Component_[7007808555566524915]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 7007808555566524915, + "sourceEndpoint": { + "nodeId": { + "id": 58624545787556 + }, + "slotId": { + "m_id": "{96271AD4-A2BB-4597-B78F-8053F0200C3D}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58581596114596 + }, + "slotId": { + "m_id": "{E7D6A002-7522-4915-BCCB-89A29E3D5582}" + } + } + } + } + }, + { + "Id": { + "id": 58753394806436 + }, + "Name": "srcEndpoint=(TickBus Handler: Number), destEndpoint=(Multiply (*): Value)", + "Components": { + "Component_[1087668963814879388]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 1087668963814879388, + "sourceEndpoint": { + "nodeId": { + "id": 58667495460516 + }, + "slotId": { + "m_id": "{02148868-388F-44F0-9E3A-C31601701F3B}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58577301147300 + }, + "slotId": { + "m_id": "{67B729E8-73F9-4B97-A67C-C986E874BD01}" + } + } + } + } + }, + { + "Id": { + "id": 58783459577508 + }, + "Name": "srcEndpoint=(Set Variable: Out), destEndpoint=(Get Variable: In)", + "Components": { + "Component_[5808096905671825435]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 5808096905671825435, + "sourceEndpoint": { + "nodeId": { + "id": 58654610558628 + }, + "slotId": { + "m_id": "{47175A2C-5EE4-4C48-8CC1-8F2C84D36FE5}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58594481016484 + }, + "slotId": { + "m_id": "{09256706-E7A2-47C3-ACE0-3BE7D80E34FD}" + } + } + } + } + }, + { + "Id": { + "id": 58787754544804 + }, + "Name": "srcEndpoint=(Multiply (*): Result), destEndpoint=(Multiply (*): Value)", + "Components": { + "Component_[11775794502882233004]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 11775794502882233004, + "sourceEndpoint": { + "nodeId": { + "id": 58577301147300 + }, + "slotId": { + "m_id": "{785A9BC8-8F65-4DFE-8809-7C8E937A8D8B}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58633135722148 + }, + "slotId": { + "m_id": "{F9E6233F-EAFB-43F5-A10D-96A3A41763A7}" + } + } + } + } + }, + { + "Id": { + "id": 58792049512100 + }, + "Name": "srcEndpoint=(Multiply (*): Out), destEndpoint=(Multiply (*): In)", + "Components": { + "Component_[80299809090156725]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 80299809090156725, + "sourceEndpoint": { + "nodeId": { + "id": 58577301147300 + }, + "slotId": { + "m_id": "{4CD5D25E-73DA-4936-89E9-8F0A7946DED1}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58633135722148 + }, + "slotId": { + "m_id": "{B6D38C30-7BB4-4549-AFFA-C5A52AE9796E}" + } + } + } + } + }, + { + "Id": { + "id": 58800639446692 + }, + "Name": "srcEndpoint=(FromTranslation: Result: Transform), destEndpoint=(Multiply (*): Transform)", + "Components": { + "Component_[7632901123910891973]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 7632901123910891973, + "sourceEndpoint": { + "nodeId": { + "id": 58671790427812 + }, + "slotId": { + "m_id": "{49236CAF-226C-4F65-9C1D-92436010232D}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58581596114596 + }, + "slotId": { + "m_id": "{0ABC384E-FBE6-40F8-B5C3-7652B814102C}" + } + } + } + } + }, + { + "Id": { + "id": 58804934413988 + }, + "Name": "srcEndpoint=(RotationZDegrees: Out), destEndpoint=(RotationXDegrees: In)", + "Components": { + "Component_[10139510177917176292]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 10139510177917176292, + "sourceEndpoint": { + "nodeId": { + "id": 58590186049188 + }, + "slotId": { + "m_id": "{A6D81A02-E3CB-44FC-80E6-61D8AB732ACC}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58688970296996 + }, + "slotId": { + "m_id": "{DC131C25-5C2C-482D-96A1-88542026DF75}" + } + } + } + } + }, + { + "Id": { + "id": 58809229381284 + }, + "Name": "srcEndpoint=(RotationXDegrees: Out), destEndpoint=(FromTranslation: In)", + "Components": { + "Component_[14430114494626818296]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 14430114494626818296, + "sourceEndpoint": { + "nodeId": { + "id": 58688970296996 + }, + "slotId": { + "m_id": "{A4D7609D-9560-43D5-9FDE-36242CE35B48}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58671790427812 + }, + "slotId": { + "m_id": "{991F134E-88CC-423B-93C9-FFE9A9406739}" + } + } + } + } + }, + { + "Id": { + "id": 58813524348580 + }, + "Name": "srcEndpoint=(FromTranslation: Result: Transform), destEndpoint=(Multiply (*): Value)", + "Components": { + "Component_[4673903760365810060]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 4673903760365810060, + "sourceEndpoint": { + "nodeId": { + "id": 58607365918372 + }, + "slotId": { + "m_id": "{F84430FF-A436-459B-B849-6456573B85E7}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58658905525924 + }, + "slotId": { + "m_id": "{73F9A12E-8255-4C5E-89A8-B2F837C29AF6}" + } + } + } + } + }, + { + "Id": { + "id": 58817819315876 + }, + "Name": "srcEndpoint=(Multiply (*): Result), destEndpoint=(Multiply (*): Transform)", + "Components": { + "Component_[8661784838702007417]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 8661784838702007417, + "sourceEndpoint": { + "nodeId": { + "id": 58658905525924 + }, + "slotId": { + "m_id": "{A56467EC-4D3B-4CBE-847D-E5367BEFF26C}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58624545787556 + }, + "slotId": { + "m_id": "{B2F820AB-9377-4A47-AD2C-03D76C6E4024}" + } + } + } + } + }, + { + "Id": { + "id": 58822114283172 + }, + "Name": "srcEndpoint=(FromTranslation: Out), destEndpoint=(Multiply (*): In)", + "Components": { + "Component_[268387562422533755]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 268387562422533755, + "sourceEndpoint": { + "nodeId": { + "id": 58607365918372 + }, + "slotId": { + "m_id": "{5F03F883-BFAC-424C-BB04-334B68B845FC}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58658905525924 + }, + "slotId": { + "m_id": "{472B1FB5-766E-4F47-9955-B655857BE135}" + } + } + } + } + }, + { + "Id": { + "id": 58826409250468 + }, + "Name": "srcEndpoint=(Multiply (*): Out), destEndpoint=(Multiply (*): In)", + "Components": { + "Component_[3775912189287118250]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 3775912189287118250, + "sourceEndpoint": { + "nodeId": { + "id": 58658905525924 + }, + "slotId": { + "m_id": "{1ACBC12A-7FB1-4ADF-92BC-56B0DCE8CFAD}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58624545787556 + }, + "slotId": { + "m_id": "{3180019C-C78A-456E-B3E2-F1601B4A629A}" + } + } + } + } + }, + { + "Id": { + "id": 58830704217764 + }, + "Name": "srcEndpoint=(RotationZDegrees: Result: Transform), destEndpoint=(Multiply (*): Transform)", + "Components": { + "Component_[2967942488527008121]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 2967942488527008121, + "sourceEndpoint": { + "nodeId": { + "id": 58590186049188 + }, + "slotId": { + "m_id": "{065D3BDE-D0B8-4F84-82EB-8B48779A80CA}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58658905525924 + }, + "slotId": { + "m_id": "{89EA50A0-3CD4-46D6-8404-534EDB8C94EE}" + } + } + } + } + }, + { + "Id": { + "id": 58834999185060 + }, + "Name": "srcEndpoint=(RotationXDegrees: Result: Transform), destEndpoint=(Multiply (*): Transform)", + "Components": { + "Component_[191558726927551301]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 191558726927551301, + "sourceEndpoint": { + "nodeId": { + "id": 58688970296996 + }, + "slotId": { + "m_id": "{24EDA641-DBF3-41A8-8D47-60A3D83E3F23}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58624545787556 + }, + "slotId": { + "m_id": "{5509933D-93A5-483E-B7B5-FDD637C997DA}" + } + } + } + } + }, + { + "Id": { + "id": 164626484669075 + }, + "Name": "srcEndpoint=(: ), destEndpoint=(: )", + "Components": { + "Component_[14524450253664706586]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 14524450253664706586, + "sourceEndpoint": { + "nodeId": { + "id": 58667495460516 + }, + "slotId": { + "m_id": "{8965578E-A29D-4468-B12B-9D4E4F814641}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58577301147300 + }, + "slotId": { + "m_id": "{F5BE2CBC-3CA3-44BD-B7D4-71D9CAC023B3}" + } + } + } + } + }, + { + "Id": { + "id": 214778817784467 + }, + "Name": "srcEndpoint=(TickBus Handler: ExecutionSlot:OnTick), destEndpoint=(Get Variable: In)", + "Components": { + "Component_[7779223725351419178]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 7779223725351419178, + "sourceEndpoint": { + "nodeId": { + "id": 58667495460516 + }, + "slotId": { + "m_id": "{8965578E-A29D-4468-B12B-9D4E4F814641}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 214400860662419 + }, + "slotId": { + "m_id": "{7500DE86-7349-4213-B3D6-F2872EDF15A6}" + } + } + } + } + }, + { + "Id": { + "id": 216011473398419 + }, + "Name": "srcEndpoint=(Get Variable: Number), destEndpoint=(Add (+): Value)", + "Components": { + "Component_[16293929363603149316]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 16293929363603149316, + "sourceEndpoint": { + "nodeId": { + "id": 214400860662419 + }, + "slotId": { + "m_id": "{5E4734A2-3CAF-4E29-AC72-5689FF1FD619}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 213241219492499 + }, + "slotId": { + "m_id": "{DB849F77-66D0-4D64-A45F-122F93E1E80C}" + } + } + } + } + }, + { + "Id": { + "id": 217330028358291 + }, + "Name": "srcEndpoint=(Add (+): Out), destEndpoint=(Set Variable: In)", + "Components": { + "Component_[13732207464069515508]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 13732207464069515508, + "sourceEndpoint": { + "nodeId": { + "id": 213241219492499 + }, + "slotId": { + "m_id": "{008FCEC7-3E86-4249-A736-F158EFDB0EFA}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58654610558628 + }, + "slotId": { + "m_id": "{F32F9B01-14B4-4DC3-AE0E-9E233DE5E941}" + } + } + } + } + }, + { + "Id": { + "id": 217626381101715 + }, + "Name": "srcEndpoint=(Add (+): Result), destEndpoint=(Set Variable: Number)", + "Components": { + "Component_[7103679238250108573]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 7103679238250108573, + "sourceEndpoint": { + "nodeId": { + "id": 213241219492499 + }, + "slotId": { + "m_id": "{53895246-F759-4328-AABB-D26E49E5208D}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 58654610558628 + }, + "slotId": { + "m_id": "{B22A0453-9715-46C7-A766-9536020AB59F}" + } + } + } + } + }, + { + "Id": { + "id": 240698945415827 + }, + "Name": "srcEndpoint=(Get Variable: Out), destEndpoint=(Multiply (*): In)", + "Components": { + "Component_[12012899230680400379]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 12012899230680400379, + "sourceEndpoint": { + "nodeId": { + "id": 214400860662419 + }, + "slotId": { + "m_id": "{8C8D3DD7-F821-42F4-AF51-5D976133DAE4}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 239870016727699 + }, + "slotId": { + "m_id": "{FEFF67A1-C89B-447E-80D4-CD94CB0C8AD7}" + } + } + } + } + }, + { + "Id": { + "id": 241158506916499 + }, + "Name": "srcEndpoint=(TickBus Handler: Number), destEndpoint=(Multiply (*): Value)", + "Components": { + "Component_[6729356013537587652]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 6729356013537587652, + "sourceEndpoint": { + "nodeId": { + "id": 58667495460516 + }, + "slotId": { + "m_id": "{02148868-388F-44F0-9E3A-C31601701F3B}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 239870016727699 + }, + "slotId": { + "m_id": "{8A243C9C-0D69-4753-B28B-A8D2FAC5D508}" + } + } + } + } + }, + { + "Id": { + "id": 241845701683859 + }, + "Name": "srcEndpoint=(Multiply (*): Out), destEndpoint=(Add (+): In)", + "Components": { + "Component_[1977054835666987502]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 1977054835666987502, + "sourceEndpoint": { + "nodeId": { + "id": 239870016727699 + }, + "slotId": { + "m_id": "{7E5D8673-F03A-4134-87DE-88AD13A74C29}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 213241219492499 + }, + "slotId": { + "m_id": "{56805D2B-0C98-4145-80FB-AA1DCB16CF1A}" + } + } + } + } + }, + { + "Id": { + "id": 242150644361875 + }, + "Name": "srcEndpoint=(Multiply (*): Result), destEndpoint=(Add (+): Number)", + "Components": { + "Component_[4687422202712804034]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 4687422202712804034, + "sourceEndpoint": { + "nodeId": { + "id": 239870016727699 + }, + "slotId": { + "m_id": "{DFD1DE7E-B470-43F6-A6CF-E9E572D0C075}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 213241219492499 + }, + "slotId": { + "m_id": "{CA8ABF3F-BF80-4A8B-A2B2-A1D2C16278C1}" + } + } + } + } + } + ] + }, + "m_assetType": "{3E2AC8CD-713F-453E-967F-29517F331784}", + "versionData": { + "_grammarVersion": 1, + "_runtimeVersion": 1, + "_fileVersion": 1 + }, + "m_variableCounter": 3, + "GraphCanvasData": [ + { + "Key": { + "id": 58577301147300 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + -40.0, + 180.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{EB1D5C50-BE43-4D5B-95CA-72BB2444355C}" + } + } + } + }, + { + "Key": { + "id": 58581596114596 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 3540.0, + 580.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{A3A4908C-0B83-483F-831F-248D6AB79C29}" + } + } + } + }, + { + "Key": { + "id": 58590186049188 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 2120.0, + 160.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{FED059CE-0252-49B1-8138-209A60BA8DA5}" + } + } + } + }, + { + "Key": { + "id": 58594481016484 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "GetVariableNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 1820.0, + 160.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".getVariable" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{4F7B87C1-4C5D-4B00-87F1-E742C7F9B0BB}" + } + } + } + }, + { + "Key": { + "id": 58607365918372 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 2120.0, + 660.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{D8112E71-9789-4C0C-8366-B4F92D91986F}" + } + } + } + }, + { + "Key": { + "id": 58624545787556 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 3180.0, + 360.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{297AAB81-3421-4A7B-8699-ECA4FF4DB2F3}" + } + } + } + }, + { + "Key": { + "id": 58628840754852 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MethodNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 3860.0, + 580.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".method" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{71F019FA-B307-4644-BB23-7869C6FEC456}" + } + } + } + }, + { + "Key": { + "id": 58633135722148 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 300.0, + 180.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{3B2FC30A-C96E-4D45-8FB8-D93213E40656}" + } + } + } + }, + { + "Key": { + "id": 58654610558628 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "SetVariableNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 1300.0, + 480.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".setVariable" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{6FF025AC-1B80-46F9-8827-C1C42EC3C298}" + } + } + } + }, + { + "Key": { + "id": 58658905525924 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 2800.0, + 140.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{145EFCDE-FC2F-4B18-AF2D-B6952DC734BA}" + } + } + } + }, + { + "Key": { + "id": 58663200493220 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MethodNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 1680.0, + 660.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".method" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{B9601350-A96F-498A-AF35-F2D8374142F8}" + } + } + } + }, + { + "Key": { + "id": 58667495460516 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + -480.0, + 500.0 + ] + }, + "{9E81C95F-89C0-4476-8E82-63CCC4E52E04}": { + "$type": "EBusHandlerNodeDescriptorSaveData", + "EventIds": [ + { + "Value": 1502188240 + } + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{A4513B84-5371-4B5E-8D78-6D5B2A9856D0}" + } + } + } + }, + { + "Key": { + "id": 58671790427812 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 1980.0, + 500.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{9414796E-C664-46D2-99AD-506FBDFA6D56}" + } + } + } + }, + { + "Key": { + "id": 58688970296996 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 2120.0, + 320.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{484503BE-9628-4885-A608-445A99D2768D}" + } + } + } + }, + { + "Key": { + "id": 213241219492499 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 940.0, + 520.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{D8706C3B-F5F1-42A9-8717-81AA7769955D}" + } + } + } + }, + { + "Key": { + "id": 214400860662419 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "GetVariableNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 100.0, + 540.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".getVariable" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{1D7795F6-7D30-4BD9-80E1-7E2525FA859A}" + } + } + } + }, + { + "Key": { + "id": 239870016727699 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 480.0, + 700.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{591ECD3E-51E5-4452-B8F0-91EEC22334EC}" + } + } + } + }, + { + "Key": { + "id": 1874297699023155003 + }, + "Value": { + "ComponentData": { + "{5F84B500-8C45-40D1-8EFC-A5306B241444}": { + "$type": "SceneComponentSaveData", + "ViewParams": { + "Scale": 0.6141249999999999, + "AnchorX": -615.5098876953125, + "AnchorY": -29.309993743896484 + } + } + } + } + } + ], + "StatisticsHelper": { + "InstanceCounter": [ + { + "Key": 1244476766431948410, + "Value": 1 + }, + { + "Key": 5842117451819972883, + "Value": 1 + }, + { + "Key": 11545666372999204726, + "Value": 2 + }, + { + "Key": 12702286953450386850, + "Value": 6 + }, + { + "Key": 12777283451032324504, + "Value": 2 + }, + { + "Key": 13282221058690490956, + "Value": 1 + }, + { + "Key": 13774516556399355685, + "Value": 1 + }, + { + "Key": 13774516556865812506, + "Value": 1 + }, + { + "Key": 16634824409549490771, + "Value": 1 + }, + { + "Key": 17750282321150628137, + "Value": 1 + } + ] + } + }, + "Component_[5106629331029292502]": { + "$type": "EditorGraphVariableManagerComponent", + "Id": 5106629331029292502, + "m_variableData": { + "m_nameVariableMap": [ + { + "Key": { + "m_id": "{0620A309-A152-4CF3-BF76-284115B30780}" + }, + "Value": { + "Datum": { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0 + }, + "VariableId": { + "m_id": "{0620A309-A152-4CF3-BF76-284115B30780}" + }, + "VariableName": "RotateCamZ" + } + }, + { + "Key": { + "m_id": "{6A2D4F20-5402-4283-8799-EB8DEABD6369}" + }, + "Value": { + "Datum": { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.1 + }, + "VariableId": { + "m_id": "{6A2D4F20-5402-4283-8799-EB8DEABD6369}" + }, + "VariableName": "JoystickDeadzone" + } + }, + { + "Key": { + "m_id": "{7062B1EE-2A8A-4E1D-8275-9DA1C5927FF0}" + }, + "Value": { + "Datum": { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0 + }, + "VariableId": { + "m_id": "{7062B1EE-2A8A-4E1D-8275-9DA1C5927FF0}" + }, + "VariableName": "JoystickRight_X" + } + }, + { + "Key": { + "m_id": "{8E040B94-3374-4228-8020-577BB7C70EE7}" + }, + "Value": { + "Datum": { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0 + }, + "VariableId": { + "m_id": "{8E040B94-3374-4228-8020-577BB7C70EE7}" + }, + "VariableName": "MoveX" + } + }, + { + "Key": { + "m_id": "{BF2919BD-19B4-4738-AC3A-81857D5204E4}" + }, + "Value": { + "Datum": { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0 + }, + "VariableId": { + "m_id": "{BF2919BD-19B4-4738-AC3A-81857D5204E4}" + }, + "VariableName": "MoveY" + } + } + ] + }, + "CopiedVariableRemapping": [ + { + "Key": { + "m_id": "{5EB17E58-0B4E-451D-A1CE-0E7C272CBDEC}" + }, + "Value": { + "m_id": "{BF2919BD-19B4-4738-AC3A-81857D5204E4}" + } + }, + { + "Key": { + "m_id": "{B48E5726-A7FF-42A8-84D2-CF43ABBD1EDC}" + }, + "Value": { + "m_id": "{8E040B94-3374-4228-8020-577BB7C70EE7}" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/Gems/MotionMatching/Assets/Levels/MotionMatching_AutomaticDemo/CharacterController_AutomaticDemo.scriptcanvas b/Gems/MotionMatching/Assets/Levels/MotionMatching_AutomaticDemo/CharacterController_AutomaticDemo.scriptcanvas new file mode 100644 index 0000000000..3582708516 --- /dev/null +++ b/Gems/MotionMatching/Assets/Levels/MotionMatching_AutomaticDemo/CharacterController_AutomaticDemo.scriptcanvas @@ -0,0 +1,6387 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "ScriptCanvasData", + "ClassData": { + "m_scriptCanvas": { + "Id": { + "id": 3794495145504990811 + }, + "Name": "CharacterController", + "Components": { + "Component_[9391142200043061739]": { + "$type": "{4D755CA9-AB92-462C-B24F-0B3376F19967} Graph", + "Id": 9391142200043061739, + "m_graphData": { + "m_nodes": [ + { + "Id": { + "id": 246132492243092 + }, + "Name": "SC-Node(InputHandlerNodeableNode)", + "Components": { + "Component_[10263047381934993123]": { + "$type": "InputHandlerNodeableNode", + "Id": 10263047381934993123, + "Slots": [ + { + "id": { + "m_id": "{223DB32E-54FF-41B5-978E-289EA97C1A1C}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Connect Event", + "toolTip": "Connect to input event name as defined in an input binding asset.", + "DisplayGroup": { + "Value": 2173756817 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{56FD98EC-6A1B-4738-898D-8B3B345315D5}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Event Name", + "toolTip": "Event name as defined in an input binding asset. Example 'Fireball'.", + "DisplayGroup": { + "Value": 2173756817 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{889B7C99-BD3C-4599-B0B4-C79A882B0395}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "On Connect Event", + "toolTip": "Connect to input event name as defined in an input binding asset.", + "DisplayGroup": { + "Value": 2173756817 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{C0E40FC8-0D14-4FFD-B8EE-6423B5059AF0}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Pressed", + "toolTip": "Signaled when the input event begins.", + "DisplayGroup": { + "Value": 458537082 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + }, + "IsLatent": true + }, + { + "id": { + "m_id": "{C88462F2-E947-4525-9DBB-AE9B3851B2DB}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "value", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 458537082 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{12DA88AD-8B23-45AF-991D-456F32377083}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Held", + "toolTip": "Signaled while the input event is active.", + "DisplayGroup": { + "Value": 308119761 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + }, + "IsLatent": true + }, + { + "id": { + "m_id": "{60393886-46EF-4B82-9009-9B4D9954255B}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Released", + "toolTip": "Signaled when the input event ends.", + "DisplayGroup": { + "Value": 4215628054 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + }, + "IsLatent": true + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 5 + }, + "isNullPointer": false, + "$type": "{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9} AZStd::string", + "value": "MoveX", + "label": "Event Name" + } + ], + "slotExecutionMap": { + "ins": [ + { + "_slotId": { + "m_id": "{223DB32E-54FF-41B5-978E-289EA97C1A1C}" + }, + "_inputs": [ + { + "_slotId": { + "m_id": "{56FD98EC-6A1B-4738-898D-8B3B345315D5}" + } + } + ], + "_outs": [ + { + "_slotId": { + "m_id": "{889B7C99-BD3C-4599-B0B4-C79A882B0395}" + }, + "_name": "On Connect Event", + "_interfaceSourceId": "{1B000000-0000-0000-0000-B376C2010000}" + } + ], + "_interfaceSourceId": "{00000000-0000-0000-4082-B80FBB000000}" + } + ], + "latents": [ + { + "_slotId": { + "m_id": "{C0E40FC8-0D14-4FFD-B8EE-6423B5059AF0}" + }, + "_name": "Pressed", + "_outputs": [ + { + "_slotId": { + "m_id": "{C88462F2-E947-4525-9DBB-AE9B3851B2DB}" + } + } + ], + "_interfaceSourceId": "{00000000-0000-0000-4082-B80FBB000000}" + }, + { + "_slotId": { + "m_id": "{12DA88AD-8B23-45AF-991D-456F32377083}" + }, + "_name": "Held", + "_outputs": [ + { + "_slotId": { + "m_id": "{C88462F2-E947-4525-9DBB-AE9B3851B2DB}" + } + } + ], + "_interfaceSourceId": "{00000000-0000-0000-4082-B80FBB000000}" + }, + { + "_slotId": { + "m_id": "{60393886-46EF-4B82-9009-9B4D9954255B}" + }, + "_name": "Released", + "_outputs": [ + { + "_slotId": { + "m_id": "{C88462F2-E947-4525-9DBB-AE9B3851B2DB}" + } + } + ], + "_interfaceSourceId": "{1B000000-0000-0000-0000-B376C2010000}" + } + ] + } + } + } + }, + { + "Id": { + "id": 246089542570132 + }, + "Name": "SC-Node(ConvertQuaternionToEulerDegrees)", + "Components": { + "Component_[12205034688404220919]": { + "$type": "{E42861BD-1956-45AE-8DD7-CCFC1E3E5ACF} Method", + "Id": 12205034688404220919, + "Slots": [ + { + "id": { + "m_id": "{A96CFD2A-531E-4330-B991-F27754DB21B4}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Quaternion: 0", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{CC9077E4-E835-4CAE-90EB-2B5FF8BA8F07}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{1286BE0C-88F5-4C0A-ADD6-EEE94AEA78A3}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{79FE68CC-45CC-4F5D-87A7-94B9144A3598}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Euler Angle", + "DisplayDataType": { + "m_type": 8 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 6 + }, + "isNullPointer": false, + "$type": "Quaternion", + "value": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "label": "Quaternion" + } + ], + "methodType": 2, + "methodName": "ConvertQuaternionToEulerDegrees", + "className": "MathUtils", + "resultSlotIDs": [ + {} + ], + "inputSlots": [ + { + "m_id": "{A96CFD2A-531E-4330-B991-F27754DB21B4}" + } + ], + "prettyClassName": "MathUtils" + } + } + }, + { + "Id": { + "id": 246136787210388 + }, + "Name": "SC-Node(ConvertQuaternionToEulerDegrees)", + "Components": { + "Component_[12205034688404220919]": { + "$type": "{E42861BD-1956-45AE-8DD7-CCFC1E3E5ACF} Method", + "Id": 12205034688404220919, + "Slots": [ + { + "id": { + "m_id": "{A96CFD2A-531E-4330-B991-F27754DB21B4}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Quaternion: 0", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{CC9077E4-E835-4CAE-90EB-2B5FF8BA8F07}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{1286BE0C-88F5-4C0A-ADD6-EEE94AEA78A3}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{79FE68CC-45CC-4F5D-87A7-94B9144A3598}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Euler Angle", + "DisplayDataType": { + "m_type": 8 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 6 + }, + "isNullPointer": false, + "$type": "Quaternion", + "value": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "label": "Quaternion" + } + ], + "methodType": 2, + "methodName": "ConvertQuaternionToEulerDegrees", + "className": "MathUtils", + "resultSlotIDs": [ + {} + ], + "inputSlots": [ + { + "m_id": "{A96CFD2A-531E-4330-B991-F27754DB21B4}" + } + ], + "prettyClassName": "MathUtils" + } + } + }, + { + "Id": { + "id": 246093837537428 + }, + "Name": "EBusEventHandler", + "Components": { + "Component_[12391893846605096594]": { + "$type": "EBusEventHandler", + "Id": 12391893846605096594, + "Slots": [ + { + "id": { + "m_id": "{47B8793A-68B3-4DF7-B485-98CF8625EE94}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Connect", + "toolTip": "Connect this event handler to the specified entity.", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{75D0B671-F876-4383-B3AE-414B37AD0638}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Disconnect", + "toolTip": "Disconnect this event handler.", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{03259091-B08D-4118-9AA3-053FD63AD6F7}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "OnConnected", + "toolTip": "Signaled when a connection has taken place.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{7C2E17C2-C3A1-4CB1-9AE6-521C4DD0D780}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "OnDisconnected", + "toolTip": "Signaled when this event handler is disconnected.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{9EB40EA9-0799-4FFF-9816-D6BEE9302D56}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "OnFailure", + "toolTip": "Signaled when it is not possible to connect this handler.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{02148868-388F-44F0-9E3A-C31601701F3B}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Number", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{1D2C5717-17A6-401F-BBF6-08EF84870CD3}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "ScriptTimePoint", + "DisplayDataType": { + "m_type": 4, + "m_azType": "{4C0F6AD4-0D4F-4354-AD4A-0C01E948245C}" + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{8965578E-A29D-4468-B12B-9D4E4F814641}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "ExecutionSlot:OnTick", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + }, + "IsLatent": true + }, + { + "id": { + "m_id": "{53E6F39B-30C2-4828-83A4-A286E22DD18D}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result: Number", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{909B60CC-693B-4DC9-9BC2-65B1D9373B5A}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "ExecutionSlot:GetTickOrder", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + }, + "IsLatent": true + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Result: Number" + } + ], + "m_eventMap": [ + { + "Key": { + "Value": 1502188240 + }, + "Value": { + "m_eventName": "OnTick", + "m_eventId": { + "Value": 1502188240 + }, + "m_eventSlotId": { + "m_id": "{8965578E-A29D-4468-B12B-9D4E4F814641}" + }, + "m_parameterSlotIds": [ + { + "m_id": "{02148868-388F-44F0-9E3A-C31601701F3B}" + }, + { + "m_id": "{1D2C5717-17A6-401F-BBF6-08EF84870CD3}" + } + ], + "m_numExpectedArguments": 2 + } + }, + { + "Key": { + "Value": 1890826333 + }, + "Value": { + "m_eventName": "GetTickOrder", + "m_eventId": { + "Value": 1890826333 + }, + "m_eventSlotId": { + "m_id": "{909B60CC-693B-4DC9-9BC2-65B1D9373B5A}" + }, + "m_resultSlotId": { + "m_id": "{53E6F39B-30C2-4828-83A4-A286E22DD18D}" + } + } + } + ], + "m_ebusName": "TickBus", + "m_busId": { + "Value": 1209186864 + } + } + } + }, + { + "Id": { + "id": 246128197275796 + }, + "Name": "SC-Node(SetNamedParameterVector3)", + "Components": { + "Component_[12793834298003501509]": { + "$type": "{E42861BD-1956-45AE-8DD7-CCFC1E3E5ACF} Method", + "Id": 12793834298003501509, + "Slots": [ + { + "id": { + "m_id": "{25F29FBA-7B7B-44FA-869A-48167AE73CD8}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "EntityID: 0", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{DB4C82E4-77C5-4CCF-960E-FFB36CE6A930}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "String: 1", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{04958BE6-8525-49CA-9130-8CECD0BE43B3}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Vector3: 2", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{5D58862C-5682-4497-A994-F240120E8E62}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{8F6FD0FC-11E5-49DD-A7D6-0AC0351E2DCA}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 1 + }, + "isNullPointer": false, + "$type": "EntityId", + "value": { + "id": 2901262558 + }, + "label": "Source" + }, + { + "scriptCanvasType": { + "m_type": 5 + }, + "isNullPointer": false, + "$type": "{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9} AZStd::string", + "value": "GoalFacingDir", + "label": "Name" + }, + { + "scriptCanvasType": { + "m_type": 8 + }, + "isNullPointer": false, + "$type": "Vector3", + "value": [ + 0.0, + 0.0, + 0.0 + ], + "label": "Value" + } + ], + "methodType": 0, + "methodName": "SetNamedParameterVector3", + "className": "AnimGraphComponentRequestBus", + "resultSlotIDs": [ + {} + ], + "inputSlots": [ + { + "m_id": "{25F29FBA-7B7B-44FA-869A-48167AE73CD8}" + }, + { + "m_id": "{DB4C82E4-77C5-4CCF-960E-FFB36CE6A930}" + }, + { + "m_id": "{04958BE6-8525-49CA-9130-8CECD0BE43B3}" + } + ], + "prettyClassName": "AnimGraphComponentRequestBus" + } + } + }, + { + "Id": { + "id": 246171146948756 + }, + "Name": "SC-Node(SetNamedParameterVector3)", + "Components": { + "Component_[12793834298003501509]": { + "$type": "{E42861BD-1956-45AE-8DD7-CCFC1E3E5ACF} Method", + "Id": 12793834298003501509, + "Slots": [ + { + "id": { + "m_id": "{25F29FBA-7B7B-44FA-869A-48167AE73CD8}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "EntityID: 0", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{DB4C82E4-77C5-4CCF-960E-FFB36CE6A930}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "String: 1", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{04958BE6-8525-49CA-9130-8CECD0BE43B3}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Vector3: 2", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{5D58862C-5682-4497-A994-F240120E8E62}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{8F6FD0FC-11E5-49DD-A7D6-0AC0351E2DCA}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 1 + }, + "isNullPointer": false, + "$type": "EntityId", + "value": { + "id": 2901262558 + }, + "label": "Source" + }, + { + "scriptCanvasType": { + "m_type": 5 + }, + "isNullPointer": false, + "$type": "{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9} AZStd::string", + "value": "GoalPos", + "label": "Name" + }, + { + "scriptCanvasType": { + "m_type": 8 + }, + "isNullPointer": false, + "$type": "Vector3", + "value": [ + 0.0, + 0.0, + 0.0 + ], + "label": "Value" + } + ], + "methodType": 0, + "methodName": "SetNamedParameterVector3", + "className": "AnimGraphComponentRequestBus", + "resultSlotIDs": [ + {} + ], + "inputSlots": [ + { + "m_id": "{25F29FBA-7B7B-44FA-869A-48167AE73CD8}" + }, + { + "m_id": "{DB4C82E4-77C5-4CCF-960E-FFB36CE6A930}" + }, + { + "m_id": "{04958BE6-8525-49CA-9130-8CECD0BE43B3}" + } + ], + "prettyClassName": "AnimGraphComponentRequestBus" + } + } + }, + { + "Id": { + "id": 246184031850644 + }, + "Name": "SC-Node((NodeFunctionGenericMultiReturn)<{Quaternion(double )}* RotationZDegreesTraits >)", + "Components": { + "Component_[13432280424019746829]": { + "$type": "(NodeFunctionGenericMultiReturn)<{Quaternion(double )}* RotationZDegreesTraits >", + "Id": 13432280424019746829, + "Slots": [ + { + "id": { + "m_id": "{424CC5BD-ACB0-4F1E-9700-DE29DE2F908E}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{6D35C87C-35B1-49F2-9FE6-2FE2991AAB8D}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{E2E3C0BC-56A7-4504-BEFA-1F1673DE7B0E}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Degrees", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{C2F9543A-928E-4405-A269-D8F952D227DB}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result", + "DisplayDataType": { + "m_type": 6 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Degrees" + } + ], + "Initialized": true + } + } + }, + { + "Id": { + "id": 246149672112276 + }, + "Name": "SC-Node(DrawTextAtLocation)", + "Components": { + "Component_[13698618923081442893]": { + "$type": "{E42861BD-1956-45AE-8DD7-CCFC1E3E5ACF} Method", + "Id": 13698618923081442893, + "Slots": [ + { + "id": { + "m_id": "{C1270538-1843-4392-B23C-12747DD40B15}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Vector3: 0", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{BE55072D-9B52-484A-BAB6-6847B191F6FA}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "String: 1", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{3DB3F6B0-8401-4068-9ACE-7EEA6717A748}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Color: 2", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{89BAE0A0-D8B2-4150-BFC2-69BACAF0D141}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Number: 3", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{31C53E9E-3EE2-4476-A4C3-4966FDA539CC}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{E3D18BB2-B5F4-4AFD-8902-91124330D9E2}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 8 + }, + "isNullPointer": false, + "$type": "Vector3", + "value": [ + 0.0, + 0.0, + 0.0 + ], + "label": "Position" + }, + { + "scriptCanvasType": { + "m_type": 5 + }, + "isNullPointer": false, + "$type": "{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9} AZStd::string", + "value": "Goal", + "label": "Text" + }, + { + "scriptCanvasType": { + "m_type": 12 + }, + "isNullPointer": false, + "$type": "Color", + "value": [ + 1.0, + 0.0, + 0.0, + 1.0 + ], + "label": "Color" + }, + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Duration" + } + ], + "NodeDisabledFlag": 1, + "methodType": 0, + "methodName": "DrawTextAtLocation", + "className": "DebugDrawRequestBus", + "resultSlotIDs": [ + {} + ], + "inputSlots": [ + { + "m_id": "{C1270538-1843-4392-B23C-12747DD40B15}" + }, + { + "m_id": "{BE55072D-9B52-484A-BAB6-6847B191F6FA}" + }, + { + "m_id": "{3DB3F6B0-8401-4068-9ACE-7EEA6717A748}" + }, + { + "m_id": "{89BAE0A0-D8B2-4150-BFC2-69BACAF0D141}" + } + ], + "prettyClassName": "DebugDrawRequestBus" + } + } + }, + { + "Id": { + "id": 246098132504724 + }, + "Name": "SC-Node((NodeFunctionGenericMultiReturn)<{Quaternion(double )}* RotationZDegreesTraits >)", + "Components": { + "Component_[15296836612744061228]": { + "$type": "(NodeFunctionGenericMultiReturn)<{Quaternion(double )}* RotationZDegreesTraits >", + "Id": 15296836612744061228, + "Slots": [ + { + "id": { + "m_id": "{C9C41693-0E85-429F-B9CF-87102B7AF399}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{DFA7AD12-D253-4B55-95BF-EBD79C9F0986}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{5076FEE2-72A1-4E2C-8FAF-649186F6F022}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Degrees", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{59A7EF4E-857E-4F2B-9466-37FE3C783EF9}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result", + "DisplayDataType": { + "m_type": 6 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Degrees" + } + ], + "Initialized": true + } + } + }, + { + "Id": { + "id": 246106722439316 + }, + "Name": "SC-Node(OperatorAdd)", + "Components": { + "Component_[16975947039966203662]": { + "$type": "OperatorAdd", + "Id": 16975947039966203662, + "Slots": [ + { + "id": { + "m_id": "{1AD1DED1-EDE6-4FAF-B3B6-FB5B26F1B294}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{E8BA15CB-A2B1-4824-92D6-2A71EEBF0378}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{FBF48C32-3CCB-47CA-BC9E-45567BF1B269}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 8 + }, + { + "m_type": 9 + }, + { + "m_type": 10 + }, + { + "m_type": 11 + }, + { + "m_type": 12 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Vector3", + "toolTip": "An operand to use in performing the specified Operation", + "DisplayDataType": { + "m_type": 8 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{C59084D6-69B9-464E-B6C9-910F69509779}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 8 + }, + { + "m_type": 9 + }, + { + "m_type": 10 + }, + { + "m_type": 11 + }, + { + "m_type": 12 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Vector3", + "toolTip": "An operand to use in performing the specified Operation", + "DisplayDataType": { + "m_type": 8 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{B87A6F0B-DE2B-4E9E-9C44-D18F1B8706E4}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 8 + }, + { + "m_type": 9 + }, + { + "m_type": 10 + }, + { + "m_type": 11 + }, + { + "m_type": 12 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Result", + "toolTip": "The result of the specified operation", + "DisplayDataType": { + "m_type": 8 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 8 + }, + "isNullPointer": false, + "$type": "Vector3", + "value": [ + 0.0, + 0.0, + 0.0 + ], + "label": "Vector3" + }, + { + "scriptCanvasType": { + "m_type": 8 + }, + "isNullPointer": false, + "$type": "Vector3", + "value": [ + 0.0, + 0.0, + 0.0 + ], + "label": "Vector3" + } + ] + } + } + }, + { + "Id": { + "id": 246085247602836 + }, + "Name": "SC-Node(Print)", + "Components": { + "Component_[17915522105314128859]": { + "$type": "Print", + "Id": 17915522105314128859, + "Slots": [ + { + "id": { + "m_id": "{9FEBBB50-0F94-4781-9B51-88CAA867EC16}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "Input signal", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{3F92B8A4-0A0C-4120-B947-9A64BE38855F}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Value", + "toolTip": "Value which replaces instances of {Value} in the resulting string.", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1015031923 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{CFDEBA73-CAD5-4A66-ACD1-4080334CEED1}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + } + ], + "Datums": [ + { + "isOverloadedStorage": false, + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Value" + } + ], + "NodeDisabledFlag": 1, + "m_format": "MoveX {Value}", + "m_arrayBindingMap": [ + { + "Key": 1, + "Value": { + "m_id": "{3F92B8A4-0A0C-4120-B947-9A64BE38855F}" + } + } + ], + "m_unresolvedString": [ + "MoveX ", + {} + ], + "m_formatSlotMap": { + "Value": { + "m_id": "{3F92B8A4-0A0C-4120-B947-9A64BE38855F}" + } + } + } + } + }, + { + "Id": { + "id": 246192621785236 + }, + "Name": "SC-Node(Print)", + "Components": { + "Component_[17915522105314128859]": { + "$type": "Print", + "Id": 17915522105314128859, + "Slots": [ + { + "id": { + "m_id": "{9FEBBB50-0F94-4781-9B51-88CAA867EC16}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "Input signal", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{3F92B8A4-0A0C-4120-B947-9A64BE38855F}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Value", + "toolTip": "Value which replaces instances of {Value} in the resulting string.", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1015031923 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{CFDEBA73-CAD5-4A66-ACD1-4080334CEED1}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + } + ], + "Datums": [ + { + "isOverloadedStorage": false, + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Value" + } + ], + "NodeDisabledFlag": 1, + "m_format": "MoveY {Value}", + "m_arrayBindingMap": [ + { + "Key": 1, + "Value": { + "m_id": "{3F92B8A4-0A0C-4120-B947-9A64BE38855F}" + } + } + ], + "m_unresolvedString": [ + "MoveY ", + {} + ], + "m_formatSlotMap": { + "Value": { + "m_id": "{3F92B8A4-0A0C-4120-B947-9A64BE38855F}" + } + } + } + } + }, + { + "Id": { + "id": 246080952635540 + }, + "Name": "SC-Node(ExtractProperty)", + "Components": { + "Component_[18182473127226221453]": { + "$type": "ExtractProperty", + "Id": 18182473127226221453, + "Slots": [ + { + "id": { + "m_id": "{B85B623A-CACB-4B96-98DB-E4F289AA01D4}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "When signaled assigns property values using the supplied source input", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{E6DBA835-98FA-4ADB-8F87-15604C8D3FD3}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "toolTip": "Signaled after all property haves have been pushed to the output slots", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{8F09882E-A82E-481D-9FBD-FED706BEFB7C}" + }, + "DynamicTypeOverride": 1, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Source", + "toolTip": "The value on which to extract properties from.", + "DisplayDataType": { + "m_type": 8 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{10C2FAA6-AA90-4D62-97FB-1C6C81BA6AB2}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "X", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{7FAB654B-42B6-4402-91F8-7AD3AB651DE1}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Y", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{E7EB179B-BACB-44DB-B2D1-D5D4788D80D8}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Z", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 8 + }, + "isNullPointer": false, + "$type": "Vector3", + "value": [ + 0.0, + 0.0, + 0.0 + ], + "label": "Source" + } + ], + "m_dataType": { + "m_type": 8 + }, + "m_propertyAccounts": [ + { + "m_propertySlotId": { + "m_id": "{10C2FAA6-AA90-4D62-97FB-1C6C81BA6AB2}" + }, + "m_propertyType": { + "m_type": 3 + }, + "m_propertyName": "x" + }, + { + "m_propertySlotId": { + "m_id": "{7FAB654B-42B6-4402-91F8-7AD3AB651DE1}" + }, + "m_propertyType": { + "m_type": 3 + }, + "m_propertyName": "y" + }, + { + "m_propertySlotId": { + "m_id": "{E7EB179B-BACB-44DB-B2D1-D5D4788D80D8}" + }, + "m_propertyType": { + "m_type": 3 + }, + "m_propertyName": "z" + } + ] + } + } + }, + { + "Id": { + "id": 246102427472020 + }, + "Name": "SC-Node(DrawSphereAtLocation)", + "Components": { + "Component_[217512573203327123]": { + "$type": "{E42861BD-1956-45AE-8DD7-CCFC1E3E5ACF} Method", + "Id": 217512573203327123, + "Slots": [ + { + "id": { + "m_id": "{67CBBEE3-F9B5-472E-8A3E-A82996CDA2D1}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Vector3: 0", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{0C39ADAF-7C3F-465C-A61F-54FD9E06B6F6}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Number: 1", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{3D43E30E-F93C-4463-AF83-60B68E277D51}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Color: 2", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{12524268-7E71-44A0-B2E2-FAB3465AF3C8}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Number: 3", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{9B026749-1D6E-4F69-9F20-777A510FD7C4}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{063DEA47-18F0-45C9-B2D6-BB6767B769DE}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 8 + }, + "isNullPointer": false, + "$type": "Vector3", + "value": [ + 0.0, + 0.0, + 0.0 + ], + "label": "Position" + }, + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.1, + "label": "Radius" + }, + { + "scriptCanvasType": { + "m_type": 12 + }, + "isNullPointer": false, + "$type": "Color", + "value": [ + 1.0, + 0.0, + 0.0, + 1.0 + ], + "label": "Color" + }, + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Duration" + } + ], + "NodeDisabledFlag": 1, + "methodType": 0, + "methodName": "DrawSphereAtLocation", + "className": "DebugDrawRequestBus", + "resultSlotIDs": [ + {} + ], + "inputSlots": [ + { + "m_id": "{67CBBEE3-F9B5-472E-8A3E-A82996CDA2D1}" + }, + { + "m_id": "{0C39ADAF-7C3F-465C-A61F-54FD9E06B6F6}" + }, + { + "m_id": "{3D43E30E-F93C-4463-AF83-60B68E277D51}" + }, + { + "m_id": "{12524268-7E71-44A0-B2E2-FAB3465AF3C8}" + } + ], + "prettyClassName": "DebugDrawRequestBus" + } + } + }, + { + "Id": { + "id": 246145377144980 + }, + "Name": "SC-Node((NodeFunctionGenericMultiReturn)<{Vector3(Quaternion Vector3 )}* RotateVector3Traits >)", + "Components": { + "Component_[2974676320576034178]": { + "$type": "(NodeFunctionGenericMultiReturn)<{Vector3(Quaternion Vector3 )}* RotateVector3Traits >", + "Id": 2974676320576034178, + "Slots": [ + { + "id": { + "m_id": "{9704DF19-1C22-4E87-B461-EBA5E86CA68C}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{C49165F2-9484-4E70-874F-4FD821B83AB0}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{6F45F01E-F700-4594-98F0-A02A5BE00617}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Quaternion", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{50A0D858-774F-4379-8B02-C178E5327BE9}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Vector", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{099EF8F5-DE6F-45FC-9AA6-716996AAB7C6}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result", + "DisplayDataType": { + "m_type": 8 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 6 + }, + "isNullPointer": false, + "$type": "Quaternion", + "value": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "label": "Quaternion" + }, + { + "scriptCanvasType": { + "m_type": 8 + }, + "isNullPointer": false, + "$type": "Vector3", + "value": [ + 0.0, + 1.0, + 0.0 + ], + "label": "Vector" + } + ], + "Initialized": true + } + } + }, + { + "Id": { + "id": 246166851981460 + }, + "Name": "SC-Node((NodeFunctionGenericMultiReturn)<{Vector3(Quaternion Vector3 )}* RotateVector3Traits >)", + "Components": { + "Component_[2974676320576034178]": { + "$type": "(NodeFunctionGenericMultiReturn)<{Vector3(Quaternion Vector3 )}* RotateVector3Traits >", + "Id": 2974676320576034178, + "Slots": [ + { + "id": { + "m_id": "{9704DF19-1C22-4E87-B461-EBA5E86CA68C}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{C49165F2-9484-4E70-874F-4FD821B83AB0}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{6F45F01E-F700-4594-98F0-A02A5BE00617}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Quaternion", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{50A0D858-774F-4379-8B02-C178E5327BE9}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Vector", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{099EF8F5-DE6F-45FC-9AA6-716996AAB7C6}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result", + "DisplayDataType": { + "m_type": 8 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 6 + }, + "isNullPointer": false, + "$type": "Quaternion", + "value": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "label": "Quaternion" + }, + { + "scriptCanvasType": { + "m_type": 8 + }, + "isNullPointer": false, + "$type": "Vector3", + "value": [ + 0.0, + 1.0, + 0.0 + ], + "label": "Vector" + } + ], + "Initialized": true + } + } + }, + { + "Id": { + "id": 246076657668244 + }, + "Name": "SC-Node((NodeFunctionGenericMultiReturn)<{Vector3(Quaternion Vector3 )}* RotateVector3Traits >)", + "Components": { + "Component_[3881104967231448701]": { + "$type": "(NodeFunctionGenericMultiReturn)<{Vector3(Quaternion Vector3 )}* RotateVector3Traits >", + "Id": 3881104967231448701, + "Slots": [ + { + "id": { + "m_id": "{8C5BE0B0-13CC-43A5-8F44-9C282B3E4D60}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{8D7093BA-E37A-48D7-ABB6-8D3960A60BF0}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{72351E0C-D3F5-4181-90CB-8A4E0557A978}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Quaternion", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{B46DDF66-DA5E-474C-A740-5472FBBF285A}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Vector", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{879125AE-8729-465D-A3E3-1DAF6B9C84C7}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result", + "DisplayDataType": { + "m_type": 8 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 6 + }, + "isNullPointer": false, + "$type": "Quaternion", + "value": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "label": "Quaternion" + }, + { + "scriptCanvasType": { + "m_type": 8 + }, + "isNullPointer": false, + "$type": "Vector3", + "value": [ + 0.0, + 0.0, + 0.0 + ], + "label": "Vector" + } + ], + "Initialized": true + } + } + }, + { + "Id": { + "id": 246153967079572 + }, + "Name": "SC-Node((NodeFunctionGenericMultiReturn)<{Vector3(double double double )}* FromValuesTraits >)", + "Components": { + "Component_[4092504572586685068]": { + "$type": "(NodeFunctionGenericMultiReturn)<{Vector3(double double double )}* FromValuesTraits >", + "Id": 4092504572586685068, + "Slots": [ + { + "id": { + "m_id": "{7A425E1D-C67F-42D7-8377-124FDDCCD02B}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{2EAFCACB-BC3D-4748-86F6-E2037A7CF487}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{8FACA5C3-ED4D-4DC2-8E6A-28AE1B340BE7}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "X", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{A01573D8-5F5D-404B-A5BC-193480C65F28}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Y", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{0F6AB874-B298-4425-B142-017E8B7F0501}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Z", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{5029C117-20D4-4CE1-9097-FBBC98B4F457}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result", + "DisplayDataType": { + "m_type": 8 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "X" + }, + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Y" + }, + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Z" + } + ], + "Initialized": true + } + } + }, + { + "Id": { + "id": 246162557014164 + }, + "Name": "SC Node(SetVariable)", + "Components": { + "Component_[4132372868512871966]": { + "$type": "SetVariableNode", + "Id": 4132372868512871966, + "Slots": [ + { + "id": { + "m_id": "{F83C85A3-77E5-4B09-8FE2-32127A1C6EDE}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "When signaled sends the variable referenced by this node to a Data Output slot", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{457D8C40-3373-4DA9-8186-C33DBFA2397F}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "toolTip": "Signaled after the referenced variable has been pushed to the Data Output slot", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{AFAC9742-D20E-4094-8720-7E4582FE7761}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Number", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{954FC5E5-8838-4ABF-8C6E-B6B100C7BD20}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Number", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Number" + } + ], + "m_variableId": { + "m_id": "{5EB17E58-0B4E-451D-A1CE-0E7C272CBDEC}" + }, + "m_variableDataInSlotId": { + "m_id": "{AFAC9742-D20E-4094-8720-7E4582FE7761}" + }, + "m_variableDataOutSlotId": { + "m_id": "{954FC5E5-8838-4ABF-8C6E-B6B100C7BD20}" + } + } + } + }, + { + "Id": { + "id": 246119607341204 + }, + "Name": "SC Node(SetVariable)", + "Components": { + "Component_[4132372868512871966]": { + "$type": "SetVariableNode", + "Id": 4132372868512871966, + "Slots": [ + { + "id": { + "m_id": "{F83C85A3-77E5-4B09-8FE2-32127A1C6EDE}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "When signaled sends the variable referenced by this node to a Data Output slot", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{457D8C40-3373-4DA9-8186-C33DBFA2397F}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "toolTip": "Signaled after the referenced variable has been pushed to the Data Output slot", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{AFAC9742-D20E-4094-8720-7E4582FE7761}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Number", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{954FC5E5-8838-4ABF-8C6E-B6B100C7BD20}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Number", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Number" + } + ], + "m_variableId": { + "m_id": "{B48E5726-A7FF-42A8-84D2-CF43ABBD1EDC}" + }, + "m_variableDataInSlotId": { + "m_id": "{AFAC9742-D20E-4094-8720-7E4582FE7761}" + }, + "m_variableDataOutSlotId": { + "m_id": "{954FC5E5-8838-4ABF-8C6E-B6B100C7BD20}" + } + } + } + }, + { + "Id": { + "id": 246175441916052 + }, + "Name": "SC Node(GetVariable)", + "Components": { + "Component_[5278854140649584684]": { + "$type": "GetVariableNode", + "Id": 5278854140649584684, + "Slots": [ + { + "id": { + "m_id": "{17B5B6FD-B942-4C0C-847E-3891BA11743B}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "When signaled sends the property referenced by this node to a Data Output slot", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{613E9634-0EEC-43EE-A680-94724628BEAB}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "toolTip": "Signaled after the referenced property has been pushed to the Data Output slot", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{D44E36FC-7879-4E15-B0AA-0E9F866D2A5C}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Number", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "m_variableId": { + "m_id": "{B48E5726-A7FF-42A8-84D2-CF43ABBD1EDC}" + }, + "m_variableDataOutSlotId": { + "m_id": "{D44E36FC-7879-4E15-B0AA-0E9F866D2A5C}" + } + } + } + }, + { + "Id": { + "id": 246068067733652 + }, + "Name": "SC-Node(GetWorldTranslation)", + "Components": { + "Component_[6584501548902644902]": { + "$type": "{E42861BD-1956-45AE-8DD7-CCFC1E3E5ACF} Method", + "Id": 6584501548902644902, + "Slots": [ + { + "id": { + "m_id": "{72B594D3-36EA-4390-A1E3-E8B296D3CC68}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "EntityID: 0", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{5AAE80BB-C549-4D61-AC36-57FF33A0F9B9}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{D6245737-F70E-431A-8DB2-D187634B6859}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{EBC733B4-A0E0-4772-888E-082AF8BA2FFF}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result: Vector3", + "DisplayDataType": { + "m_type": 8 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 1 + }, + "isNullPointer": false, + "$type": "EntityId", + "value": { + "id": 2901262558 + }, + "label": "Source" + } + ], + "methodType": 0, + "methodName": "GetWorldTranslation", + "className": "TransformBus", + "resultSlotIDs": [ + {} + ], + "inputSlots": [ + { + "m_id": "{72B594D3-36EA-4390-A1E3-E8B296D3CC68}" + } + ], + "prettyClassName": "TransformBus" + } + } + }, + { + "Id": { + "id": 246115312373908 + }, + "Name": "SC-Node(InputHandlerNodeableNode)", + "Components": { + "Component_[6810487525300205505]": { + "$type": "InputHandlerNodeableNode", + "Id": 6810487525300205505, + "Slots": [ + { + "id": { + "m_id": "{59344FA8-1BCE-4323-B7ED-963439BB3D43}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Connect Event", + "toolTip": "Connect to input event name as defined in an input binding asset.", + "DisplayGroup": { + "Value": 2173756817 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{DBEB7EFF-188D-498B-A0C7-62A6A379E121}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Event Name", + "toolTip": "Event name as defined in an input binding asset. Example 'Fireball'.", + "DisplayGroup": { + "Value": 2173756817 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{ABD24CF7-E97C-41E9-8AC3-EC4C586DDBFC}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "On Connect Event", + "toolTip": "Connect to input event name as defined in an input binding asset.", + "DisplayGroup": { + "Value": 2173756817 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{DD88EDED-AD61-4BD4-9A2A-2A8F7339CD66}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Pressed", + "toolTip": "Signaled when the input event begins.", + "DisplayGroup": { + "Value": 458537082 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + }, + "IsLatent": true + }, + { + "id": { + "m_id": "{CDB5418B-2ECA-421E-827E-44835EF28954}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "value", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 458537082 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{C20E41AE-74BA-4E93-B278-841E4F3848DD}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Held", + "toolTip": "Signaled while the input event is active.", + "DisplayGroup": { + "Value": 308119761 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + }, + "IsLatent": true + }, + { + "id": { + "m_id": "{001FCBA0-A022-47DB-A346-782286DA3DE5}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Released", + "toolTip": "Signaled when the input event ends.", + "DisplayGroup": { + "Value": 4215628054 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + }, + "IsLatent": true + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 5 + }, + "isNullPointer": false, + "$type": "{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9} AZStd::string", + "value": "MoveY", + "label": "Event Name" + } + ], + "slotExecutionMap": { + "ins": [ + { + "_slotId": { + "m_id": "{59344FA8-1BCE-4323-B7ED-963439BB3D43}" + }, + "_inputs": [ + { + "_slotId": { + "m_id": "{DBEB7EFF-188D-498B-A0C7-62A6A379E121}" + } + } + ], + "_outs": [ + { + "_slotId": { + "m_id": "{ABD24CF7-E97C-41E9-8AC3-EC4C586DDBFC}" + }, + "_name": "On Connect Event" + } + ], + "_interfaceSourceId": "{8087B80F-BB00-0000-0241-25E7FC7F0000}" + } + ], + "latents": [ + { + "_slotId": { + "m_id": "{DD88EDED-AD61-4BD4-9A2A-2A8F7339CD66}" + }, + "_name": "Pressed", + "_outputs": [ + { + "_slotId": { + "m_id": "{CDB5418B-2ECA-421E-827E-44835EF28954}" + } + } + ], + "_interfaceSourceId": "{8087B80F-BB00-0000-0241-25E7FC7F0000}" + }, + { + "_slotId": { + "m_id": "{C20E41AE-74BA-4E93-B278-841E4F3848DD}" + }, + "_name": "Held", + "_outputs": [ + { + "_slotId": { + "m_id": "{CDB5418B-2ECA-421E-827E-44835EF28954}" + } + } + ], + "_interfaceSourceId": "{8087B80F-BB00-0000-0241-25E7FC7F0000}" + }, + { + "_slotId": { + "m_id": "{001FCBA0-A022-47DB-A346-782286DA3DE5}" + }, + "_name": "Released", + "_outputs": [ + { + "_slotId": { + "m_id": "{CDB5418B-2ECA-421E-827E-44835EF28954}" + } + } + ] + } + ] + } + } + } + }, + { + "Id": { + "id": 246072362700948 + }, + "Name": "SC Node(GetVariable)", + "Components": { + "Component_[7093473538212812857]": { + "$type": "GetVariableNode", + "Id": 7093473538212812857, + "Slots": [ + { + "id": { + "m_id": "{276F5B6E-2FC8-4EFF-A161-2548BC0E0885}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "When signaled sends the property referenced by this node to a Data Output slot", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{6D512DCD-4CA6-421F-9C23-214E4A757D5F}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "toolTip": "Signaled after the referenced property has been pushed to the Data Output slot", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{68E66FCE-1DF4-445A-9DD5-7D06D3A3146D}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Number", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "m_variableId": { + "m_id": "{5EB17E58-0B4E-451D-A1CE-0E7C272CBDEC}" + }, + "m_variableDataOutSlotId": { + "m_id": "{68E66FCE-1DF4-445A-9DD5-7D06D3A3146D}" + } + } + } + }, + { + "Id": { + "id": 246179736883348 + }, + "Name": "EBusEventHandler", + "Components": { + "Component_[7486482846458186344]": { + "$type": "EBusEventHandler", + "Id": 7486482846458186344, + "Slots": [ + { + "id": { + "m_id": "{ACA820F7-7515-4904-AB73-975DE0723CBE}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Connect", + "toolTip": "Connect this event handler to the specified entity.", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{DAA6A799-AB8F-448D-92CD-F431D4A9424C}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Disconnect", + "toolTip": "Disconnect this event handler.", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{1E01CC28-0CD3-4E29-9AF5-F51958FBEB44}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "OnConnected", + "toolTip": "Signaled when a connection has taken place.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{A9AF6E0E-6D77-45C4-9720-CF4F488243ED}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "OnDisconnected", + "toolTip": "Signaled when this event handler is disconnected.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{4D7CF661-9343-4A44-8095-AFC33096AB20}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "OnFailure", + "toolTip": "Signaled when it is not possible to connect this handler.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{96E803E6-7979-48B7-B473-21D317FC97BD}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Source", + "toolTip": "ID used to connect on a specific Event address (Type: EntityId)", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{8449A9CC-591E-40CD-8122-CDA20BCD226D}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "EntityID", + "DisplayDataType": { + "m_type": 1 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{FC0FCD21-DA19-4674-9F8C-D79960777B1E}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "ExecutionSlot:OnEntityActivated", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + }, + "IsLatent": true + }, + { + "id": { + "m_id": "{24D001AB-332B-41B2-B2AA-AF7D6418DB8F}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "EntityID", + "DisplayDataType": { + "m_type": 1 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{3DE59EA1-6365-458A-8806-D42BA4B79F07}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "ExecutionSlot:OnEntityDeactivated", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + }, + "IsLatent": true + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 1 + }, + "isNullPointer": false, + "$type": "EntityId", + "value": { + "id": 2901262558 + }, + "label": "Source" + } + ], + "m_eventMap": [ + { + "Key": { + "Value": 245425936 + }, + "Value": { + "m_eventName": "OnEntityActivated", + "m_eventId": { + "Value": 245425936 + }, + "m_eventSlotId": { + "m_id": "{FC0FCD21-DA19-4674-9F8C-D79960777B1E}" + }, + "m_parameterSlotIds": [ + { + "m_id": "{8449A9CC-591E-40CD-8122-CDA20BCD226D}" + } + ], + "m_numExpectedArguments": 1 + } + }, + { + "Key": { + "Value": 4273369222 + }, + "Value": { + "m_eventName": "OnEntityDeactivated", + "m_eventId": { + "Value": 4273369222 + }, + "m_eventSlotId": { + "m_id": "{3DE59EA1-6365-458A-8806-D42BA4B79F07}" + }, + "m_parameterSlotIds": [ + { + "m_id": "{24D001AB-332B-41B2-B2AA-AF7D6418DB8F}" + } + ], + "m_numExpectedArguments": 1 + } + } + ], + "m_ebusName": "EntityBus", + "m_busId": { + "Value": 3358774020 + } + } + } + }, + { + "Id": { + "id": 246188326817940 + }, + "Name": "SC-Node(GetWorldRotationQuaternion)", + "Components": { + "Component_[9629672380141390157]": { + "$type": "{E42861BD-1956-45AE-8DD7-CCFC1E3E5ACF} Method", + "Id": 9629672380141390157, + "Slots": [ + { + "id": { + "m_id": "{C9D6C354-CB4F-40E3-892E-19D7940A9399}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "EntityID: 0", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{6BCB6E4B-E9C5-4D6E-A4AF-C2C76271E115}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{E7F58DBD-3882-43D3-B44F-D2E68E5E7098}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{81953E27-7B51-462D-9AB8-B122F33A7391}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result: Quaternion", + "DisplayDataType": { + "m_type": 6 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 1 + }, + "isNullPointer": false, + "$type": "EntityId", + "value": { + "id": 623788559886 + }, + "label": "Source" + } + ], + "methodType": 0, + "methodName": "GetWorldRotationQuaternion", + "className": "TransformBus", + "resultSlotIDs": [ + {} + ], + "inputSlots": [ + { + "m_id": "{C9D6C354-CB4F-40E3-892E-19D7940A9399}" + } + ], + "prettyClassName": "TransformBus" + } + } + }, + { + "Id": { + "id": 246141082177684 + }, + "Name": "SC-Node(GetWorldRotationQuaternion)", + "Components": { + "Component_[9629672380141390157]": { + "$type": "{E42861BD-1956-45AE-8DD7-CCFC1E3E5ACF} Method", + "Id": 9629672380141390157, + "Slots": [ + { + "id": { + "m_id": "{C9D6C354-CB4F-40E3-892E-19D7940A9399}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "EntityID: 0", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{6BCB6E4B-E9C5-4D6E-A4AF-C2C76271E115}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{E7F58DBD-3882-43D3-B44F-D2E68E5E7098}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{81953E27-7B51-462D-9AB8-B122F33A7391}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result: Quaternion", + "DisplayDataType": { + "m_type": 6 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 1 + }, + "isNullPointer": false, + "$type": "EntityId", + "value": { + "id": 653853330958 + }, + "label": "Source" + } + ], + "methodType": 0, + "methodName": "GetWorldRotationQuaternion", + "className": "TransformBus", + "resultSlotIDs": [ + {} + ], + "inputSlots": [ + { + "m_id": "{C9D6C354-CB4F-40E3-892E-19D7940A9399}" + } + ], + "prettyClassName": "TransformBus" + } + } + }, + { + "Id": { + "id": 246158262046868 + }, + "Name": "SC-Node(ExtractProperty)", + "Components": { + "Component_[973963568861268593]": { + "$type": "ExtractProperty", + "Id": 973963568861268593, + "Slots": [ + { + "id": { + "m_id": "{A6542FE6-CFED-4033-AC3A-64614B1BF141}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "When signaled assigns property values using the supplied source input", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{3EB4F651-DA08-45E5-94FE-432A8FC45914}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "toolTip": "Signaled after all property haves have been pushed to the output slots", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{E14C7719-5419-444C-BCDF-23BFDFD40DA4}" + }, + "DynamicTypeOverride": 1, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Source", + "toolTip": "The value on which to extract properties from.", + "DisplayDataType": { + "m_type": 8 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{663DD576-8088-485C-AFFE-DCB0FCAEE679}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "X", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{DB27CBA5-2F04-44AD-A73E-A3772236908A}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Y", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{B138BB48-9B60-4DE3-8DF0-DCC17D0D6358}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Z", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 8 + }, + "isNullPointer": false, + "$type": "Vector3", + "value": [ + 0.0, + 0.0, + 0.0 + ], + "label": "Source" + } + ], + "m_dataType": { + "m_type": 8 + }, + "m_propertyAccounts": [ + { + "m_propertySlotId": { + "m_id": "{663DD576-8088-485C-AFFE-DCB0FCAEE679}" + }, + "m_propertyType": { + "m_type": 3 + }, + "m_propertyName": "x" + }, + { + "m_propertySlotId": { + "m_id": "{DB27CBA5-2F04-44AD-A73E-A3772236908A}" + }, + "m_propertyType": { + "m_type": 3 + }, + "m_propertyName": "y" + }, + { + "m_propertySlotId": { + "m_id": "{B138BB48-9B60-4DE3-8DF0-DCC17D0D6358}" + }, + "m_propertyType": { + "m_type": 3 + }, + "m_propertyName": "z" + } + ] + } + } + } + ], + "m_connections": [ + { + "Id": { + "id": 246196916752532 + }, + "Name": "srcEndpoint=(EntityBus Handler: ExecutionSlot:OnEntityActivated), destEndpoint=(InputHandler: Connect Event)", + "Components": { + "Component_[8456523383292490588]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 8456523383292490588, + "sourceEndpoint": { + "nodeId": { + "id": 246179736883348 + }, + "slotId": { + "m_id": "{FC0FCD21-DA19-4674-9F8C-D79960777B1E}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246132492243092 + }, + "slotId": { + "m_id": "{223DB32E-54FF-41B5-978E-289EA97C1A1C}" + } + } + } + } + }, + { + "Id": { + "id": 246201211719828 + }, + "Name": "srcEndpoint=(EntityBus Handler: ExecutionSlot:OnEntityActivated), destEndpoint=(InputHandler: Connect Event)", + "Components": { + "Component_[12635346401763282235]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 12635346401763282235, + "sourceEndpoint": { + "nodeId": { + "id": 246179736883348 + }, + "slotId": { + "m_id": "{FC0FCD21-DA19-4674-9F8C-D79960777B1E}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246115312373908 + }, + "slotId": { + "m_id": "{59344FA8-1BCE-4323-B7ED-963439BB3D43}" + } + } + } + } + }, + { + "Id": { + "id": 246205506687124 + }, + "Name": "srcEndpoint=(InputHandler: value), destEndpoint=(Set Variable: Number)", + "Components": { + "Component_[12090259056249954518]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 12090259056249954518, + "sourceEndpoint": { + "nodeId": { + "id": 246132492243092 + }, + "slotId": { + "m_id": "{C88462F2-E947-4525-9DBB-AE9B3851B2DB}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246119607341204 + }, + "slotId": { + "m_id": "{AFAC9742-D20E-4094-8720-7E4582FE7761}" + } + } + } + } + }, + { + "Id": { + "id": 246209801654420 + }, + "Name": "srcEndpoint=(InputHandler: Held), destEndpoint=(Set Variable: In)", + "Components": { + "Component_[10496578309013320974]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 10496578309013320974, + "sourceEndpoint": { + "nodeId": { + "id": 246132492243092 + }, + "slotId": { + "m_id": "{12DA88AD-8B23-45AF-991D-456F32377083}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246119607341204 + }, + "slotId": { + "m_id": "{F83C85A3-77E5-4B09-8FE2-32127A1C6EDE}" + } + } + } + } + }, + { + "Id": { + "id": 246214096621716 + }, + "Name": "srcEndpoint=(Set Variable: Out), destEndpoint=(Print: In)", + "Components": { + "Component_[5484696725367231856]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 5484696725367231856, + "sourceEndpoint": { + "nodeId": { + "id": 246119607341204 + }, + "slotId": { + "m_id": "{457D8C40-3373-4DA9-8186-C33DBFA2397F}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246085247602836 + }, + "slotId": { + "m_id": "{9FEBBB50-0F94-4781-9B51-88CAA867EC16}" + } + } + } + } + }, + { + "Id": { + "id": 246218391589012 + }, + "Name": "srcEndpoint=(Set Variable: Number), destEndpoint=(Print: Value)", + "Components": { + "Component_[14254771563627471531]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 14254771563627471531, + "sourceEndpoint": { + "nodeId": { + "id": 246119607341204 + }, + "slotId": { + "m_id": "{954FC5E5-8838-4ABF-8C6E-B6B100C7BD20}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246085247602836 + }, + "slotId": { + "m_id": "{3F92B8A4-0A0C-4120-B947-9A64BE38855F}" + } + } + } + } + }, + { + "Id": { + "id": 246222686556308 + }, + "Name": "srcEndpoint=(Set Variable: Out), destEndpoint=(Print: In)", + "Components": { + "Component_[4813008405212019270]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 4813008405212019270, + "sourceEndpoint": { + "nodeId": { + "id": 246162557014164 + }, + "slotId": { + "m_id": "{457D8C40-3373-4DA9-8186-C33DBFA2397F}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246192621785236 + }, + "slotId": { + "m_id": "{9FEBBB50-0F94-4781-9B51-88CAA867EC16}" + } + } + } + } + }, + { + "Id": { + "id": 246226981523604 + }, + "Name": "srcEndpoint=(Set Variable: Number), destEndpoint=(Print: Value)", + "Components": { + "Component_[7843206400486067015]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 7843206400486067015, + "sourceEndpoint": { + "nodeId": { + "id": 246162557014164 + }, + "slotId": { + "m_id": "{954FC5E5-8838-4ABF-8C6E-B6B100C7BD20}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246192621785236 + }, + "slotId": { + "m_id": "{3F92B8A4-0A0C-4120-B947-9A64BE38855F}" + } + } + } + } + }, + { + "Id": { + "id": 246231276490900 + }, + "Name": "srcEndpoint=(InputHandler: Held), destEndpoint=(Set Variable: In)", + "Components": { + "Component_[6686521440104763914]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 6686521440104763914, + "sourceEndpoint": { + "nodeId": { + "id": 246115312373908 + }, + "slotId": { + "m_id": "{C20E41AE-74BA-4E93-B278-841E4F3848DD}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246162557014164 + }, + "slotId": { + "m_id": "{F83C85A3-77E5-4B09-8FE2-32127A1C6EDE}" + } + } + } + } + }, + { + "Id": { + "id": 246235571458196 + }, + "Name": "srcEndpoint=(InputHandler: value), destEndpoint=(Set Variable: Number)", + "Components": { + "Component_[9188879004341290744]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 9188879004341290744, + "sourceEndpoint": { + "nodeId": { + "id": 246115312373908 + }, + "slotId": { + "m_id": "{CDB5418B-2ECA-421E-827E-44835EF28954}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246162557014164 + }, + "slotId": { + "m_id": "{AFAC9742-D20E-4094-8720-7E4582FE7761}" + } + } + } + } + }, + { + "Id": { + "id": 246239866425492 + }, + "Name": "srcEndpoint=(Get Variable: Out), destEndpoint=(Get Variable: In)", + "Components": { + "Component_[11528425355392281835]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 11528425355392281835, + "sourceEndpoint": { + "nodeId": { + "id": 246175441916052 + }, + "slotId": { + "m_id": "{613E9634-0EEC-43EE-A680-94724628BEAB}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246072362700948 + }, + "slotId": { + "m_id": "{276F5B6E-2FC8-4EFF-A161-2548BC0E0885}" + } + } + } + } + }, + { + "Id": { + "id": 246244161392788 + }, + "Name": "srcEndpoint=(GetWorldRotationQuaternion: Result: Quaternion), destEndpoint=(ConvertQuaternionToEulerDegrees: Quaternion: 0)", + "Components": { + "Component_[12030435265785289825]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 12030435265785289825, + "sourceEndpoint": { + "nodeId": { + "id": 246188326817940 + }, + "slotId": { + "m_id": "{81953E27-7B51-462D-9AB8-B122F33A7391}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246136787210388 + }, + "slotId": { + "m_id": "{A96CFD2A-531E-4330-B991-F27754DB21B4}" + } + } + } + } + }, + { + "Id": { + "id": 246248456360084 + }, + "Name": "srcEndpoint=(GetWorldRotationQuaternion: Out), destEndpoint=(ConvertQuaternionToEulerDegrees: In)", + "Components": { + "Component_[9017349526511387231]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 9017349526511387231, + "sourceEndpoint": { + "nodeId": { + "id": 246188326817940 + }, + "slotId": { + "m_id": "{E7F58DBD-3882-43D3-B44F-D2E68E5E7098}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246136787210388 + }, + "slotId": { + "m_id": "{CC9077E4-E835-4CAE-90EB-2B5FF8BA8F07}" + } + } + } + } + }, + { + "Id": { + "id": 246252751327380 + }, + "Name": "srcEndpoint=(GetWorldRotationQuaternion: Out), destEndpoint=(ConvertQuaternionToEulerDegrees: In)", + "Components": { + "Component_[13772266509420181725]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 13772266509420181725, + "sourceEndpoint": { + "nodeId": { + "id": 246141082177684 + }, + "slotId": { + "m_id": "{E7F58DBD-3882-43D3-B44F-D2E68E5E7098}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246089542570132 + }, + "slotId": { + "m_id": "{CC9077E4-E835-4CAE-90EB-2B5FF8BA8F07}" + } + } + } + } + }, + { + "Id": { + "id": 246257046294676 + }, + "Name": "srcEndpoint=(GetWorldRotationQuaternion: Result: Quaternion), destEndpoint=(ConvertQuaternionToEulerDegrees: Quaternion: 0)", + "Components": { + "Component_[11761142503816878011]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 11761142503816878011, + "sourceEndpoint": { + "nodeId": { + "id": 246141082177684 + }, + "slotId": { + "m_id": "{81953E27-7B51-462D-9AB8-B122F33A7391}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246089542570132 + }, + "slotId": { + "m_id": "{A96CFD2A-531E-4330-B991-F27754DB21B4}" + } + } + } + } + }, + { + "Id": { + "id": 246261341261972 + }, + "Name": "srcEndpoint=(GetWorldTranslation: Result: Vector3), destEndpoint=(Add (+): Value)", + "Components": { + "Component_[11437964710666113038]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 11437964710666113038, + "sourceEndpoint": { + "nodeId": { + "id": 246068067733652 + }, + "slotId": { + "m_id": "{EBC733B4-A0E0-4772-888E-082AF8BA2FFF}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246106722439316 + }, + "slotId": { + "m_id": "{C59084D6-69B9-464E-B6C9-910F69509779}" + } + } + } + } + }, + { + "Id": { + "id": 246265636229268 + }, + "Name": "srcEndpoint=(Add (+): Out), destEndpoint=(SetNamedParameterVector3: In)", + "Components": { + "Component_[12246932468136989673]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 12246932468136989673, + "sourceEndpoint": { + "nodeId": { + "id": 246106722439316 + }, + "slotId": { + "m_id": "{E8BA15CB-A2B1-4824-92D6-2A71EEBF0378}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246171146948756 + }, + "slotId": { + "m_id": "{5D58862C-5682-4497-A994-F240120E8E62}" + } + } + } + } + }, + { + "Id": { + "id": 246269931196564 + }, + "Name": "srcEndpoint=(SetNamedParameterVector3: Out), destEndpoint=(DrawSphereAtLocation: In)", + "Components": { + "Component_[14273979275766531928]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 14273979275766531928, + "sourceEndpoint": { + "nodeId": { + "id": 246171146948756 + }, + "slotId": { + "m_id": "{8F6FD0FC-11E5-49DD-A7D6-0AC0351E2DCA}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246102427472020 + }, + "slotId": { + "m_id": "{9B026749-1D6E-4F69-9F20-777A510FD7C4}" + } + } + } + } + }, + { + "Id": { + "id": 246282816098452 + }, + "Name": "srcEndpoint=(RotateVector3: Result), destEndpoint=(DrawRayEntityToDirection: Vector3: 1)", + "Components": { + "Component_[2134692939135952702]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 2134692939135952702, + "sourceEndpoint": { + "nodeId": { + "id": 246166851981460 + }, + "slotId": { + "m_id": "{099EF8F5-DE6F-45FC-9AA6-716996AAB7C6}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246123902308500 + }, + "slotId": { + "m_id": "{1ADF8F5A-62E9-41F0-B5BA-A53FA4D60311}" + } + } + } + } + }, + { + "Id": { + "id": 246287111065748 + }, + "Name": "srcEndpoint=(DrawSphereAtLocation: Out), destEndpoint=(DrawTextAtLocation: In)", + "Components": { + "Component_[5472516653628902263]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 5472516653628902263, + "sourceEndpoint": { + "nodeId": { + "id": 246102427472020 + }, + "slotId": { + "m_id": "{063DEA47-18F0-45C9-B2D6-BB6767B769DE}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246149672112276 + }, + "slotId": { + "m_id": "{31C53E9E-3EE2-4476-A4C3-4966FDA539CC}" + } + } + } + } + }, + { + "Id": { + "id": 246291406033044 + }, + "Name": "srcEndpoint=(Add (+): Result), destEndpoint=(SetNamedParameterVector3: Vector3: 2)", + "Components": { + "Component_[1844199703114757013]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 1844199703114757013, + "sourceEndpoint": { + "nodeId": { + "id": 246106722439316 + }, + "slotId": { + "m_id": "{B87A6F0B-DE2B-4E9E-9C44-D18F1B8706E4}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246171146948756 + }, + "slotId": { + "m_id": "{04958BE6-8525-49CA-9130-8CECD0BE43B3}" + } + } + } + } + }, + { + "Id": { + "id": 246295701000340 + }, + "Name": "srcEndpoint=(Add (+): Result), destEndpoint=(DrawSphereAtLocation: Vector3: 0)", + "Components": { + "Component_[2568324937444780316]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 2568324937444780316, + "sourceEndpoint": { + "nodeId": { + "id": 246106722439316 + }, + "slotId": { + "m_id": "{B87A6F0B-DE2B-4E9E-9C44-D18F1B8706E4}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246102427472020 + }, + "slotId": { + "m_id": "{67CBBEE3-F9B5-472E-8A3E-A82996CDA2D1}" + } + } + } + } + }, + { + "Id": { + "id": 246299995967636 + }, + "Name": "srcEndpoint=(Add (+): Result), destEndpoint=(DrawTextAtLocation: Vector3: 0)", + "Components": { + "Component_[14096020408718838099]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 14096020408718838099, + "sourceEndpoint": { + "nodeId": { + "id": 246106722439316 + }, + "slotId": { + "m_id": "{B87A6F0B-DE2B-4E9E-9C44-D18F1B8706E4}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246149672112276 + }, + "slotId": { + "m_id": "{C1270538-1843-4392-B23C-12747DD40B15}" + } + } + } + } + }, + { + "Id": { + "id": 246304290934932 + }, + "Name": "srcEndpoint=(RotateVector3: Result), destEndpoint=(SetNamedParameterVector3: Vector3: 2)", + "Components": { + "Component_[16993908198950428448]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 16993908198950428448, + "sourceEndpoint": { + "nodeId": { + "id": 246145377144980 + }, + "slotId": { + "m_id": "{099EF8F5-DE6F-45FC-9AA6-716996AAB7C6}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246128197275796 + }, + "slotId": { + "m_id": "{04958BE6-8525-49CA-9130-8CECD0BE43B3}" + } + } + } + } + }, + { + "Id": { + "id": 246308585902228 + }, + "Name": "srcEndpoint=(RotateVector3: Result), destEndpoint=(DrawRayEntityToDirection: Vector3: 1)", + "Components": { + "Component_[8785804684259133429]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 8785804684259133429, + "sourceEndpoint": { + "nodeId": { + "id": 246145377144980 + }, + "slotId": { + "m_id": "{099EF8F5-DE6F-45FC-9AA6-716996AAB7C6}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246111017406612 + }, + "slotId": { + "m_id": "{1ADF8F5A-62E9-41F0-B5BA-A53FA4D60311}" + } + } + } + } + }, + { + "Id": { + "id": 246312880869524 + }, + "Name": "srcEndpoint=(ConvertQuaternionToEulerDegrees: Euler Angle), destEndpoint=(Extract Properties: Source)", + "Components": { + "Component_[9719260768879508209]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 9719260768879508209, + "sourceEndpoint": { + "nodeId": { + "id": 246136787210388 + }, + "slotId": { + "m_id": "{79FE68CC-45CC-4F5D-87A7-94B9144A3598}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246080952635540 + }, + "slotId": { + "m_id": "{8F09882E-A82E-481D-9FBD-FED706BEFB7C}" + } + } + } + } + }, + { + "Id": { + "id": 246317175836820 + }, + "Name": "srcEndpoint=(RotateVector3: Out), destEndpoint=(SetNamedParameterVector3: In)", + "Components": { + "Component_[14742201364563474463]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 14742201364563474463, + "sourceEndpoint": { + "nodeId": { + "id": 246145377144980 + }, + "slotId": { + "m_id": "{C49165F2-9484-4E70-874F-4FD821B83AB0}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246128197275796 + }, + "slotId": { + "m_id": "{5D58862C-5682-4497-A994-F240120E8E62}" + } + } + } + } + }, + { + "Id": { + "id": 246321470804116 + }, + "Name": "srcEndpoint=(ConvertQuaternionToEulerDegrees: Out), destEndpoint=(Extract Properties: In)", + "Components": { + "Component_[3599648585598226717]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 3599648585598226717, + "sourceEndpoint": { + "nodeId": { + "id": 246136787210388 + }, + "slotId": { + "m_id": "{1286BE0C-88F5-4C0A-ADD6-EEE94AEA78A3}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246080952635540 + }, + "slotId": { + "m_id": "{B85B623A-CACB-4B96-98DB-E4F289AA01D4}" + } + } + } + } + }, + { + "Id": { + "id": 246325765771412 + }, + "Name": "srcEndpoint=(TickBus Handler: ExecutionSlot:OnTick), destEndpoint=(Get Variable: In)", + "Components": { + "Component_[10064893477852139475]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 10064893477852139475, + "sourceEndpoint": { + "nodeId": { + "id": 246093837537428 + }, + "slotId": { + "m_id": "{8965578E-A29D-4468-B12B-9D4E4F814641}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246175441916052 + }, + "slotId": { + "m_id": "{17B5B6FD-B942-4C0C-847E-3891BA11743B}" + } + } + } + } + }, + { + "Id": { + "id": 246330060738708 + }, + "Name": "srcEndpoint=(ConvertQuaternionToEulerDegrees: Euler Angle), destEndpoint=(Extract Properties: Source)", + "Components": { + "Component_[15688788304938115651]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 15688788304938115651, + "sourceEndpoint": { + "nodeId": { + "id": 246089542570132 + }, + "slotId": { + "m_id": "{79FE68CC-45CC-4F5D-87A7-94B9144A3598}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246158262046868 + }, + "slotId": { + "m_id": "{E14C7719-5419-444C-BCDF-23BFDFD40DA4}" + } + } + } + } + }, + { + "Id": { + "id": 246334355706004 + }, + "Name": "srcEndpoint=(ConvertQuaternionToEulerDegrees: Out), destEndpoint=(Extract Properties: In)", + "Components": { + "Component_[18268274144592574791]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 18268274144592574791, + "sourceEndpoint": { + "nodeId": { + "id": 246089542570132 + }, + "slotId": { + "m_id": "{1286BE0C-88F5-4C0A-ADD6-EEE94AEA78A3}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246158262046868 + }, + "slotId": { + "m_id": "{A6542FE6-CFED-4033-AC3A-64614B1BF141}" + } + } + } + } + }, + { + "Id": { + "id": 246338650673300 + }, + "Name": "srcEndpoint=(Get Variable: Out), destEndpoint=(FromValues: In)", + "Components": { + "Component_[10607947307671971003]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 10607947307671971003, + "sourceEndpoint": { + "nodeId": { + "id": 246072362700948 + }, + "slotId": { + "m_id": "{6D512DCD-4CA6-421F-9C23-214E4A757D5F}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246153967079572 + }, + "slotId": { + "m_id": "{7A425E1D-C67F-42D7-8377-124FDDCCD02B}" + } + } + } + } + }, + { + "Id": { + "id": 246342945640596 + }, + "Name": "srcEndpoint=(Get Variable: Number), destEndpoint=(FromValues: X)", + "Components": { + "Component_[6093226963465581941]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 6093226963465581941, + "sourceEndpoint": { + "nodeId": { + "id": 246175441916052 + }, + "slotId": { + "m_id": "{D44E36FC-7879-4E15-B0AA-0E9F866D2A5C}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246153967079572 + }, + "slotId": { + "m_id": "{8FACA5C3-ED4D-4DC2-8E6A-28AE1B340BE7}" + } + } + } + } + }, + { + "Id": { + "id": 246347240607892 + }, + "Name": "srcEndpoint=(Get Variable: Number), destEndpoint=(FromValues: Y)", + "Components": { + "Component_[9930139812119100083]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 9930139812119100083, + "sourceEndpoint": { + "nodeId": { + "id": 246072362700948 + }, + "slotId": { + "m_id": "{68E66FCE-1DF4-445A-9DD5-7D06D3A3146D}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246153967079572 + }, + "slotId": { + "m_id": "{A01573D8-5F5D-404B-A5BC-193480C65F28}" + } + } + } + } + }, + { + "Id": { + "id": 246351535575188 + }, + "Name": "srcEndpoint=(FromValues: Out), destEndpoint=(GetWorldRotationQuaternion: In)", + "Components": { + "Component_[13049608142141105699]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 13049608142141105699, + "sourceEndpoint": { + "nodeId": { + "id": 246153967079572 + }, + "slotId": { + "m_id": "{2EAFCACB-BC3D-4748-86F6-E2037A7CF487}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246141082177684 + }, + "slotId": { + "m_id": "{6BCB6E4B-E9C5-4D6E-A4AF-C2C76271E115}" + } + } + } + } + }, + { + "Id": { + "id": 246355830542484 + }, + "Name": "srcEndpoint=(Extract Properties: Out), destEndpoint=(RotationZDegrees: In)", + "Components": { + "Component_[2074083843557420377]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 2074083843557420377, + "sourceEndpoint": { + "nodeId": { + "id": 246158262046868 + }, + "slotId": { + "m_id": "{3EB4F651-DA08-45E5-94FE-432A8FC45914}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246098132504724 + }, + "slotId": { + "m_id": "{C9C41693-0E85-429F-B9CF-87102B7AF399}" + } + } + } + } + }, + { + "Id": { + "id": 246360125509780 + }, + "Name": "srcEndpoint=(RotationZDegrees: Result), destEndpoint=(RotateVector3: Quaternion)", + "Components": { + "Component_[10610503423712691047]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 10610503423712691047, + "sourceEndpoint": { + "nodeId": { + "id": 246098132504724 + }, + "slotId": { + "m_id": "{59A7EF4E-857E-4F2B-9466-37FE3C783EF9}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246166851981460 + }, + "slotId": { + "m_id": "{6F45F01E-F700-4594-98F0-A02A5BE00617}" + } + } + } + } + }, + { + "Id": { + "id": 246364420477076 + }, + "Name": "srcEndpoint=(RotationZDegrees: Out), destEndpoint=(RotateVector3: In)", + "Components": { + "Component_[14370414046864144573]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 14370414046864144573, + "sourceEndpoint": { + "nodeId": { + "id": 246098132504724 + }, + "slotId": { + "m_id": "{DFA7AD12-D253-4B55-95BF-EBD79C9F0986}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246166851981460 + }, + "slotId": { + "m_id": "{9704DF19-1C22-4E87-B461-EBA5E86CA68C}" + } + } + } + } + }, + { + "Id": { + "id": 246368715444372 + }, + "Name": "srcEndpoint=(Extract Properties: Out), destEndpoint=(RotationZDegrees: In)", + "Components": { + "Component_[1859237677254936695]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 1859237677254936695, + "sourceEndpoint": { + "nodeId": { + "id": 246080952635540 + }, + "slotId": { + "m_id": "{E6DBA835-98FA-4ADB-8F87-15604C8D3FD3}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246184031850644 + }, + "slotId": { + "m_id": "{424CC5BD-ACB0-4F1E-9700-DE29DE2F908E}" + } + } + } + } + }, + { + "Id": { + "id": 246373010411668 + }, + "Name": "srcEndpoint=(RotationZDegrees: Out), destEndpoint=(RotateVector3: In)", + "Components": { + "Component_[5790169301321537787]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 5790169301321537787, + "sourceEndpoint": { + "nodeId": { + "id": 246184031850644 + }, + "slotId": { + "m_id": "{6D35C87C-35B1-49F2-9FE6-2FE2991AAB8D}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246145377144980 + }, + "slotId": { + "m_id": "{9704DF19-1C22-4E87-B461-EBA5E86CA68C}" + } + } + } + } + }, + { + "Id": { + "id": 246377305378964 + }, + "Name": "srcEndpoint=(RotationZDegrees: Result), destEndpoint=(RotateVector3: Quaternion)", + "Components": { + "Component_[9113874641309049506]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 9113874641309049506, + "sourceEndpoint": { + "nodeId": { + "id": 246184031850644 + }, + "slotId": { + "m_id": "{C2F9543A-928E-4405-A269-D8F952D227DB}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246145377144980 + }, + "slotId": { + "m_id": "{6F45F01E-F700-4594-98F0-A02A5BE00617}" + } + } + } + } + }, + { + "Id": { + "id": 246381600346260 + }, + "Name": "srcEndpoint=(Extract Properties: Z), destEndpoint=(RotationZDegrees: Degrees)", + "Components": { + "Component_[16079074013127612900]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 16079074013127612900, + "sourceEndpoint": { + "nodeId": { + "id": 246080952635540 + }, + "slotId": { + "m_id": "{E7EB179B-BACB-44DB-B2D1-D5D4788D80D8}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246184031850644 + }, + "slotId": { + "m_id": "{E2E3C0BC-56A7-4504-BEFA-1F1673DE7B0E}" + } + } + } + } + }, + { + "Id": { + "id": 246385895313556 + }, + "Name": "srcEndpoint=(Extract Properties: Z), destEndpoint=(RotationZDegrees: Degrees)", + "Components": { + "Component_[324913126273692816]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 324913126273692816, + "sourceEndpoint": { + "nodeId": { + "id": 246158262046868 + }, + "slotId": { + "m_id": "{B138BB48-9B60-4DE3-8DF0-DCC17D0D6358}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246098132504724 + }, + "slotId": { + "m_id": "{5076FEE2-72A1-4E2C-8FAF-649186F6F022}" + } + } + } + } + }, + { + "Id": { + "id": 246394485248148 + }, + "Name": "srcEndpoint=(FromValues: Result), destEndpoint=(RotateVector3: Vector)", + "Components": { + "Component_[8522346125520723525]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 8522346125520723525, + "sourceEndpoint": { + "nodeId": { + "id": 246153967079572 + }, + "slotId": { + "m_id": "{5029C117-20D4-4CE1-9097-FBBC98B4F457}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246076657668244 + }, + "slotId": { + "m_id": "{B46DDF66-DA5E-474C-A740-5472FBBF285A}" + } + } + } + } + }, + { + "Id": { + "id": 246398780215444 + }, + "Name": "srcEndpoint=(RotationZDegrees: Result), destEndpoint=(RotateVector3: Quaternion)", + "Components": { + "Component_[16701501001610934713]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 16701501001610934713, + "sourceEndpoint": { + "nodeId": { + "id": 246184031850644 + }, + "slotId": { + "m_id": "{C2F9543A-928E-4405-A269-D8F952D227DB}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246076657668244 + }, + "slotId": { + "m_id": "{72351E0C-D3F5-4181-90CB-8A4E0557A978}" + } + } + } + } + }, + { + "Id": { + "id": 246407370150036 + }, + "Name": "srcEndpoint=(RotateVector3: Result), destEndpoint=(Add (+): Vector3)", + "Components": { + "Component_[725702429325335637]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 725702429325335637, + "sourceEndpoint": { + "nodeId": { + "id": 246076657668244 + }, + "slotId": { + "m_id": "{879125AE-8729-465D-A3E3-1DAF6B9C84C7}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246106722439316 + }, + "slotId": { + "m_id": "{FBF48C32-3CCB-47CA-BC9E-45567BF1B269}" + } + } + } + } + }, + { + "Id": { + "id": 246411665117332 + }, + "Name": "srcEndpoint=(RotateVector3: Out), destEndpoint=(GetWorldTranslation: In)", + "Components": { + "Component_[18297619570874862126]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 18297619570874862126, + "sourceEndpoint": { + "nodeId": { + "id": 246076657668244 + }, + "slotId": { + "m_id": "{8D7093BA-E37A-48D7-ABB6-8D3960A60BF0}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246068067733652 + }, + "slotId": { + "m_id": "{5AAE80BB-C549-4D61-AC36-57FF33A0F9B9}" + } + } + } + } + }, + { + "Id": { + "id": 246415960084628 + }, + "Name": "srcEndpoint=(GetWorldTranslation: Out), destEndpoint=(Add (+): In)", + "Components": { + "Component_[5276081108224246554]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 5276081108224246554, + "sourceEndpoint": { + "nodeId": { + "id": 246068067733652 + }, + "slotId": { + "m_id": "{D6245737-F70E-431A-8DB2-D187634B6859}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246106722439316 + }, + "slotId": { + "m_id": "{1AD1DED1-EDE6-4FAF-B3B6-FB5B26F1B294}" + } + } + } + } + }, + { + "Id": { + "id": 201379649551848 + }, + "Name": "srcEndpoint=(SetNamedParameterVector3: Out), destEndpoint=(RotateVector3: In)", + "Components": { + "Component_[203700470042535327]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 203700470042535327, + "sourceEndpoint": { + "nodeId": { + "id": 246128197275796 + }, + "slotId": { + "m_id": "{8F6FD0FC-11E5-49DD-A7D6-0AC0351E2DCA}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246076657668244 + }, + "slotId": { + "m_id": "{8C5BE0B0-13CC-43A5-8F44-9C282B3E4D60}" + } + } + } + } + }, + { + "Id": { + "id": 202487751114216 + }, + "Name": "srcEndpoint=(RotateVector3: Out), destEndpoint=(GetWorldRotationQuaternion: In)", + "Components": { + "Component_[1253233322229674061]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 1253233322229674061, + "sourceEndpoint": { + "nodeId": { + "id": 246166851981460 + }, + "slotId": { + "m_id": "{C49165F2-9484-4E70-874F-4FD821B83AB0}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 246188326817940 + }, + "slotId": { + "m_id": "{6BCB6E4B-E9C5-4D6E-A4AF-C2C76271E115}" + } + } + } + } + } + ] + }, + "m_assetType": "{3E2AC8CD-713F-453E-967F-29517F331784}", + "versionData": { + "_grammarVersion": 1, + "_runtimeVersion": 1, + "_fileVersion": 1 + }, + "m_variableCounter": 3, + "GraphCanvasData": [ + { + "Key": { + "id": 246068067733652 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MethodNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 4540.0, + 2340.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".method" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{2FF9E28B-794F-4595-B6CD-F3894076C6E7}" + } + } + } + }, + { + "Key": { + "id": 246072362700948 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "GetVariableNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 740.0, + 1760.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".getVariable" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{507C10BA-288C-4E68-AEFB-949E4EDA039B}" + } + } + } + }, + { + "Key": { + "id": 246076657668244 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 4200.0, + 2500.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{4BF9A06C-7D50-4FF3-9A4C-55C44FA8F7EF}" + } + } + } + }, + { + "Key": { + "id": 246080952635540 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "DefaultNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 1300.0, + 2580.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{4DED46A4-C20B-43F8-A242-8A379685D4F4}" + } + } + } + }, + { + "Key": { + "id": 246085247602836 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "StringNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 860.0, + -20.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{504E5761-0540-481C-98F1-7C473F7FAD1B}" + } + } + } + }, + { + "Key": { + "id": 246089542570132 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MethodNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 1980.0, + 1200.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".method" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{C7970815-CCB0-4FE2-874D-09968D0C09A5}" + } + } + } + }, + { + "Key": { + "id": 246093837537428 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + -160.0, + 1840.0 + ] + }, + "{9E81C95F-89C0-4476-8E82-63CCC4E52E04}": { + "$type": "EBusHandlerNodeDescriptorSaveData", + "EventIds": [ + { + "Value": 1502188240 + } + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{4BD96082-9121-41AC-9C04-7EED59FF8183}" + } + } + } + }, + { + "Key": { + "id": 246098132504724 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 2780.0, + 1240.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{8A15E090-E8AA-4A1E-ABC2-161955729668}" + } + } + } + }, + { + "Key": { + "id": 246102427472020 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MethodNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 6080.0, + 2280.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".method" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{7CCBB240-958D-4A7F-AAB2-C538251C427D}" + } + } + } + }, + { + "Key": { + "id": 246106722439316 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 5140.0, + 2340.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{11DD29F2-0432-479B-AA6A-D1A0A40A6A44}" + } + } + } + }, + { + "Key": { + "id": 246115312373908 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "DefaultNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + -160.0, + 280.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{CEFD1956-B60A-4727-BEEF-A31E5D712BBF}" + } + } + } + }, + { + "Key": { + "id": 246119607341204 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "SetVariableNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 440.0, + 0.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".setVariable" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{A6009AED-C3E9-4891-8FB8-E7778CB5A9B5}" + } + } + } + }, + { + "Key": { + "id": 246128197275796 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MethodNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 2840.0, + 2580.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".method" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{039ADCFA-B370-4150-96A0-D404A8784B5A}" + } + } + } + }, + { + "Key": { + "id": 246132492243092 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "DefaultNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + -160.0, + -100.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{E129EEFF-892C-4C5A-86F2-DB349E0BEB1E}" + } + } + } + }, + { + "Key": { + "id": 246136787210388 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MethodNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 860.0, + 2580.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".method" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{181C9959-F9CE-4E83-A28F-34A6122A5FBD}" + } + } + } + }, + { + "Key": { + "id": 246141082177684 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MethodNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 1540.0, + 1200.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".method" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{1F571FC4-D31A-45B9-912B-553620D3BD9D}" + } + } + } + }, + { + "Key": { + "id": 246145377144980 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 2040.0, + 2580.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{92D9C6D1-1261-4404-8F47-B372687FAC49}" + } + } + } + }, + { + "Key": { + "id": 246149672112276 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MethodNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 6520.0, + 2280.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".method" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{86F8827F-9F3C-4A93-89E2-F120EB7DD2AC}" + } + } + } + }, + { + "Key": { + "id": 246153967079572 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 1080.0, + 1740.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{070D096C-3906-46C6-9004-AB47CC3CA321}" + } + } + } + }, + { + "Key": { + "id": 246158262046868 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "DefaultNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 2420.0, + 1200.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{A11A84D2-7BF6-4FE1-8C29-E7178CA9AF50}" + } + } + } + }, + { + "Key": { + "id": 246162557014164 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "SetVariableNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 440.0, + 360.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".setVariable" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{5B946F71-A93B-438E-A7B0-27CE24D672B0}" + } + } + } + }, + { + "Key": { + "id": 246166851981460 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 3440.0, + 1120.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{E9D6CBC8-A0AF-48AF-9432-644B01BD8E87}" + } + } + } + }, + { + "Key": { + "id": 246171146948756 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MethodNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 5740.0, + 2280.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".method" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{96C3A2DA-6948-4773-BBF7-E72690F95D27}" + } + } + } + }, + { + "Key": { + "id": 246175441916052 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "GetVariableNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 500.0, + 1760.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".getVariable" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{C91F684B-BDF4-45B6-8A21-BE01BD0B57E6}" + } + } + } + }, + { + "Key": { + "id": 246179736883348 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + -600.0, + 140.0 + ] + }, + "{9E81C95F-89C0-4476-8E82-63CCC4E52E04}": { + "$type": "EBusHandlerNodeDescriptorSaveData", + "EventIds": [ + { + "Value": 245425936 + } + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{ED62B820-56F3-4907-8297-93FACF11D6C7}" + } + } + } + }, + { + "Key": { + "id": 246184031850644 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 1640.0, + 2620.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{CA7C06D5-1C74-4682-8475-77B4590D1068}" + } + } + } + }, + { + "Key": { + "id": 246188326817940 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MethodNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 420.0, + 2580.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".method" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{40F91617-FB9A-44B0-AB46-7BC49BFA9695}" + } + } + } + }, + { + "Key": { + "id": 246192621785236 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "StringNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 860.0, + 340.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{25B84E08-7E28-49F4-8D3F-2D5436AD4A59}" + } + } + } + }, + { + "Key": { + "id": 3794495145504990811 + }, + "Value": { + "ComponentData": { + "{5F84B500-8C45-40D1-8EFC-A5306B241444}": { + "$type": "SceneComponentSaveData", + "ViewParams": { + "Scale": 0.41824002620728956, + "AnchorX": 1786.0557861328125, + "AnchorY": 686.2088623046875 + } + } + } + } + } + ], + "StatisticsHelper": { + "InstanceCounter": [ + { + "Key": 1244476766431948410, + "Value": 1 + }, + { + "Key": 5842116761103598202, + "Value": 1 + }, + { + "Key": 5842117451819972883, + "Value": 1 + }, + { + "Key": 5933558821430063196, + "Value": 1 + }, + { + "Key": 7413323401356093379, + "Value": 2 + }, + { + "Key": 8023800818767041160, + "Value": 2 + }, + { + "Key": 8443300848607535552, + "Value": 1 + }, + { + "Key": 10242161751377247902, + "Value": 1 + }, + { + "Key": 10684225535275896474, + "Value": 2 + }, + { + "Key": 12812762535860395237, + "Value": 1 + }, + { + "Key": 13501032720093015244, + "Value": 2 + }, + { + "Key": 13774516225767375488, + "Value": 1 + }, + { + "Key": 13774516226110902316, + "Value": 1 + }, + { + "Key": 13774516341676861545, + "Value": 2 + }, + { + "Key": 13774516555191045853, + "Value": 2 + }, + { + "Key": 13774516556399355685, + "Value": 1 + }, + { + "Key": 14285852892804039565, + "Value": 2 + }, + { + "Key": 14759916521179134347, + "Value": 1 + }, + { + "Key": 18182167487771916815, + "Value": 3 + } + ] + } + }, + "Component_[9726965826837406164]": { + "$type": "EditorGraphVariableManagerComponent", + "Id": 9726965826837406164, + "m_variableData": { + "m_nameVariableMap": [ + { + "Key": { + "m_id": "{5EB17E58-0B4E-451D-A1CE-0E7C272CBDEC}" + }, + "Value": { + "Datum": { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0 + }, + "VariableId": { + "m_id": "{5EB17E58-0B4E-451D-A1CE-0E7C272CBDEC}" + }, + "VariableName": "MoveY" + } + }, + { + "Key": { + "m_id": "{B48E5726-A7FF-42A8-84D2-CF43ABBD1EDC}" + }, + "Value": { + "Datum": { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0 + }, + "VariableId": { + "m_id": "{B48E5726-A7FF-42A8-84D2-CF43ABBD1EDC}" + }, + "VariableName": "MoveX" + } + } + ] + } + } + } + } + } +} \ No newline at end of file diff --git a/Gems/MotionMatching/Assets/Levels/MotionMatching_AutomaticDemo/MotionMatching_AutomaticDemo.animgraph b/Gems/MotionMatching/Assets/Levels/MotionMatching_AutomaticDemo/MotionMatching_AutomaticDemo.animgraph new file mode 100644 index 0000000000..c8633beba2 --- /dev/null +++ b/Gems/MotionMatching/Assets/Levels/MotionMatching_AutomaticDemo/MotionMatching_AutomaticDemo.animgraph @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6476e88fb2bcda44f7ef57d87f2ff7f4d6e3a30f677ad1b7b235c43b3858aa7f +size 39099 diff --git a/Gems/MotionMatching/Assets/Levels/MotionMatching_AutomaticDemo/MotionMatching_AutomaticDemo.ly b/Gems/MotionMatching/Assets/Levels/MotionMatching_AutomaticDemo/MotionMatching_AutomaticDemo.ly new file mode 100644 index 0000000000..592531e632 --- /dev/null +++ b/Gems/MotionMatching/Assets/Levels/MotionMatching_AutomaticDemo/MotionMatching_AutomaticDemo.ly @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:065170c8b2dca1e481b70ed648fc149767e795b0794b6b4b0af3f79054a2f93c +size 11797 diff --git a/Gems/MotionMatching/Assets/MotionMatching.animgraph b/Gems/MotionMatching/Assets/MotionMatching.animgraph index 10707eb6c4..e5656a3d05 100644 --- a/Gems/MotionMatching/Assets/MotionMatching.animgraph +++ b/Gems/MotionMatching/Assets/MotionMatching.animgraph @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:09cc2374b99c219421812ec39b68a350912d44777a50e3078c8cd67e91cc74cf -size 30927 +oid sha256:f974fa29f3542311ee6a4b6fbb92047186c3124b3bd6f88b728646bf9d686c45 +size 39099 diff --git a/Gems/MotionMatching/Assets/MotionMatching.motionset b/Gems/MotionMatching/Assets/MotionMatching.motionset index 276895e3a0..f3f0510e6d 100644 --- a/Gems/MotionMatching/Assets/MotionMatching.motionset +++ b/Gems/MotionMatching/Assets/MotionMatching.motionset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9c895a1696f5f37492089ceaef11210d704c1fb7e3dd31305cf4732d63489507 -size 13645 +oid sha256:35bb5b196bc07687004aed1ddff7e3b922f8abe82b0e6b25c88854d9032c6f06 +size 13482 diff --git a/Gems/MotionMatching/preview.png b/Gems/MotionMatching/preview.png index 2979dbb6a4..b3b6192ad5 100644 --- a/Gems/MotionMatching/preview.png +++ b/Gems/MotionMatching/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de0e6e480ece5b423222f4feacf56553d73713fe9afea8bbc9a2660a3cd54ec7 -size 1232 +oid sha256:e0f8ffb4980f6cfc34135f4a4b9967293ff34bcdb37019181cb22c6a07067ce8 +size 57461