From a71a5746ccd6734a68b8c592cab757879993301f Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Tue, 13 Apr 2021 17:15:10 +0100 Subject: [PATCH 001/330] [LY-113714] Jira: LY-113714 https://jira.agscollab.com/browse/LY-113714 --- .../Serialization/EditContextConstants.inl | 2 + .../Components/img/UI20/line.svg | 7 ++++ .../AzQtComponents/Components/resources.qrc | 1 + .../UI/PropertyEditor/PropertyRowWidget.cpp | 37 ++++++++++++++++++- .../UI/PropertyEditor/PropertyRowWidget.hxx | 8 ++++ Code/Sandbox/Editor/Style/Editor.qss | 5 +++ Gems/Vegetation/Code/Source/Descriptor.cpp | 2 + 7 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg diff --git a/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl b/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl index d4658d4e8c..de67013741 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl +++ b/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl @@ -62,6 +62,8 @@ namespace AZ const static AZ::Crc32 ButtonTooltip = AZ_CRC("ButtonTooltip", 0x1605a7d2); const static AZ::Crc32 CheckboxTooltip = AZ_CRC("CheckboxTooltip", 0x1159eb78); const static AZ::Crc32 CheckboxDefaultValue = AZ_CRC("CheckboxDefaultValue", 0x03f117e6); + //! Emboldens the text and adds a line above this item within the RPE. + const static AZ::Crc32 RPESectionSeparator = AZ_CRC("RPESectionSeparator", 0xc6249a95); //! Affects the display order of a node relative to it's parent/children. Higher values display further down (after) lower values. Default is 0, negative values are allowed. Must be applied as an attribute to the EditorData element const static AZ::Crc32 DisplayOrder = AZ_CRC("DisplayOrder", 0x23660ec2); //! Specifies whether the UI should support multi-edit for aggregate instances of this property diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg b/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg new file mode 100644 index 0000000000..60f7c07c8d --- /dev/null +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg @@ -0,0 +1,7 @@ + + + line + + + + \ No newline at end of file diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc b/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc index 77dead96a1..decc1bd72f 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc @@ -627,6 +627,7 @@ img/UI20/Settings.svg img/UI20/Asset_Folder.svg img/UI20/Asset_File.svg + img/UI20/line.svg img/UI20/AssetEditor/default_document.svg diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp index 8c371baf8c..a16c1f7480 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp @@ -44,6 +44,24 @@ namespace AzToolsFramework m_iconOpen = s_iconOpen; m_iconClosed = s_iconClosed; + m_outerLayout = new QVBoxLayout(nullptr); + m_outerLayout->setSpacing(0); + m_outerLayout->setContentsMargins(0, 0, 0, 0); + + // separatorLayout will contain a spacer and a separator line. The width of the spacer is adjusted later to ensure the line is the + // correct length. + QHBoxLayout* separatorLayout = new QHBoxLayout(nullptr); + m_outerLayout->addLayout(separatorLayout); + + m_separatorIndent = new QSpacerItem(1, 1); + separatorLayout->addItem(m_separatorIndent); + + m_separatorLine.load(QStringLiteral(":/Gallery/line.svg")); + m_separatorLine.setFixedHeight(3); + + separatorLayout->addWidget(&m_separatorLine); + m_separatorLine.setVisible(false); + m_mainLayout = new QHBoxLayout(); m_mainLayout->setSpacing(0); m_mainLayout->setContentsMargins(0, 1, 0, 1); @@ -118,7 +136,8 @@ namespace AzToolsFramework m_handler = nullptr; m_containerSize = 0; - setLayout(m_mainLayout); + m_outerLayout->addLayout(m_mainLayout); + setLayout(m_outerLayout); } bool PropertyRowWidget::HasChildWidgetAlready() const @@ -301,6 +320,9 @@ namespace AzToolsFramework } } + m_isSectionSeparator = false; + m_separatorLine.setVisible(false); + RefreshAttributesFromNode(true); // --------------------- HANDLER discovery: @@ -946,6 +968,11 @@ namespace AzToolsFramework { HandleChangeNotifyAttribute(reader, m_sourceNode ? m_sourceNode->GetParent() : nullptr, m_editingCompleteNotifiers); } + else if (attributeName == AZ::Edit::Attributes::RPESectionSeparator) + { + m_separatorLine.setVisible(true); + m_isSectionSeparator = true; + } } void PropertyRowWidget::SetReadOnlyQueryFunction(const ReadOnlyQueryFunction& readOnlyQueryFunction) @@ -1070,6 +1097,7 @@ namespace AzToolsFramework { m_dropDownArrow->hide(); } + m_separatorIndent->changeSize((m_treeDepth * m_treeIndentation) + m_leafIndentation, 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_indent->changeSize((m_treeDepth * m_treeIndentation) + m_leafIndentation, 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_leftHandSideLayout->invalidate(); m_leftHandSideLayout->update(); @@ -1085,6 +1113,7 @@ namespace AzToolsFramework connect(m_dropDownArrow, &QCheckBox::clicked, this, &PropertyRowWidget::OnClickedExpansionButton); } m_dropDownArrow->show(); + m_separatorIndent->changeSize((m_treeDepth * m_treeIndentation), 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_indent->changeSize((m_treeDepth * m_treeIndentation), 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_leftHandSideLayout->invalidate(); m_leftHandSideLayout->update(); @@ -1095,6 +1124,7 @@ namespace AzToolsFramework void PropertyRowWidget::SetIndentSize(int w) { + m_separatorIndent->changeSize(w, 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_indent->changeSize(w, 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_leftHandSideLayout->invalidate(); m_leftHandSideLayout->update(); @@ -1318,6 +1348,11 @@ namespace AzToolsFramework return canBeTopLevel(this); } + bool PropertyRowWidget::IsSectionSeparator() const + { + return m_isSectionSeparator; + } + bool PropertyRowWidget::GetAppendDefaultLabelToName() { return false; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx index e4b538ccdc..af1b68b66f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx @@ -25,6 +25,7 @@ AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // class '...' needs t #include #include #include +#include #include AZ_POP_DISABLE_WARNING @@ -44,6 +45,7 @@ namespace AzToolsFramework Q_PROPERTY(bool hasChildRows READ HasChildRows); Q_PROPERTY(bool isTopLevel READ IsTopLevel); Q_PROPERTY(int getLevel READ GetLevel); + Q_PROPERTY(bool isSectionSeparator READ IsSectionSeparator); Q_PROPERTY(bool appendDefaultLabelToName READ GetAppendDefaultLabelToName WRITE AppendDefaultLabelToName) public: AZ_CLASS_ALLOCATOR(PropertyRowWidget, AZ::SystemAllocator, 0) @@ -82,6 +84,7 @@ namespace AzToolsFramework PropertyRowWidget* GetParentRow() const { return m_parentRow; } int GetLevel() const; bool IsTopLevel() const; + bool IsSectionSeparator() const; // Remove the default label and append the text to the name label. bool GetAppendDefaultLabelToName(); @@ -161,6 +164,9 @@ namespace AzToolsFramework QHBoxLayout* m_leftHandSideLayout; QHBoxLayout* m_middleLayout; QHBoxLayout* m_rightHandSideLayout; + QVBoxLayout* m_outerLayout; + QSvgWidget m_separatorLine; + QSpacerItem* m_separatorIndent; QPointer m_dropDownArrow; QPointer m_containerClearButton; @@ -229,6 +235,8 @@ namespace AzToolsFramework int m_treeIndentation = 14; int m_leafIndentation = 16; + bool m_isSectionSeparator = false; + QIcon m_iconOpen; QIcon m_iconClosed; diff --git a/Code/Sandbox/Editor/Style/Editor.qss b/Code/Sandbox/Editor/Style/Editor.qss index 0c3f64b85c..7887560105 100644 --- a/Code/Sandbox/Editor/Style/Editor.qss +++ b/Code/Sandbox/Editor/Style/Editor.qss @@ -38,6 +38,11 @@ AzToolsFramework--ComponentPaletteWidget > QTreeView background-color: #222222; } +AzToolsFramework--PropertyRowWidget[isSectionSeparator="true"] QLabel#Name +{ + font-weight: bold; +} + /* Style for visualizing property values overridden from their prefab values */ AzToolsFramework--PropertyRowWidget[IsOverridden=true] #Name QLabel, AzToolsFramework--ComponentEditorHeader #Title[IsOverridden="true"] diff --git a/Gems/Vegetation/Code/Source/Descriptor.cpp b/Gems/Vegetation/Code/Source/Descriptor.cpp index 93a301f073..4fd1037f09 100644 --- a/Gems/Vegetation/Code/Source/Descriptor.cpp +++ b/Gems/Vegetation/Code/Source/Descriptor.cpp @@ -170,6 +170,8 @@ namespace Vegetation { edit->Class( "Vegetation Descriptor", "Details used to create vegetation instances") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::RPESectionSeparator, true) // For this ComboBox to actually work, there is a PropertyHandler registration in EditorVegetationSystemComponent.cpp ->DataElement(AZ::Edit::UIHandlers::ComboBox, &Descriptor::m_spawnerType, "Instance Spawner", "The type of instances to spawn") ->Attribute(AZ::Edit::Attributes::GenericValueList, &Descriptor::GetSpawnerTypeList) From a94700786133526d14971bddfdd87e5d989d8678 Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Wed, 14 Apr 2021 15:15:42 +0100 Subject: [PATCH 002/330] Darken line --- .../AzQtComponents/AzQtComponents/Components/img/UI20/line.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg b/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg index 60f7c07c8d..fe01efddba 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg @@ -2,6 +2,6 @@ line - + \ No newline at end of file From 490b2afd319c595850df82f6038e13a4fa179102 Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Fri, 16 Apr 2021 10:59:27 +0100 Subject: [PATCH 003/330] [LY-105687] Jira: LY-105687 https://jira.agscollab.com/browse/LY-105687 --- .../Components/FancyDocking.cpp | 14 +++++++ .../AzQtComponents/Components/FancyDocking.h | 2 + .../Components/Widgets/TabWidget.cpp | 24 ++++++++---- .../Components/Widgets/TabWidget.h | 3 ++ .../img/UI20/Cursors/Grab_release.svg | 37 +++++++++++++++++++ .../Components/img/UI20/Cursors/Grabbing.svg | 23 ++++++++++++ .../AzQtComponents/Components/resources.qrc | 2 + 7 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grab_release.svg create mode 100644 Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grabbing.svg diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp index 76df971108..bc8da127a6 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp @@ -159,6 +159,8 @@ namespace AzQtComponents // Timer for updating our hovered drop zone opacity QObject::connect(m_dropZoneHoverFadeInTimer, &QTimer::timeout, this, &FancyDocking::onDropZoneHoverFadeInUpdate); m_dropZoneHoverFadeInTimer->setInterval(g_FancyDockingConstants.dropZoneHoverFadeUpdateIntervalMS); + QIcon dragIcon = QIcon(QStringLiteral(":/Cursors/Grabbing.svg")); + m_dragCursor = QCursor(dragIcon.pixmap(32), 10, 5); } FancyDocking::~FancyDocking() @@ -1884,6 +1886,8 @@ namespace AzQtComponents return; } + QApplication::setOverrideCursor(m_dragCursor); + QPoint relativePressPos = pressPos; // If we are dragging a floating window, we need to grab a reference to its @@ -1999,6 +2003,11 @@ namespace AzQtComponents clearDraggingState(); } + if (QApplication::overrideCursor()) + { + QApplication::restoreOverrideCursor(); + } + return true; } @@ -2376,6 +2385,11 @@ namespace AzQtComponents */ void FancyDocking::dropDockWidget(QDockWidget* dock, QWidget* onto, Qt::DockWidgetArea area) { + if (QApplication::overrideCursor()) + { + QApplication::restoreOverrideCursor(); + } + // If the dock widget we are dropping is currently a tab, we need to retrieve it from // the tab widget, and remove it as a tab. We also need to remove its item from our // cache of widget <-> tab container since we are moving it somewhere else. diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.h b/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.h index a466ce7087..20b90ad25c 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.h +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.h @@ -266,6 +266,8 @@ namespace AzQtComponents QString m_floatingWindowIdentifierPrefix; QString m_tabContainerIdentifierPrefix; + + QCursor m_dragCursor; }; } // namespace AzQtComponents diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.cpp index 895785f47b..8f408b01ef 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -419,6 +420,14 @@ namespace AzQtComponents // a mouse move. The paint handler updates the close button's visibility setAttribute(Qt::WA_Hover); AzQtComponents::Style::addClass(this, g_emptyStyleClass); + + QIcon icon = QIcon(QStringLiteral(":/Cursors/Grab release.svg")); + m_hoverCursor = QCursor(icon.pixmap(32), 10, 5); + + icon = QIcon(QStringLiteral(":/Cursors/Grabbing.svg")); + m_dragCursor = QCursor(icon.pixmap(32), 10, 5); + + this->setCursor(m_hoverCursor); } void TabBar::setHandleOverflow(bool handleOverflow) @@ -479,6 +488,13 @@ namespace AzQtComponents void TabBar::mouseReleaseEvent(QMouseEvent* mouseEvent) { + // Ensure we don't reset the cursor in the case of a dummy event being sent from DockTabWidget to trigger the animation. + Qt::MouseButtons realButtons = QApplication::mouseButtons(); + if (QApplication::overrideCursor() && !(realButtons & Qt::LeftButton)) + { + QApplication::restoreOverrideCursor(); + } + if (m_movingTab && !(mouseEvent->buttons() & Qt::LeftButton)) { // When a moving tab is released, there is a short animation to put the moving tab @@ -632,13 +648,7 @@ namespace AzQtComponents { QPoint p = tabRect(i).topLeft(); - int rightPadding = g_closeButtonPadding; - if (m_overflowing == Overflowing) - { - rightPadding = 0; - } - - p.setX(p.x() + tabRect(i).width() - rightPadding - g_closeButtonWidth); + p.setX(p.x() + tabRect(i).width() - g_closeButtonPadding - g_closeButtonWidth); p.setY(p.y() + 1 + (tabRect(i).height() - g_closeButtonWidth) / 2); tabBtn->move(p); } diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.h b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.h index 3f12f79907..e86deef7b4 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.h +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.h @@ -203,6 +203,9 @@ namespace AzQtComponents bool m_movingTab = false; QPoint m_lastMousePress; + QCursor m_dragCursor; + QCursor m_hoverCursor; + void resetOverflow(); void overflowIfNeeded(); void showCloseButtonAt(int index); diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grab_release.svg b/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grab_release.svg new file mode 100644 index 0000000000..c0da9b802f --- /dev/null +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grab_release.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grabbing.svg b/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grabbing.svg new file mode 100644 index 0000000000..e70be77d51 --- /dev/null +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grabbing.svg @@ -0,0 +1,23 @@ + + + + + + + + + + diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc b/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc index f9e601fb6d..048758c283 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc @@ -636,5 +636,7 @@ img/UI20/Cursors/Pointer.svg + img/UI20/Cursors/Grab_release.svg + img/UI20/Cursors/Grabbing.svg From db4b080544d305052602e0ba7e8260ee90bf0a87 Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Fri, 16 Apr 2021 14:55:39 +0100 Subject: [PATCH 004/330] Renamed svg --- .../AzQtComponents/Components/Widgets/TabWidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.cpp index 8f408b01ef..121f35db61 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.cpp @@ -421,7 +421,7 @@ namespace AzQtComponents setAttribute(Qt::WA_Hover); AzQtComponents::Style::addClass(this, g_emptyStyleClass); - QIcon icon = QIcon(QStringLiteral(":/Cursors/Grab release.svg")); + QIcon icon = QIcon(QStringLiteral(":/Cursors/Grab_release.svg")); m_hoverCursor = QCursor(icon.pixmap(32), 10, 5); icon = QIcon(QStringLiteral(":/Cursors/Grabbing.svg")); From 2a339edc4e9ddafedc6dac297abdd1d9c8faf3ca Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Fri, 16 Apr 2021 10:59:27 +0100 Subject: [PATCH 005/330] [LY-105687] Jira: LY-105687 https://jira.agscollab.com/browse/LY-105687 Renamed svg --- .../Components/FancyDocking.cpp | 14 +++++++ .../AzQtComponents/Components/FancyDocking.h | 2 + .../Components/Widgets/TabWidget.cpp | 24 ++++++++---- .../Components/Widgets/TabWidget.h | 3 ++ .../img/UI20/Cursors/Grab_release.svg | 37 +++++++++++++++++++ .../Components/img/UI20/Cursors/Grabbing.svg | 23 ++++++++++++ .../AzQtComponents/Components/resources.qrc | 2 + 7 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grab_release.svg create mode 100644 Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grabbing.svg diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp index 76df971108..bc8da127a6 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp @@ -159,6 +159,8 @@ namespace AzQtComponents // Timer for updating our hovered drop zone opacity QObject::connect(m_dropZoneHoverFadeInTimer, &QTimer::timeout, this, &FancyDocking::onDropZoneHoverFadeInUpdate); m_dropZoneHoverFadeInTimer->setInterval(g_FancyDockingConstants.dropZoneHoverFadeUpdateIntervalMS); + QIcon dragIcon = QIcon(QStringLiteral(":/Cursors/Grabbing.svg")); + m_dragCursor = QCursor(dragIcon.pixmap(32), 10, 5); } FancyDocking::~FancyDocking() @@ -1884,6 +1886,8 @@ namespace AzQtComponents return; } + QApplication::setOverrideCursor(m_dragCursor); + QPoint relativePressPos = pressPos; // If we are dragging a floating window, we need to grab a reference to its @@ -1999,6 +2003,11 @@ namespace AzQtComponents clearDraggingState(); } + if (QApplication::overrideCursor()) + { + QApplication::restoreOverrideCursor(); + } + return true; } @@ -2376,6 +2385,11 @@ namespace AzQtComponents */ void FancyDocking::dropDockWidget(QDockWidget* dock, QWidget* onto, Qt::DockWidgetArea area) { + if (QApplication::overrideCursor()) + { + QApplication::restoreOverrideCursor(); + } + // If the dock widget we are dropping is currently a tab, we need to retrieve it from // the tab widget, and remove it as a tab. We also need to remove its item from our // cache of widget <-> tab container since we are moving it somewhere else. diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.h b/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.h index a466ce7087..20b90ad25c 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.h +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.h @@ -266,6 +266,8 @@ namespace AzQtComponents QString m_floatingWindowIdentifierPrefix; QString m_tabContainerIdentifierPrefix; + + QCursor m_dragCursor; }; } // namespace AzQtComponents diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.cpp index 895785f47b..121f35db61 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -419,6 +420,14 @@ namespace AzQtComponents // a mouse move. The paint handler updates the close button's visibility setAttribute(Qt::WA_Hover); AzQtComponents::Style::addClass(this, g_emptyStyleClass); + + QIcon icon = QIcon(QStringLiteral(":/Cursors/Grab_release.svg")); + m_hoverCursor = QCursor(icon.pixmap(32), 10, 5); + + icon = QIcon(QStringLiteral(":/Cursors/Grabbing.svg")); + m_dragCursor = QCursor(icon.pixmap(32), 10, 5); + + this->setCursor(m_hoverCursor); } void TabBar::setHandleOverflow(bool handleOverflow) @@ -479,6 +488,13 @@ namespace AzQtComponents void TabBar::mouseReleaseEvent(QMouseEvent* mouseEvent) { + // Ensure we don't reset the cursor in the case of a dummy event being sent from DockTabWidget to trigger the animation. + Qt::MouseButtons realButtons = QApplication::mouseButtons(); + if (QApplication::overrideCursor() && !(realButtons & Qt::LeftButton)) + { + QApplication::restoreOverrideCursor(); + } + if (m_movingTab && !(mouseEvent->buttons() & Qt::LeftButton)) { // When a moving tab is released, there is a short animation to put the moving tab @@ -632,13 +648,7 @@ namespace AzQtComponents { QPoint p = tabRect(i).topLeft(); - int rightPadding = g_closeButtonPadding; - if (m_overflowing == Overflowing) - { - rightPadding = 0; - } - - p.setX(p.x() + tabRect(i).width() - rightPadding - g_closeButtonWidth); + p.setX(p.x() + tabRect(i).width() - g_closeButtonPadding - g_closeButtonWidth); p.setY(p.y() + 1 + (tabRect(i).height() - g_closeButtonWidth) / 2); tabBtn->move(p); } diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.h b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.h index 3f12f79907..e86deef7b4 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.h +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.h @@ -203,6 +203,9 @@ namespace AzQtComponents bool m_movingTab = false; QPoint m_lastMousePress; + QCursor m_dragCursor; + QCursor m_hoverCursor; + void resetOverflow(); void overflowIfNeeded(); void showCloseButtonAt(int index); diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grab_release.svg b/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grab_release.svg new file mode 100644 index 0000000000..c0da9b802f --- /dev/null +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grab_release.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grabbing.svg b/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grabbing.svg new file mode 100644 index 0000000000..e70be77d51 --- /dev/null +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grabbing.svg @@ -0,0 +1,23 @@ + + + + + + + + + + diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc b/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc index f9e601fb6d..048758c283 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc @@ -636,5 +636,7 @@ img/UI20/Cursors/Pointer.svg + img/UI20/Cursors/Grab_release.svg + img/UI20/Cursors/Grabbing.svg From 2dbd9e4a050e141a45c85a12b4d245f0695f581a Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Mon, 19 Apr 2021 15:54:41 +0100 Subject: [PATCH 006/330] Changed to use direct line drawing rather than adding svg. --- .../Components/img/UI20/line.svg | 7 ---- .../UI/PropertyEditor/PropertyRowWidget.cpp | 40 +++++++------------ .../UI/PropertyEditor/PropertyRowWidget.hxx | 4 +- 3 files changed, 16 insertions(+), 35 deletions(-) delete mode 100644 Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg b/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg deleted file mode 100644 index fe01efddba..0000000000 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/line.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - line - - - - \ No newline at end of file diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp index a16c1f7480..5b02e81e6d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp @@ -27,6 +27,7 @@ AZ_PUSH_DISABLE_WARNING(4244 4251 4800, "-Wunknown-warning-option") // 4244: con #include #include #include +#include AZ_POP_DISABLE_WARNING static const int LabelColumnStretch = 2; @@ -44,24 +45,6 @@ namespace AzToolsFramework m_iconOpen = s_iconOpen; m_iconClosed = s_iconClosed; - m_outerLayout = new QVBoxLayout(nullptr); - m_outerLayout->setSpacing(0); - m_outerLayout->setContentsMargins(0, 0, 0, 0); - - // separatorLayout will contain a spacer and a separator line. The width of the spacer is adjusted later to ensure the line is the - // correct length. - QHBoxLayout* separatorLayout = new QHBoxLayout(nullptr); - m_outerLayout->addLayout(separatorLayout); - - m_separatorIndent = new QSpacerItem(1, 1); - separatorLayout->addItem(m_separatorIndent); - - m_separatorLine.load(QStringLiteral(":/Gallery/line.svg")); - m_separatorLine.setFixedHeight(3); - - separatorLayout->addWidget(&m_separatorLine); - m_separatorLine.setVisible(false); - m_mainLayout = new QHBoxLayout(); m_mainLayout->setSpacing(0); m_mainLayout->setContentsMargins(0, 1, 0, 1); @@ -136,8 +119,20 @@ namespace AzToolsFramework m_handler = nullptr; m_containerSize = 0; - m_outerLayout->addLayout(m_mainLayout); - setLayout(m_outerLayout); + setLayout(m_mainLayout); + } + + void PropertyRowWidget::paintEvent(QPaintEvent* event) + { + QStylePainter p(this); + + if (IsSectionSeparator()) + { + const QPen linePen(QColor(0x3B3E3F)); + p.setPen(linePen); + int indent = m_treeDepth * m_treeIndentation; + p.drawLine(event->rect().topLeft() + QPoint(indent, 0), event->rect().topRight()); + } } bool PropertyRowWidget::HasChildWidgetAlready() const @@ -321,7 +316,6 @@ namespace AzToolsFramework } m_isSectionSeparator = false; - m_separatorLine.setVisible(false); RefreshAttributesFromNode(true); @@ -970,7 +964,6 @@ namespace AzToolsFramework } else if (attributeName == AZ::Edit::Attributes::RPESectionSeparator) { - m_separatorLine.setVisible(true); m_isSectionSeparator = true; } } @@ -1097,7 +1090,6 @@ namespace AzToolsFramework { m_dropDownArrow->hide(); } - m_separatorIndent->changeSize((m_treeDepth * m_treeIndentation) + m_leafIndentation, 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_indent->changeSize((m_treeDepth * m_treeIndentation) + m_leafIndentation, 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_leftHandSideLayout->invalidate(); m_leftHandSideLayout->update(); @@ -1113,7 +1105,6 @@ namespace AzToolsFramework connect(m_dropDownArrow, &QCheckBox::clicked, this, &PropertyRowWidget::OnClickedExpansionButton); } m_dropDownArrow->show(); - m_separatorIndent->changeSize((m_treeDepth * m_treeIndentation), 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_indent->changeSize((m_treeDepth * m_treeIndentation), 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_leftHandSideLayout->invalidate(); m_leftHandSideLayout->update(); @@ -1124,7 +1115,6 @@ namespace AzToolsFramework void PropertyRowWidget::SetIndentSize(int w) { - m_separatorIndent->changeSize(w, 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_indent->changeSize(w, 1, QSizePolicy::Fixed, QSizePolicy::Fixed); m_leftHandSideLayout->invalidate(); m_leftHandSideLayout->update(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx index af1b68b66f..d08779caa4 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx @@ -129,6 +129,7 @@ namespace AzToolsFramework void SetSelectionEnabled(bool selectionEnabled); void SetSelected(bool selected); bool eventFilter(QObject *watched, QEvent *event) override; + void paintEvent(QPaintEvent*) override; /// Apply tooltip to widget and some of its children. void SetDescription(const QString& text); @@ -164,9 +165,6 @@ namespace AzToolsFramework QHBoxLayout* m_leftHandSideLayout; QHBoxLayout* m_middleLayout; QHBoxLayout* m_rightHandSideLayout; - QVBoxLayout* m_outerLayout; - QSvgWidget m_separatorLine; - QSpacerItem* m_separatorIndent; QPointer m_dropDownArrow; QPointer m_containerClearButton; From b1d8330870f31399d05aa0c1d1b28e5f55512580 Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Tue, 20 Apr 2021 17:20:19 +0100 Subject: [PATCH 007/330] Review fixes. --- .../AzQtComponents/AzQtComponents/Components/resources.qrc | 1 - .../AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx | 1 - 2 files changed, 2 deletions(-) diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc b/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc index e253c6492d..f9e601fb6d 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/resources.qrc @@ -630,7 +630,6 @@ img/UI20/Settings.svg img/UI20/Asset_Folder.svg img/UI20/Asset_File.svg - img/UI20/line.svg img/UI20/AssetEditor/default_document.svg diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx index d08779caa4..2fb695fca5 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx @@ -25,7 +25,6 @@ AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // class '...' needs t #include #include #include -#include #include AZ_POP_DISABLE_WARNING From bfa964a23edd6bce5f6ae0da11506aa0895f0961 Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Wed, 21 Apr 2021 10:19:12 +0100 Subject: [PATCH 008/330] Moved TabWidget grab animation to mouse press to match fancy docking behavior, fixed missed mouse up cursor restore --- .../AzQtComponents/Components/FancyDocking.cpp | 15 +++++---------- .../Components/Widgets/TabWidget.cpp | 6 ++++++ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp index bc8da127a6..2fb06a8367 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp @@ -2003,11 +2003,6 @@ namespace AzQtComponents clearDraggingState(); } - if (QApplication::overrideCursor()) - { - QApplication::restoreOverrideCursor(); - } - return true; } @@ -2385,11 +2380,6 @@ namespace AzQtComponents */ void FancyDocking::dropDockWidget(QDockWidget* dock, QWidget* onto, Qt::DockWidgetArea area) { - if (QApplication::overrideCursor()) - { - QApplication::restoreOverrideCursor(); - } - // If the dock widget we are dropping is currently a tab, we need to retrieve it from // the tab widget, and remove it as a tab. We also need to remove its item from our // cache of widget <-> tab container since we are moving it somewhere else. @@ -3573,6 +3563,11 @@ namespace AzQtComponents */ void FancyDocking::clearDraggingState() { + if (QApplication::overrideCursor()) + { + QApplication::restoreOverrideCursor(); + } + m_ghostWidget->hide(); // Release the mouse and keyboard from our main window since we grab them when we start dragging diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.cpp index 121f35db61..a0006d7979 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.cpp @@ -464,6 +464,11 @@ namespace AzQtComponents { if (mouseEvent->buttons() & Qt::LeftButton) { + if (!QApplication::overrideCursor() || *QApplication::overrideCursor() != m_dragCursor) + { + QApplication::setOverrideCursor(m_dragCursor); + } + m_lastMousePress = mouseEvent->pos(); } @@ -478,6 +483,7 @@ namespace AzQtComponents // selected tab is moved around. The close button is not explicitly rendered for the // moved tab during this operation. We need to make sure not to set it visible again // while the tab is moving. This flag makes sure it happens. + m_movingTab = true; } From 099f43237debb61e3ac047f27451760599445eac Mon Sep 17 00:00:00 2001 From: greerdv Date: Fri, 23 Apr 2021 10:13:24 +0100 Subject: [PATCH 009/330] removing long deprecated transform scale functions --- .../AzCore/AzCore/Component/TransformBus.h | 40 -------- .../Components/TransformComponent.cpp | 93 ------------------- .../Components/TransformComponent.h | 10 -- .../ToolsComponents/TransformComponent.cpp | 70 -------------- .../ToolsComponents/TransformComponent.h | 10 -- .../Tests/ShapeColliderComponentTests.cpp | 2 +- 6 files changed, 1 insertion(+), 224 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/Component/TransformBus.h b/Code/Framework/AzCore/AzCore/Component/TransformBus.h index 2003b949e2..e34fa6a97b 100644 --- a/Code/Framework/AzCore/AzCore/Component/TransformBus.h +++ b/Code/Framework/AzCore/AzCore/Component/TransformBus.h @@ -287,46 +287,6 @@ namespace AZ //! Scale modifiers //! @{ - //! @deprecated Use SetLocalScale() - //! Scales the entity along the world's axes. The origin of the axes is the entity's position in the world. - //! @param scale A three-dimensional vector that represents the multipliers with which to scale the entity in world space. - virtual void SetScale([[maybe_unused]] const AZ::Vector3& scale) {} - - //! @deprecated Use SetLocalScaleX() - //! Scales the entity along the world's X axis. The origin of the axis is the entity's position in the world. - //! @param scaleX The multiplier by which to scale the entity along the X axis in world space. - virtual void SetScaleX([[maybe_unused]] float scaleX) {} - - //! @deprecated Use SetLocalScaleY() - //! Scales the entity along the world's Y axis. The origin of the axis is the entity's position in the world. - //! @param scaleY The multiplier by which to scale the entity along the Y axis in world space. - virtual void SetScaleY([[maybe_unused]] float scaleY) {} - - //! @deprecated Use SetLocalScaleZ() - //! Scales the entity along the world's Z axis. The origin of the axis is the entity's position in the world. - //! @param scaleZ The multiplier by which to scale the entity along the Z axis in world space. - virtual void SetScaleZ([[maybe_unused]] float scaleZ) {} - - //! @deprecated Use GetLocalScale() - //! Gets the scale of the entity in world space. - //! @return A three-dimensional vector that represents the scale of the entity in world space. - virtual AZ::Vector3 GetScale() { return AZ::Vector3(FLT_MAX); } - - //! @deprecated Use GetLocalScale() - //! Gets the amount by which an entity is scaled along the world's X axis. - //! @return The amount by which an entity is scaled along the X axis in world space. - virtual float GetScaleX() { return FLT_MAX; } - - //! @deprecated Use GetLocalScale() - //! Gets the amount by which an entity is scaled along the world's Y axis. - //! @return The amount by which an entity is scaled along the Y axis in world space. - virtual float GetScaleY() { return FLT_MAX; } - - //! @deprecated Use GetLocalScale() - //! Gets the amount by which an entity is scaled along the world's Z axis. - //! @return The amount by which an entity is scaled along the Z axis in world space. - virtual float GetScaleZ() { return FLT_MAX; } - //! Set local scale of the transform. //! @param scale The new scale to set along three local axes. virtual void SetLocalScale([[maybe_unused]] const AZ::Vector3& scale) {} diff --git a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp index 910d6749af..4914279e7e 100644 --- a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp @@ -510,75 +510,6 @@ namespace AzFramework return m_localTM.GetRotation(); } - void TransformComponent::SetScale(const AZ::Vector3& scale) - { - AZ_Warning("TransformComponent", false, "SetScale is deprecated, please use SetLocalScale"); - - if (!m_worldTM.GetScale().IsClose(scale)) - { - AZ::Transform newWorldTransform = m_worldTM; - newWorldTransform.SetScale(scale); - SetWorldTM(newWorldTransform); - } - } - - void TransformComponent::SetScaleX(float scaleX) - { - AZ_Warning("TransformComponent", false, "SetScaleX is deprecated, please use SetLocalScaleX"); - - AZ::Vector3 newScale = m_worldTM.GetScale(); - newScale.SetX(scaleX); - AZ::Transform newWorldTransform = m_worldTM; - newWorldTransform.SetScale(newScale); - SetWorldTM(newWorldTransform); - } - - void TransformComponent::SetScaleY(float scaleY) - { - AZ_Warning("TransformComponent", false, "SetScaleY is deprecated, please use SetLocalScaleY"); - - AZ::Vector3 newScale = m_worldTM.GetScale(); - newScale.SetY(scaleY); - AZ::Transform newWorldTransform = m_worldTM; - newWorldTransform.SetScale(newScale); - SetWorldTM(newWorldTransform); - } - - void TransformComponent::SetScaleZ(float scaleZ) - { - AZ_Warning("TransformComponent", false, "SetScaleZ is deprecated, please use SetLocalScaleZ"); - - AZ::Vector3 newScale = m_worldTM.GetScale(); - newScale.SetZ(scaleZ); - AZ::Transform newWorldTransform = m_worldTM; - newWorldTransform.SetScale(newScale); - SetWorldTM(newWorldTransform); - } - - AZ::Vector3 TransformComponent::GetScale() - { - AZ_Warning("TransformComponent", false, "GetScale is deprecated, please use GetLocalScale"); - return m_worldTM.GetScale(); - } - - float TransformComponent::GetScaleX() - { - AZ_Warning("TransformComponent", false, "GetScaleX is deprecated, please use GetLocalScale"); - return m_worldTM.GetScale().GetX(); - } - - float TransformComponent::GetScaleY() - { - AZ_Warning("TransformComponent", false, "GetScaleY is deprecated, please use GetLocalScale"); - return m_worldTM.GetScale().GetY(); - } - - float TransformComponent::GetScaleZ() - { - AZ_Warning("TransformComponent", false, "GetScaleZ is deprecated, please use GetLocalScale"); - return m_worldTM.GetScale().GetZ(); - } - void TransformComponent::SetLocalScale(const AZ::Vector3& scale) { AZ::Transform newLocalTM = m_localTM; @@ -972,30 +903,6 @@ namespace AzFramework ->Event("GetLocalRotationQuaternion", &AZ::TransformBus::Events::GetLocalRotationQuaternion) ->Attribute("Rotation", AZ::Edit::Attributes::PropertyRotation) ->VirtualProperty("Rotation", "GetLocalRotationQuaternion", "SetLocalRotationQuaternion") - ->Event("SetScale", &AZ::TransformBus::Events::SetScale) - ->Attribute(AZ::Script::Attributes::Deprecated, true) - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) - ->Event("SetScaleX", &AZ::TransformBus::Events::SetScaleX) - ->Attribute(AZ::Script::Attributes::Deprecated, true) - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) - ->Event("SetScaleY", &AZ::TransformBus::Events::SetScaleY) - ->Attribute(AZ::Script::Attributes::Deprecated, true) - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) - ->Event("SetScaleZ", &AZ::TransformBus::Events::SetScaleZ) - ->Attribute(AZ::Script::Attributes::Deprecated, true) - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) - ->Event("GetScale", &AZ::TransformBus::Events::GetScale) - ->Attribute(AZ::Script::Attributes::Deprecated, true) - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) - ->Event("GetScaleX", &AZ::TransformBus::Events::GetScaleX) - ->Attribute(AZ::Script::Attributes::Deprecated, true) - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) - ->Event("GetScaleY", &AZ::TransformBus::Events::GetScaleY) - ->Attribute(AZ::Script::Attributes::Deprecated, true) - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) - ->Event("GetScaleZ", &AZ::TransformBus::Events::GetScaleZ) - ->Attribute(AZ::Script::Attributes::Deprecated, true) - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) ->Event("SetLocalScale", &AZ::TransformBus::Events::SetLocalScale) ->Event("SetLocalScaleX", &AZ::TransformBus::Events::SetLocalScaleX) ->Event("SetLocalScaleY", &AZ::TransformBus::Events::SetLocalScaleY) diff --git a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h index abea0bd4dd..f78a621096 100644 --- a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h +++ b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h @@ -145,16 +145,6 @@ namespace AzFramework AZ::Quaternion GetLocalRotationQuaternion() override; // Scale Modifiers - void SetScale(const AZ::Vector3& scale) override; - void SetScaleX(float scaleX) override; - void SetScaleY(float scaleY) override; - void SetScaleZ(float scaleZ) override; - - AZ::Vector3 GetScale() override; - float GetScaleX() override; - float GetScaleY() override; - float GetScaleZ() override; - void SetLocalScale(const AZ::Vector3& scale) override; void SetLocalScaleX(float scaleX) override; void SetLocalScaleY(float scaleY) override; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.cpp index 7ce11e5957..147667ae75 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.cpp @@ -643,76 +643,6 @@ namespace AzToolsFramework return result; } - void TransformComponent::SetScale(const AZ::Vector3& newScale) - { - AZ_Warning("AzToolsFramework::TransformComponent", false, "SetScale is deprecated, please use SetLocalScale"); - - AZ::Transform newWorldTransform = GetWorldTM(); - AZ::Vector3 prevScale = newWorldTransform.ExtractScale(); - if (!prevScale.IsClose(newScale)) - { - newWorldTransform.MultiplyByScale(newScale); - SetWorldTM(newWorldTransform); - } - } - - void TransformComponent::SetScaleX(float newScale) - { - AZ_Warning("AzToolsFramework::TransformComponent", false, "SetScaleX is deprecated, please use SetLocalScaleX"); - - AZ::Transform newWorldTransform = GetWorldTM(); - AZ::Vector3 scale = newWorldTransform.ExtractScale(); - scale.SetX(newScale); - newWorldTransform.MultiplyByScale(scale); - SetWorldTM(newWorldTransform); - } - - void TransformComponent::SetScaleY(float newScale) - { - AZ_Warning("AzToolsFramework::TransformComponent", false, "SetScaleY is deprecated, please use SetLocalScaleY"); - - AZ::Transform newWorldTransform = GetWorldTM(); - AZ::Vector3 scale = newWorldTransform.ExtractScale(); - scale.SetY(newScale); - newWorldTransform.MultiplyByScale(scale); - SetWorldTM(newWorldTransform); - } - - void TransformComponent::SetScaleZ(float newScale) - { - AZ_Warning("AzToolsFramework::TransformComponent", false, "SetScaleZ is deprecated, please use SetLocalScaleZ"); - - AZ::Transform newWorldTransform = GetWorldTM(); - AZ::Vector3 scale = newWorldTransform.ExtractScale(); - scale.SetZ(newScale); - newWorldTransform.MultiplyByScale(scale); - SetWorldTM(newWorldTransform); - } - - AZ::Vector3 TransformComponent::GetScale() - { - AZ_Warning("AzToolsFramework::TransformComponent", false, "GetScale is deprecated, please use GetLocalScale"); - return GetWorldTM().GetScale(); - } - - float TransformComponent::GetScaleX() - { - AZ_Warning("AzToolsFramework::TransformComponent", false, "GetScaleX is deprecated, please use GetLocalScale"); - return GetWorldTM().GetScale().GetX(); - } - - float TransformComponent::GetScaleY() - { - AZ_Warning("AzToolsFramework::TransformComponent", false, "GetScaleY is deprecated, please use GetLocalScale"); - return GetWorldTM().GetScale().GetY(); - } - - float TransformComponent::GetScaleZ() - { - AZ_Warning("AzToolsFramework::TransformComponent", false, "GetScaleZ is deprecated, please use GetLocalScale"); - return GetWorldTM().GetScale().GetZ(); - } - void TransformComponent::SetLocalScale(const AZ::Vector3& scale) { m_editorTransform.m_scale = scale; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.h index 8327c5f128..0898e14d6b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.h @@ -129,16 +129,6 @@ namespace AzToolsFramework AZ::Quaternion GetLocalRotationQuaternion() override; // Scale Modifiers - void SetScale(const AZ::Vector3& newScale) override; - void SetScaleX(float newScale) override; - void SetScaleY(float newScale) override; - void SetScaleZ(float newScale) override; - - AZ::Vector3 GetScale() override; - float GetScaleX() override; - float GetScaleY() override; - float GetScaleZ() override; - void SetLocalScale(const AZ::Vector3& scale) override; void SetLocalScaleX(float scaleX) override; void SetLocalScaleY(float scaleY) override; diff --git a/Gems/PhysX/Code/Tests/ShapeColliderComponentTests.cpp b/Gems/PhysX/Code/Tests/ShapeColliderComponentTests.cpp index 422385a777..8af9defff8 100644 --- a/Gems/PhysX/Code/Tests/ShapeColliderComponentTests.cpp +++ b/Gems/PhysX/Code/Tests/ShapeColliderComponentTests.cpp @@ -241,7 +241,7 @@ namespace PhysXEditorTests SetPolygonPrismHeight(entityId, 2.0f); // update the transform scale and non-uniform scale - AZ::TransformBus::Event(entityId, &AZ::TransformBus::Events::SetScale, AZ::Vector3(2.0f)); + AZ::TransformBus::Event(entityId, &AZ::TransformBus::Events::SetLocalScale, AZ::Vector3(2.0f)); AZ::NonUniformScaleRequestBus::Event(entityId, &AZ::NonUniformScaleRequests::SetScale, AZ::Vector3(0.5f, 1.5f, 2.0f)); EntityPtr gameEntity = CreateActiveGameEntityFromEditorEntity(editorEntity.get()); From 40655eba030b0ed6c02cd795993beda647abef53 Mon Sep 17 00:00:00 2001 From: greerdv Date: Fri, 23 Apr 2021 11:30:17 +0100 Subject: [PATCH 010/330] removing element-wise scale setters from transform bus --- .../AzCore/AzCore/Component/TransformBus.h | 12 ----- .../Components/TransformComponent.cpp | 30 ------------- .../Components/TransformComponent.h | 3 -- .../ToolsComponents/TransformComponent.cpp | 18 -------- .../ToolsComponents/TransformComponent.h | 3 -- Code/Framework/Tests/TransformComponent.cpp | 45 ------------------- Gems/Blast/Code/Tests/Mocks/BlastMocks.h | 11 ----- 7 files changed, 122 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/Component/TransformBus.h b/Code/Framework/AzCore/AzCore/Component/TransformBus.h index e34fa6a97b..64af853e99 100644 --- a/Code/Framework/AzCore/AzCore/Component/TransformBus.h +++ b/Code/Framework/AzCore/AzCore/Component/TransformBus.h @@ -291,18 +291,6 @@ namespace AZ //! @param scale The new scale to set along three local axes. virtual void SetLocalScale([[maybe_unused]] const AZ::Vector3& scale) {} - //! Set local scale of the transform on x-axis. - //! @param scaleX The new x-axis scale to set. - virtual void SetLocalScaleX([[maybe_unused]] float scaleX) {} - - //! Set local scale of the transform on y-axis. - //! @param scaleY The new y-axis scale to set. - virtual void SetLocalScaleY([[maybe_unused]] float scaleY) {} - - //! Set local scale of the transform on z-axis. - //! @param scaleZ The new z-axis scale to set. - virtual void SetLocalScaleZ([[maybe_unused]] float scaleZ) {} - //! Get the scale value on each axis in local space //! @return The scale value of type Vector3 along each axis in local space. virtual AZ::Vector3 GetLocalScale() { return AZ::Vector3(FLT_MAX); } diff --git a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp index 4914279e7e..4b805fd3e1 100644 --- a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp @@ -517,33 +517,6 @@ namespace AzFramework SetLocalTM(newLocalTM); } - void TransformComponent::SetLocalScaleX(float scaleX) - { - AZ::Transform newLocalTM = m_localTM; - AZ::Vector3 newScale = newLocalTM.GetScale(); - newScale.SetX(scaleX); - newLocalTM.SetScale(newScale); - SetLocalTM(newLocalTM); - } - - void TransformComponent::SetLocalScaleY(float scaleY) - { - AZ::Transform newLocalTM = m_localTM; - AZ::Vector3 newScale = newLocalTM.GetScale(); - newScale.SetY(scaleY); - newLocalTM.SetScale(newScale); - SetLocalTM(newLocalTM); - } - - void TransformComponent::SetLocalScaleZ(float scaleZ) - { - AZ::Transform newLocalTM = m_localTM; - AZ::Vector3 newScale = newLocalTM.GetScale(); - newScale.SetZ(scaleZ); - newLocalTM.SetScale(newScale); - SetLocalTM(newLocalTM); - } - AZ::Vector3 TransformComponent::GetLocalScale() { return m_localTM.GetScale(); @@ -904,9 +877,6 @@ namespace AzFramework ->Attribute("Rotation", AZ::Edit::Attributes::PropertyRotation) ->VirtualProperty("Rotation", "GetLocalRotationQuaternion", "SetLocalRotationQuaternion") ->Event("SetLocalScale", &AZ::TransformBus::Events::SetLocalScale) - ->Event("SetLocalScaleX", &AZ::TransformBus::Events::SetLocalScaleX) - ->Event("SetLocalScaleY", &AZ::TransformBus::Events::SetLocalScaleY) - ->Event("SetLocalScaleZ", &AZ::TransformBus::Events::SetLocalScaleZ) ->Event("GetLocalScale", &AZ::TransformBus::Events::GetLocalScale) ->Attribute("Scale", AZ::Edit::Attributes::PropertyScale) ->VirtualProperty("Scale", "GetLocalScale", "SetLocalScale") diff --git a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h index f78a621096..f393e22064 100644 --- a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h +++ b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h @@ -146,9 +146,6 @@ namespace AzFramework // Scale Modifiers void SetLocalScale(const AZ::Vector3& scale) override; - void SetLocalScaleX(float scaleX) override; - void SetLocalScaleY(float scaleY) override; - void SetLocalScaleZ(float scaleZ) override; AZ::Vector3 GetLocalScale() override; AZ::Vector3 GetWorldScale() override; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.cpp index 147667ae75..671a56b4d4 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.cpp @@ -649,24 +649,6 @@ namespace AzToolsFramework TransformChanged(); } - void TransformComponent::SetLocalScaleX(float scaleX) - { - m_editorTransform.m_scale.SetX(scaleX); - TransformChanged(); - } - - void TransformComponent::SetLocalScaleY(float scaleY) - { - m_editorTransform.m_scale.SetY(scaleY); - TransformChanged(); - } - - void TransformComponent::SetLocalScaleZ(float scaleZ) - { - m_editorTransform.m_scale.SetZ(scaleZ); - TransformChanged(); - } - AZ::Vector3 TransformComponent::GetLocalScale() { return m_editorTransform.m_scale; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.h index 0898e14d6b..bbdf770dab 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.h @@ -130,9 +130,6 @@ namespace AzToolsFramework // Scale Modifiers void SetLocalScale(const AZ::Vector3& scale) override; - void SetLocalScaleX(float scaleX) override; - void SetLocalScaleY(float scaleY) override; - void SetLocalScaleZ(float scaleZ) override; AZ::Vector3 GetLocalScale() override; AZ::Vector3 GetWorldScale() override; diff --git a/Code/Framework/Tests/TransformComponent.cpp b/Code/Framework/Tests/TransformComponent.cpp index de172dc051..44008686cd 100644 --- a/Code/Framework/Tests/TransformComponent.cpp +++ b/Code/Framework/Tests/TransformComponent.cpp @@ -584,51 +584,6 @@ namespace UnitTest EXPECT_TRUE(scales.IsClose(expectedScales)); } - TEST_F(TransformComponentTransformMatrixSetGet, SetLocalScaleX_SimpleValues_Set) - { - float sx = 64.336f; - Transform tm; - TransformBus::EventResult(tm, m_childId, &TransformBus::Events::GetLocalTM); - Vector3 expectedScales = tm.GetScale(); - expectedScales.SetX(sx); - - TransformBus::Event(m_childId, &TransformBus::Events::SetLocalScaleX, sx); - - TransformBus::EventResult(tm, m_childId, &TransformBus::Events::GetLocalTM); - Vector3 scales = tm.GetScale(); - EXPECT_TRUE(scales.IsClose(expectedScales)); - } - - TEST_F(TransformComponentTransformMatrixSetGet, SetLocalScaleY_SimpleValues_Set) - { - float sy = 23.754f; - Transform tm; - TransformBus::EventResult(tm, m_childId, &TransformBus::Events::GetLocalTM); - Vector3 expectedScales = tm.GetScale(); - expectedScales.SetY(sy); - - TransformBus::Event(m_childId, &TransformBus::Events::SetLocalScaleY, sy); - - TransformBus::EventResult(tm, m_childId, &TransformBus::Events::GetLocalTM); - Vector3 scales = tm.GetScale(); - EXPECT_TRUE(scales.IsClose(expectedScales)); - } - - TEST_F(TransformComponentTransformMatrixSetGet, SetLocalScaleZ_SimpleValues_Set) - { - float sz = 65.140f; - Transform tm; - TransformBus::EventResult(tm, m_childId, &TransformBus::Events::GetLocalTM); - Vector3 expectedScales = tm.GetScale(); - expectedScales.SetZ(sz); - - TransformBus::Event(m_childId, &TransformBus::Events::SetLocalScaleZ, sz); - - TransformBus::EventResult(tm, m_childId, &TransformBus::Events::GetLocalTM); - Vector3 scales = tm.GetScale(); - EXPECT_TRUE(scales.IsClose(expectedScales)); - } - TEST_F(TransformComponentTransformMatrixSetGet, GetLocalScale_SimpleValues_Return) { float sx = 43.463f; diff --git a/Gems/Blast/Code/Tests/Mocks/BlastMocks.h b/Gems/Blast/Code/Tests/Mocks/BlastMocks.h index 95a16c311f..88c22cf67f 100644 --- a/Gems/Blast/Code/Tests/Mocks/BlastMocks.h +++ b/Gems/Blast/Code/Tests/Mocks/BlastMocks.h @@ -666,18 +666,7 @@ namespace Blast MOCK_METHOD1(RotateAroundLocalZ, void(float)); MOCK_METHOD0(GetLocalRotation, AZ::Vector3()); MOCK_METHOD0(GetLocalRotationQuaternion, AZ::Quaternion()); - MOCK_METHOD1(SetScale, void(const AZ::Vector3&)); - MOCK_METHOD1(SetScaleX, void(float)); - MOCK_METHOD1(SetScaleY, void(float)); - MOCK_METHOD1(SetScaleZ, void(float)); - MOCK_METHOD0(GetScale, AZ::Vector3()); - MOCK_METHOD0(GetScaleX, float()); - MOCK_METHOD0(GetScaleY, float()); - MOCK_METHOD0(GetScaleZ, float()); MOCK_METHOD1(SetLocalScale, void(const AZ::Vector3&)); - MOCK_METHOD1(SetLocalScaleX, void(float)); - MOCK_METHOD1(SetLocalScaleY, void(float)); - MOCK_METHOD1(SetLocalScaleZ, void(float)); MOCK_METHOD0(GetLocalScale, AZ::Vector3()); MOCK_METHOD0(GetWorldScale, AZ::Vector3()); MOCK_METHOD0(GetParentId, AZ::EntityId()); From b113f09a713833b1e98a4e7179568fce27a6fe06 Mon Sep 17 00:00:00 2001 From: greerdv Date: Tue, 27 Apr 2021 18:12:46 +0100 Subject: [PATCH 011/330] first pass of changing transform to use float for scale internally rather than Vector3 --- Code/CryEngine/CryCommon/IMovieSystem.h | 4 +- .../AzCore/AzCore/Component/TransformBus.h | 22 ++-- Code/Framework/AzCore/AzCore/Math/Spline.h | 4 +- .../AzCore/AzCore/Math/Transform.cpp | 14 +-- Code/Framework/AzCore/AzCore/Math/Transform.h | 23 ++-- .../AzCore/AzCore/Math/Transform.inl | 78 +++++++++---- .../AzCore/Math/TransformSerializer.cpp | 10 +- .../AzCore/Tests/Math/MathTestData.h | 4 +- Code/Framework/AzCore/Tests/Math/ObbTests.cpp | 8 +- .../Tests/Math/TransformPerformanceTests.cpp | 6 +- .../AzCore/Tests/Math/TransformTests.cpp | 36 ++---- Code/Framework/AzCore/Tests/ScriptMath.cpp | 24 ++-- .../Json/TransformSerializerTests.cpp | 6 +- .../Components/TransformComponent.cpp | 38 +++--- .../Components/TransformComponent.h | 5 +- .../Manipulators/ScaleManipulators.cpp | 4 +- .../AzToolsFramework/Slice/SliceUtilities.cpp | 2 - .../ToolsComponents/TransformComponent.cpp | 37 +++--- .../ToolsComponents/TransformComponent.h | 6 +- .../ToolsComponents/TransformComponentBus.h | 5 +- .../EditorTransformComponentSelection.cpp | 37 +++--- .../EditorTransformComponentSelection.h | 8 +- ...torTransformComponentSelectionRequestBus.h | 4 +- .../GridMate/Serialize/CompressionMarshal.cpp | 15 ++- Code/Framework/Tests/TransformComponent.cpp | 110 +++++++----------- .../Sandbox/Editor/Objects/SelectionGroup.cpp | 22 ---- Code/Sandbox/Editor/Objects/SelectionGroup.h | 1 - .../Editor/TrackView/TrackViewAnimNode.cpp | 18 +-- .../Editor/TrackView/TrackViewAnimNode.h | 4 +- .../Editor/TrackView/TrackViewSequence.cpp | 6 +- .../Containers/Utilities/SceneUtilities.cpp | 2 +- .../RowWidgets/TransformRowHandler.cpp | 12 +- .../SceneUI/RowWidgets/TransformRowHandler.h | 2 +- .../SceneUI/RowWidgets/TransformRowWidget.cpp | 28 ++--- .../SceneUI/RowWidgets/TransformRowWidget.h | 16 ++- .../RowWidgets/TransformRowWidgetTests.cpp | 20 ++-- .../Viewport/MaterialViewportRenderer.cpp | 4 +- .../CoreLights/PolygonLightDelegate.cpp | 1 - Gems/Blast/Code/Tests/BlastFamilyTest.cpp | 2 +- Gems/Blast/Code/Tests/Mocks/BlastMocks.h | 3 + .../EMotionFX/Rendering/Common/RenderUtil.cpp | 2 +- .../Code/Include/GradientSignal/Util.h | 9 +- .../Components/GradientTransformComponent.cpp | 13 ++- .../Components/GradientTransformComponent.h | 5 +- .../Source/Animation/AttachmentComponent.cpp | 4 +- .../Animation/EditorAttachmentComponent.cpp | 2 +- .../Scripting/EditorLookAtComponent.cpp | 14 ++- .../Code/Source/Shape/BoxShape.cpp | 2 +- .../Code/Source/Shape/PolygonPrismShape.cpp | 22 ++-- .../Code/Source/Shape/ShapeDisplay.h | 9 +- .../Code/Source/Shape/TubeShape.cpp | 32 ++--- Gems/LmbrCentral/Code/Tests/BoxShapeTest.cpp | 28 ++--- .../Code/Tests/CapsuleShapeTest.cpp | 18 +-- .../Code/Tests/CylinderShapeTest.cpp | 14 +-- Gems/LmbrCentral/Code/Tests/DiskShapeTest.cpp | 2 +- .../Code/Tests/PolygonPrismShapeTest.cpp | 12 +- Gems/LmbrCentral/Code/Tests/QuadShapeTest.cpp | 12 +- .../Code/Tests/SphereShapeTest.cpp | 14 +-- Gems/LmbrCentral/Code/Tests/TubeShapeTest.cpp | 2 +- .../Source/Cinematics/AnimAZEntityNode.cpp | 6 +- .../Code/Source/Cinematics/AnimAZEntityNode.h | 4 +- .../Source/Cinematics/AnimComponentNode.cpp | 17 ++- .../Source/Cinematics/AnimComponentNode.h | 6 +- .../Maestro/Code/Source/Cinematics/AnimNode.h | 4 +- Gems/PhysX/Code/Source/RigidBodyComponent.cpp | 17 +-- Gems/PhysX/Code/Source/RigidBodyComponent.h | 1 - Gems/PhysX/Code/Source/Utils.cpp | 6 +- .../PhysX/Code/Tests/ColliderScalingTests.cpp | 12 +- Gems/PhysX/Code/Tests/DebugDrawTests.cpp | 14 +-- .../Code/Tests/RigidBodyComponentTests.cpp | 8 +- .../Tests/ShapeColliderComponentTests.cpp | 12 +- .../Code/Include/ScriptCanvas/Core/Datum.cpp | 6 +- .../ScriptCanvas/Libraries/Entity/Rotate.cpp | 15 +-- .../Libraries/Entity/RotateMethod.cpp | 18 +-- .../Libraries/Math/TransformNodes.h | 4 +- .../RotateCameraLookAt.cpp | 16 +-- 76 files changed, 487 insertions(+), 546 deletions(-) diff --git a/Code/CryEngine/CryCommon/IMovieSystem.h b/Code/CryEngine/CryCommon/IMovieSystem.h index c22394e1b3..ca723eb6bd 100644 --- a/Code/CryEngine/CryCommon/IMovieSystem.h +++ b/Code/CryEngine/CryCommon/IMovieSystem.h @@ -696,7 +696,7 @@ public: //! Rotate entity node. virtual void SetRotate(float time, const Quat& quat) = 0; //! Scale entity node. - virtual void SetScale(float time, const Vec3& scale) = 0; + virtual void SetScale(float time, const float scale) = 0; //! Compute and return the offset which brings the current position to the given position virtual Vec3 GetOffsetPosition(const Vec3& position) { return position - GetPos(); } @@ -708,7 +708,7 @@ public: //! Get entity rotation at specified time. virtual Quat GetRotate(float time) = 0; //! Get current entity scale. - virtual Vec3 GetScale() = 0; + virtual float GetScale() = 0; // General Set param. // Set float/vec3/vec4 parameter at given time. diff --git a/Code/Framework/AzCore/AzCore/Component/TransformBus.h b/Code/Framework/AzCore/AzCore/Component/TransformBus.h index 64af853e99..95c8f6e719 100644 --- a/Code/Framework/AzCore/AzCore/Component/TransformBus.h +++ b/Code/Framework/AzCore/AzCore/Component/TransformBus.h @@ -288,18 +288,26 @@ namespace AZ //! Scale modifiers //! @{ //! Set local scale of the transform. - //! @param scale The new scale to set along three local axes. + //! @param scale The new scale to set. virtual void SetLocalScale([[maybe_unused]] const AZ::Vector3& scale) {} - //! Get the scale value on each axis in local space - //! @return The scale value of type Vector3 along each axis in local space. + //! Get the scale value in local space. + //! @return The scale value in local space. virtual AZ::Vector3 GetLocalScale() { return AZ::Vector3(FLT_MAX); } - //! Get the scale value on each axis in world space. - //! Note the transform will be skewed when it is rotated and has a parent transform scaled, in which - //! case the returned world-scale from this function will be inaccurate. - //! @return The scale value of type Vector3 along each axis in world space. + //! Get the scale value in world space. + //! @return The scale value in world space. virtual AZ::Vector3 GetWorldScale() { return AZ::Vector3(FLT_MAX); } + + + virtual void SetLocalUniformScale([[maybe_unused]] float scale) {} + + virtual float GetLocalUniformScale() { return FLT_MAX; } + + virtual float GetWorldUniformScale() { return FLT_MAX; } + + + //! @} //! Transform hierarchy diff --git a/Code/Framework/AzCore/AzCore/Math/Spline.h b/Code/Framework/AzCore/AzCore/Math/Spline.h index 912c10f46f..1adfc9b2fa 100644 --- a/Code/Framework/AzCore/AzCore/Math/Spline.h +++ b/Code/Framework/AzCore/AzCore/Math/Spline.h @@ -441,10 +441,10 @@ namespace AZ const Transform& worldFromLocal, const Vector3& src, const Vector3& dir, const Spline& spline) { Transform worldFromLocalNormalized = worldFromLocal; - const Vector3 scale = worldFromLocalNormalized.ExtractScale(); + const float scale = worldFromLocalNormalized.ExtractUniformScale(); const Transform localFromWorldNormalized = worldFromLocalNormalized.GetInverse(); - const Vector3 localRayOrigin = localFromWorldNormalized.TransformPoint(src) * scale.GetReciprocal(); + const Vector3 localRayOrigin = localFromWorldNormalized.TransformPoint(src) / scale; const Vector3 localRayDirection = localFromWorldNormalized.TransformVector(dir); return spline.GetNearestAddressRay(localRayOrigin, localRayDirection); } diff --git a/Code/Framework/AzCore/AzCore/Math/Transform.cpp b/Code/Framework/AzCore/AzCore/Math/Transform.cpp index bb3f764492..ad57daa5e4 100644 --- a/Code/Framework/AzCore/AzCore/Math/Transform.cpp +++ b/Code/Framework/AzCore/AzCore/Math/Transform.cpp @@ -130,7 +130,7 @@ namespace AZ const Transform* transform = reinterpret_cast(classPtr); float data[NumFloats]; transform->GetRotation().StoreToFloat4(data); - transform->GetScale().StoreToFloat3(&data[4]); + Vector3(transform->GetScale()).StoreToFloat3(&data[4]); transform->GetTranslation().StoreToFloat3(&data[7]); for (int i = 0; i < NumFloats; i++) @@ -220,7 +220,7 @@ namespace AZ Vector3 translation = Vector3::CreateFromFloat3(&data[7]); *reinterpret_cast(classPtr) = - Transform::CreateFromQuaternionAndTranslation(rotation, translation) * Transform::CreateScale(scale); + Transform::CreateFromQuaternionAndTranslation(rotation, translation) * Transform::CreateUniformScale(scale.GetMaxElement()); return true; } @@ -250,7 +250,7 @@ namespace AZ Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)-> Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::Value)-> Attribute(Script::Attributes::GenericConstructorOverride, &Internal::TransformDefaultConstructor)-> - Constructor()-> + Constructor()-> Method("GetBasis", &Transform::GetBasis)-> Method("GetBasisX", &Transform::GetBasisX)-> Method("GetBasisY", &Transform::GetBasisY)-> @@ -284,7 +284,7 @@ namespace AZ Method("GetRotation", &Transform::GetRotation)-> Method("SetRotation", &Transform::SetRotation)-> Method("GetScale", &Transform::GetScale)-> - Method("SetScale", &Transform::SetScale)-> + Method("SetScale", static_cast(&Transform::SetScale))-> Method("ExtractScale", &Transform::ExtractScale)-> Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)-> Method("MultiplyByScale", &Transform::MultiplyByScale)-> @@ -315,7 +315,7 @@ namespace AZ { Transform result; Matrix3x3 tmp = value; - result.m_scale = tmp.ExtractScale(); + result.m_scale = tmp.ExtractScale().GetMaxElement(); result.m_rotation = Quaternion::CreateFromMatrix3x3(tmp); result.m_translation = Vector3::CreateZero(); return result; @@ -325,7 +325,7 @@ namespace AZ { Transform result; Matrix3x3 tmp = value; - result.m_scale = tmp.ExtractScale(); + result.m_scale = tmp.ExtractScale().GetMaxElement(); result.m_rotation = Quaternion::CreateFromMatrix3x3(tmp); result.m_translation = p; return result; @@ -335,7 +335,7 @@ namespace AZ { Transform result; Matrix3x4 tmp = value; - result.m_scale = tmp.ExtractScale(); + result.m_scale = tmp.ExtractScale().GetMaxElement(); result.m_rotation = Quaternion::CreateFromMatrix3x4(tmp); result.m_translation = value.GetTranslation(); return result; diff --git a/Code/Framework/AzCore/AzCore/Math/Transform.h b/Code/Framework/AzCore/AzCore/Math/Transform.h index eb1a12a912..ba08722338 100644 --- a/Code/Framework/AzCore/AzCore/Math/Transform.h +++ b/Code/Framework/AzCore/AzCore/Math/Transform.h @@ -63,7 +63,7 @@ namespace AZ Transform() = default; //! Construct a transform from components. - Transform(const Vector3& translation, const Quaternion& rotation, const Vector3& scale); + Transform(const Vector3& translation, const Quaternion& rotation, const float scale); //! Creates an identity transform. static Transform CreateIdentity(); @@ -89,8 +89,11 @@ namespace AZ static Transform CreateFromMatrix3x4(const Matrix3x4& value); - //! Sets the matrix to be a scale matrix, translation is set to zero. - static Transform CreateScale(const Vector3& scale); + //! Sets the transform to apply (uniform) scale only, no rotation or translation. + static Transform CreateScale(const AZ::Vector3& scale); + + //! Sets the transform to apply (uniform) scale only, no rotation or translation. + static Transform CreateUniformScale(const float scale); //! Sets the matrix to be a translation matrix, rotation part is set to identity. static Transform CreateTranslation(const Vector3& translation); @@ -119,13 +122,19 @@ namespace AZ const Quaternion& GetRotation() const; void SetRotation(const Quaternion& rotation); - const Vector3& GetScale() const; + Vector3 GetScale() const; + float GetUniformScale() const; void SetScale(const Vector3& v); + void SetUniformScale(const float scale); - //! Sets the transforms scale to a unit value and returns the previous scale value. + //! Sets the transform's scale to a unit value and returns the previous scale value. Vector3 ExtractScale(); - void MultiplyByScale(const Vector3& scale); + //! Sets the transform's scale to a unit value and returns the previous scale value. + float ExtractUniformScale(); + + void MultiplyByScale(const AZ::Vector3& scale); + void MultiplyByUniformScale(float scale); Transform operator*(const Transform& rhs) const; Transform& operator*=(const Transform& rhs); @@ -159,7 +168,7 @@ namespace AZ private: Quaternion m_rotation; - Vector3 m_scale; + float m_scale; Vector3 m_translation; }; diff --git a/Code/Framework/AzCore/AzCore/Math/Transform.inl b/Code/Framework/AzCore/AzCore/Math/Transform.inl index 63425e6e41..c92208da54 100644 --- a/Code/Framework/AzCore/AzCore/Math/Transform.inl +++ b/Code/Framework/AzCore/AzCore/Math/Transform.inl @@ -12,7 +12,7 @@ namespace AZ { - AZ_MATH_INLINE Transform::Transform(const Vector3& translation, const Quaternion& rotation, const Vector3& scale) + AZ_MATH_INLINE Transform::Transform(const Vector3& translation, const Quaternion& rotation, const float scale) : m_translation(translation) , m_rotation(rotation) , m_scale(scale) @@ -25,7 +25,7 @@ namespace AZ { Transform result; result.m_rotation = Quaternion::CreateIdentity(); - result.m_scale = Vector3::CreateOne(); + result.m_scale = 1.0f; result.m_translation = Vector3::CreateZero(); return result; } @@ -49,7 +49,7 @@ namespace AZ { Transform result; result.m_rotation = q; - result.m_scale = Vector3::CreateOne(); + result.m_scale = 1.0f; result.m_translation = Vector3::CreateZero(); return result; } @@ -58,12 +58,22 @@ namespace AZ { Transform result; result.m_rotation = q; - result.m_scale = Vector3::CreateOne(); + result.m_scale = 1.0f; result.m_translation = p; return result; } - AZ_MATH_INLINE Transform Transform::CreateScale(const Vector3& scale) + AZ_MATH_INLINE Transform Transform::CreateScale(const AZ::Vector3& scale) + { + AZ_Warning("Transform", false, "CreateScale is deprecated, please use CreateUniformScale instead."); + Transform result; + result.m_rotation = Quaternion::CreateIdentity(); + result.m_scale = scale.GetMaxElement(); + result.m_translation = Vector3::CreateZero(); + return result; + } + + AZ_MATH_INLINE Transform Transform::CreateUniformScale(float scale) { Transform result; result.m_rotation = Quaternion::CreateIdentity(); @@ -76,7 +86,7 @@ namespace AZ { Transform result; result.m_rotation = Quaternion::CreateIdentity(); - result.m_scale = Vector3::CreateOne(); + result.m_scale = 1.0f; result.m_translation = translation; return result; } @@ -104,17 +114,17 @@ namespace AZ AZ_MATH_INLINE Vector3 Transform::GetBasisX() const { - return m_rotation.TransformVector(Vector3::CreateAxisX(m_scale.GetX())); + return m_rotation.TransformVector(Vector3::CreateAxisX(m_scale)); } AZ_MATH_INLINE Vector3 Transform::GetBasisY() const { - return m_rotation.TransformVector(Vector3::CreateAxisY(m_scale.GetY())); + return m_rotation.TransformVector(Vector3::CreateAxisY(m_scale)); } AZ_MATH_INLINE Vector3 Transform::GetBasisZ() const { - return m_rotation.TransformVector(Vector3::CreateAxisZ(m_scale.GetZ())); + return m_rotation.TransformVector(Vector3::CreateAxisZ(m_scale)); } AZ_MATH_INLINE void Transform::GetBasisAndTranslation(Vector3* basisX, Vector3* basisY, Vector3* basisZ, Vector3* pos) const @@ -150,24 +160,50 @@ namespace AZ m_rotation = rotation; } - AZ_MATH_INLINE const Vector3& Transform::GetScale() const + AZ_MATH_INLINE Vector3 Transform::GetScale() const + { + AZ_Warning("Transform", false, "GetScale is deprecated, please use GetUniformScale instead."); + return Vector3(m_scale); + } + + AZ_MATH_INLINE float Transform::GetUniformScale() const { return m_scale; } AZ_MATH_INLINE void Transform::SetScale(const Vector3& scale) + { + AZ_Warning("Transform", false, "SetScale is deprecated, please use SetUniformScale instead."); + m_scale = scale.GetMaxElement(); + } + + AZ_MATH_INLINE void Transform::SetUniformScale(const float scale) { m_scale = scale; } AZ_MATH_INLINE Vector3 Transform::ExtractScale() { - const Vector3 scale = m_scale; - m_scale = Vector3::CreateOne(); + AZ_Warning("Transform", false, "ExtractScale is deprecated, please use ExtractUniformScale instead."); + const float scale = m_scale; + m_scale = 1.0f; + return Vector3(scale); + } + + AZ_MATH_INLINE float Transform::ExtractUniformScale() + { + const float scale = m_scale; + m_scale = 1.0f; return scale; } - AZ_MATH_INLINE void Transform::MultiplyByScale(const Vector3& scale) + AZ_MATH_INLINE void Transform::MultiplyByScale(const AZ::Vector3& scale) + { + AZ_Warning("Transform", false, "MultiplyByScale is deprecated, please use MultiplyByUniformScale instead."); + m_scale *= scale.GetMaxElement(); + } + + AZ_MATH_INLINE void Transform::MultiplyByUniformScale(float scale) { m_scale *= scale; } @@ -207,7 +243,7 @@ namespace AZ // note - need to be careful about how to calculate inverse when there is non-uniform scale Transform out; out.m_rotation = m_rotation.GetConjugate(); - out.m_scale = m_scale.GetReciprocal(); + out.m_scale = 1.0f / m_scale; out.m_translation = -out.m_scale * (out.m_rotation.TransformVector(m_translation)); return out; } @@ -219,27 +255,27 @@ namespace AZ AZ_MATH_INLINE bool Transform::IsOrthogonal(float tolerance) const { - return m_scale.IsClose(Vector3::CreateOne(), tolerance); + return AZ::IsClose(m_scale, 1.0f, tolerance); } AZ_MATH_INLINE Transform Transform::GetOrthogonalized() const { Transform result; result.m_rotation = m_rotation; - result.m_scale = Vector3::CreateOne(); + result.m_scale = 1.0f; result.m_translation = m_translation; return result; } AZ_MATH_INLINE void Transform::Orthogonalize() { - *this = GetOrthogonalized(); + m_scale = 1.0f; } AZ_MATH_INLINE bool Transform::IsClose(const Transform& rhs, float tolerance) const { return m_rotation.IsClose(rhs.m_rotation, tolerance) - && m_scale.IsClose(rhs.m_scale, tolerance) + && AZ::IsClose(m_scale, rhs.m_scale, tolerance) && m_translation.IsClose(rhs.m_translation, tolerance); } @@ -268,21 +304,21 @@ namespace AZ AZ_MATH_INLINE void Transform::SetFromEulerDegrees(const Vector3& eulerDegrees) { m_translation = Vector3::CreateZero(); - m_scale = Vector3::CreateOne(); + m_scale = 1.0f; m_rotation.SetFromEulerDegrees(eulerDegrees); } AZ_MATH_INLINE void Transform::SetFromEulerRadians(const Vector3& eulerRadians) { m_translation = Vector3::CreateZero(); - m_scale = Vector3::CreateOne(); + m_scale = 1.0f; m_rotation.SetFromEulerRadians(eulerRadians); } AZ_MATH_INLINE bool Transform::IsFinite() const { return m_rotation.IsFinite() - && m_scale.IsFinite() + && IsFiniteFloat(m_scale) && m_translation.IsFinite(); } diff --git a/Code/Framework/AzCore/AzCore/Math/TransformSerializer.cpp b/Code/Framework/AzCore/AzCore/Math/TransformSerializer.cpp index 0a3e02ee0d..46440ac000 100644 --- a/Code/Framework/AzCore/AzCore/Math/TransformSerializer.cpp +++ b/Code/Framework/AzCore/AzCore/Math/TransformSerializer.cpp @@ -58,9 +58,7 @@ namespace AZ } { - // Scale is transitioning to a single uniform scale value, but since it's still internally represented as a Vector3, - // we need to pick one number to use for load/store operations. - float scale = transformInstance->GetScale().GetMaxElement(); + float scale = transformInstance->GetUniformScale(); JSR::ResultCode loadResult = ContinueLoadingFromJsonObjectField(&scale, azrtti_typeid(), inputValue, ScaleTag, context); @@ -122,10 +120,8 @@ namespace AZ { AZ::ScopedContextPath subPathName(context, ScaleTag); - // Scale is transitioning to a single uniform scale value, but since it's still internally represented as a Vector3, - // we need to pick one number to use for load/store operations. - float scale = transformInstance->GetScale().GetMaxElement(); - float defaultScale = defaultTransformInstance ? defaultTransformInstance->GetScale().GetMaxElement() : 0.0f; + float scale = transformInstance->GetUniformScale(); + float defaultScale = defaultTransformInstance ? defaultTransformInstance->GetUniformScale() : 0.0f; JSR::ResultCode storeResult = ContinueStoringToJsonObjectField( outputValue, ScaleTag, &scale, defaultTransformInstance ? &defaultScale : nullptr, azrtti_typeid(), diff --git a/Code/Framework/AzCore/Tests/Math/MathTestData.h b/Code/Framework/AzCore/Tests/Math/MathTestData.h index cd3ade7854..c82c5caea7 100644 --- a/Code/Framework/AzCore/Tests/Math/MathTestData.h +++ b/Code/Framework/AzCore/Tests/Math/MathTestData.h @@ -61,8 +61,8 @@ namespace MathTestData }; static const AZ::Transform NonOrthogonalTransforms[] = { - AZ::Transform::CreateScale(AZ::Vector3(2.4f, 0.3f, 1.7f)), - AZ::Transform::CreateRotationX(2.2f) * AZ::Transform::CreateScale(AZ::Vector3(0.2f, 0.8f, 1.4f)) + AZ::Transform::CreateUniformScale(2.4f), + AZ::Transform::CreateRotationX(2.2f) * AZ::Transform::CreateUniformScale(0.8f) }; static const AZ::Transform OrthogonalTransforms[] = { diff --git a/Code/Framework/AzCore/Tests/Math/ObbTests.cpp b/Code/Framework/AzCore/Tests/Math/ObbTests.cpp index a90d66f288..de267b4265 100644 --- a/Code/Framework/AzCore/Tests/Math/ObbTests.cpp +++ b/Code/Framework/AzCore/Tests/Math/ObbTests.cpp @@ -59,11 +59,11 @@ namespace UnitTest TEST(MATH_Obb, TestScaleTransform) { Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(position, rotation, halfLengths); - Vector3 scaleFactors = Vector3(1.0f, 2.0f, 3.0f); - Transform transform = Transform::CreateScale(scaleFactors); + float scale = 3.0f; + Transform transform = Transform::CreateUniformScale(scale); obb = transform * obb; - EXPECT_THAT(obb.GetPosition(), IsClose(Vector3(1.0f, 4.0f, 9.0f))); - EXPECT_THAT(obb.GetHalfLengths(), IsClose(Vector3(0.5f, 1.0f, 1.5f))); + EXPECT_THAT(obb.GetPosition(), IsClose(Vector3(3.0f, 6.0f, 9.0f))); + EXPECT_THAT(obb.GetHalfLengths(), IsClose(Vector3(1.5f, 1.5f, 1.5f))); } TEST(MATH_Obb, TestSetPosition) diff --git a/Code/Framework/AzCore/Tests/Math/TransformPerformanceTests.cpp b/Code/Framework/AzCore/Tests/Math/TransformPerformanceTests.cpp index 400a845913..a9788375ad 100644 --- a/Code/Framework/AzCore/Tests/Math/TransformPerformanceTests.cpp +++ b/Code/Framework/AzCore/Tests/Math/TransformPerformanceTests.cpp @@ -186,7 +186,7 @@ namespace Benchmark { for (auto& testData : m_testDataArray) { - AZ::Transform result = AZ::Transform::CreateScale(testData.v3); + AZ::Transform result = AZ::Transform::CreateUniformScale(testData.value[0]); benchmark::DoNotOptimize(result); } } @@ -350,7 +350,7 @@ namespace Benchmark { for (auto& testData : m_testDataArray) { - AZ::Vector3 result = testData.t1.GetScale(); + float result = testData.t1.GetUniformScale(); benchmark::DoNotOptimize(result); } } @@ -376,7 +376,7 @@ namespace Benchmark for (auto& testData : m_testDataArray) { AZ::Transform testTransform = testData.t2; - AZ::Vector3 result = testTransform.ExtractScale(); + float result = testTransform.ExtractUniformScale(); benchmark::DoNotOptimize(result); } } diff --git a/Code/Framework/AzCore/Tests/Math/TransformTests.cpp b/Code/Framework/AzCore/Tests/Math/TransformTests.cpp index d0343211c3..49607573ce 100644 --- a/Code/Framework/AzCore/Tests/Math/TransformTests.cpp +++ b/Code/Framework/AzCore/Tests/Math/TransformTests.cpp @@ -184,12 +184,12 @@ namespace UnitTest TEST(MATH_Transform, CreateScale) { - const AZ::Vector3 scale(1.7f, 0.3f, 2.4f); - const AZ::Transform transform = AZ::Transform::CreateScale(scale); + const float scale = 1.7f; + const AZ::Transform transform = AZ::Transform::CreateUniformScale(scale); const AZ::Vector3 vector(0.2f, -1.6f, 0.4f); EXPECT_THAT(transform.GetTranslation(), IsClose(AZ::Vector3::CreateZero())); const AZ::Vector3 transformedVector = transform.TransformPoint(vector); - const AZ::Vector3 expected(0.34f, -0.48f, 0.96f); + const AZ::Vector3 expected(0.34f, -2.72f, 0.68f); EXPECT_THAT(transformedVector, IsClose(expected)); } @@ -237,10 +237,10 @@ namespace UnitTest TEST(MATH_Transform, MultiplyByTransform) { const AZ::Transform transform1 = AZ::Transform::CreateRotationY(0.3f); - const AZ::Transform transform2 = AZ::Transform::CreateScale(AZ::Vector3(1.3f, 1.5f, 0.4f)); + const AZ::Transform transform2 = AZ::Transform::CreateUniformScale(1.3f); const AZ::Transform transform3 = AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion(0.42f, 0.46f, -0.66f, 0.42f), AZ::Vector3(2.8f, -3.7f, 1.6f)); - const AZ::Transform transform4 = AZ::Transform::CreateRotationX(-0.7f) * AZ::Transform::CreateScale(AZ::Vector3(0.6f, 1.3f, 0.7f)); + const AZ::Transform transform4 = AZ::Transform::CreateRotationX(-0.7f) * AZ::Transform::CreateUniformScale(0.6f); AZ::Transform transform5 = transform1; transform5 *= transform4; const AZ::Vector3 vector(1.9f, 2.3f, 0.2f); @@ -341,10 +341,10 @@ namespace UnitTest AZ::Transform unscaledTransform = orthogonalTransform; unscaledTransform.ExtractScale(); EXPECT_THAT(unscaledTransform.GetScale(), IsClose(AZ::Vector3::CreateOne())); - const AZ::Vector3 scale(2.8f, 0.7f, 1.3f); + const float scale = 2.8f; AZ::Transform scaledTransform = orthogonalTransform; - scaledTransform.MultiplyByScale(scale); - EXPECT_THAT(scaledTransform.GetScale(), IsClose(scale)); + scaledTransform.MultiplyByUniformScale(scale); + EXPECT_NEAR(scaledTransform.GetUniformScale(), scale, AZ::Constants::Tolerance); } INSTANTIATE_TEST_CASE_P(MATH_Transform, TransformScaleFixture, ::testing::ValuesIn(MathTestData::OrthogonalTransforms)); @@ -353,24 +353,11 @@ namespace UnitTest { EXPECT_TRUE(AZ::Transform::CreateIdentity().IsOrthogonal()); EXPECT_TRUE(AZ::Transform::CreateRotationZ(0.3f).IsOrthogonal()); - EXPECT_FALSE(AZ::Transform::CreateScale(AZ::Vector3(0.8f, 0.3f, 1.2f)).IsOrthogonal()); + EXPECT_FALSE(AZ::Transform::CreateUniformScale(0.8f).IsOrthogonal()); EXPECT_TRUE(AZ::Transform::CreateFromQuaternion(AZ::Quaternion(-0.52f, -0.08f, 0.56f, 0.64f)).IsOrthogonal()); AZ::Transform transform; transform.SetFromEulerRadians(AZ::Vector3(0.2f, 0.4f, 0.1f)); EXPECT_TRUE(transform.IsOrthogonal()); - - // want to test each possible way the transform could fail to be orthogonal, which we can do by testing for one - // axis, then using a rotation which cycles the axes - const AZ::Transform axisCycle = AZ::Transform::CreateFromQuaternion(AZ::Quaternion(0.5f, 0.5f, 0.5f, 0.5f)); - - // a transform which is normalized in 2 axes, but not the third - AZ::Transform nonOrthogonalTransform1 = AZ::Transform::CreateScale(AZ::Vector3(1.0f, 1.0f, 2.0f)); - - for (int i = 0; i < 3; i++) - { - EXPECT_FALSE(nonOrthogonalTransform1.IsOrthogonal()); - nonOrthogonalTransform1 = axisCycle * nonOrthogonalTransform1; - } } using TransformSetFromEulerDegreesFixture = ::testing::TestWithParam; @@ -465,10 +452,11 @@ namespace UnitTest AZ::Transform* deserializedTransform = AZ::Utils::LoadObjectFromBuffer(objectStreamBuffer, strlen(objectStreamBuffer) + 1); const AZ::Vector3 expectedTranslation(513.7845459f, 492.5420837f, 32.0000000f); - const AZ::Vector3 expectedScale(1.5f, 0.5f, 1.2f); + const float expectedScale = 1.5f; const AZ::Quaternion expectedRotation(0.2624075f, 0.4405251f, 0.2029076f, 0.8342113f); const AZ::Transform expectedTransform = - AZ::Transform::CreateFromQuaternionAndTranslation(expectedRotation, expectedTranslation) * AZ::Transform::CreateScale(expectedScale); + AZ::Transform::CreateFromQuaternionAndTranslation(expectedRotation, expectedTranslation) * + AZ::Transform::CreateUniformScale(expectedScale); EXPECT_TRUE(deserializedTransform->IsClose(expectedTransform)); azfree(deserializedTransform); diff --git a/Code/Framework/AzCore/Tests/ScriptMath.cpp b/Code/Framework/AzCore/Tests/ScriptMath.cpp index cb673e92e7..493a21de36 100644 --- a/Code/Framework/AzCore/Tests/ScriptMath.cpp +++ b/Code/Framework/AzCore/Tests/ScriptMath.cpp @@ -1275,10 +1275,10 @@ namespace UnitTest script->Execute("AZTestAssert(t1:TransformVector(Vector3(1, 0, 0)):IsClose(Vector3(1, 0, 0)))"); script->Execute("AZTestAssert(t1:TransformVector(Vector3(0, 1, 0)):IsClose(Vector3(0, 0.866, 0.5)))"); script->Execute("AZTestAssert(t1:TransformVector(Vector3(0, 0, 1)):IsClose(Vector3(0, -0.5, 0.866)))"); - script->Execute("t1 = Transform.CreateScale(Vector3(1, 2, 3))"); - script->Execute("AZTestAssert(t1:TransformVector(Vector3(1, 0, 0)):IsClose(Vector3(1, 0, 0)))"); + script->Execute("t1 = Transform.CreateScale(2)"); + script->Execute("AZTestAssert(t1:TransformVector(Vector3(1, 0, 0)):IsClose(Vector3(2, 0, 0)))"); script->Execute("AZTestAssert(t1:TransformVector(Vector3(0, 1, 0)):IsClose(Vector3(0, 2, 0)))"); - script->Execute("AZTestAssert(t1:TransformVector(Vector3(0, 0, 1)):IsClose(Vector3(0, 0, 3)))"); + script->Execute("AZTestAssert(t1:TransformVector(Vector3(0, 0, 1)):IsClose(Vector3(0, 0, 2)))"); script->Execute("t1 = Transform.CreateTranslation(Vector3(1, 2, 3))"); script->Execute("AZTestAssert(t1:TransformVector(Vector3(1, 0, 0)):IsClose(Vector3(1, 0, 0)))"); script->Execute("AZTestAssert(t1:TransformVector(Vector3(0, 1, 0)):IsClose(Vector3(0, 1, 0)))"); @@ -1341,19 +1341,19 @@ namespace UnitTest script->Execute("AZTestAssert(t3:GetTranslation():IsClose(Vector3(-5.90, 25.415, 19.645), 0.001))"); ////test inverse, should handle non-orthogonal matrices - script->Execute("t1 = Transform.CreateRotationX(1) * Transform.CreateScale(Vector3(1, 2, 3))"); + script->Execute("t1 = Transform.CreateRotationX(1) * Transform.CreateScale(2)"); script->Execute("AZTestAssert((t1*t1:GetInverse()):IsClose(Transform.CreateIdentity()))"); ////scale access - script->Execute("t1 = Transform.CreateRotationX(Math.DegToRad(40)) * Transform.CreateScale(Vector3(2, 3, 4))"); - script->Execute("AZTestAssert(t1:GetScale():IsClose(Vector3(2, 3, 4)))"); - script->Execute("AZTestAssert(t1:ExtractScale():IsClose(Vector3(2, 3, 4)))"); - script->Execute("AZTestAssert(t1:GetScale():IsClose(Vector3.CreateOne()))"); - script->Execute("t1:MultiplyByScale(Vector3(3, 4, 5))"); - script->Execute("AZTestAssert(t1:GetScale():IsClose(Vector3(3, 4, 5)))"); + script->Execute("t1 = Transform.CreateRotationX(Math.DegToRad(40)) * Transform.CreateScale(3)"); + script->Execute("AZTestAssert(t1:GetScale():IsClose(3))"); + script->Execute("AZTestAssert(t1:ExtractScale():IsClose(3))"); + script->Execute("AZTestAssert(t1:GetScale():IsClose(1))"); + script->Execute("t1:MultiplyByScale(2)"); + script->Execute("AZTestAssert(t1:GetScale():IsClose(2))"); ////orthogonalize - script->Execute("t1 = Transform.CreateRotationX(Math.DegToRad(30)) * Transform.CreateScale(Vector3(2, 3, 4))"); + script->Execute("t1 = Transform.CreateRotationX(Math.DegToRad(30)) * Transform.CreateScale(3)"); script->Execute("t1:SetTranslation(Vector3(1,2,3))"); script->Execute("t2 = t1:GetOrthogonalized()"); script->Execute("AZTestAssertFloatClose(t2:GetBasisX():GetLength(), 1)"); @@ -1372,7 +1372,7 @@ namespace UnitTest script->Execute("t1 = Transform.CreateRotationX(Math.DegToRad(30))"); script->Execute("t1:SetTranslation(Vector3(1, 2, 3))"); script->Execute("AZTestAssert(t1:IsOrthogonal(0.05))"); - script->Execute("t1 = Transform.CreateRotationX(Math.DegToRad(30)) * Transform.CreateScale(Vector3(2, 3, 4))"); + script->Execute("t1 = Transform.CreateRotationX(Math.DegToRad(30)) * Transform.CreateScale(2)"); script->Execute("AZTestAssert( not t1:IsOrthogonal(0.05))"); ////IsClose diff --git a/Code/Framework/AzCore/Tests/Serialization/Json/TransformSerializerTests.cpp b/Code/Framework/AzCore/Tests/Serialization/Json/TransformSerializerTests.cpp index 12711b1e1a..7febbbb5d9 100644 --- a/Code/Framework/AzCore/Tests/Serialization/Json/TransformSerializerTests.cpp +++ b/Code/Framework/AzCore/Tests/Serialization/Json/TransformSerializerTests.cpp @@ -44,7 +44,7 @@ namespace JsonSerializationTests AZStd::shared_ptr CreateFullySetInstance() override { return AZStd::make_shared( - AZ::Vector3(1.0f, 2.0f, 3.0f), AZ::Quaternion(0.25f, 0.5f, 0.75f, 1.0f), AZ::Vector3(9.0f)); + AZ::Vector3(1.0f, 2.0f, 3.0f), AZ::Quaternion(0.25f, 0.5f, 0.75f, 1.0f), 9.0f); } AZStd::string_view GetJsonForFullySetInstance() override @@ -95,7 +95,7 @@ namespace JsonSerializationTests AZ::Transform expectedTransform( AZ::Vector3(2.25f, 3.5f, 4.75f), AZ::Quaternion(0.25f, 0.5f, 0.75f, 1.0f), - AZ::Vector3(5.5f)); + 5.5f); rapidjson::Document json; json.Parse(R"({ "Translation": [ 2.25, 3.5, 4.75 ], "Rotation": [ 0.25, 0.5, 0.75, 1.0 ], "Scale": 5.5 })"); @@ -189,7 +189,7 @@ namespace JsonSerializationTests TEST_F(JsonTransformSerializerTests, Load_FullySetTransform_ReturnsSuccessWithOnlyScale) { AZ::Transform testTransform = AZ::Transform::CreateIdentity(); - AZ::Transform expectedTransform = AZ::Transform::CreateScale(AZ::Vector3(5.5f)); + AZ::Transform expectedTransform = AZ::Transform::CreateUniformScale(5.5f); rapidjson::Document json; json.Parse(R"({ "Scale" : 5.5 })"); diff --git a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp index 4b805fd3e1..b1b2378174 100644 --- a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp @@ -447,29 +447,12 @@ namespace AzFramework static AZ::Transform RotateAroundLocalHelper(float eulerAngleRadian, const AZ::Transform& localTM, AZ::Vector3 axis) { - //get the existing translation and scale - AZ::Vector3 translation = localTM.GetTranslation(); - AZ::Vector3 scale = localTM.GetScale(); - //normalize the axis before creating rotation axis.Normalize(); AZ::Quaternion rotate = AZ::Quaternion::CreateFromAxisAngle(axis, eulerAngleRadian); - //create new rotation transform - AZ::Quaternion currentRotate = localTM.GetRotation(); - AZ::Quaternion newRotate = rotate * currentRotate; - newRotate.Normalize(); - - //scale - AZ::Transform newLocalTM = AZ::Transform::CreateScale(scale); - - //rotate - AZ::Transform rotateLocalTM = AZ::Transform::CreateFromQuaternion(newRotate); - newLocalTM = rotateLocalTM * newLocalTM; - - //translate - newLocalTM.SetTranslation(translation); - + AZ::Transform newLocalTM = localTM; + newLocalTM.SetRotation((rotate * localTM.GetRotation()).GetNormalized()); return newLocalTM; } @@ -527,6 +510,23 @@ namespace AzFramework return m_worldTM.GetScale(); } + void TransformComponent::SetLocalUniformScale(float scale) + { + AZ::Transform newLocalTM = m_localTM; + newLocalTM.SetUniformScale(scale); + SetLocalTM(newLocalTM); + } + + float TransformComponent::GetLocalUniformScale() + { + return m_localTM.GetUniformScale(); + } + + float TransformComponent::GetWorldUniformScale() + { + return m_worldTM.GetUniformScale(); + } + AZStd::vector TransformComponent::GetChildren() { AZStd::vector children; diff --git a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h index f393e22064..f0fa1b6985 100644 --- a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h +++ b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h @@ -146,10 +146,13 @@ namespace AzFramework // Scale Modifiers void SetLocalScale(const AZ::Vector3& scale) override; - AZ::Vector3 GetLocalScale() override; AZ::Vector3 GetWorldScale() override; + void SetLocalUniformScale(float scale) override; + float GetLocalUniformScale() override; + float GetWorldUniformScale() override; + // Transform hierarchy AZStd::vector GetChildren() override; AZStd::vector GetAllDescendants() override; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ScaleManipulators.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ScaleManipulators.cpp index 3af09f644d..caeedd834f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ScaleManipulators.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ScaleManipulators.cpp @@ -82,9 +82,7 @@ namespace AzToolsFramework m_uniformScaleManipulator->SetVisualOrientationOverride( QuaternionFromTransformNoScaling(localTransform)); - m_uniformScaleManipulator->SetLocalTransform( - AZ::Transform::CreateTranslation(localTransform.GetTranslation()) * - AZ::Transform::CreateScale(localTransform.GetScale())); + m_uniformScaleManipulator->SetLocalOrientation(AZ::Quaternion::CreateIdentity()); } void ScaleManipulators::SetLocalPositionImpl(const AZ::Vector3& localPosition) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Slice/SliceUtilities.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Slice/SliceUtilities.cpp index 75070d9b41..e13d4fdb47 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Slice/SliceUtilities.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Slice/SliceUtilities.cpp @@ -1475,10 +1475,8 @@ namespace AzToolsFramework // to avoid pushing them to the slice. // Only scale is preserved on the root entity of a slice. transformComponent->SetParent(AZ::EntityId()); - AZ::Vector3 scale = transformComponent->GetLocalScale(); transformComponent->SetWorldTranslation(AZ::Vector3::CreateZero()); transformComponent->SetLocalRotation(AZ::Vector3::CreateZero()); - transformComponent->SetLocalScale(scale); } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.cpp index 671a56b4d4..bcd8e3fa4a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.cpp @@ -46,9 +46,9 @@ namespace AzToolsFramework const AZ::u32 ParentEntityCRC = AZ_CRC("Parent Entity", 0x5b1b276c); // Decompose a transform into euler angles in degrees, scale (along basis, any shear will be dropped), and translation. - void DecomposeTransform(const AZ::Transform& transform, AZ::Vector3& translation, AZ::Vector3& rotation, AZ::Vector3& scale) + void DecomposeTransform(const AZ::Transform& transform, AZ::Vector3& translation, AZ::Vector3& rotation, float& scale) { - scale = transform.GetScale(); + scale = transform.GetUniformScale(); translation = transform.GetTranslation(); rotation = transform.GetRotation().GetEulerDegrees(); } @@ -323,7 +323,7 @@ namespace AzToolsFramework AZ::Transform TransformComponent::GetLocalScaleTM() const { - return AZ::Transform::CreateScale(m_editorTransform.m_scale); + return AZ::Transform::CreateUniformScale(m_editorTransform.m_scale); } const AZ::Transform& TransformComponent::GetLocalTM() @@ -340,7 +340,8 @@ namespace AzToolsFramework // given a local transform, update local transform. void TransformComponent::SetLocalTM(const AZ::Transform& finalTx) { - AZ::Vector3 tx, rot, scale; + AZ::Vector3 tx, rot; + float scale; Internal::DecomposeTransform(finalTx, tx, rot, scale); m_editorTransform.m_translate = tx; @@ -645,13 +646,13 @@ namespace AzToolsFramework void TransformComponent::SetLocalScale(const AZ::Vector3& scale) { - m_editorTransform.m_scale = scale; + m_editorTransform.m_scale = scale.GetMaxElement(); TransformChanged(); } AZ::Vector3 TransformComponent::GetLocalScale() { - return m_editorTransform.m_scale; + return AZ::Vector3(m_editorTransform.m_scale); } AZ::Vector3 TransformComponent::GetWorldScale() @@ -659,6 +660,22 @@ namespace AzToolsFramework return GetWorldTM().GetScale(); } + void TransformComponent::SetLocalUniformScale(float scale) + { + m_editorTransform.m_scale = scale; + TransformChanged(); + } + + float TransformComponent::GetLocalUniformScale() + { + return m_editorTransform.m_scale; + } + + float TransformComponent::GetWorldUniformScale() + { + return GetWorldTM().GetUniformScale(); + } + const AZ::Transform& TransformComponent::GetParentWorldTM() const { auto parent = GetParentTransformComponent(); @@ -1062,12 +1079,6 @@ namespace AzToolsFramework ModifyEditorTransform(m_editorTransform.m_rotate, data, parent); } - void TransformComponent::ScaleBy(const AZ::Vector3& data) - { - //scale is always local - ModifyEditorTransform(m_editorTransform.m_scale, data, AZ::Transform::Identity()); - } - AZ::EntityId TransformComponent::GetSliceEntityParentId() { return GetParentId(); @@ -1214,7 +1225,7 @@ namespace AzToolsFramework { AzToolsFramework::ScopedUndoBatch undo("Reset transform values"); m_editorTransform.m_translate = AZ::Vector3::CreateZero(); - m_editorTransform.m_scale = AZ::Vector3::CreateOne(); + m_editorTransform.m_scale = 1.0f; m_editorTransform.m_rotate = AZ::Vector3::CreateZero(); OnTransformChanged(); SetDirty(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.h index bbdf770dab..06d1101c58 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.h @@ -130,10 +130,13 @@ namespace AzToolsFramework // Scale Modifiers void SetLocalScale(const AZ::Vector3& scale) override; - AZ::Vector3 GetLocalScale() override; AZ::Vector3 GetWorldScale() override; + void SetLocalUniformScale(float scale) override; + float GetLocalUniformScale() override; + float GetWorldUniformScale() override; + AZ::EntityId GetParentId() override; AZ::TransformInterface* GetParent() override; void SetParent(AZ::EntityId parentId) override; @@ -147,7 +150,6 @@ namespace AzToolsFramework // TransformComponentMessages::Bus void TranslateBy(const AZ::Vector3&) override; void RotateBy(const AZ::Vector3&) override; // euler in degrees - void ScaleBy(const AZ::Vector3&) override; const EditorTransform& GetLocalEditorTransform() override; void SetLocalEditorTransform(const EditorTransform& dest) override; bool IsTransformLocked() override; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponentBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponentBus.h index f1cb8459c4..6082bda4bd 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponentBus.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponentBus.h @@ -30,7 +30,7 @@ namespace AzToolsFramework EditorTransform() { m_translate = AZ::Vector3::CreateZero(); - m_scale = AZ::Vector3::CreateOne(); + m_scale = 1.0f; m_rotate = AZ::Vector3::CreateZero(); m_locked = false; } @@ -41,7 +41,7 @@ namespace AzToolsFramework } AZ::Vector3 m_translate; //! Translation in engine units (meters) - AZ::Vector3 m_scale; + float m_scale; AZ::Vector3 m_rotate; //! Rotation in degrees bool m_locked; }; @@ -65,7 +65,6 @@ namespace AzToolsFramework virtual void TranslateBy(const AZ::Vector3&) = 0; virtual void RotateBy(const AZ::Vector3&) = 0; - virtual void ScaleBy(const AZ::Vector3&) = 0; virtual bool IsTransformLocked() = 0; }; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp index 433602e6d8..65ba51908d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp @@ -1472,7 +1472,7 @@ namespace AzToolsFramework { const AZ::Quaternion rotation = entityIdLookupIt->second.m_initial.GetRotation().GetNormalized(); const AZ::Vector3 position = entityIdLookupIt->second.m_initial.GetTranslation(); - const AZ::Vector3 scale = entityIdLookupIt->second.m_initial.GetScale(); + const float scale = entityIdLookupIt->second.m_initial.GetUniformScale(); const AZ::Vector3 centerOffset = CalculateCenterOffset(entityId, m_pivotMode); @@ -1483,7 +1483,7 @@ namespace AzToolsFramework AZ::Transform::CreateFromQuaternion(rotation) * AZ::Transform::CreateTranslation(centerOffset) * offsetRotation * AZ::Transform::CreateTranslation(-centerOffset) * - AZ::Transform::CreateScale(scale)); + AZ::Transform::CreateUniformScale(scale)); } break; case ReferenceFrame::Parent: @@ -1595,16 +1595,15 @@ namespace AzToolsFramework } const AZ::Transform initial = entityIdLookupIt->second.m_initial; - const AZ::Vector3 initialScale = initial.GetScale(); + const float initialScale = initial.GetUniformScale(); const auto sumVectorElements = [](const AZ::Vector3& vec) { return vec.GetX() + vec.GetY() + vec.GetZ(); }; - const AZ::Vector3 uniformScale = AZ::Vector3(action.m_start.m_sign * sumVectorElements(action.LocalScaleOffset())); - const AZ::Vector3 scale = (AZ::Vector3::CreateOne() + - (uniformScale / initialScale)).GetClamp(AZ::Vector3(AZ::MinTransformScale), AZ::Vector3(AZ::MaxTransformScale)); - const AZ::Transform scaleTransform = AZ::Transform::CreateScale(scale); + const float uniformScale = action.m_start.m_sign * sumVectorElements(action.LocalScaleOffset()); + const float scale = AZ::GetClamp(1.0f + uniformScale / initialScale, AZ::MinTransformScale, AZ::MaxTransformScale); + const AZ::Transform scaleTransform = AZ::Transform::CreateUniformScale(scale); if (action.m_modifiers.Alt()) { @@ -1866,7 +1865,7 @@ namespace AzToolsFramework CopyOrientationToSelectedEntitiesGroup(QuaternionFromTransformNoScaling(worldFromLocal)); break; case Mode::Scale: - CopyScaleToSelectedEntitiesIndividualWorld(worldFromLocal.GetScale()); + CopyScaleToSelectedEntitiesIndividualWorld(worldFromLocal.GetUniformScale()); break; case Mode::Translation: CopyTranslationToSelectedEntitiesGroup(worldFromLocal.GetTranslation()); @@ -1895,7 +1894,7 @@ namespace AzToolsFramework CopyOrientationToSelectedEntitiesIndividual(QuaternionFromTransformNoScaling(worldFromLocal)); break; case Mode::Scale: - CopyScaleToSelectedEntitiesIndividualWorld(worldFromLocal.GetScale()); + CopyScaleToSelectedEntitiesIndividualWorld(worldFromLocal.GetUniformScale()); break; case Mode::Translation: CopyTranslationToSelectedEntitiesIndividual(worldFromLocal.GetTranslation()); @@ -2388,7 +2387,7 @@ namespace AzToolsFramework ResetOrientationForSelectedEntitiesLocal(); break; case Mode::Scale: - CopyScaleToSelectedEntitiesIndividualLocal(AZ::Vector3::CreateOne()); + CopyScaleToSelectedEntitiesIndividualLocal(1.0f); break; case Mode::Translation: ResetTranslationForSelectedEntitiesLocal(); @@ -2414,7 +2413,7 @@ namespace AzToolsFramework ResetOrientationForSelectedEntitiesLocal(); break; case Mode::Scale: - CopyScaleToSelectedEntitiesIndividualWorld(AZ::Vector3::CreateOne()); + CopyScaleToSelectedEntitiesIndividualWorld(1.0f); break; case Mode::Translation: // do nothing @@ -2934,7 +2933,7 @@ namespace AzToolsFramework } } - void EditorTransformComponentSelection::CopyScaleToSelectedEntitiesIndividualWorld(const AZ::Vector3& scale) + void EditorTransformComponentSelection::CopyScaleToSelectedEntitiesIndividualWorld(float scale) { AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework); @@ -2949,7 +2948,7 @@ namespace AzToolsFramework const auto transformsBefore = RecordTransformsBefore(manipulatorEntityIds.m_entityIds); // update scale relative to initial - const AZ::Transform scaleTransform = AZ::Transform::CreateScale(scale); + const AZ::Transform scaleTransform = AZ::Transform::CreateUniformScale(scale); for (AZ::EntityId entityId : manipulatorEntityIds.m_entityIds) { ScopedUndoBatch::MarkEntityDirty(entityId); @@ -2968,7 +2967,7 @@ namespace AzToolsFramework RefreshUiAfterChange(manipulatorEntityIds.m_entityIds); } - void EditorTransformComponentSelection::CopyScaleToSelectedEntitiesIndividualLocal(const AZ::Vector3& scale) + void EditorTransformComponentSelection::CopyScaleToSelectedEntitiesIndividualLocal(float scale) { AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework); @@ -3014,9 +3013,9 @@ namespace AzToolsFramework if (transformIt != transformsBefore.end()) { AZ::Transform newWorldFromLocal = transformIt->second; - const AZ::Vector3 scale = newWorldFromLocal.GetScale(); + const float scale = newWorldFromLocal.GetUniformScale(); newWorldFromLocal.SetRotation(orientation); - newWorldFromLocal *= AZ::Transform::CreateScale(scale); + newWorldFromLocal *= AZ::Transform::CreateUniformScale(scale); SetEntityWorldTransform(entityId, newWorldFromLocal); } @@ -3661,7 +3660,7 @@ namespace AzToolsFramework } void EditorTransformComponentSelection::SetEntityLocalScale( - const AZ::EntityId entityId, const AZ::Vector3& localScale) + const AZ::EntityId entityId, const float localScale) { ETCS::SetEntityLocalScale(entityId, localScale, m_transformChangedInternally); } @@ -3714,11 +3713,11 @@ namespace AzToolsFramework entityId, &AZ::TransformBus::Events::SetWorldTM, worldTransform); } - void SetEntityLocalScale(AZ::EntityId entityId, const AZ::Vector3& localScale, bool& internal) + void SetEntityLocalScale(AZ::EntityId entityId, float localScale, bool& internal) { ScopeSwitch sw(internal); AZ::TransformBus::Event( - entityId, &AZ::TransformBus::Events::SetLocalScale, localScale); + entityId, &AZ::TransformBus::Events::SetLocalUniformScale, localScale); } void SetEntityLocalRotation(AZ::EntityId entityId, const AZ::Vector3& localRotation, bool& internal) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h index 99265313fe..9fd16a6e21 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h @@ -212,8 +212,8 @@ namespace AzToolsFramework void CopyOrientationToSelectedEntitiesIndividual(const AZ::Quaternion& orientation); void CopyOrientationToSelectedEntitiesGroup(const AZ::Quaternion& orientation); void ResetOrientationForSelectedEntitiesLocal(); - void CopyScaleToSelectedEntitiesIndividualLocal(const AZ::Vector3& scale); - void CopyScaleToSelectedEntitiesIndividualWorld(const AZ::Vector3& scale); + void CopyScaleToSelectedEntitiesIndividualLocal(float scale); + void CopyScaleToSelectedEntitiesIndividualWorld(float scale); // EditorManipulatorCommandUndoRedoRequestBus ... void UndoRedoEntityManipulatorCommand( @@ -248,7 +248,7 @@ namespace AzToolsFramework void SetEntityWorldTranslation(AZ::EntityId entityId, const AZ::Vector3& worldTranslation); void SetEntityLocalTranslation(AZ::EntityId entityId, const AZ::Vector3& localTranslation); void SetEntityWorldTransform(AZ::EntityId entityId, const AZ::Transform& worldTransform); - void SetEntityLocalScale(AZ::EntityId entityId, const AZ::Vector3& localScale); + void SetEntityLocalScale(AZ::EntityId entityId, float localScale); void SetEntityLocalRotation(AZ::EntityId entityId, const AZ::Vector3& localRotation); AZ::EntityId m_hoveredEntityId; ///< What EntityId is the mouse currently hovering over (if any). @@ -316,7 +316,7 @@ namespace AzToolsFramework void SetEntityWorldTranslation(AZ::EntityId entityId, const AZ::Vector3& worldTranslation, bool& internal); void SetEntityLocalTranslation(AZ::EntityId entityId, const AZ::Vector3& localTranslation, bool& internal); void SetEntityWorldTransform(AZ::EntityId entityId, const AZ::Transform& worldTransform, bool& internal); - void SetEntityLocalScale(AZ::EntityId entityId, const AZ::Vector3& localScale, bool& internal); + void SetEntityLocalScale(AZ::EntityId entityId, float localScale, bool& internal); void SetEntityLocalRotation(AZ::EntityId entityId, const AZ::Vector3& localRotation, bool& internal); } // namespace ETCS } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelectionRequestBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelectionRequestBus.h index 59c250b8f7..9cd78f8c50 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelectionRequestBus.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelectionRequestBus.h @@ -101,10 +101,10 @@ namespace AzToolsFramework virtual void ResetOrientationForSelectedEntitiesLocal() = 0; /// Copy scale to each individual entity in local space without moving position. - virtual void CopyScaleToSelectedEntitiesIndividualLocal(const AZ::Vector3& scale) = 0; + virtual void CopyScaleToSelectedEntitiesIndividualLocal(float scale) = 0; /// Copy scale to to each individual entity in world (absolute) space. - virtual void CopyScaleToSelectedEntitiesIndividualWorld(const AZ::Vector3& scale) = 0; + virtual void CopyScaleToSelectedEntitiesIndividualWorld(float scale) = 0; protected: ~EditorTransformComponentSelectionRequests() = default; diff --git a/Code/Framework/GridMate/GridMate/Serialize/CompressionMarshal.cpp b/Code/Framework/GridMate/GridMate/Serialize/CompressionMarshal.cpp index 751e151ec6..9dc7de1cf7 100644 --- a/Code/Framework/GridMate/GridMate/Serialize/CompressionMarshal.cpp +++ b/Code/Framework/GridMate/GridMate/Serialize/CompressionMarshal.cpp @@ -488,18 +488,17 @@ void TransformCompressor::Marshal(WriteBuffer& wb, const AZ::Transform& value) c { AZ::u8 flags = 0; auto flagsMarker = wb.InsertMarker(flags); - AZ::Matrix3x3 m33 = AZ::Matrix3x3::CreateFromTransform(value); - AZ::Vector3 scale = m33.ExtractScale(); - AZ::Quaternion rot = AZ::Quaternion::CreateFromMatrix3x3(m33.GetOrthogonalized()); + float scale = value.GetUniformScale(); + AZ::Quaternion rot = value.GetRotation(); if (!rot.IsIdentity()) { flags |= HAS_ROT; wb.Write(rot, QuatCompMarshaler()); } - if (!scale.IsClose(AZ::Vector3::CreateOne())) + if (!AZ::IsClose(scale, 1.0f)) { flags |= HAS_SCALE; - wb.Write(scale, Vec3CompMarshaler()); + wb.Write(scale, HalfMarshaler()); } AZ::Vector3 pos = value.GetTranslation(); if (!pos.IsZero()) @@ -527,9 +526,9 @@ void TransformCompressor::Unmarshal(AZ::Transform& value, ReadBuffer& rb) const } if (flags & HAS_SCALE) { - AZ::Vector3 scale; - rb.Read(scale, Vec3CompMarshaler()); - xform.MultiplyByScale(scale); + float scale; + rb.Read(scale, HalfMarshaler()); + xform.MultiplyByUniformScale(scale); } if (flags & HAS_POS) { diff --git a/Code/Framework/Tests/TransformComponent.cpp b/Code/Framework/Tests/TransformComponent.cpp index 44008686cd..0b6110e3aa 100644 --- a/Code/Framework/Tests/TransformComponent.cpp +++ b/Code/Framework/Tests/TransformComponent.cpp @@ -24,6 +24,8 @@ #include #include +#include + using namespace AZ; using namespace AzFramework; @@ -362,8 +364,8 @@ namespace UnitTest TEST_F(TransformComponentTransformMatrixSetGet, SetLocalRotation_SimpleValues_Set) { // add some scale first - float sx = 1.03f, sy = 0.67f, sz = 1.23f; - Transform tm = Transform::CreateScale(Vector3(sx, sy, sz)); + float scale = 1.23f; + Transform tm = Transform::CreateUniformScale(scale); TransformBus::Event(m_childId, &TransformBus::Events::SetLocalTM, tm); float rx = 42.435f; @@ -379,13 +381,13 @@ namespace UnitTest Matrix3x3 finalRotate = rotateX * rotateY * rotateZ; Vector3 basisX = tm.GetBasisX(); - Vector3 expectedBasisX = finalRotate.GetBasisX() * sx; + Vector3 expectedBasisX = finalRotate.GetBasisX() * scale; EXPECT_TRUE(basisX.IsClose(expectedBasisX)); Vector3 basisY = tm.GetBasisY(); - Vector3 expectedBasisY = finalRotate.GetBasisY() * sy; + Vector3 expectedBasisY = finalRotate.GetBasisY() * scale; EXPECT_TRUE(basisY.IsClose(expectedBasisY)); Vector3 basisZ = tm.GetBasisZ(); - Vector3 expectedBasisZ = finalRotate.GetBasisZ() * sz; + Vector3 expectedBasisZ = finalRotate.GetBasisZ() * scale; EXPECT_TRUE(basisZ.IsClose(expectedBasisZ)); } @@ -476,18 +478,15 @@ namespace UnitTest { TransformBus::Event(m_childId, &TransformBus::Events::RotateAroundLocalX, rx); } - Vector3 localScale; - TransformBus::EventResult(localScale, m_childId, &TransformBus::Events::GetLocalScale); - EXPECT_TRUE(localScale.IsClose(Vector3(1.0f, 1.0f, 1.0f))); + float localScale = FLT_MAX; + TransformBus::EventResult(localScale, m_childId, &TransformBus::Events::GetLocalUniformScale); + EXPECT_NEAR(localScale, 1.0f, AZ::Constants::Tolerance); } TEST_F(TransformComponentTransformMatrixSetGet, RotateAroundLocalX_ScaleDoesNotSkewRotation) { - float sx = 42.564f; - float sy = 12.460f; - float sz = 28.692f; - Vector3 expectedScales(sx, sy, sz); - TransformBus::Event(m_childId, &TransformBus::Events::SetLocalScale, expectedScales); + float expectedScale = 42.564f; + TransformBus::Event(m_childId, &TransformBus::Events::SetLocalUniformScale, expectedScale); float rx = 1.43f; TransformBus::Event(m_childId, &TransformBus::Events::RotateAroundLocalX, rx); @@ -513,18 +512,15 @@ namespace UnitTest { TransformBus::Event(m_childId, &TransformBus::Events::RotateAroundLocalY, ry); } - Vector3 localScale; - TransformBus::EventResult(localScale, m_childId, &TransformBus::Events::GetLocalScale); - EXPECT_TRUE(localScale.IsClose(Vector3(1.0f, 1.0f, 1.0f))); + float localScale = FLT_MAX; + TransformBus::EventResult(localScale, m_childId, &TransformBus::Events::GetLocalUniformScale); + EXPECT_NEAR(localScale, 1.0f, AZ::Constants::Tolerance); } TEST_F(TransformComponentTransformMatrixSetGet, RotateAroundLocalY_ScaleDoesNotSkewRotation) { - float sx = 42.564f; - float sy = 12.460f; - float sz = 28.692f; - Vector3 expectedScales(sx, sy, sz); - TransformBus::Event(m_childId, &TransformBus::Events::SetLocalScale, expectedScales); + float expectedScale = 42.564f; + TransformBus::Event(m_childId, &TransformBus::Events::SetLocalUniformScale, expectedScale); float ry = 1.43f; TransformBus::Event(m_childId, &TransformBus::Events::RotateAroundLocalY, ry); @@ -550,18 +546,15 @@ namespace UnitTest { TransformBus::Event(m_childId, &TransformBus::Events::RotateAroundLocalZ, rz); } - Vector3 localScale; - TransformBus::EventResult(localScale, m_childId, &TransformBus::Events::GetLocalScale); - EXPECT_TRUE(localScale.IsClose(Vector3(1.0f, 1.0f, 1.0f))); + float localScale = FLT_MAX; + TransformBus::EventResult(localScale, m_childId, &TransformBus::Events::GetLocalUniformScale); + EXPECT_NEAR(localScale, 1.0f, AZ::Constants::Tolerance); } TEST_F(TransformComponentTransformMatrixSetGet, RotateAroundLocalZ_ScaleDoesNotSkewRotation) { - float sx = 42.564f; - float sy = 12.460f; - float sz = 28.692f; - Vector3 expectedScales(sx, sy, sz); - TransformBus::Event(m_childId, &TransformBus::Events::SetLocalScale, expectedScales); + float expectedScale = 42.564f; + TransformBus::Event(m_childId, &TransformBus::Events::SetLocalUniformScale, expectedScale); float rz = 1.43f; TransformBus::Event(m_childId, &TransformBus::Events::RotateAroundLocalZ, rz); @@ -572,65 +565,50 @@ namespace UnitTest TEST_F(TransformComponentTransformMatrixSetGet, SetLocalScale_SimpleValues_Set) { - float sx = 42.564f; - float sy = 12.460f; - float sz = 28.692f; - Vector3 expectedScales(sx, sy, sz); - TransformBus::Event(m_childId, &TransformBus::Events::SetLocalScale, expectedScales); + float expectedScale = 42.564f; + TransformBus::Event(m_childId, &TransformBus::Events::SetLocalUniformScale, expectedScale); - Transform tm ; + Transform tm; TransformBus::EventResult(tm, m_childId, &TransformBus::Events::GetLocalTM); - Vector3 scales = tm.GetScale(); - EXPECT_TRUE(scales.IsClose(expectedScales)); + float scale = tm.GetUniformScale(); + EXPECT_NEAR(scale, expectedScale, AZ::Constants::Tolerance); } TEST_F(TransformComponentTransformMatrixSetGet, GetLocalScale_SimpleValues_Return) { - float sx = 43.463f; - float sy = 346.22f; - float sz = 863.32f; - Vector3 expectedScales(sx, sy, sz); - Transform scaleTM = Transform::CreateScale(expectedScales); + float expectedScale = 43.463f; + Transform scaleTM = Transform::CreateUniformScale(expectedScale); TransformBus::Event(m_childId, &TransformBus::Events::SetLocalTM, scaleTM); - Vector3 scales; - TransformBus::EventResult(scales, m_childId, &TransformBus::Events::GetLocalScale); - EXPECT_TRUE(scales.IsClose(expectedScales)); + float scale; + TransformBus::EventResult(scale, m_childId, &TransformBus::Events::GetLocalUniformScale); + EXPECT_NEAR(scale, expectedScale, AZ::Constants::Tolerance); } TEST_F(TransformComponentTransformMatrixSetGet, GetWorldScale_ChildHasNoScale_ReturnScaleSameAsParent) { - float sx = 43.463f; - float sy = 346.22f; - float sz = 863.32f; - Vector3 expectedScales(sx, sy, sz); - Transform scaleTM = Transform::CreateScale(expectedScales); + float expectedScale = 43.463f; + Transform scaleTM = Transform::CreateUniformScale(expectedScale); TransformBus::Event(m_parentId, &TransformBus::Events::SetLocalTM, scaleTM); - Vector3 scales; - TransformBus::EventResult(scales, m_childId, &TransformBus::Events::GetWorldScale); - EXPECT_TRUE(scales.IsClose(expectedScales)); + float scale = FLT_MAX; + TransformBus::EventResult(scale, m_childId, &TransformBus::Events::GetWorldUniformScale); + EXPECT_NEAR(scale, expectedScale, AZ::Constants::Tolerance); } TEST_F(TransformComponentTransformMatrixSetGet, GetWorldScale_ChildHasScale_ReturnCompoundScale) { - float sx = 4.463f; - float sy = 3.22f; - float sz = 8.32f; - Vector3 parentScales(sx, sy, sz); - Transform parentScaleTM = Transform::CreateScale(parentScales); + float parentScale = 4.463f; + Transform parentScaleTM = Transform::CreateUniformScale(parentScale); TransformBus::Event(m_parentId, &TransformBus::Events::SetLocalTM, parentScaleTM); - float csx = 1.64f; - float csy = 9.35f; - float csz = 1.57f; - Vector3 childScales(csx, csy, csz); - Transform childScaleTM = Transform::CreateScale(childScales); + float childScale = 1.64f; + Transform childScaleTM = Transform::CreateUniformScale(childScale); TransformBus::Event(m_childId, &TransformBus::Events::SetLocalTM, childScaleTM); - Vector3 scales; - TransformBus::EventResult(scales, m_childId, &TransformBus::Events::GetWorldScale); - EXPECT_TRUE(scales.IsClose(parentScales * childScales)); + float scale = FLT_MAX; + TransformBus::EventResult(scale, m_childId, &TransformBus::Events::GetWorldUniformScale); + EXPECT_NEAR(scale, parentScale * childScale, AZ::Constants::Tolerance); } class TransformComponentHierarchy diff --git a/Code/Sandbox/Editor/Objects/SelectionGroup.cpp b/Code/Sandbox/Editor/Objects/SelectionGroup.cpp index b883b78e49..6363257e2f 100644 --- a/Code/Sandbox/Editor/Objects/SelectionGroup.cpp +++ b/Code/Sandbox/Editor/Objects/SelectionGroup.cpp @@ -490,28 +490,6 @@ void CSelectionGroup::StartScaling() } -void CSelectionGroup::FinishScaling(const Vec3& scale, [[maybe_unused]] int referenceCoordSys) -{ - if (fabs(scale.x - scale.y) < 0.001f && - fabs(scale.y - scale.z) < 0.001f && - fabs(scale.z - scale.x) < 0.001f) - { - return; - } - - for (int i = 0; i < GetFilteredCount(); ++i) - { - CBaseObject* obj = GetFilteredObject(i); - Vec3 OriginalScale; - if (obj->GetUntransformedScale(OriginalScale)) - { - obj->TransformScale(scale); - obj->SetScale(OriginalScale); - } - } -} - - ////////////////////////////////////////////////////////////////////////// void CSelectionGroup::Align() { diff --git a/Code/Sandbox/Editor/Objects/SelectionGroup.h b/Code/Sandbox/Editor/Objects/SelectionGroup.h index 277ff2a492..9f672f28b2 100644 --- a/Code/Sandbox/Editor/Objects/SelectionGroup.h +++ b/Code/Sandbox/Editor/Objects/SelectionGroup.h @@ -103,7 +103,6 @@ public: void StartScaling(); void Scale(const Vec3& scale, int referenceCoordSys); void SetScale(const Vec3& scale, int referenceCoordSys); - void FinishScaling(const Vec3& scale, int referenceCoordSys); //! Align objects in selection to surface normal void Align(); //! Very special method to move contents of a voxel. diff --git a/Code/Sandbox/Editor/TrackView/TrackViewAnimNode.cpp b/Code/Sandbox/Editor/TrackView/TrackViewAnimNode.cpp index ae7077b4fc..b9814e66c1 100644 --- a/Code/Sandbox/Editor/TrackView/TrackViewAnimNode.cpp +++ b/Code/Sandbox/Editor/TrackView/TrackViewAnimNode.cpp @@ -1869,7 +1869,7 @@ void CTrackViewAnimNode::SetPos(const Vec3& position) } ////////////////////////////////////////////////////////////////////////// -void CTrackViewAnimNode::SetScale(const Vec3& scale) +void CTrackViewAnimNode::SetScale(float scale) { CTrackViewTrack* track = GetTrackForParameter(AnimParamType::Scale); @@ -2012,9 +2012,9 @@ void CTrackViewAnimNode::SetPosRotScaleTracksDefaultValues(bool positionAllowed, } if (scaleAllowed) { - AZ::Vector3 scale = AZ::Vector3::CreateOne(); - AZ::TransformBus::EventResult(scale, entityId, &AZ::TransformBus::Events::GetWorldScale); - m_animNode->SetScale(time, AZVec3ToLYVec3(scale)); + float scale = 1.0f; + AZ::TransformBus::EventResult(scale, entityId, &AZ::TransformBus::Events::GetWorldUniformScale); + m_animNode->SetScale(time, scale); } } } @@ -2482,11 +2482,11 @@ Quat CTrackViewAnimNode::GetTransformDelegateRotation(const Quat& baseRotation) ////////////////////////////////////////////////////////////////////////// Vec3 CTrackViewAnimNode::GetTransformDelegateScale(const Vec3& baseScale) const { - const Vec3 scale = GetScale(); + float scale = GetScale(); - return Vec3(CheckTrackAnimated(AnimParamType::ScaleX) ? scale.x : baseScale.x, - CheckTrackAnimated(AnimParamType::ScaleY) ? scale.y : baseScale.y, - CheckTrackAnimated(AnimParamType::ScaleZ) ? scale.z : baseScale.z); + return Vec3(CheckTrackAnimated(AnimParamType::ScaleX) ? scale : baseScale.x, + CheckTrackAnimated(AnimParamType::ScaleY) ? scale : baseScale.y, + CheckTrackAnimated(AnimParamType::ScaleZ) ? scale : baseScale.z); } ////////////////////////////////////////////////////////////////////////// @@ -2504,7 +2504,7 @@ void CTrackViewAnimNode::SetTransformDelegateRotation(const Quat& rotation) ////////////////////////////////////////////////////////////////////////// void CTrackViewAnimNode::SetTransformDelegateScale(const Vec3& scale) { - SetScale(scale); + SetScale(scale.x); } bool CTrackViewAnimNode::IsTransformAnimParamTypeDelegated(const AnimParamType animParamType) const diff --git a/Code/Sandbox/Editor/TrackView/TrackViewAnimNode.h b/Code/Sandbox/Editor/TrackView/TrackViewAnimNode.h index 1e0cc2262a..4435d0efc7 100644 --- a/Code/Sandbox/Editor/TrackView/TrackViewAnimNode.h +++ b/Code/Sandbox/Editor/TrackView/TrackViewAnimNode.h @@ -182,8 +182,8 @@ public: // Rotation/Position & Scale void SetPos(const Vec3& position); Vec3 GetPos() const { return m_animNode->GetPos(); } - void SetScale(const Vec3& scale); - Vec3 GetScale() const { return m_animNode->GetScale(); } + void SetScale(float scale); + float GetScale() const { return m_animNode->GetScale(); } void SetRotation(const Quat& rotation); Quat GetRotation() const { return m_animNode->GetRotate(); } Quat GetRotation(float time) const { return m_animNode != nullptr ? m_animNode->GetRotate(time) : Quat(0,0,0,0); } diff --git a/Code/Sandbox/Editor/TrackView/TrackViewSequence.cpp b/Code/Sandbox/Editor/TrackView/TrackViewSequence.cpp index f915c804f8..3642f21da6 100644 --- a/Code/Sandbox/Editor/TrackView/TrackViewSequence.cpp +++ b/Code/Sandbox/Editor/TrackView/TrackViewSequence.cpp @@ -825,10 +825,10 @@ void CTrackViewSequence::SyncSelectedTracksToBase() { const Vec3 position = pAnimNode->GetPos(); const Quat rotation = pAnimNode->GetRotation(); - const Vec3 scale = pAnimNode->GetScale(); + const float scale = pAnimNode->GetScale(); AZ::Transform transform = AZ::Transform::CreateIdentity(); - transform.SetScale(LYVec3ToAZVec3(scale)); + transform.SetUniformScale(scale); transform.SetRotation(LYQuaternionToAZQuaternion(rotation)); transform.SetTranslation(LYVec3ToAZVec3(position)); @@ -870,7 +870,7 @@ void CTrackViewSequence::SyncSelectedTracksFromBase() pAnimNode->SetPos(AZVec3ToLYVec3(transform.GetTranslation())); pAnimNode->SetRotation(AZQuaternionToLYQuaternion(transform.GetRotation())); - pAnimNode->SetScale(AZVec3ToLYVec3(transform.GetScale())); + pAnimNode->SetScale(transform.GetUniformScale()); bNothingWasSynced = false; } diff --git a/Code/Tools/SceneAPI/SceneCore/Containers/Utilities/SceneUtilities.cpp b/Code/Tools/SceneAPI/SceneCore/Containers/Utilities/SceneUtilities.cpp index f5bb9d28a6..ac27ed54eb 100644 --- a/Code/Tools/SceneAPI/SceneCore/Containers/Utilities/SceneUtilities.cpp +++ b/Code/Tools/SceneAPI/SceneCore/Containers/Utilities/SceneUtilities.cpp @@ -79,7 +79,7 @@ namespace AZ if (coordinateSystemRule->GetScale() != 1.0f) { float scale = coordinateSystemRule->GetScale(); - matrix.MultiplyByScale(Vector3(scale, scale, scale)); + matrix.MultiplyByScale(Vector3(scale)); } if (!coordinateSystemRule->GetOriginNodeName().empty()) { diff --git a/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowHandler.cpp b/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowHandler.cpp index 09588a606e..640c092070 100644 --- a/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowHandler.cpp +++ b/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowHandler.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace AZ @@ -58,10 +59,11 @@ namespace AZ } else { - AzToolsFramework::Vector3PropertyHandler handler; - handler.ConsumeAttribute(widget->GetTranslationWidget(), attrib, attrValue, debugName); - handler.ConsumeAttribute(widget->GetRotationWidget(), attrib, attrValue, debugName); - handler.ConsumeAttribute(widget->GetScaleWidget(), attrib, attrValue, debugName); + AzToolsFramework::Vector3PropertyHandler vector3Handler; + vector3Handler.ConsumeAttribute(widget->GetTranslationWidget(), attrib, attrValue, debugName); + vector3Handler.ConsumeAttribute(widget->GetRotationWidget(), attrib, attrValue, debugName); + AzToolsFramework::doublePropertySpinboxHandler spinboxHandler; + spinboxHandler.ConsumeAttribute(widget->GetScaleWidget(), attrib, attrValue, debugName); } } @@ -109,4 +111,4 @@ namespace AZ } // namespace SceneAPI } // namespace AZ -#include \ No newline at end of file +#include diff --git a/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowHandler.h b/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowHandler.h index a020ee8198..582f32649e 100644 --- a/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowHandler.h +++ b/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowHandler.h @@ -60,4 +60,4 @@ namespace AZ }; } // namespace SceneUI } // namespace SceneAPI -} // namespace AZ \ No newline at end of file +} // namespace AZ diff --git a/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowWidget.cpp b/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowWidget.cpp index 10e0fd2a68..e8ecaa0c27 100644 --- a/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowWidget.cpp +++ b/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowWidget.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -47,7 +48,7 @@ namespace AZ ExpandedTransform::ExpandedTransform() : m_translation(0, 0, 0) , m_rotation(0, 0, 0) - , m_scale(1, 1, 1) + , m_scale(1) { } @@ -60,14 +61,14 @@ namespace AZ { m_translation = transform.GetTranslation(); m_rotation = transform.GetEulerDegrees(); - m_scale = transform.GetScale(); + m_scale = transform.GetUniformScale(); } void ExpandedTransform::GetTransform(AZ::Transform& transform) const { transform = Transform::CreateTranslation(m_translation); transform *= AZ::ConvertEulerDegreesToTransform(m_rotation); - transform.MultiplyByScale(m_scale); + transform.MultiplyByUniformScale(m_scale); } const AZ::Vector3& ExpandedTransform::GetTranslation() const @@ -90,12 +91,12 @@ namespace AZ m_rotation = rotation; } - const AZ::Vector3& ExpandedTransform::GetScale() const + const float ExpandedTransform::GetScale() const { return m_scale; } - void ExpandedTransform::SetScale(const AZ::Vector3& scale) + void ExpandedTransform::SetScale(const float scale) { m_scale = scale; } @@ -131,7 +132,7 @@ namespace AZ m_rotationWidget->setMaximum(360); m_rotationWidget->setSuffix(" degrees"); - m_scaleWidget = new AzQtComponents::VectorInput(this, 3); + m_scaleWidget = new AzToolsFramework::PropertyDoubleSpinCtrl(this); m_scaleWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); m_scaleWidget->setMinimum(0); m_scaleWidget->setMaximum(10000); @@ -191,13 +192,10 @@ namespace AZ AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&AzToolsFramework::PropertyEditorGUIMessages::RequestWrite, this); }); - QObject::connect(m_scaleWidget, &AzQtComponents::VectorInput::valueChanged, this, [this] + QObject::connect(m_scaleWidget, &AzToolsFramework::PropertyDoubleSpinCtrl::valueChanged, this, [this] { - AzQtComponents::VectorInput* widget = this->GetScaleWidget(); - AZ::Vector3 scale; - - PopulateVector3(widget, scale); - + AzToolsFramework::PropertyDoubleSpinCtrl* widget = this->GetScaleWidget(); + float scale = aznumeric_cast(widget->value()); m_transform.SetScale(scale); AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&AzToolsFramework::PropertyEditorGUIMessages::RequestWrite, this); }); @@ -224,9 +222,7 @@ namespace AZ m_rotationWidget->setValuebyIndex(m_transform.GetRotation().GetY(), 1); m_rotationWidget->setValuebyIndex(m_transform.GetRotation().GetZ(), 2); - m_scaleWidget->setValuebyIndex(m_transform.GetScale().GetX(), 0); - m_scaleWidget->setValuebyIndex(m_transform.GetScale().GetY(), 1); - m_scaleWidget->setValuebyIndex(m_transform.GetScale().GetZ(), 2); + m_scaleWidget->setValue(m_transform.GetScale()); blockSignals(false); } @@ -251,7 +247,7 @@ namespace AZ return m_rotationWidget; } - AzQtComponents::VectorInput* TransformRowWidget::GetScaleWidget() + AzToolsFramework::PropertyDoubleSpinCtrl* TransformRowWidget::GetScaleWidget() { return m_scaleWidget; } diff --git a/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowWidget.h b/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowWidget.h index dc3286f80e..3977d26c7c 100644 --- a/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowWidget.h +++ b/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowWidget.h @@ -21,6 +21,7 @@ #include #include #include + #endif namespace AzQtComponents @@ -28,6 +29,11 @@ namespace AzQtComponents class VectorInput; } +namespace AzToolsFramework +{ + class PropertyDoubleSpinCtrl; +} + namespace AZ { namespace SceneAPI @@ -51,14 +57,14 @@ namespace AZ const AZ::Vector3& GetRotation() const; void SetRotation(const AZ::Vector3& translation); - const AZ::Vector3& GetScale() const; - void SetScale(const AZ::Vector3& scale); + const float GetScale() const; + void SetScale(const float scale); private: AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING AZ::Vector3 m_translation; AZ::Vector3 m_rotation; - AZ::Vector3 m_scale; + float m_scale; AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING }; @@ -78,7 +84,7 @@ namespace AZ AzQtComponents::VectorInput* GetTranslationWidget(); AzQtComponents::VectorInput* GetRotationWidget(); - AzQtComponents::VectorInput* GetScaleWidget(); + AzToolsFramework::PropertyDoubleSpinCtrl* GetScaleWidget(); protected: ExpandedTransform m_transform; @@ -87,7 +93,7 @@ namespace AZ AzQtComponents::VectorInput* m_translationWidget; AzQtComponents::VectorInput* m_rotationWidget; - AzQtComponents::VectorInput* m_scaleWidget; + AzToolsFramework::PropertyDoubleSpinCtrl* m_scaleWidget; }; } // namespace SceneUI } // namespace SceneAPI diff --git a/Code/Tools/SceneAPI/SceneUI/Tests/RowWidgets/TransformRowWidgetTests.cpp b/Code/Tools/SceneAPI/SceneUI/Tests/RowWidgets/TransformRowWidgetTests.cpp index 05082f29fb..cda6582e63 100644 --- a/Code/Tools/SceneAPI/SceneUI/Tests/RowWidgets/TransformRowWidgetTests.cpp +++ b/Code/Tools/SceneAPI/SceneUI/Tests/RowWidgets/TransformRowWidgetTests.cpp @@ -30,7 +30,7 @@ namespace AZ Vector3 m_translation = Vector3(10.0f, 20.0f, 30.0f); Vector3 m_rotation = Vector3(30.0f, 45.0f, 60.0f); - Vector3 m_scale = Vector3(2.0f, 3.0f, 4.0f); + float m_scale = 3.0f; }; TEST_F(TransformRowWidgetTest, GetTranslation_TranslationInMatrix_TranslationCanBeRetrievedDirectly) @@ -83,26 +83,22 @@ namespace AZ TEST_F(TransformRowWidgetTest, GetScale_ScaleInMatrix_ScaleCanBeRetrievedDirectly) { - m_transform = Transform::CreateScale(m_scale); + m_transform = Transform::CreateUniformScale(m_scale); m_expanded.SetTransform(m_transform); - const Vector3& returned = m_expanded.GetScale(); - EXPECT_NEAR(m_scale.GetX(), returned.GetX(), 0.1f); - EXPECT_NEAR(m_scale.GetY(), returned.GetY(), 0.1f); - EXPECT_NEAR(m_scale.GetZ(), returned.GetZ(), 0.1f); + const float returned = m_expanded.GetScale(); + EXPECT_NEAR(m_scale, returned, 0.1f); } TEST_F(TransformRowWidgetTest, GetScale_ScaleInMatrix_ScaleCanBeRetrievedFromTransform) { - m_transform = Transform::CreateScale(m_scale); + m_transform = Transform::CreateUniformScale(m_scale); m_expanded.SetTransform(m_transform); Transform rebuild; m_expanded.GetTransform(rebuild); - Vector3 returned = rebuild.GetScale(); - EXPECT_NEAR(m_scale.GetX(), returned.GetX(), 0.1f); - EXPECT_NEAR(m_scale.GetY(), returned.GetY(), 0.1f); - EXPECT_NEAR(m_scale.GetZ(), returned.GetZ(), 0.1f); + float returned = rebuild.GetUniformScale(); + EXPECT_NEAR(m_scale, returned, 0.1f); } TEST_F(TransformRowWidgetTest, GetTransform_RotateAndTranslateInMatrix_ReconstructedTransformMatchesOriginal) @@ -121,7 +117,7 @@ namespace AZ { Quaternion quaternion = AZ::ConvertEulerDegreesToQuaternion(m_rotation); m_transform = Transform::CreateFromQuaternionAndTranslation(quaternion, m_translation); - m_transform.MultiplyByScale(m_scale); + m_transform.MultiplyByUniformScale(m_scale); m_expanded.SetTransform(m_transform); Transform rebuild; diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp index e1bfaa3872..b55dcf2088 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -178,9 +179,10 @@ namespace MaterialEditor m_shadowCatcherEntity->CreateComponent(AZ::Render::MeshComponentTypeId); m_shadowCatcherEntity->CreateComponent(AZ::Render::MaterialComponentTypeId); m_shadowCatcherEntity->CreateComponent(azrtti_typeid()); + m_shadowCatcherEntity->CreateComponent(azrtti_typeid()); m_shadowCatcherEntity->Activate(); - AZ::TransformBus::Event(m_shadowCatcherEntity->GetId(), &AZ::TransformBus::Events::SetLocalScale, AZ::Vector3{ 100, 100, 1.0 }); + AZ::NonUniformScaleRequestBus::Event(m_shadowCatcherEntity->GetId(), &AZ::NonUniformScaleRequests::SetScale, AZ::Vector3{ 100, 100, 1.0 }); AZ::Data::AssetId shadowCatcherModelAssetId = RPI::AssetUtils::GetAssetIdForProductPath("materialeditor/viewportmodels/plane_1x1.azmodel", RPI::AssetUtils::TraceLevel::Error); AZ::Render::MeshComponentRequestBus::Event(m_shadowCatcherEntity->GetId(), diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/PolygonLightDelegate.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/PolygonLightDelegate.cpp index c2bcc566e0..6ec780c2ab 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/PolygonLightDelegate.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/PolygonLightDelegate.cpp @@ -50,7 +50,6 @@ namespace AZ AZStd::vector vertices = m_shapeBus->GetPolygonPrism()->m_vertexContainer.GetVertices(); Transform transform = GetTransform(); - transform.SetScale(Vector3(transform.GetScale().GetMaxElement())); // Poly Prism only supports uniform scale, so use max element. AZStd::vector transformedVertices; transformedVertices.reserve(vertices.size()); diff --git a/Gems/Blast/Code/Tests/BlastFamilyTest.cpp b/Gems/Blast/Code/Tests/BlastFamilyTest.cpp index 2e6fd7f2bb..7af9f41e9c 100644 --- a/Gems/Blast/Code/Tests/BlastFamilyTest.cpp +++ b/Gems/Blast/Code/Tests/BlastFamilyTest.cpp @@ -137,7 +137,7 @@ namespace Blast .Times(1) .WillOnce(Return(false)); - AZ::Transform transform = AZ::Transform::CreateScale(AZ::Vector3::CreateOne()); + AZ::Transform transform = AZ::Transform::CreateUniformScale(1.0f); blastFamily->Spawn(transform); } diff --git a/Gems/Blast/Code/Tests/Mocks/BlastMocks.h b/Gems/Blast/Code/Tests/Mocks/BlastMocks.h index 88c22cf67f..c9455c2fb3 100644 --- a/Gems/Blast/Code/Tests/Mocks/BlastMocks.h +++ b/Gems/Blast/Code/Tests/Mocks/BlastMocks.h @@ -669,6 +669,9 @@ namespace Blast MOCK_METHOD1(SetLocalScale, void(const AZ::Vector3&)); MOCK_METHOD0(GetLocalScale, AZ::Vector3()); MOCK_METHOD0(GetWorldScale, AZ::Vector3()); + MOCK_METHOD1(SetLocalUniformScale, void(float)); + MOCK_METHOD0(GetLocalUniformScale, float()); + MOCK_METHOD0(GetWorldUniformScale, float()); MOCK_METHOD0(GetParentId, AZ::EntityId()); MOCK_METHOD0(GetParent, TransformInterface*()); MOCK_METHOD1(SetParent, void(AZ::EntityId)); diff --git a/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/RenderUtil.cpp b/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/RenderUtil.cpp index 0aef3f9d4f..3a23241385 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/RenderUtil.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/RenderUtil.cpp @@ -1157,7 +1157,7 @@ namespace MCommon void RenderUtil::RenderSphere(const AZ::Vector3& position, float radius, const MCore::RGBAColor& color) { // setup the world space matrix of the sphere - AZ::Transform sphereTransform = AZ::Transform::CreateScale(AZ::Vector3(radius, radius, radius)); + AZ::Transform sphereTransform = AZ::Transform::CreateUniformScale(radius); sphereTransform.SetTranslation(position); // render the sphere diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/Util.h b/Gems/GradientSignal/Code/Include/GradientSignal/Util.h index 4e15bdc293..1a8bb00eba 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/Util.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/Util.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -64,15 +65,15 @@ namespace GradientSignal AZ::LerpInverse(bounds.GetMin().GetZ(), bounds.GetMax().GetZ(), point.GetZ())); } - inline void GetObbParamsFromShape(const AZ::EntityId& entity, AZ::Aabb& bounds, AZ::Transform& worldToBoundsTransform) + inline void GetObbParamsFromShape(const AZ::EntityId& entity, AZ::Aabb& bounds, AZ::Matrix3x4& worldToBoundsTransform) { //get bound and transform data for associated shape bounds = AZ::Aabb::CreateNull(); - worldToBoundsTransform = AZ::Transform::CreateIdentity(); + AZ::Transform transform = AZ::Transform::CreateIdentity(); if (entity.IsValid()) { - LmbrCentral::ShapeComponentRequestsBus::Event(entity, &LmbrCentral::ShapeComponentRequestsBus::Events::GetTransformAndLocalBounds, worldToBoundsTransform, bounds); - worldToBoundsTransform.Invert(); + LmbrCentral::ShapeComponentRequestsBus::Event(entity, &LmbrCentral::ShapeComponentRequestsBus::Events::GetTransformAndLocalBounds, transform, bounds); + worldToBoundsTransform = AZ::Matrix3x4::CreateFromTransform(transform.GetInverse()); } } diff --git a/Gems/GradientSignal/Code/Source/Components/GradientTransformComponent.cpp b/Gems/GradientSignal/Code/Source/Components/GradientTransformComponent.cpp index 0b967a6957..6cee088a1c 100644 --- a/Gems/GradientSignal/Code/Source/Components/GradientTransformComponent.cpp +++ b/Gems/GradientSignal/Code/Source/Components/GradientTransformComponent.cpp @@ -333,7 +333,7 @@ namespace GradientSignal AZStd::lock_guard lock(m_cacheMutex); //transforming coordinate into "local" relative space of shape bounds - outUVW = m_shapeTransformInverse.TransformPoint(inPosition); + outUVW = m_shapeTransformInverse * inPosition; if (!m_configuration.m_advancedMode || !m_configuration.m_is3d) { @@ -387,7 +387,7 @@ namespace GradientSignal void GradientTransformComponent::GetGradientEncompassingBounds(AZ::Aabb& bounds) const { bounds = m_shapeBounds; - bounds.ApplyTransform(m_shapeTransformInverse.GetInverse()); + bounds.ApplyMatrix3x4(m_shapeTransformInverse.GetInverseFull()); } void GradientTransformComponent::OnCompositionChanged() @@ -500,10 +500,11 @@ namespace GradientSignal m_shapeBounds = AZ::Aabb::CreateFromMinMax(-m_configuration.m_bounds * 0.5f, m_configuration.m_bounds * 0.5f); //rebuild transform from parameters - AZ::Quaternion rotation; - rotation.SetFromEulerDegrees(m_configuration.m_rotate); - const AZ::Transform shapeTransformFinal(m_configuration.m_translate, rotation, m_configuration.m_scale); - m_shapeTransformInverse = shapeTransformFinal.GetInverse(); + AZ::Matrix3x4 shapeTransformFinal; + shapeTransformFinal.SetFromEulerDegrees(m_configuration.m_rotate); + shapeTransformFinal.SetTranslation(m_configuration.m_translate); + shapeTransformFinal.MultiplyByScale(m_configuration.m_scale); + m_shapeTransformInverse = shapeTransformFinal.GetInverseFull(); } AZ::EntityId GradientTransformComponent::GetShapeEntityId() const diff --git a/Gems/GradientSignal/Code/Source/Components/GradientTransformComponent.h b/Gems/GradientSignal/Code/Source/Components/GradientTransformComponent.h index 15aaf40494..5955da95c7 100644 --- a/Gems/GradientSignal/Code/Source/Components/GradientTransformComponent.h +++ b/Gems/GradientSignal/Code/Source/Components/GradientTransformComponent.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -172,8 +173,8 @@ namespace GradientSignal mutable AZStd::recursive_mutex m_cacheMutex; GradientTransformConfig m_configuration; AZ::Aabb m_shapeBounds = AZ::Aabb::CreateNull(); - AZ::Transform m_shapeTransformInverse = AZ::Transform::CreateIdentity(); + AZ::Matrix3x4 m_shapeTransformInverse = AZ::Matrix3x4::CreateIdentity(); LmbrCentral::DependencyMonitor m_dependencyMonitor; AZStd::atomic_bool m_dirty{ false }; }; -} //namespace GradientSignal \ No newline at end of file +} //namespace GradientSignal diff --git a/Gems/LmbrCentral/Code/Source/Animation/AttachmentComponent.cpp b/Gems/LmbrCentral/Code/Source/Animation/AttachmentComponent.cpp index 537585f48c..efef5761bc 100644 --- a/Gems/LmbrCentral/Code/Source/Animation/AttachmentComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Animation/AttachmentComponent.cpp @@ -242,14 +242,14 @@ namespace LmbrCentral { // apply offset in world-space finalTransform = m_targetEntityTransform * m_targetBoneTransform; - finalTransform.SetScale(AZ::Vector3::CreateOne()); + finalTransform.SetUniformScale(1.0f); finalTransform *= m_targetOffset; } else if (m_scaleSource == AttachmentConfiguration::ScaleSource::TargetEntityScale) { // apply offset in target-entity-space (ignoring bone scale) AZ::Transform boneNoScale = m_targetBoneTransform; - boneNoScale.SetScale(AZ::Vector3::CreateOne()); + boneNoScale.SetUniformScale(1.0f); finalTransform = m_targetEntityTransform * boneNoScale * m_targetOffset; } diff --git a/Gems/LmbrCentral/Code/Source/Animation/EditorAttachmentComponent.cpp b/Gems/LmbrCentral/Code/Source/Animation/EditorAttachmentComponent.cpp index 337da51159..6190f976a0 100644 --- a/Gems/LmbrCentral/Code/Source/Animation/EditorAttachmentComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Animation/EditorAttachmentComponent.cpp @@ -124,7 +124,7 @@ namespace LmbrCentral { AZ::Transform offset = AZ::ConvertEulerDegreesToTransform(m_rotationOffset); offset.SetTranslation(m_positionOffset); - offset.MultiplyByScale(m_scaleOffset); + offset.MultiplyByUniformScale(m_scaleOffset.GetMaxElement()); return offset; } diff --git a/Gems/LmbrCentral/Code/Source/Scripting/EditorLookAtComponent.cpp b/Gems/LmbrCentral/Code/Source/Scripting/EditorLookAtComponent.cpp index ab22514cf8..7a2cc52627 100644 --- a/Gems/LmbrCentral/Code/Source/Scripting/EditorLookAtComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Scripting/EditorLookAtComponent.cpp @@ -169,22 +169,24 @@ namespace LmbrCentral { AZ::TransformNotificationBus::MultiHandler::BusDisconnect(GetEntityId()); { - AZ::Transform currentTM = AZ::Transform::CreateIdentity(); - EBUS_EVENT_ID_RESULT(currentTM, GetEntityId(), AZ::TransformBus, GetWorldTM); - AZ::Vector3 currentScale = currentTM.ExtractScale(); + AZ::Transform sourceTM = AZ::Transform::CreateIdentity(); + AZ::TransformBus::EventResult(sourceTM, GetEntityId(), &AZ::TransformBus::Events::GetWorldTM); AZ::Transform targetTM = AZ::Transform::CreateIdentity(); - EBUS_EVENT_ID_RESULT(targetTM, m_targetId, AZ::TransformBus, GetWorldTM); + AZ::TransformBus::EventResult(targetTM, m_targetId, &AZ::TransformBus::Events::GetWorldTM); AZ::Transform lookAtTransform = AZ::Transform::CreateLookAt( - currentTM.GetTranslation(), + sourceTM.GetTranslation(), targetTM.GetTranslation(), m_forwardAxis ); - lookAtTransform.MultiplyByScale(currentScale); + // update the rotation and translation for sourceTM based on lookAtTransform, but leave scale unchanged + sourceTM.SetRotation(lookAtTransform.GetRotation()); + sourceTM.SetTranslation(lookAtTransform.GetTranslation()); EBUS_EVENT_ID(GetEntityId(), AZ::TransformBus, SetWorldTM, lookAtTransform); + AZ::TransformBus::Event(GetEntityId(), &AZ::TransformBus::Events::SetWorldTM, sourceTM); } AZ::TransformNotificationBus::MultiHandler::BusConnect(GetEntityId()); } diff --git a/Gems/LmbrCentral/Code/Source/Shape/BoxShape.cpp b/Gems/LmbrCentral/Code/Source/Shape/BoxShape.cpp index 2830f514fe..a2ee4d986f 100644 --- a/Gems/LmbrCentral/Code/Source/Shape/BoxShape.cpp +++ b/Gems/LmbrCentral/Code/Source/Shape/BoxShape.cpp @@ -251,7 +251,7 @@ namespace LmbrCentral const AZ::Transform& currentTransform, const BoxShapeConfig& configuration, const AZ::Vector3& currentNonUniformScale) { AZ::Transform worldFromLocalNormalized = currentTransform; - const float entityScale = worldFromLocalNormalized.ExtractScale().GetMaxElement(); + const float entityScale = worldFromLocalNormalized.ExtractUniformScale(); m_currentPosition = worldFromLocalNormalized.GetTranslation(); m_scaledDimensions = configuration.m_dimensions * currentNonUniformScale * entityScale; diff --git a/Gems/LmbrCentral/Code/Source/Shape/PolygonPrismShape.cpp b/Gems/LmbrCentral/Code/Source/Shape/PolygonPrismShape.cpp index 89c5028e93..db5aaf097c 100644 --- a/Gems/LmbrCentral/Code/Source/Shape/PolygonPrismShape.cpp +++ b/Gems/LmbrCentral/Code/Source/Shape/PolygonPrismShape.cpp @@ -437,22 +437,18 @@ namespace LmbrCentral const float height = polygonPrism.GetHeight(); const AZ::Vector3& nonUniformScale = polygonPrism.GetNonUniformScale(); - AZ::Transform worldFromLocalUniformScale = worldFromLocal; - const float entityScale = worldFromLocalUniformScale.ExtractScale().GetMaxElement(); - worldFromLocalUniformScale *= AZ::Transform::CreateScale(AZ::Vector3(entityScale)); - AZ::Aabb aabb = AZ::Aabb::CreateNull(); // check base of prism for (const AZ::Vector2& vertex : vertexContainer.GetVertices()) { - aabb.AddPoint(worldFromLocalUniformScale.TransformPoint(nonUniformScale * AZ::Vector3(vertex.GetX(), vertex.GetY(), 0.0f))); + aabb.AddPoint(worldFromLocal.TransformPoint(nonUniformScale * AZ::Vector3(vertex.GetX(), vertex.GetY(), 0.0f))); } // check top of prism // set aabb to be height of prism - ensure entire polygon prism shape is enclosed in aabb for (const AZ::Vector2& vertex : vertexContainer.GetVertices()) { - aabb.AddPoint(worldFromLocalUniformScale.TransformPoint(nonUniformScale * AZ::Vector3(vertex.GetX(), vertex.GetY(), height))); + aabb.AddPoint(worldFromLocal.TransformPoint(nonUniformScale * AZ::Vector3(vertex.GetX(), vertex.GetY(), height))); } return aabb; @@ -468,14 +464,10 @@ namespace LmbrCentral const AZStd::vector& vertices = polygonPrism.m_vertexContainer.GetVertices(); const size_t vertexCount = vertices.size(); - AZ::Transform worldFromLocalWithUniformScale = worldFromLocal; - const float transformScale = worldFromLocalWithUniformScale.ExtractScale().GetMaxElement(); - worldFromLocalWithUniformScale *= AZ::Transform::CreateScale(AZ::Vector3(transformScale)); - // transform point to local space // it's fine to invert the transform including scale here, because it won't affect whether the point is inside the prism const AZ::Vector3 localPoint = - worldFromLocalWithUniformScale.GetInverse().TransformPoint(point) / polygonPrism.GetNonUniformScale(); + worldFromLocal.GetInverse().TransformPoint(point) / polygonPrism.GetNonUniformScale(); // ensure the point is not above or below the prism (in its local space) if (localPoint.GetZ() < 0.0f || localPoint.GetZ() > polygonPrism.GetHeight()) @@ -534,7 +526,7 @@ namespace LmbrCentral // but inverting any scale in the transform would mess up the distance, so extract that first and apply scale separately to the // prism AZ::Transform worldFromLocalNoScale = worldFromLocal; - const float transformScale = worldFromLocalNoScale.ExtractScale().GetMaxElement(); + const float transformScale = worldFromLocalNoScale.ExtractUniformScale(); const AZ::Vector3 combinedScale = transformScale * nonUniformScale; const float scaledHeight = height * combinedScale.GetZ(); @@ -610,9 +602,9 @@ namespace LmbrCentral } // transform ray into local space - AZ::Transform worldFromLocalNomalized = worldFromLocal; - const float entityScale = worldFromLocalNomalized.ExtractScale().GetMaxElement(); - const AZ::Transform localFromWorldNormalized = worldFromLocalNomalized.GetInverse(); + AZ::Transform worldFromLocalNormalized = worldFromLocal; + const float entityScale = worldFromLocalNormalized.ExtractUniformScale(); + const AZ::Transform localFromWorldNormalized = worldFromLocalNormalized.GetInverse(); const float rayLength = 1000.0f; const AZ::Vector3 localSrc = localFromWorldNormalized.TransformPoint(src); const AZ::Vector3 localDir = localFromWorldNormalized.TransformVector(dir); diff --git a/Gems/LmbrCentral/Code/Source/Shape/ShapeDisplay.h b/Gems/LmbrCentral/Code/Source/Shape/ShapeDisplay.h index 09088f06cd..3591ecde36 100644 --- a/Gems/LmbrCentral/Code/Source/Shape/ShapeDisplay.h +++ b/Gems/LmbrCentral/Code/Source/Shape/ShapeDisplay.h @@ -42,15 +42,10 @@ namespace LmbrCentral return; } - // only uniform scale is supported in physics so the debug visuals reflect this fact - AZ::Transform worldFromLocalWithUniformScale = worldFromLocal; - const AZ::Vector3 scale = worldFromLocalWithUniformScale.ExtractScale(); - worldFromLocalWithUniformScale.MultiplyByScale(AZ::Vector3(scale.GetMaxElement())); - - debugDisplay.PushMatrix(worldFromLocalWithUniformScale); + debugDisplay.PushMatrix(worldFromLocal); drawShape(debugDisplay); debugDisplay.PopMatrix(); } -} // namespace LmbrCentral \ No newline at end of file +} // namespace LmbrCentral diff --git a/Gems/LmbrCentral/Code/Source/Shape/TubeShape.cpp b/Gems/LmbrCentral/Code/Source/Shape/TubeShape.cpp index 6ca2e55de8..2691ab1557 100644 --- a/Gems/LmbrCentral/Code/Source/Shape/TubeShape.cpp +++ b/Gems/LmbrCentral/Code/Source/Shape/TubeShape.cpp @@ -216,11 +216,7 @@ namespace LmbrCentral return AZ::Aabb::CreateNull(); } - AZ::Transform worldFromLocalUniformScale = m_currentTransform; - const float maxScale = worldFromLocalUniformScale.ExtractScale().GetMaxElement(); - worldFromLocalUniformScale *= AZ::Transform::CreateScale(AZ::Vector3(maxScale)); - - return CalculateTubeBounds(*this, worldFromLocalUniformScale); + return CalculateTubeBounds(*this, m_currentTransform); } void TubeShape::GetTransformAndLocalBounds(AZ::Transform& transform, AZ::Aabb& bounds) @@ -236,46 +232,38 @@ namespace LmbrCentral return false; } - AZ::Transform worldFromLocalNormalized = m_currentTransform; - const AZ::Vector3 scale = AZ::Vector3(worldFromLocalNormalized.ExtractScale().GetMaxElement()); - const AZ::Transform localFromWorldNormalized = worldFromLocalNormalized.GetInverse(); - const AZ::Vector3 localPoint = localFromWorldNormalized.TransformPoint(point) * scale.GetReciprocal(); + const float scale = m_currentTransform.GetUniformScale(); + const AZ::Vector3 localPoint = m_currentTransform.GetInverse().TransformPoint(point); const auto address = m_spline->GetNearestAddressPosition(localPoint).m_splineAddress; const float radiusSq = powf(m_radius, 2.0f); const float variableRadiusSq = powf(m_variableRadius.GetElementInterpolated(address, Lerpf), 2.0f); - return (m_spline->GetPosition(address) - localPoint).GetLengthSq() < (radiusSq + variableRadiusSq) * - scale.GetMaxElement(); + return (m_spline->GetPosition(address) - localPoint).GetLengthSq() < (radiusSq + variableRadiusSq) * scale; } float TubeShape::DistanceSquaredFromPoint(const AZ::Vector3& point) { - AZ::Transform worldFromLocalNormalized = m_currentTransform; - const AZ::Vector3 maxScale = AZ::Vector3(worldFromLocalNormalized.ExtractScale().GetMaxElement()); - const AZ::Transform localFromWorldNormalized = worldFromLocalNormalized.GetInverse(); - const AZ::Vector3 localPoint = localFromWorldNormalized.TransformPoint(point) * maxScale.GetReciprocal(); + const float scale = m_currentTransform.GetUniformScale(); + const AZ::Transform localFromWorld = m_currentTransform.GetInverse(); + const AZ::Vector3 localPoint = localFromWorld.TransformPoint(point); const auto splineQueryResult = m_spline->GetNearestAddressPosition(localPoint); const float variableRadius = m_variableRadius.GetElementInterpolated(splineQueryResult.m_splineAddress, Lerpf); - return powf((sqrtf(splineQueryResult.m_distanceSq) - (m_radius + variableRadius)) * maxScale.GetMaxElement(), 2.0f); + return powf((sqrtf(splineQueryResult.m_distanceSq) - (m_radius + variableRadius)) * scale, 2.0f); } bool TubeShape::IntersectRay(const AZ::Vector3& src, const AZ::Vector3& dir, float& distance) { - AZ::Transform transformUniformScale = m_currentTransform; - const float maxScale = transformUniformScale.ExtractScale().GetMaxElement(); - transformUniformScale *= AZ::Transform::CreateScale(AZ::Vector3(maxScale)); - - const auto splineQueryResult = IntersectSpline(transformUniformScale, src, dir, *m_spline); + const auto splineQueryResult = IntersectSpline(m_currentTransform, src, dir, *m_spline); const float variableRadius = m_variableRadius.GetElementInterpolated( splineQueryResult.m_splineAddress, Lerpf); const float totalRadius = m_radius + variableRadius; - distance = (splineQueryResult.m_rayDistance - totalRadius) * m_currentTransform.GetScale().GetMaxElement(); + distance = (splineQueryResult.m_rayDistance - totalRadius) * m_currentTransform.GetUniformScale(); return static_cast(sqrtf(splineQueryResult.m_distanceSq)) < totalRadius; } diff --git a/Gems/LmbrCentral/Code/Tests/BoxShapeTest.cpp b/Gems/LmbrCentral/Code/Tests/BoxShapeTest.cpp index fd1e4ae4f1..24ee8ace2f 100644 --- a/Gems/LmbrCentral/Code/Tests/BoxShapeTest.cpp +++ b/Gems/LmbrCentral/Code/Tests/BoxShapeTest.cpp @@ -262,7 +262,7 @@ namespace UnitTest AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisY(), AZ::Constants::QuarterPi), AZ::Vector3(0.0f, 0.0f, 5.0f)) * - AZ::Transform::CreateScale(AZ::Vector3(3.0f)), + AZ::Transform::CreateUniformScale(3.0f), AZ::Vector3(2.0f, 4.0f, 1.0f), entity); bool rayHit = false; @@ -295,7 +295,7 @@ namespace UnitTest { AZ::Entity entity; AZ::Transform transform = AZ::Transform::CreateTranslation(AZ::Vector3(2.0f, -5.0f, 3.0f)); - transform.MultiplyByScale(AZ::Vector3(0.5f)); + transform.MultiplyByUniformScale(0.5f); const AZ::Vector3 dimensions(2.2f, 1.8f, 0.4f); const AZ::Vector3 nonUniformScale(0.2f, 2.6f, 1.2f); CreateBoxWithNonUniformScale(transform, dimensions, nonUniformScale, entity); @@ -340,7 +340,7 @@ namespace UnitTest AZ::Entity entity; AZ::Transform transform = AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion(0.50f, 0.10f, 0.02f, 0.86f), AZ::Vector3(4.0f, 1.0f, -2.0f)); - transform.MultiplyByScale(AZ::Vector3(1.5f)); + transform.MultiplyByUniformScale(1.5f); const AZ::Vector3 dimensions(1.2f, 0.7f, 2.1f); const AZ::Vector3 nonUniformScale(0.8f, 0.6f, 0.7f); CreateBoxWithNonUniformScale(transform, dimensions, nonUniformScale, entity); @@ -433,7 +433,7 @@ namespace UnitTest AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisY(), AZ::Constants::QuarterPi), AZ::Vector3::CreateZero()) * - AZ::Transform::CreateScale(AZ::Vector3(3.0f)), + AZ::Transform::CreateUniformScale(3.0f), AZ::Vector3(2.0f, 4.0f, 1.0f), entity); AZ::Aabb aabb; @@ -483,7 +483,7 @@ namespace UnitTest AZ::Transform transformIn = AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::QuarterPi) * AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisY(), AZ::Constants::QuarterPi), AZ::Vector3(9.0f, 11.0f, 13.0f)); - transformIn.MultiplyByScale(AZ::Vector3(3.0f)); + transformIn.MultiplyByUniformScale(3.0f); CreateBox(transformIn, AZ::Vector3(1.5f, 3.5f, 5.5f), entity); AZ::Transform transformOut; @@ -500,7 +500,7 @@ namespace UnitTest AZ::Entity entity; AZ::Transform transformIn = AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion(0.62f, 0.62f, 0.14f, 0.46f), AZ::Vector3(0.8f, -1.2f, 2.7f)); - transformIn.MultiplyByScale(AZ::Vector3(2.0f)); + transformIn.MultiplyByUniformScale(2.0f); const AZ::Vector3 nonUniformScale(1.5f, 2.0f, 0.4f); const AZ::Vector3 boxDimensions(2.0f, 1.7f, 0.5f); CreateBoxWithNonUniformScale(transformIn, nonUniformScale, boxDimensions, entity); @@ -531,7 +531,7 @@ namespace UnitTest AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisZ(), AZ::Constants::QuarterPi), AZ::Vector3(23.0f, 12.0f, 40.0f)) * - AZ::Transform::CreateScale(AZ::Vector3(3.0f)), + AZ::Transform::CreateUniformScale(3.0f), AZ::Vector3(2.0f, 6.0f, 3.5f), entity); // test some pairs of nearby points which should be just either side of the surface of the box @@ -551,7 +551,7 @@ namespace UnitTest AZ::Transform::CreateTranslation(AZ::Vector3(23.0f, 12.0f, 40.0f)) * AZ::Transform::CreateRotationX(-AZ::Constants::QuarterPi) * AZ::Transform::CreateRotationZ(AZ::Constants::QuarterPi) * - AZ::Transform::CreateScale(AZ::Vector3(2.0f)), + AZ::Transform::CreateUniformScale(2.0f), AZ::Vector3(4.0f, 7.0f, 3.5f), entity); // test some pairs of nearby points which should be just either side of the surface of the box @@ -588,8 +588,8 @@ namespace UnitTest CreateBox( AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 37.0f, 32.0f)) * AZ::Transform::CreateRotationZ(AZ::Constants::QuarterPi) * - AZ::Transform::CreateScale(AZ::Vector3(3.0f, 1.0f, 1.0f)), - AZ::Vector3(4.0f, 2.0f, 10.0f), entity); + AZ::Transform::CreateUniformScale(2.0f), + AZ::Vector3(6.0f, 1.0f, 5.0f), entity); float distance; LmbrCentral::ShapeComponentRequestsBus::EventResult( @@ -606,8 +606,8 @@ namespace UnitTest AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 37.0f, 32.0f)) * AZ::Transform::CreateRotationX(AZ::Constants::HalfPi) * AZ::Transform::CreateRotationY(AZ::Constants::HalfPi) * - AZ::Transform::CreateScale(AZ::Vector3(3.0f, 1.0f, 1.0f)), - AZ::Vector3(4.0f, 2.0f, 10.0f), entity); + AZ::Transform::CreateUniformScale(0.5f), + AZ::Vector3(24.0f, 4.0f, 20.0f), entity); float distance; LmbrCentral::ShapeComponentRequestsBus::EventResult( @@ -621,7 +621,7 @@ namespace UnitTest AZ::Entity entity; AZ::Transform transform = AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion::CreateRotationY(AZ::DegToRad(30.0f)), AZ::Vector3(3.0f, 4.0f, 5.0f)); - transform.MultiplyByScale(AZ::Vector3(2.0f)); + transform.MultiplyByUniformScale(2.0f); const AZ::Vector3 dimensions(2.0f, 3.0f, 1.5f); const AZ::Vector3 nonUniformScale(1.4f, 2.2f, 0.8f); CreateBoxWithNonUniformScale(transform, nonUniformScale, dimensions, entity); @@ -638,7 +638,7 @@ namespace UnitTest AZ::Entity entity; AZ::Transform transform = AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion(0.70f, 0.10f, 0.34f, 0.62f), AZ::Vector3(3.0f, -1.0f, 2.0f)); - transform.MultiplyByScale(AZ::Vector3(2.0f)); + transform.MultiplyByUniformScale(2.0f); const AZ::Vector3 dimensions(1.2f, 0.8f, 1.7f); const AZ::Vector3 nonUniformScale(2.4f, 1.3f, 1.8f); CreateBoxWithNonUniformScale(transform, nonUniformScale, dimensions, entity); diff --git a/Gems/LmbrCentral/Code/Tests/CapsuleShapeTest.cpp b/Gems/LmbrCentral/Code/Tests/CapsuleShapeTest.cpp index 9b57cd46c7..3274585e63 100644 --- a/Gems/LmbrCentral/Code/Tests/CapsuleShapeTest.cpp +++ b/Gems/LmbrCentral/Code/Tests/CapsuleShapeTest.cpp @@ -144,7 +144,7 @@ namespace UnitTest CreateCapsule( AZ::Transform::CreateTranslation(AZ::Vector3(-4.0f, -12.0f, -3.0f)) * AZ::Transform::CreateRotationX(AZ::Constants::HalfPi) * - AZ::Transform::CreateScale(AZ::Vector3(6.0f)), + AZ::Transform::CreateUniformScale(6.0f), 0.25f, 1.5f, entity); bool rayHit = false; @@ -208,7 +208,7 @@ namespace UnitTest TEST_F(CapsuleShapeTest, GetAabb3) { AZ::Entity entity; - CreateCapsule(AZ::Transform::CreateScale(AZ::Vector3(3.5f)), 2.0f, 4.0f, entity); + CreateCapsule(AZ::Transform::CreateUniformScale(3.5f), 2.0f, 4.0f, entity); AZ::Aabb aabb; LmbrCentral::ShapeComponentRequestsBus::EventResult( @@ -224,7 +224,7 @@ namespace UnitTest AZ::Entity entity; CreateCapsule( AZ::Transform::CreateTranslation(AZ::Vector3(5.0f, 20.0f, 0.0f)) * - AZ::Transform::CreateScale(AZ::Vector3(2.5f)), 1.0f, 5.0f, entity); + AZ::Transform::CreateUniformScale(2.5f), 1.0f, 5.0f, entity); AZ::Aabb aabb; LmbrCentral::ShapeComponentRequestsBus::EventResult( @@ -255,7 +255,7 @@ namespace UnitTest AZ::Transform transformIn = AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::HalfPi) * AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisY(), AZ::Constants::QuarterPi), AZ::Vector3(-10.0f, -10.0f, 0.0f)); - transformIn.MultiplyByScale(AZ::Vector3(3.0f)); + transformIn.MultiplyByUniformScale(3.0f); CreateCapsule(transformIn, 5.0f, 2.0f, entity); AZ::Transform transformOut; @@ -273,7 +273,7 @@ namespace UnitTest AZ::Transform transformIn = AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::HalfPi) * AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisY(), AZ::Constants::QuarterPi), AZ::Vector3(-10.0f, -10.0f, 0.0f)); - transformIn.MultiplyByScale(AZ::Vector3(3.0f)); + transformIn.MultiplyByUniformScale(3.0f); CreateCapsule(transformIn, 2.0f, 5.0f, entity); AZ::Transform transformOut; @@ -291,7 +291,7 @@ namespace UnitTest AZ::Entity entity; CreateCapsule( AZ::Transform::CreateTranslation(AZ::Vector3(27.0f, 28.0f, 38.0f)) * - AZ::Transform::CreateScale(AZ::Vector3(2.5f, 1.0f, 1.0f)), // test max scale + AZ::Transform::CreateUniformScale(2.5f), 0.5f, 2.0f, entity); bool inside; @@ -309,7 +309,7 @@ namespace UnitTest AZ::Transform::CreateTranslation(AZ::Vector3(27.0f, 28.0f, 38.0f)) * AZ::Transform::CreateRotationX(AZ::Constants::HalfPi) * AZ::Transform::CreateRotationY(AZ::Constants::QuarterPi) * - AZ::Transform::CreateScale(AZ::Vector3(0.5f)), + AZ::Transform::CreateUniformScale(0.5f), 0.5f, 2.0f, entity); bool inside; @@ -327,7 +327,7 @@ namespace UnitTest AZ::Transform::CreateTranslation(AZ::Vector3(27.0f, 28.0f, 38.0f)) * AZ::Transform::CreateRotationX(AZ::Constants::HalfPi) * AZ::Transform::CreateRotationY(AZ::Constants::QuarterPi) * - AZ::Transform::CreateScale(AZ::Vector3(2.0f)), + AZ::Transform::CreateUniformScale(2.0f), 0.5f, 4.0f, entity); float distance; @@ -345,7 +345,7 @@ namespace UnitTest AZ::Transform::CreateTranslation(AZ::Vector3(27.0f, 28.0f, 38.0f)) * AZ::Transform::CreateRotationX(AZ::Constants::HalfPi) * AZ::Transform::CreateRotationY(AZ::Constants::QuarterPi) * - AZ::Transform::CreateScale(AZ::Vector3(2.0f)), + AZ::Transform::CreateUniformScale(2.0f), 0.5f, 4.0f, entity); float distance; diff --git a/Gems/LmbrCentral/Code/Tests/CylinderShapeTest.cpp b/Gems/LmbrCentral/Code/Tests/CylinderShapeTest.cpp index 893ed3fcac..115abcf5ff 100644 --- a/Gems/LmbrCentral/Code/Tests/CylinderShapeTest.cpp +++ b/Gems/LmbrCentral/Code/Tests/CylinderShapeTest.cpp @@ -138,7 +138,7 @@ namespace UnitTest { AZ::Transform::CreateTranslation(AZ::Vector3(-14.0f, -14.0f, -1.0f)) * AZ::Transform::CreateRotationY(AZ::Constants::HalfPi) * AZ::Transform::CreateRotationZ(AZ::Constants::HalfPi) * - AZ::Transform::CreateScale(AZ::Vector3(4.0f)), + AZ::Transform::CreateUniformScale(4.0f), 1.0f, 1.25f }, // Result: hit, distance, epsilon { true, 2.5f, 1e-2f } @@ -203,7 +203,7 @@ namespace UnitTest // Test case 2 { // Cylinder: transform, radius, height { AZ::Transform::CreateTranslation(AZ::Vector3(-10.0f, -10.0f, 10.0f)) * - AZ::Transform::CreateScale(AZ::Vector3(3.5f)), + AZ::Transform::CreateUniformScale(3.5f), 1.0f, 5.0f }, // AABB: min, max { AZ::Vector3(-13.5f, -13.5f, 1.25f), AZ::Vector3(-6.5f, -6.5f, 18.75f) } }, @@ -236,7 +236,7 @@ namespace UnitTest { AZ::Vector3(-5.0f, -5.0f, -0.5f), AZ::Vector3(5.0f, 5.0f, 0.5f) } }, // Test case 1 { // Cylinder: transform, radius, height - { AZ::Transform::CreateTranslation(AZ::Vector3(-10.0f, -10.0f, 10.0f)) * AZ::Transform::CreateScale(AZ::Vector3(3.5f)), + { AZ::Transform::CreateTranslation(AZ::Vector3(-10.0f, -10.0f, 10.0f)) * AZ::Transform::CreateUniformScale(3.5f), 5.0f, 5.0f }, // Local bounds: min, max { AZ::Vector3(-5.0f, -5.0f, -2.5f), AZ::Vector3(5.0f, 5.0f, 2.5f) } }, @@ -264,7 +264,7 @@ namespace UnitTest // Test case 0 { // Cylinder: transform, radius, height {AZ::Transform::CreateTranslation(AZ::Vector3(27.0f, 28.0f, 38.0f)) * - AZ::Transform::CreateScale(AZ::Vector3(2.5f, 1.0f, 1.0f)), // test max scale + AZ::Transform::CreateUniformScale(2.5f), 0.5f, 2.0f}, // Point AZ::Vector3(27.0f, 28.5f, 40.0f), @@ -275,7 +275,7 @@ namespace UnitTest {AZ::Transform::CreateTranslation(AZ::Vector3(27.0f, 28.0f, 38.0f)) * AZ::Transform::CreateRotationX(AZ::Constants::HalfPi) * AZ::Transform::CreateRotationY(AZ::Constants::QuarterPi) * - AZ::Transform::CreateScale(AZ::Vector3(0.5f)), + AZ::Transform::CreateUniformScale(0.5f), 0.5f, 2.0f}, // Point AZ::Vector3(27.0f, 28.155f, 37.82f), @@ -316,7 +316,7 @@ namespace UnitTest { AZ::Transform::CreateTranslation(AZ::Vector3(27.0f, 28.0f, 38.0f)) * AZ::Transform::CreateRotationX(AZ::Constants::HalfPi) * AZ::Transform::CreateRotationY(AZ::Constants::QuarterPi) * - AZ::Transform::CreateScale(AZ::Vector3(2.0f)), + AZ::Transform::CreateUniformScale(2.0f), 0.5f, 4.0f }, // Point AZ::Vector3(27.0f, 28.0f, 41.0f), @@ -327,7 +327,7 @@ namespace UnitTest { AZ::Transform::CreateTranslation(AZ::Vector3(27.0f, 28.0f, 38.0f)) * AZ::Transform::CreateRotationX(AZ::Constants::HalfPi) * AZ::Transform::CreateRotationY(AZ::Constants::QuarterPi) * - AZ::Transform::CreateScale(AZ::Vector3(2.0f)), + AZ::Transform::CreateUniformScale(2.0f), 0.5f, 4.0f }, // Point AZ::Vector3(22.757f, 32.243f, 38.0f), diff --git a/Gems/LmbrCentral/Code/Tests/DiskShapeTest.cpp b/Gems/LmbrCentral/Code/Tests/DiskShapeTest.cpp index 4a39cd966e..c3683cc33b 100644 --- a/Gems/LmbrCentral/Code/Tests/DiskShapeTest.cpp +++ b/Gems/LmbrCentral/Code/Tests/DiskShapeTest.cpp @@ -307,7 +307,7 @@ namespace UnitTest AZ::Entity entity; CreateDisk( AZ::Transform::CreateTranslation(AZ::Vector3(100.0f, 200.0f, 300.0f)) * - AZ::Transform::CreateScale(AZ::Vector3(2.5f)), + AZ::Transform::CreateUniformScale(2.5f), 0.5f, entity); AZ::Aabb aabb; diff --git a/Gems/LmbrCentral/Code/Tests/PolygonPrismShapeTest.cpp b/Gems/LmbrCentral/Code/Tests/PolygonPrismShapeTest.cpp index 46c3da90ab..f12ca69425 100644 --- a/Gems/LmbrCentral/Code/Tests/PolygonPrismShapeTest.cpp +++ b/Gems/LmbrCentral/Code/Tests/PolygonPrismShapeTest.cpp @@ -329,7 +329,7 @@ namespace UnitTest AZ::Entity entity; AZ::Transform transform = AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion::CreateRotationY(AZ::DegToRad(45.0f)), AZ::Vector3(3.0f, 4.0f, 5.0f)); - transform.MultiplyByScale(AZ::Vector3(1.5f, 1.5f, 1.5f)); + transform.MultiplyByUniformScale(1.5f); const float height = 1.2f; const AZ::Vector3 nonUniformScale(2.0f, 1.2f, 0.5f); const AZStd::vector vertices = @@ -447,7 +447,7 @@ namespace UnitTest AZ::Entity entity; AZ::Transform transform = AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion::CreateRotationY(AZ::DegToRad(45.0f)), AZ::Vector3(3.0f, 4.0f, 5.0f)); - transform.MultiplyByScale(AZ::Vector3(1.5f, 1.5f, 1.5f)); + transform.MultiplyByUniformScale(1.5f); const float height = 1.2f; const AZ::Vector3 nonUniformScale(2.0f, 1.2f, 0.5f); const AZStd::vector vertices = @@ -608,7 +608,7 @@ namespace UnitTest AZ::Entity entity; CreatePolygonPrism( AZ::Transform::CreateTranslation(AZ::Vector3(5.0f, 15.0f, 40.0f)) * - AZ::Transform::CreateScale(AZ::Vector3(3.0f)), 2.0f, + AZ::Transform::CreateUniformScale(3.0f), 2.0f, AZStd::vector( { AZ::Vector2(-2.0f, -2.0f), @@ -669,7 +669,7 @@ namespace UnitTest AZ::Entity entity; AZ::Transform transform = AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion::CreateRotationY(AZ::DegToRad(60.0f)), AZ::Vector3(1.0f, 2.5f, -1.0f)); - transform.MultiplyByScale(AZ::Vector3(2.0f, 2.0f, 2.0f)); + transform.MultiplyByUniformScale(2.0f); const float height = 1.5f; const AZ::Vector3 nonUniformScale(0.5f, 1.5f, 2.0f); @@ -772,7 +772,7 @@ namespace UnitTest AZ::Entity entity; CreatePolygonPrism( AZ::Transform::CreateTranslation(AZ::Vector3(5.0f, 15.0f, 40.0f)) * - AZ::Transform::CreateScale(AZ::Vector3(3.0f)), 1.5f, + AZ::Transform::CreateUniformScale(3.0f), 1.5f, AZStd::vector( { AZ::Vector2(-2.0f, -2.0f), @@ -795,7 +795,7 @@ namespace UnitTest AZ::Entity entity; AZ::Transform transform = AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion::CreateRotationX(AZ::DegToRad(30.0f)), AZ::Vector3(2.0f, -5.0f, 3.0f)); - transform.MultiplyByScale(AZ::Vector3(2.0f, 2.0f, 2.0f)); + transform.MultiplyByUniformScale(2.0f); const float height = 1.2f; const AZ::Vector3 nonUniformScale(1.5f, 0.8f, 2.0f); const AZStd::vector vertices = diff --git a/Gems/LmbrCentral/Code/Tests/QuadShapeTest.cpp b/Gems/LmbrCentral/Code/Tests/QuadShapeTest.cpp index d70a9344a4..4b6eae4bb8 100644 --- a/Gems/LmbrCentral/Code/Tests/QuadShapeTest.cpp +++ b/Gems/LmbrCentral/Code/Tests/QuadShapeTest.cpp @@ -188,7 +188,7 @@ namespace UnitTest AZ::Entity entity; AZ::Transform transformIn = AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion(0.46f, 0.34f, 0.02f, 0.82f), AZ::Vector3(1.7f, -0.4f, 2.3f)); - transformIn.MultiplyByScale(AZ::Vector3(2.2f)); + transformIn.MultiplyByUniformScale(2.2f); const AZ::Vector3 nonUniformScale(0.8f, 0.6f, 1.3f); const float width = 0.7f; const float height = 1.3f; @@ -327,7 +327,7 @@ namespace UnitTest AZ::Entity entity; AZ::Transform transform = AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion(0.64f, 0.16f, 0.68f, 0.32f), AZ::Vector3(0.4f, -2.3f, -0.9f)); - transform.MultiplyByScale(AZ::Vector3(1.3f)); + transform.MultiplyByUniformScale(1.3f); const AZ::Vector3 nonUniformScale(0.7f, 0.5f, 1.3f); const float width = 0.9f; const float height = 1.3f; @@ -384,7 +384,7 @@ namespace UnitTest AZ::Entity entity; CreateQuad( AZ::Transform::CreateTranslation(AZ::Vector3(100.0f, 200.0f, 300.0f)) * - AZ::Transform::CreateScale(AZ::Vector3(2.5f)), + AZ::Transform::CreateUniformScale(2.5f), 1.0f, 2.0f, entity); AZ::Aabb aabb; @@ -425,7 +425,7 @@ namespace UnitTest AZ::Entity entity; AZ::Transform transform = AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion(0.44f, 0.24f, 0.48f, 0.72f), AZ::Vector3(3.4f, 1.2f, -2.8f)); - transform.MultiplyByScale(AZ::Vector3(1.5f)); + transform.MultiplyByUniformScale(1.5f); const AZ::Vector3 nonUniformScale(1.2f, 1.1f, 0.8f); const float width = 1.2f; const float height = 1.7f; @@ -518,7 +518,7 @@ namespace UnitTest AZ::Entity entity; AZ::Transform transform = AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion(0.24f, 0.72f, 0.44f, 0.48f), AZ::Vector3(2.7f, 2.3f, -1.8f)); - transform.MultiplyByScale(AZ::Vector3(1.2f)); + transform.MultiplyByUniformScale(1.2f); const AZ::Vector3 nonUniformScale(0.4f, 2.2f, 1.3f); const float width = 1.6f; const float height = 0.7f; @@ -546,7 +546,7 @@ namespace UnitTest AZ::Entity entity; AZ::Transform transform = AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion(0.70f, 0.10f, 0.34f, 0.62f), AZ::Vector3(3.0f, -1.0f, 2.0f)); - transform.MultiplyByScale(AZ::Vector3(2.0f)); + transform.MultiplyByUniformScale(2.0f); const AZ::Vector3 nonUniformScale(2.4f, 1.3f, 1.8f); const float width = 0.8f; const float height = 1.4f; diff --git a/Gems/LmbrCentral/Code/Tests/SphereShapeTest.cpp b/Gems/LmbrCentral/Code/Tests/SphereShapeTest.cpp index 563bb16caa..b5e45f2cda 100644 --- a/Gems/LmbrCentral/Code/Tests/SphereShapeTest.cpp +++ b/Gems/LmbrCentral/Code/Tests/SphereShapeTest.cpp @@ -179,7 +179,7 @@ namespace UnitTest AZ::Entity entity; CreateSphere( AZ::Transform::CreateTranslation(AZ::Vector3(-8.0f, -15.0f, 5.0f)) * - AZ::Transform::CreateScale(AZ::Vector3(5.0f)), + AZ::Transform::CreateUniformScale(5.0f), 0.25f, entity); bool rayHit = false; @@ -240,7 +240,7 @@ namespace UnitTest AZ::Entity entity; CreateSphere( AZ::Transform::CreateTranslation(AZ::Vector3(100.0f, 200.0f, 300.0f)) * - AZ::Transform::CreateScale(AZ::Vector3(2.5f)), + AZ::Transform::CreateUniformScale(2.5f), 0.5f, entity); AZ::Aabb aabb; @@ -269,7 +269,7 @@ namespace UnitTest TEST_F(SphereShapeTest, GetTransformAndLocalBounds2) { AZ::Entity entity; - AZ::Transform transformIn = AZ::Transform::CreateTranslation(AZ::Vector3(100.0f, 200.0f, 300.0f)) * AZ::Transform::CreateScale(AZ::Vector3(2.5f)); + AZ::Transform transformIn = AZ::Transform::CreateTranslation(AZ::Vector3(100.0f, 200.0f, 300.0f)) * AZ::Transform::CreateUniformScale(2.5f); CreateSphere(transformIn, 2.0f, entity); AZ::Transform transformOut; @@ -287,7 +287,7 @@ namespace UnitTest AZ::Entity entity; CreateSphere( AZ::Transform::CreateTranslation(AZ::Vector3(-30.0f, -30.0f, 22.0f)) * - AZ::Transform::CreateScale(AZ::Vector3(2.0f)), + AZ::Transform::CreateUniformScale(2.0f), 1.2f, entity); bool inside; @@ -303,7 +303,7 @@ namespace UnitTest AZ::Entity entity; CreateSphere( AZ::Transform::CreateTranslation(AZ::Vector3(-30.0f, -30.0f, 22.0f)) * - AZ::Transform::CreateScale(AZ::Vector3(1.5f)), + AZ::Transform::CreateUniformScale(1.5f), 1.6f, entity); bool inside; @@ -319,7 +319,7 @@ namespace UnitTest AZ::Entity entity; CreateSphere( AZ::Transform::CreateTranslation(AZ::Vector3(19.0f, 34.0f, 37.0f)) * - AZ::Transform::CreateScale(AZ::Vector3(2.0f)), + AZ::Transform::CreateUniformScale(2.0f), 1.0f, entity); float distance; @@ -335,7 +335,7 @@ namespace UnitTest AZ::Entity entity; CreateSphere( AZ::Transform::CreateTranslation(AZ::Vector3(19.0f, 34.0f, 37.0f)) * - AZ::Transform::CreateScale(AZ::Vector3(0.5f)), + AZ::Transform::CreateUniformScale(0.5f), 1.0f, entity); float distance; diff --git a/Gems/LmbrCentral/Code/Tests/TubeShapeTest.cpp b/Gems/LmbrCentral/Code/Tests/TubeShapeTest.cpp index 65388d1fe5..b8f58d5d20 100644 --- a/Gems/LmbrCentral/Code/Tests/TubeShapeTest.cpp +++ b/Gems/LmbrCentral/Code/Tests/TubeShapeTest.cpp @@ -139,7 +139,7 @@ namespace UnitTest AZ::Entity entity; CreateTube( AZ::Transform::CreateTranslation(AZ::Vector3(-40.0f, 6.0f, 1.0f)) * - AZ::Transform::CreateScale(AZ::Vector3(2.5f, 1.0f, 1.0f)), // test max scale + AZ::Transform::CreateUniformScale(2.5f), 1.0f, entity); diff --git a/Gems/Maestro/Code/Source/Cinematics/AnimAZEntityNode.cpp b/Gems/Maestro/Code/Source/Cinematics/AnimAZEntityNode.cpp index b0ebb10b0e..54eeb1fcc9 100644 --- a/Gems/Maestro/Code/Source/Cinematics/AnimAZEntityNode.cpp +++ b/Gems/Maestro/Code/Source/Cinematics/AnimAZEntityNode.cpp @@ -189,7 +189,7 @@ Quat CAnimAzEntityNode::GetRotate(float time) } ////////////////////////////////////////////////////////////////////////// -void CAnimAzEntityNode::SetScale(float time, const Vec3& scale) +void CAnimAzEntityNode::SetScale(float time, float scale) { CAnimComponentNode* transformComponent = GetTransformComponentNode(); if (transformComponent) @@ -198,7 +198,7 @@ void CAnimAzEntityNode::SetScale(float time, const Vec3& scale) } } -Vec3 CAnimAzEntityNode::GetScale() +float CAnimAzEntityNode::GetScale() { CAnimComponentNode* transformComponent = GetTransformComponentNode(); if (transformComponent) @@ -206,7 +206,7 @@ Vec3 CAnimAzEntityNode::GetScale() return transformComponent->GetScale(); } - return Vec3(.0f, .0f, .0f); + return 0.0f; } Vec3 CAnimAzEntityNode::GetOffsetPosition(const Vec3& position) diff --git a/Gems/Maestro/Code/Source/Cinematics/AnimAZEntityNode.h b/Gems/Maestro/Code/Source/Cinematics/AnimAZEntityNode.h index 863d0e927f..d5af311b70 100644 --- a/Gems/Maestro/Code/Source/Cinematics/AnimAZEntityNode.h +++ b/Gems/Maestro/Code/Source/Cinematics/AnimAZEntityNode.h @@ -57,14 +57,14 @@ public: void SetPos(float time, const Vec3& pos) override; void SetRotate(float time, const Quat& quat) override; - void SetScale(float time, const Vec3& scale) override; + void SetScale(float time, float scale) override; Vec3 GetOffsetPosition(const Vec3& position) override; Vec3 GetPos() override; Quat GetRotate() override; Quat GetRotate(float time) override; - Vec3 GetScale() override; + float GetScale() override; ////////////////////////////////////////////////////////////////////////// void Serialize(XmlNodeRef& xmlNode, bool bLoading, bool bLoadEmptyTracks); diff --git a/Gems/Maestro/Code/Source/Cinematics/AnimComponentNode.cpp b/Gems/Maestro/Code/Source/Cinematics/AnimComponentNode.cpp index ea7322014c..13a5d4ed63 100644 --- a/Gems/Maestro/Code/Source/Cinematics/AnimComponentNode.cpp +++ b/Gems/Maestro/Code/Source/Cinematics/AnimComponentNode.cpp @@ -341,10 +341,10 @@ void CAnimComponentNode::ConvertBetweenWorldAndLocalRotation(Quat& rotation, ETr } ////////////////////////////////////////////////////////////////////////// -void CAnimComponentNode::ConvertBetweenWorldAndLocalScale(Vec3& scale, ETransformSpaceConversionDirection conversionDirection) const +void CAnimComponentNode::ConvertBetweenWorldAndLocalScale(float& scale, ETransformSpaceConversionDirection conversionDirection) const { AZ::Transform parentTransform = AZ::Transform::Identity(); - AZ::Transform scaleTransform = AZ::Transform::CreateScale(AZ::Vector3(scale.x, scale.y, scale.z)); + AZ::Transform scaleTransform = AZ::Transform::CreateUniformScale(scale); GetParentWorldTransform(parentTransform); if (conversionDirection == eTransformConverstionDirection_toLocalSpace) @@ -353,8 +353,7 @@ void CAnimComponentNode::ConvertBetweenWorldAndLocalScale(Vec3& scale, ETransfor } scaleTransform = parentTransform * scaleTransform; - AZ::Vector3 vScale = scaleTransform.GetScale(); - scale.Set(vScale.GetX(), vScale.GetY(), vScale.GetZ()); + scale = scaleTransform.GetUniformScale(); } ////////////////////////////////////////////////////////////////////////// @@ -457,7 +456,7 @@ Quat CAnimComponentNode::GetRotate() } ////////////////////////////////////////////////////////////////////////// -void CAnimComponentNode::SetScale(float time, const Vec3& scale) +void CAnimComponentNode::SetScale(float time, float scale) { if (m_componentTypeId == AZ::Uuid(AZ::EditorTransformComponentTypeId) || m_componentTypeId == AzFramework::TransformComponent::TYPEINFO_Uuid()) { @@ -468,7 +467,7 @@ void CAnimComponentNode::SetScale(float time, const Vec3& scale) { // Scale is in World space, even if the entity is parented - because Component Entity AZ::Transforms do not correctly set // CBaseObject parenting, so we convert it to Local space here. This should probably be fixed, but for now, we explicitly change from World to Local space here. - Vec3 localScale(scale); + float localScale = scale; ConvertBetweenWorldAndLocalScale(localScale, eTransformConverstionDirection_toLocalSpace); scaleTrack->SetValue(time, localScale, bDefault); } @@ -480,15 +479,15 @@ void CAnimComponentNode::SetScale(float time, const Vec3& scale) } } -Vec3 CAnimComponentNode::GetScale() +float CAnimComponentNode::GetScale() { Maestro::SequenceComponentRequests::AnimatablePropertyAddress animatableAddress(m_componentId, "Scale"); - Maestro::SequenceComponentRequests::AnimatedVector3Value scaleValue(AZ::Vector3::CreateZero()); + Maestro::SequenceComponentRequests::AnimatedFloatValue scaleValue(0.0f); Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, scaleValue, GetParentAzEntityId(), animatableAddress); // Always return World scale because Component Entity AZ::Transforms do not correctly set // CBaseObject parenting. This should probably be fixed, but for now, we explicitly change from Local to World space here. - Vec3 worldScale(scaleValue.GetVector3Value()); + float worldScale = scaleValue.GetFloatValue(); ConvertBetweenWorldAndLocalScale(worldScale, eTransformConverstionDirection_toWorldSpace); return worldScale; diff --git a/Gems/Maestro/Code/Source/Cinematics/AnimComponentNode.h b/Gems/Maestro/Code/Source/Cinematics/AnimComponentNode.h index 5d83f7ba0d..48913e5b85 100644 --- a/Gems/Maestro/Code/Source/Cinematics/AnimComponentNode.h +++ b/Gems/Maestro/Code/Source/Cinematics/AnimComponentNode.h @@ -71,12 +71,12 @@ public: void SetPos(float time, const Vec3& pos) override; void SetRotate(float time, const Quat& quat) override; - void SetScale(float time, const Vec3& scale) override; + void SetScale(float time, float scale) override; Vec3 GetPos() override; Quat GetRotate() override; Quat GetRotate(float time) override; - Vec3 GetScale() override; + float GetScale() override; void Activate(bool bActivate) override; ////////////////////////////////////////////////////////////////////////// @@ -128,7 +128,7 @@ private: void GetParentWorldTransform(AZ::Transform& retTransform) const; void ConvertBetweenWorldAndLocalPosition(Vec3& position, ETransformSpaceConversionDirection conversionDirection) const; void ConvertBetweenWorldAndLocalRotation(Quat& rotation, ETransformSpaceConversionDirection conversionDirection) const; - void ConvertBetweenWorldAndLocalScale(Vec3& scale, ETransformSpaceConversionDirection conversionDirection) const; + void ConvertBetweenWorldAndLocalScale(float& scale, ETransformSpaceConversionDirection conversionDirection) const; // Utility function to query the units for a track and set the track multiplier if needed. Returns true if track multiplier was set. bool SetTrackMultiplier(IAnimTrack* track) const; diff --git a/Gems/Maestro/Code/Source/Cinematics/AnimNode.h b/Gems/Maestro/Code/Source/Cinematics/AnimNode.h index 0c52ac5a48..f29ba2eab0 100644 --- a/Gems/Maestro/Code/Source/Cinematics/AnimNode.h +++ b/Gems/Maestro/Code/Source/Cinematics/AnimNode.h @@ -79,12 +79,12 @@ public: ////////////////////////////////////////////////////////////////////////// void SetPos([[maybe_unused]] float time, [[maybe_unused]] const Vec3& pos) override {}; void SetRotate([[maybe_unused]] float time, [[maybe_unused]] const Quat& quat) override {}; - void SetScale([[maybe_unused]] float time, [[maybe_unused]] const Vec3& scale) override {}; + void SetScale([[maybe_unused]] float time, [[maybe_unused]] const float scale) override {}; Vec3 GetPos() override { return Vec3(0, 0, 0); }; Quat GetRotate() override { return Quat(0, 0, 0, 0); }; Quat GetRotate(float /*time*/) override { return Quat(0, 0, 0, 0); }; - Vec3 GetScale() override { return Vec3(0, 0, 0); }; + float GetScale() override { return 0.0f; }; virtual Matrix34 GetReferenceMatrix() const; diff --git a/Gems/PhysX/Code/Source/RigidBodyComponent.cpp b/Gems/PhysX/Code/Source/RigidBodyComponent.cpp index 8984343fe5..6709412ffe 100644 --- a/Gems/PhysX/Code/Source/RigidBodyComponent.cpp +++ b/Gems/PhysX/Code/Source/RigidBodyComponent.cpp @@ -203,9 +203,8 @@ namespace PhysX AZ::Quaternion newRotation = AZ::Quaternion::CreateIdentity(); m_interpolator->GetInterpolated(newPosition, newRotation, deltaTime); - AZ::Transform interpolatedTransform = AZ::Transform::CreateFromQuaternionAndTranslation(newRotation, newPosition); - interpolatedTransform.MultiplyByScale(m_initialScale); - AZ::TransformBus::Event(GetEntityId(), &AZ::TransformInterface::SetWorldTM, interpolatedTransform); + AZ::TransformBus::Event(GetEntityId(), &AZ::TransformInterface::SetRotationQuaternion, newRotation); + AZ::TransformBus::Event(GetEntityId(), &AZ::TransformInterface::SetWorldTranslation, newPosition); } } @@ -244,14 +243,8 @@ namespace PhysX } else { - AZ::Transform transform = m_rigidBody->GetTransform(); - - // Maintain scale (this must be precise). - AZ::Transform entityTransform = AZ::Transform::Identity(); - AZ::TransformBus::EventResult(entityTransform, GetEntityId(), &AZ::TransformInterface::GetWorldTM); - transform.MultiplyByScale(m_initialScale); - - AZ::TransformBus::Event(GetEntityId(), &AZ::TransformInterface::SetWorldTM, transform); + AZ::TransformBus::Event(GetEntityId(), &AZ::TransformInterface::SetRotationQuaternion, m_rigidBody->GetOrientation()); + AZ::TransformBus::Event(GetEntityId(), &AZ::TransformInterface::SetWorldTranslation, m_rigidBody->GetPosition()); } m_isLastMovementFromKinematicSource = false; } @@ -338,8 +331,6 @@ namespace PhysX m_interpolator = std::make_unique(); m_interpolator->Reset(transform.GetTranslation(), rotation); - m_initialScale = transform.ExtractScale(); - Physics::RigidBodyNotificationBus::Event(GetEntityId(), &Physics::RigidBodyNotificationBus::Events::OnPhysicsEnabled); Physics::WorldBodyNotificationBus::Event(GetEntityId(), &Physics::WorldBodyNotifications::OnPhysicsEnabled); } diff --git a/Gems/PhysX/Code/Source/RigidBodyComponent.h b/Gems/PhysX/Code/Source/RigidBodyComponent.h index c46e136669..6e5a45ea36 100644 --- a/Gems/PhysX/Code/Source/RigidBodyComponent.h +++ b/Gems/PhysX/Code/Source/RigidBodyComponent.h @@ -159,7 +159,6 @@ namespace PhysX AzPhysics::RigidBody* m_rigidBody = nullptr; AzPhysics::SceneHandle m_attachedSceneHandle = AzPhysics::InvalidSceneHandle; - AZ::Vector3 m_initialScale = AZ::Vector3::CreateOne(); bool m_staticTransformAtActivation = false; ///< Whether the transform was static when the component last activated. bool m_isLastMovementFromKinematicSource = false; ///< True when the source of the movement comes from SetKinematicTarget as opposed to coming from a Transform change bool m_rigidBodyTransformNeedsUpdateOnPhysReEnable = false; ///< True if rigid body transform needs to be synced to the entity's when physics is re-enabled diff --git a/Gems/PhysX/Code/Source/Utils.cpp b/Gems/PhysX/Code/Source/Utils.cpp index 767f386b6f..d04db0d37c 100644 --- a/Gems/PhysX/Code/Source/Utils.cpp +++ b/Gems/PhysX/Code/Source/Utils.cpp @@ -920,9 +920,9 @@ namespace PhysX AZ::Vector3 GetTransformScale(AZ::EntityId entityId) { - AZ::Vector3 worldScale = AZ::Vector3::CreateOne(); - AZ::TransformBus::EventResult(worldScale, entityId, &AZ::TransformBus::Events::GetWorldScale); - return worldScale; + float worldScale = 1.0f; + AZ::TransformBus::EventResult(worldScale, entityId, &AZ::TransformBus::Events::GetWorldUniformScale); + return AZ::Vector3(worldScale); } AZ::Vector3 GetUniformScale(AZ::EntityId entityId) diff --git a/Gems/PhysX/Code/Tests/ColliderScalingTests.cpp b/Gems/PhysX/Code/Tests/ColliderScalingTests.cpp index 51a11c7605..5c8fcb70d5 100644 --- a/Gems/PhysX/Code/Tests/ColliderScalingTests.cpp +++ b/Gems/PhysX/Code/Tests/ColliderScalingTests.cpp @@ -68,7 +68,7 @@ namespace PhysXEditorTests AZ::EntityId editorId = editorEntity->GetId(); AZ::Transform worldTM; - worldTM.SetScale(AZ::Vector3(1.5f)); + worldTM.SetUniformScale(1.5f); worldTM.SetTranslation(AZ::Vector3(5.0f, 6.0f, 7.0f)); worldTM.SetRotation(AZ::Quaternion::CreateRotationX(AZ::DegToRad(30.0f))); AZ::TransformBus::Event(editorId, &AZ::TransformBus::Events::SetWorldTM, worldTM); @@ -99,7 +99,7 @@ namespace PhysXEditorTests AZ::EntityId editorId = editorEntity->GetId(); AZ::Transform worldTM; - worldTM.SetScale(AZ::Vector3(1.5f)); + worldTM.SetUniformScale(1.5f); worldTM.SetTranslation(AZ::Vector3(5.0f, 6.0f, 7.0f)); worldTM.SetRotation(AZ::Quaternion::CreateRotationX(AZ::DegToRad(30.0f))); AZ::TransformBus::Event(editorId, &AZ::TransformBus::Events::SetWorldTM, worldTM); @@ -144,7 +144,7 @@ namespace PhysXEditorTests AZ::EntityId capsuleId = editorEntity->GetId(); AZ::Transform worldTM; - worldTM.SetScale(AZ::Vector3(0.5f)); + worldTM.SetUniformScale(0.5f); worldTM.SetTranslation(AZ::Vector3(3.0f, 1.0f, -4.0f)); worldTM.SetRotation(AZ::Quaternion::CreateRotationY(AZ::DegToRad(90.0f))); AZ::TransformBus::Event(capsuleId, &AZ::TransformBus::Events::SetWorldTM, worldTM); @@ -176,7 +176,7 @@ namespace PhysXEditorTests AZ::EntityId capsuleId = editorEntity->GetId(); AZ::Transform worldTM; - worldTM.SetScale(AZ::Vector3(0.5f)); + worldTM.SetUniformScale(0.5f); worldTM.SetTranslation(AZ::Vector3(3.0f, 1.0f, -4.0f)); worldTM.SetRotation(AZ::Quaternion::CreateRotationY(AZ::DegToRad(90.0f))); AZ::TransformBus::Event(capsuleId, &AZ::TransformBus::Events::SetWorldTM, worldTM); @@ -222,7 +222,7 @@ namespace PhysXEditorTests AZ::EntityId sphereId = editorEntity->GetId(); AZ::Transform worldTM; - worldTM.SetScale(AZ::Vector3(1.2f)); + worldTM.SetUniformScale(1.2f); worldTM.SetTranslation(AZ::Vector3(-2.0f, -1.0f, 3.0f)); worldTM.SetRotation(AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f))); AZ::TransformBus::Event(sphereId, &AZ::TransformBus::Events::SetWorldTM, worldTM); @@ -254,7 +254,7 @@ namespace PhysXEditorTests AZ::EntityId sphereId = editorEntity->GetId(); AZ::Transform worldTM; - worldTM.SetScale(AZ::Vector3(1.2f)); + worldTM.SetUniformScale(1.2f); worldTM.SetTranslation(AZ::Vector3(-2.0f, -1.0f, 3.0f)); worldTM.SetRotation(AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f))); AZ::TransformBus::Event(sphereId, &AZ::TransformBus::Events::SetWorldTM, worldTM); diff --git a/Gems/PhysX/Code/Tests/DebugDrawTests.cpp b/Gems/PhysX/Code/Tests/DebugDrawTests.cpp index 5b41c37f34..610ba8d6f6 100644 --- a/Gems/PhysX/Code/Tests/DebugDrawTests.cpp +++ b/Gems/PhysX/Code/Tests/DebugDrawTests.cpp @@ -32,7 +32,7 @@ namespace PhysXEditorTests AZ::EntityId boxId = boxEntity->GetId(); AZ::Transform worldTM; - worldTM.SetScale(AZ::Vector3(1.5f)); + worldTM.SetUniformScale(1.5f); worldTM.SetTranslation(AZ::Vector3(5.0f, 6.0f, 7.0f)); worldTM.SetRotation(AZ::Quaternion::CreateRotationX(AZ::DegToRad(30.0f))); AZ::TransformBus::Event(boxId, &AZ::TransformBus::Events::SetWorldTM, worldTM); @@ -61,7 +61,7 @@ namespace PhysXEditorTests AZ::EntityId boxId = boxEntity->GetId(); AZ::Transform worldTM; - worldTM.SetScale(AZ::Vector3(1.2f)); + worldTM.SetUniformScale(1.2f); worldTM.SetTranslation(AZ::Vector3(4.0f, -3.0f, 1.0f)); worldTM.SetRotation(AZ::Quaternion::CreateRotationZ(AZ::DegToRad(45.0f))); AZ::TransformBus::Event(boxId, &AZ::TransformBus::Events::SetWorldTM, worldTM); @@ -91,7 +91,7 @@ namespace PhysXEditorTests AZ::EntityId boxId = boxEntity->GetId(); AZ::Transform worldTM; - worldTM.SetScale(AZ::Vector3(1.2f)); + worldTM.SetUniformScale(1.2f); worldTM.SetTranslation(AZ::Vector3(4.0f, -3.0f, 1.0f)); worldTM.SetRotation(AZ::Quaternion::CreateRotationZ(AZ::DegToRad(45.0f))); AZ::TransformBus::Event(boxId, &AZ::TransformBus::Events::SetWorldTM, worldTM); @@ -129,7 +129,7 @@ namespace PhysXEditorTests AZ::EntityId capsuleId = capsuleEntity->GetId(); AZ::Transform worldTM; - worldTM.SetScale(AZ::Vector3(0.5f)); + worldTM.SetUniformScale(0.5f); worldTM.SetTranslation(AZ::Vector3(3.0f, 1.0f, -4.0f)); worldTM.SetRotation(AZ::Quaternion::CreateRotationY(AZ::DegToRad(90.0f))); AZ::TransformBus::Event(capsuleId, &AZ::TransformBus::Events::SetWorldTM, worldTM); @@ -158,7 +158,7 @@ namespace PhysXEditorTests AZ::EntityId capsuleId = capsuleEntity->GetId(); AZ::Transform worldTM; - worldTM.SetScale(AZ::Vector3(1.4f)); + worldTM.SetUniformScale(1.4f); worldTM.SetTranslation(AZ::Vector3(1.0f, -4.0f, 4.0f)); worldTM.SetRotation(AZ::Quaternion::CreateRotationX(AZ::DegToRad(45.0f))); AZ::TransformBus::Event(capsuleId, &AZ::TransformBus::Events::SetWorldTM, worldTM); @@ -189,7 +189,7 @@ namespace PhysXEditorTests AZ::EntityId sphereId = sphereEntity->GetId(); AZ::Transform worldTM; - worldTM.SetScale(AZ::Vector3(1.2f)); + worldTM.SetUniformScale(1.2f); worldTM.SetTranslation(AZ::Vector3(-2.0f, -1.0f, 3.0f)); worldTM.SetRotation(AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f))); AZ::TransformBus::Event(sphereId, &AZ::TransformBus::Events::SetWorldTM, worldTM); @@ -218,7 +218,7 @@ namespace PhysXEditorTests AZ::EntityId sphereId = sphereEntity->GetId(); AZ::Transform worldTM; - worldTM.SetScale(AZ::Vector3(0.8f)); + worldTM.SetUniformScale(0.8f); worldTM.SetTranslation(AZ::Vector3(2.0f, -1.0f, 3.0f)); worldTM.SetRotation(AZ::Quaternion::CreateRotationY(AZ::DegToRad(45.0f))); AZ::TransformBus::Event(sphereId, &AZ::TransformBus::Events::SetWorldTM, worldTM); diff --git a/Gems/PhysX/Code/Tests/RigidBodyComponentTests.cpp b/Gems/PhysX/Code/Tests/RigidBodyComponentTests.cpp index eee747d390..4ff88c0b3b 100644 --- a/Gems/PhysX/Code/Tests/RigidBodyComponentTests.cpp +++ b/Gems/PhysX/Code/Tests/RigidBodyComponentTests.cpp @@ -38,8 +38,8 @@ namespace PhysXEditorTests const AZ::Aabb originalAabb = rigidBodyComponent->GetRigidBody()->GetAabb(); // Update the scale - const AZ::Vector3 scale(2.0f); - AZ::TransformBus::Event(editorEntity->GetId(), &AZ::TransformInterface::SetLocalScale, scale); + float scale = 2.0f; + AZ::TransformBus::Event(editorEntity->GetId(), &AZ::TransformInterface::SetLocalUniformScale, scale); // Trigger editor physics world update so EditorRigidBodyComponent can process scale change auto* physicsSystem = AZ::Interface::Get(); @@ -89,8 +89,8 @@ namespace PhysXEditorTests idPair, &PhysX::EditorColliderComponentRequests::SetColliderOffset, offset); // Update the scale - const AZ::Vector3 scale(2.0f); - AZ::TransformBus::Event(editorEntity->GetId(), &AZ::TransformInterface::SetLocalScale, scale); + float scale = 2.0f; + AZ::TransformBus::Event(editorEntity->GetId(), &AZ::TransformInterface::SetLocalUniformScale, scale); // Update editor world to let updates to be applied physicsSystem->Simulate(0.1f); diff --git a/Gems/PhysX/Code/Tests/ShapeColliderComponentTests.cpp b/Gems/PhysX/Code/Tests/ShapeColliderComponentTests.cpp index 8af9defff8..ad632b579d 100644 --- a/Gems/PhysX/Code/Tests/ShapeColliderComponentTests.cpp +++ b/Gems/PhysX/Code/Tests/ShapeColliderComponentTests.cpp @@ -241,7 +241,7 @@ namespace PhysXEditorTests SetPolygonPrismHeight(entityId, 2.0f); // update the transform scale and non-uniform scale - AZ::TransformBus::Event(entityId, &AZ::TransformBus::Events::SetLocalScale, AZ::Vector3(2.0f)); + AZ::TransformBus::Event(entityId, &AZ::TransformBus::Events::SetLocalUniformScale, 2.0f); AZ::NonUniformScaleRequestBus::Event(entityId, &AZ::NonUniformScaleRequests::SetScale, AZ::Vector3(0.5f, 1.5f, 2.0f)); EntityPtr gameEntity = CreateActiveGameEntityFromEditorEntity(editorEntity.get()); @@ -435,8 +435,8 @@ namespace PhysXEditorTests &LmbrCentral::BoxShapeComponentRequests::GetBoxDimensions); // update the transform - const AZ::Vector3 scale(2.0f); - AZ::TransformBus::Event(editorEntityId, &AZ::TransformInterface::SetLocalScale, scale); + const float scale = 2.0f; + AZ::TransformBus::Event(editorEntityId, &AZ::TransformInterface::SetLocalUniformScale, scale); const AZ::Vector3 translation(10.0f, 20.0f, 30.0f); AZ::TransformBus::Event(editorEntityId, &AZ::TransformInterface::SetWorldTranslation, translation); @@ -527,10 +527,8 @@ namespace PhysXEditorTests editorParentEntity->Activate(); // set some scale to parent entity - const AZ::Vector3 parentScale(2.0f); - AZ::TransformBus::Event(editorParentEntity->GetId(), - &AZ::TransformInterface::SetLocalScale, - parentScale); + const float parentScale = 2.0f; + AZ::TransformBus::Event(editorParentEntity->GetId(), &AZ::TransformInterface::SetLocalUniformScale, parentScale); // create an editor child entity with a shape collider component and a box shape component EntityPtr editorChildEntity = CreateInactiveEditorEntity("ChildEntity"); diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.cpp index a0db02c3a8..30e55cee49 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.cpp @@ -2527,15 +2527,15 @@ namespace ScriptCanvas { Data::TransformType copy(source); AZ::Vector3 pos = copy.GetTranslation(); - AZ::Vector3 scale = copy.ExtractScale(); + float scale = copy.ExtractUniformScale(); AZ::Vector3 rotation = AZ::ConvertTransformToEulerDegrees(copy); return AZStd::string::format ( "(Position: X: %f, Y: %f, Z: %f," " Rotation: X: %f, Y: %f, Z: %f," - " Scale: X: %f, Y: %f, Z: %f)" + " Scale: %f)" , static_cast(pos.GetX()), static_cast(pos.GetY()), static_cast(pos.GetZ()) , static_cast(rotation.GetX()), static_cast(rotation.GetY()), static_cast(rotation.GetZ()) - , static_cast(scale.GetX()), static_cast(scale.GetY()), static_cast(scale.GetZ())); + , scale); } AZStd::string Datum::ToStringVector2(const AZ::Vector2& source) const diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/Rotate.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/Rotate.cpp index c510b55314..2f79d4218a 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/Rotate.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/Rotate.cpp @@ -47,21 +47,10 @@ namespace ScriptCanvas AZ::Transform currentTransform = AZ::Transform::CreateIdentity(); AZ::TransformBus::EventResult(currentTransform, targetEntity, &AZ::TransformInterface::GetWorldTM); - - AZ::Vector3 position = currentTransform.GetTranslation(); - AZ::Quaternion currentRotation = currentTransform.GetRotation(); + currentTransform.SetRotation((rotation * currentTransform.GetRotation().GetNormalized())); - AZ::Quaternion newRotation = (rotation * currentRotation); - newRotation.Normalize(); - - AZ::Transform newTransform = AZ::Transform::CreateIdentity(); - - newTransform.SetScale(currentTransform.GetScale()); - newTransform.SetRotation(newRotation); - newTransform.SetTranslation(position); - - AZ::TransformBus::Event(targetEntity, &AZ::TransformInterface::SetWorldTM, newTransform); + AZ::TransformBus::Event(targetEntity, &AZ::TransformInterface::SetWorldTM, currentTransform); } } diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/RotateMethod.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/RotateMethod.cpp index 20ea4e1b33..53884f4dd8 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/RotateMethod.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/RotateMethod.cpp @@ -44,22 +44,12 @@ namespace ScriptCanvas { AZ::Quaternion rotation = AZ::ConvertEulerDegreesToQuaternion(angles); - AZ::Transform currentTransform = AZ::Transform::CreateIdentity(); - AZ::TransformBus::EventResult(currentTransform, targetEntity, &AZ::TransformInterface::GetWorldTM); + AZ::Transform transform = AZ::Transform::CreateIdentity(); + AZ::TransformBus::EventResult(transform, targetEntity, &AZ::TransformInterface::GetWorldTM); - AZ::Vector3 position = currentTransform.GetTranslation(); - AZ::Quaternion currentRotation = currentTransform.GetRotation(); + transform.SetRotation((rotation * transform.GetRotation()).GetNormalized()); - AZ::Quaternion newRotation = (rotation * currentRotation); - newRotation.Normalize(); - - AZ::Transform newTransform = AZ::Transform::CreateIdentity(); - - newTransform.CreateScale(currentTransform.ExtractScale()); - newTransform.SetRotation(newRotation); - newTransform.SetTranslation(position); - - AZ::TransformBus::Event(targetEntity, &AZ::TransformInterface::SetWorldTM, newTransform); + AZ::TransformBus::Event(targetEntity, &AZ::TransformInterface::SetWorldTM, transform); } } } diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/TransformNodes.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/TransformNodes.h index 6a0f082272..7aafdf584e 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/TransformNodes.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/TransformNodes.h @@ -26,9 +26,9 @@ namespace ScriptCanvas using namespace MathNodeUtilities; static const char* k_categoryName = "Math/Transform"; - AZ_INLINE std::tuple ExtractScale(TransformType source) + AZ_INLINE std::tuple ExtractScale(TransformType source) { - auto scale(source.ExtractScale()); + auto scale(source.ExtractUniformScale()); return std::make_tuple( scale, source ); } SCRIPT_CANVAS_GENERIC_FUNCTION_MULTI_RESULTS_NODE(ExtractScale, k_categoryName, "{8DFE5247-0950-4CD1-87E6-0CAAD42F1637}", "returns a vector which is the length of the scale components, and a transform with the scale extracted ", "Source", "Scale", "Extracted"); diff --git a/Gems/StartingPointCamera/Code/Source/CameraLookAtBehaviors/RotateCameraLookAt.cpp b/Gems/StartingPointCamera/Code/Source/CameraLookAtBehaviors/RotateCameraLookAt.cpp index 2ab7e0bd01..e2e818f3e7 100644 --- a/Gems/StartingPointCamera/Code/Source/CameraLookAtBehaviors/RotateCameraLookAt.cpp +++ b/Gems/StartingPointCamera/Code/Source/CameraLookAtBehaviors/RotateCameraLookAt.cpp @@ -58,19 +58,9 @@ namespace Camera float axisPolarity = m_shouldInvertAxis ? -1.0f : 1.0f; float rotationAmount = axisPolarity * m_rotationAmount; - // remove translation and scale - AZ::Vector3 translation = outLookAtTargetTransform.GetTranslation(); - outLookAtTargetTransform.SetTranslation(AZ::Vector3::CreateZero()); - AZ::Vector3 transformScale = outLookAtTargetTransform.ExtractScale(); - - // perform our rotation - AZ::Transform desiredRotationTransform = AZ::Transform::CreateFromQuaternion(AZ::Quaternion::CreateFromAxisAngle(outLookAtTargetTransform.GetBasis(m_axisOfRotation), rotationAmount)); - - outLookAtTargetTransform = desiredRotationTransform * outLookAtTargetTransform; - - // return scale and translate - outLookAtTargetTransform.SetScale(transformScale); - outLookAtTargetTransform.SetTranslation(translation); + AZ::Quaternion desiredRotation = AZ::Quaternion::CreateFromAxisAngle( + outLookAtTargetTransform.GetBasis(m_axisOfRotation), rotationAmount); + outLookAtTargetTransform.SetRotation(desiredRotation * outLookAtTargetTransform.GetRotation()); } void RotateCameraLookAt::Activate(AZ::EntityId entityId) From 304696fa5cc59b3b517d5545844d2d25d35ee610 Mon Sep 17 00:00:00 2001 From: moudgils Date: Tue, 27 Apr 2021 19:26:35 -0700 Subject: [PATCH 012/330] Shader compile fixes --- .../DiffuseComposite_nomsaa.azsl | 8 ++++---- Gems/Atom/RHI/Code/Source/RHI.Edit/Utils.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseComposite_nomsaa.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseComposite_nomsaa.azsl index 74571b5c6a..c243af0855 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseComposite_nomsaa.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseComposite_nomsaa.azsl @@ -91,7 +91,7 @@ float3 SampleProbeIrradiance(uint2 probeIrradianceCoords, float depth, float3 no { for (int x = -extent; x <= extent; ++x) { - float3 downsampledNormal = PassSrg::m_downsampledNormal.Load(int3(probeIrradianceCoords, 0), int2(x, y)).rgb; + float3 downsampledNormal = PassSrg::m_downsampledNormal.Load(int3(probeIrradianceCoords + int2(x, y), 0)).rgb; downsampledNormal = downsampledNormal * 2.0f - 1.0f; float normalDot = dot(downsampledNormal, normal); @@ -100,10 +100,10 @@ float3 SampleProbeIrradiance(uint2 probeIrradianceCoords, float depth, float3 no if (normalDot > NormalMatchTolerance) { // the normals are almost identical, if the depth is within the tolerance we can optimize by just taking this sample - float downsampledDepth = PassSrg::m_downsampledDepth.Load(int3(probeIrradianceCoords, 0), int2(x, y)).r; + float downsampledDepth = PassSrg::m_downsampledDepth.Load(int3(probeIrradianceCoords + int2(x, y), 0)).r; if (abs(depth - downsampledDepth) <= DepthTolerance) { - float3 probeIrradiance = PassSrg::m_downsampledProbeIrradiance.Load(int3(probeIrradianceCoords,0), int2(x, y)).rgb; + float3 probeIrradiance = PassSrg::m_downsampledProbeIrradiance.Load(int3(probeIrradianceCoords + int2(x, y),0)).rgb; probeIrradiance = saturate(probeIrradiance); return probeIrradiance; } @@ -115,7 +115,7 @@ float3 SampleProbeIrradiance(uint2 probeIrradianceCoords, float depth, float3 no } } - float3 probeIrradiance = PassSrg::m_downsampledProbeIrradiance.Load(int3(probeIrradianceCoords, 0), closestOffset).rgb; + float3 probeIrradiance = PassSrg::m_downsampledProbeIrradiance.Load(int3(probeIrradianceCoords + closestOffset, 0)).rgb; probeIrradiance = saturate(probeIrradiance); return probeIrradiance; } diff --git a/Gems/Atom/RHI/Code/Source/RHI.Edit/Utils.cpp b/Gems/Atom/RHI/Code/Source/RHI.Edit/Utils.cpp index 00b5dada69..957c076592 100644 --- a/Gems/Atom/RHI/Code/Source/RHI.Edit/Utils.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI.Edit/Utils.cpp @@ -308,7 +308,7 @@ namespace AZ uint32_t exitCode = 0; bool timedOut = false; - const AZStd::sys_time_t maxWaitTimeSeconds = 120; + const AZStd::sys_time_t maxWaitTimeSeconds = 240; const AZStd::sys_time_t startTimeSeconds = AZStd::GetTimeNowSecond(); const AZStd::sys_time_t startTime = AZStd::GetTimeNowTicks(); From 12d5288e32ea98420d2585498e2d5fb02c731963 Mon Sep 17 00:00:00 2001 From: puvvadar Date: Wed, 28 Apr 2021 15:15:14 -0700 Subject: [PATCH 013/330] Add codegen for BehaviorContext binding of RPC Send functions --- .../Source/AutoGen/AutoComponent_Header.jinja | 1 + .../Source/AutoGen/AutoComponent_Source.jinja | 34 +++++++++++++++++++ ...tionPlayerInputComponent.AutoComponent.xml | 6 ++-- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja index f5774b07c0..b2d2792e9b 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja @@ -416,6 +416,7 @@ namespace {{ Component.attrib['Namespace'] }} static void Reflect(AZ::ReflectContext* context); static void ReflectToEditContext(AZ::ReflectContext* context); + static void ReflectToBehaviorContext(AZ::ReflectContext* context); static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja index d6907876e1..f2d6ca71c6 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja @@ -315,6 +315,22 @@ void {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(par {% endmacro %} {# +#} +{% macro ReflectRpcInvocations(Component, ClassName, InvokeFrom, HandleOn) %} +{% call(Property) AutoComponentMacros.ParseRemoteProcedures(Component, InvokeFrom, HandleOn) %} +{% if Property.attrib['CanScript']|booleanTrue == true %} +{% set paramNames = [] %} +{% set paramTypes = [] %} +{% set paramDefines = [] %} +{{ AutoComponentMacros.ParseRpcParams(Property, paramNames, paramTypes, paramDefines) }} + ->Method("{{ UpperFirst(Property.attrib['Name']) }}", [](const {{ ClassName }}* self, {{ ', '.join(paramDefines) }}) { + self->m_controller->{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(paramNames) }}); + }) +{% endif %} +{% endcall %} +{% endmacro %} +{# + #} {% macro DeclareRpcHandleCases(Component, ComponentDerived, InvokeFrom, HandleOn, ValidationFunction) %} {% call(Property) AutoComponentMacros.ParseRemoteProcedures(Component, InvokeFrom, HandleOn) %} @@ -1114,6 +1130,7 @@ namespace {{ Component.attrib['Namespace'] }} {{ DefineArchetypePropertyReflection(Component, ComponentBaseName)|indent(16) }}; } ReflectToEditContext(context); + ReflectToBehaviorContext(context); } void {{ ComponentBaseName }}::{{ ComponentBaseName }}::ReflectToEditContext(AZ::ReflectContext* context) @@ -1138,6 +1155,23 @@ namespace {{ Component.attrib['Namespace'] }} } } + void {{ ComponentBaseName }}::{{ ComponentBaseName }}::ReflectToBehaviorContext(AZ::ReflectContext* context) + { + AZ::BehaviorContext* behaviorContext = azrtti_cast(context); + if (behaviorContext) + { + behaviorContext->Class<{{ ComponentName }}>("{{ ComponentName }}") + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Category, "Multiplayer") + ->Attribute(AZ::Script::Attributes::Module, "Multiplayer") + {{ ReflectRpcInvocations(Component, ComponentName, 'Server', 'Authority')|indent(4) -}} + {{ ReflectRpcInvocations(Component, ComponentName, 'Autonomous', 'Authority')|indent(4) -}} + {{ ReflectRpcInvocations(Component, ComponentName, 'Authority', 'Autonomous')|indent(4) -}} + {{ ReflectRpcInvocations(Component, ComponentName, 'Authority', 'Client')|indent(4) -}} + ; + } + } + void {{ ComponentBaseName }}::{{ ComponentBaseName }}::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC_CE("{{ ComponentName }}Service")); diff --git a/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml index d38ebbb1b8..336909a102 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml @@ -18,18 +18,18 @@ - + - + - + From 2f4120cdfbbd736d18fdc5f3187a94f6640ed28b Mon Sep 17 00:00:00 2001 From: puvvadar Date: Mon, 3 May 2021 13:07:11 -0700 Subject: [PATCH 014/330] Update Ctrl+G logic to account for prefab processing status and timing --- .../PrefabEditorEntityOwnershipInterface.h | 2 + .../PrefabEditorEntityOwnershipService.cpp | 5 ++ .../PrefabEditorEntityOwnershipService.h | 2 + Gems/Multiplayer/Code/CMakeLists.txt | 1 + .../Code/Include/IMultiplayerTools.h | 39 ++++++++++++ .../MultiplayerEditorSystemComponent.cpp | 63 ++++++++++++------- .../Editor/MultiplayerEditorSystemComponent.h | 12 +++- .../Code/Source/MultiplayerToolsModule.cpp | 35 ++++------- .../Code/Source/MultiplayerToolsModule.h | 27 ++++++++ .../Pipeline/NetworkPrefabProcessor.cpp | 3 + .../Code/multiplayer_tools_files.cmake | 1 + 11 files changed, 142 insertions(+), 48 deletions(-) create mode 100644 Gems/Multiplayer/Code/Include/IMultiplayerTools.h diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h index 19c236f509..4476876b59 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h @@ -46,6 +46,8 @@ namespace AzToolsFramework virtual Prefab::InstanceOptionalReference GetRootPrefabInstance() = 0; + virtual const AZStd::vector>& GetPlayInEditorAssetData() = 0; + virtual bool LoadFromStream(AZ::IO::GenericStream& stream, AZStd::string_view filename) = 0; virtual bool SaveToStream(AZ::IO::GenericStream& stream, AZStd::string_view filename) = 0; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp index 81233069a9..e81d6bf08e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp @@ -321,6 +321,11 @@ namespace AzToolsFramework return *m_rootInstance; } + const AZStd::vector>& PrefabEditorEntityOwnershipService::GetPlayInEditorAssetData() + { + return m_playInEditorData.m_assets; + } + void PrefabEditorEntityOwnershipService::OnEntityRemoved(AZ::EntityId entityId) { AzFramework::SliceEntityRequestBus::MultiHandler::BusDisconnect(entityId); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h index 9c483e61c5..cf62220e67 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h @@ -195,6 +195,8 @@ namespace AzToolsFramework AZ::IO::PathView filePath, Prefab::InstanceOptionalReference instanceToParentUnder) override; Prefab::InstanceOptionalReference GetRootPrefabInstance() override; + + const AZStd::vector>& GetPlayInEditorAssetData() override; ////////////////////////////////////////////////////////////////////////// void OnEntityRemoved(AZ::EntityId entityId); diff --git a/Gems/Multiplayer/Code/CMakeLists.txt b/Gems/Multiplayer/Code/CMakeLists.txt index 4eeee15c47..46f56ef315 100644 --- a/Gems/Multiplayer/Code/CMakeLists.txt +++ b/Gems/Multiplayer/Code/CMakeLists.txt @@ -119,6 +119,7 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) BUILD_DEPENDENCIES PRIVATE Gem::Multiplayer.Editor.Static + Gem::Multiplayer.Tools ) endif() diff --git a/Gems/Multiplayer/Code/Include/IMultiplayerTools.h b/Gems/Multiplayer/Code/Include/IMultiplayerTools.h new file mode 100644 index 0000000000..c621808f7a --- /dev/null +++ b/Gems/Multiplayer/Code/Include/IMultiplayerTools.h @@ -0,0 +1,39 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include + +namespace Multiplayer +{ + //! IMultiplayer provides insight into the Multiplayer session and its Agents + class IMultiplayerTools + { + public: + // NetworkPrefabProcessor is the only class that should be setting process network prefab status + friend class NetworkPrefabProcessor; + + AZ_RTTI(IMultiplayerTools, "{E8A80EAB-29CB-4E3B-A0B2-FFCB37060FB0}"); + + virtual ~IMultiplayerTools() = default; + + //! Returns if network prefab processing has created currently active or pending spawnables + //! @return If network prefab processing has created currently active or pending spawnables + virtual bool DidProcessNetworkPrefabs() = 0; + + private: + //! Sets if network prefab processing has created currently active or pending spawnables + //! @param didProcessNetPrefabs if network prefab processing has created currently active or pending spawnables + virtual void SetDidProcessNetworkPrefabs(bool didProcessNetPrefabs) = 0; + }; +} diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index aec3e7870f..0850b858a2 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -10,12 +10,14 @@ * */ +#include #include #include #include #include #include #include +#include namespace Multiplayer { @@ -57,12 +59,14 @@ namespace Multiplayer void MultiplayerEditorSystemComponent::Activate() { + AzFramework::GameEntityContextEventBus::Handler::BusConnect(); AzToolsFramework::EditorEvents::Bus::Handler::BusConnect(); } void MultiplayerEditorSystemComponent::Deactivate() { AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect(); + AzFramework::GameEntityContextEventBus::Handler::BusDisconnect(); } void MultiplayerEditorSystemComponent::NotifyRegisterViews() @@ -77,11 +81,42 @@ namespace Multiplayer { switch (event) { - case eNotify_OnBeginGameMode: - { + case eNotify_OnQuit: + AZ_Warning("Multiplayer Editor", m_editor != nullptr, "Multiplayer Editor received On Quit without an Editor pointer."); + if (m_editor) + { + m_editor->UnregisterNotifyListener(this); + m_editor = nullptr; + } + [[fallthrough]]; + case eNotify_OnEndGameMode: + AZ::TickBus::Handler::BusDisconnect(); + // Kill the configured server if it's active + if (m_serverProcess) + { + m_serverProcess->TerminateProcess(0); + m_serverProcess = nullptr; + } + break; + } + } + + void MultiplayerEditorSystemComponent::OnGameEntitiesStarted() + { + // BeginGameMode and Prefab Processing have completed at this point + IMultiplayerTools* mpTools = AZ::Interface::Get(); + if (editorsv_enabled && mpTools != nullptr && mpTools->DidProcessNetworkPrefabs()) + { AZ::TickBus::Handler::BusConnect(); - if (editorsv_enabled) + auto prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); + if (!prefabEditorEntityOwnershipInterface) + { + AZ_Error("MultiplayerEditor", prefabEditorEntityOwnershipInterface != nullptr, "PrefabEditorEntityOwnershipInterface unavailable"); + } + const AZStd::vector>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData(); + + if (assetData.size() > 0) { // Assemble the server's path AZ::CVarFixedString serverProcess = editorsv_process; @@ -111,33 +146,13 @@ namespace Multiplayer // Start the configured server if it's available AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - processLaunchInfo.m_commandlineParameters = - AZStd::string::format("\"%s\"", serverPath.c_str()); + processLaunchInfo.m_commandlineParameters = AZStd::string::format("\"%s\"", serverPath.c_str()); processLaunchInfo.m_showWindow = true; processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL; m_serverProcess = AzFramework::ProcessWatcher::LaunchProcess( processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE); } - break; - } - case eNotify_OnQuit: - AZ_Warning("Multiplayer Editor", m_editor != nullptr, "Multiplayer Editor received On Quit without an Editor pointer."); - if (m_editor) - { - m_editor->UnregisterNotifyListener(this); - m_editor = nullptr; - } - [[fallthrough]]; - case eNotify_OnEndGameMode: - AZ::TickBus::Handler::BusDisconnect(); - // Kill the configured server if it's active - if (m_serverProcess) - { - m_serverProcess->TerminateProcess(0); - m_serverProcess = nullptr; - } - break; } } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h index 8c18a2e57a..31ecdf83a3 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -34,6 +35,7 @@ namespace Multiplayer class MultiplayerEditorSystemComponent final : public AZ::Component , private AZ::TickBus::Handler + , private AzFramework::GameEntityContextEventBus::Handler , private AzToolsFramework::EditorEvents::Bus::Handler , private IEditorNotifyListener { @@ -66,8 +68,16 @@ namespace Multiplayer void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; int GetTickOrder() override; //! @} - //! + + //! EditorEvents::Handler overrides + //! @{ void OnEditorNotifyEvent(EEditorNotifyEvent event) override; + //! @} + + //! GameEntityContextEventBus::Handler overrides + //! @{ + void OnGameEntitiesStarted() override; + //! @} IEditor* m_editor = nullptr; AzFramework::ProcessWatcher* m_serverProcess = nullptr; diff --git a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp index 5a223d6214..a5df3c1dc5 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp @@ -18,32 +18,21 @@ namespace Multiplayer { - //! Multiplayer Tools system component provides serialize context reflection for tools-only systems. - class MultiplayerToolsSystemComponent final - : public AZ::Component - { - public: - AZ_COMPONENT(MultiplayerToolsSystemComponent, "{65AF5342-0ECE-423B-B646-AF55A122F72B}"); - - static void Reflect(AZ::ReflectContext* context) - { - NetworkPrefabProcessor::Reflect(context); - } - - MultiplayerToolsSystemComponent() = default; - ~MultiplayerToolsSystemComponent() override = default; - /// AZ::Component overrides. - void Activate() override - { - - } + void MultiplayerToolsSystemComponent::Reflect(AZ::ReflectContext* context) + { + NetworkPrefabProcessor::Reflect(context); + } - void Deactivate() override - { + bool MultiplayerToolsSystemComponent::DidProcessNetworkPrefabs() + { + return m_didProcessNetPrefabs; + } - } - }; + void MultiplayerToolsSystemComponent::SetDidProcessNetworkPrefabs(bool didProcessNetPrefabs) + { + m_didProcessNetPrefabs = didProcessNetPrefabs; + } MultiplayerToolsModule::MultiplayerToolsModule() : AZ::Module() diff --git a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h index 823bd63a1d..82d0415c5a 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h @@ -12,10 +12,37 @@ #pragma once +#include #include +#include namespace Multiplayer { + class MultiplayerToolsSystemComponent final + : public AZ::Component + , public IMultiplayerTools + { + public: + AZ_COMPONENT(MultiplayerToolsSystemComponent, "{65AF5342-0ECE-423B-B646-AF55A122F72B}"); + + static void Reflect(AZ::ReflectContext* context); + + MultiplayerToolsSystemComponent() = default; + ~MultiplayerToolsSystemComponent() override = default; + + /// AZ::Component overrides. + void Activate() override {}; + + void Deactivate() override {}; + + bool DidProcessNetworkPrefabs() override; + + private: + void SetDidProcessNetworkPrefabs(bool didProcessNetPrefabs) override; + + bool m_didProcessNetPrefabs = false; + }; + class MultiplayerToolsModule : public AZ::Module { diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp index 2006272135..8528b3d564 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,8 @@ namespace Multiplayer void NetworkPrefabProcessor::Process(PrefabProcessorContext& context) { + IMultiplayerTools* mpTools = AZ::Interface::Get(); + mpTools->SetDidProcessNetworkPrefabs(false); context.ListPrefabs([&context](AZStd::string_view prefabName, PrefabDom& prefab) { ProcessPrefab(context, prefabName, prefab); }); diff --git a/Gems/Multiplayer/Code/multiplayer_tools_files.cmake b/Gems/Multiplayer/Code/multiplayer_tools_files.cmake index 1be02fd999..12f12479ba 100644 --- a/Gems/Multiplayer/Code/multiplayer_tools_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_tools_files.cmake @@ -10,6 +10,7 @@ # set(FILES + Include/IMultiplayerTools.h Source/Multiplayer_precompiled.cpp Source/Multiplayer_precompiled.h Source/Pipeline/NetworkPrefabProcessor.cpp From f5414050e36906541cad6fcf1377918f602ecae6 Mon Sep 17 00:00:00 2001 From: darapan Date: Wed, 5 May 2021 00:37:53 -0700 Subject: [PATCH 015/330] smoke test cases migration --- .../Gem/PythonTests/CMakeLists.txt | 15 ++ .../smoke/Editor_NewExistingLevels.py | 155 ++++++++++++++++++ .../Gem/PythonTests/smoke/ImportPathHelper.py | 16 ++ .../Gem/PythonTests/smoke/__init__.py | 10 ++ .../PythonTests/smoke/test_AssetBuilder.py | 44 +++++ .../smoke/test_AssetProcessorBatch.py | 43 +++++ .../PythonTests/smoke/test_AzTestRunner.py | 46 ++++++ .../smoke/test_Editor_NewExistingLevels.py | 34 ++++ .../smoke/test_PythonBindingsExample.py | 45 +++++ .../smoke/test_SerializeContextTools.py | 44 +++++ .../smoke/test_Statictool_Scripts.py | 48 ++++++ 11 files changed, 500 insertions(+) create mode 100644 AutomatedTesting/Gem/PythonTests/smoke/Editor_NewExistingLevels.py create mode 100644 AutomatedTesting/Gem/PythonTests/smoke/ImportPathHelper.py create mode 100644 AutomatedTesting/Gem/PythonTests/smoke/__init__.py create mode 100644 AutomatedTesting/Gem/PythonTests/smoke/test_AssetBuilder.py create mode 100644 AutomatedTesting/Gem/PythonTests/smoke/test_AssetProcessorBatch.py create mode 100644 AutomatedTesting/Gem/PythonTests/smoke/test_AzTestRunner.py create mode 100644 AutomatedTesting/Gem/PythonTests/smoke/test_Editor_NewExistingLevels.py create mode 100644 AutomatedTesting/Gem/PythonTests/smoke/test_PythonBindingsExample.py create mode 100644 AutomatedTesting/Gem/PythonTests/smoke/test_SerializeContextTools.py create mode 100644 AutomatedTesting/Gem/PythonTests/smoke/test_Statictool_Scripts.py diff --git a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt index 6d3727195e..26c425fd79 100644 --- a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt @@ -307,3 +307,18 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) #) endif() +## Smoke ## +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) + ly_add_pytest( + NAME AutomatedTesting::SmokeTest_Periodic + TEST_SUITE periodic + TEST_SERIAL + PATH ${CMAKE_CURRENT_LIST_DIR}/smoke + TIMEOUT 3600 + RUNTIME_DEPENDENCIES + AZ::AssetProcessor + AZ::PythonBindingsExample + Legacy::Editor + AutomatedTesting.GameLauncher + AutomatedTesting.Assets + diff --git a/AutomatedTesting/Gem/PythonTests/smoke/Editor_NewExistingLevels.py b/AutomatedTesting/Gem/PythonTests/smoke/Editor_NewExistingLevels.py new file mode 100644 index 0000000000..4e4a037ec9 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/smoke/Editor_NewExistingLevels.py @@ -0,0 +1,155 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + + +Test case ID: LY-123945 +Test Case Title: Create Test for UI apps- Editor +URL of the test case: https://jira.agscollab.com/browse/LY-123945 +""" + + +# fmt: off +class Tests(): + level_created = ("Level created", "Failed to create level") + entity_found = ("New Entity created in level", "Failed to create New Entity in level") + mesh_added = ("Mesh Component added", "Failed to add Mesh Component") + enter_game_mode = ("Game Mode successfully entered", "Failed to enter in Game Mode") + exit_game_mode = ("Game Mode successfully exited", "Failed to exit in Game Mode") + level_opened = ("Level opened successfully", "Failed to open level") + level_exported = ("Level exported successfully", "Failed to export level") + mesh_removed = ("Mesh Component removed", "Failed to remove Mesh Component") + entity_deleted = ("Entity deleted", "Failed to delete Entity") + level_edits_present = ("Level edits persist after saving", "Failed to save level edits after saving") +# fmt: on + + +def Editor_NewExistingLevels(): + """ + Summary: Perform the below operations on Editor + + 1) Launch & Close editor + 2) Create new level + 3) Saving and loading levels + 4) Level edits persist after saving + 5) Export Level + 6) Can switch to play mode (ctrl+g) and exit that + 7) Run editor python bindings test + 8) Create an Entity + 9) Delete an Entity + 10) Add a component to an Entity + + Expected Behavior: + All operations succeed and do not cause a crash + + Test Steps: + 1) Launch editor and Create a new level + 2) Create a new entity + 3) Add Mesh component + 4) Verify enter/exit game mode + 5) Save, Load and Export level + 6) Remove Mesh component + 7) Delete entity + 8) Open an existing level + 9) Create a new entity in an existing level + 10) Save, Load and Export an existing level and close editor + + Note: + - This test file must be called from the Lumberyard Editor command terminal + - Any passed and failed tests are written to the Editor.log file. + Parsing the file or running a log_monitor are required to observe the test results. + + :return: None + """ + + import os + import hydra_editor_utils as hydra + from utils import TestHelper as helper + from utils import Report + import azlmbr.bus as bus + import azlmbr.editor as editor + import azlmbr.legacy.general as general + import azlmbr.math as math + + # 1) Launch editor and Create a new level + helper.init_idle() + test_level_name = "temp_level" + general.create_level_no_prompt(test_level_name, 128, 1, 128, False) + general.idle_wait(2.0) + Report.result(Tests.level_created, general.get_current_level_name() == test_level_name) + + # 2) Create a new entity + entity_position = math.Vector3(200.0, 200.0, 38.0) + new_entity = hydra.Entity("Entity1") + new_entity.create_entity(entity_position, []) + test_entity = hydra.find_entity_by_name("Entity1") + Report.result(Tests.entity_found, test_entity.IsValid()) + + # 3) Add Mesh component + new_entity.add_component("Mesh") + Report.result(Tests.mesh_added, hydra.has_components(new_entity.id, ["Mesh"])) + + # 4) Verify enter/exit game mode + helper.enter_game_mode(Tests.enter_game_mode) + helper.exit_game_mode(Tests.exit_game_mode) + + # 5) Save, Load and Export level + # Save Level + general.save_level() + # Open Level + general.open_level(test_level_name) + Report.result(Tests.level_opened, general.get_current_level_name() == test_level_name) + # Export Level + general.idle_wait(1.0) + general.export_to_engine() + level_pak_file = os.path.join("AutomatedTesting", "Levels", test_level_name, "level.pak") + Report.result(Tests.level_exported, os.path.exists(level_pak_file)) + + # 6) Remove Mesh component + new_entity.remove_component("Mesh") + Report.result(Tests.mesh_removed, not hydra.has_components(new_entity.id, ["Mesh"])) + + # 7) Delete entity + editor.ToolsApplicationRequestBus(bus.Broadcast, "DeleteEntityById", new_entity.id) + test_entity = hydra.find_entity_by_name("Entity1") + Report.result(Tests.entity_deleted, len(test_entity) == 0) + + # 8) Open an existing level + general.open_level(test_level_name) + Report.result(Tests.level_opened, general.get_current_level_name() == test_level_name) + + # 9) Create a new entity in an existing level + entity_position = math.Vector3(200.0, 200.0, 38.0) + new_entity_2 = hydra.Entity("Entity2") + new_entity_2.create_entity(entity_position, []) + test_entity = hydra.find_entity_by_name("Entity2") + Report.result(Tests.entity_found, test_entity.IsValid()) + + # 10) Save, Load and Export an existing level + # Save Level + general.save_level() + # Open Level + general.open_level(test_level_name) + Report.result(Tests.level_opened, general.get_current_level_name() == test_level_name) + entity_id = hydra.find_entity_by_name(new_entity_2.name) + Report.result(Tests.level_edits_present, entity_id == new_entity_2.id) + # Export Level + general.export_to_engine() + level_pak_file = os.path.join("AutomatedTesting", "Levels", test_level_name, "level.pak") + Report.result(Tests.level_exported, os.path.exists(level_pak_file)) + + +if __name__ == "__main__": + import ImportPathHelper as imports + + imports.init() + + from utils import Report + + Report.start_test(Editor_NewExistingLevels) diff --git a/AutomatedTesting/Gem/PythonTests/smoke/ImportPathHelper.py b/AutomatedTesting/Gem/PythonTests/smoke/ImportPathHelper.py new file mode 100644 index 0000000000..70bed6e526 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/smoke/ImportPathHelper.py @@ -0,0 +1,16 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + +def init(): + import os + import sys + sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../automatedtesting_shared') + sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../EditorPythonTestTools/editor_python_test_tools') diff --git a/AutomatedTesting/Gem/PythonTests/smoke/__init__.py b/AutomatedTesting/Gem/PythonTests/smoke/__init__.py new file mode 100644 index 0000000000..6ed3dc4bda --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/smoke/__init__.py @@ -0,0 +1,10 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_AssetBuilder.py b/AutomatedTesting/Gem/PythonTests/smoke/test_AssetBuilder.py new file mode 100644 index 0000000000..da2abff50f --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_AssetBuilder.py @@ -0,0 +1,44 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + +""" +LY-124059 : CLI tool - AssetBuilder +Launch AssetBuilder and Verify the help message +""" + +import os +import pytest +import subprocess +import ly_test_tools.environment.process_utils as process_utils + + +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +@pytest.mark.usefixtures("automatic_process_killer") +@pytest.mark.SUITE_smoke +class TestAssetBuilder(object): + @pytest.fixture(autouse=True) + def setup_teardown(self, request): + def teardown(): + process_utils.kill_processes_named("AssetBuilder", True) + + request.addfinalizer(teardown) + + @pytest.mark.test_case_id("LY-124059") + def test_AssetBuilder(self, request, editor, build_directory): + file_path = os.path.join(build_directory, "AssetBuilder") + help_message = "AssetBuilder is part of the Asset Processor" + # Launch AssetBuilder + output = subprocess.run([file_path, "-help"], capture_output=True) + assert ( + len(output.stderr) == 0 and output.returncode == 0 + ), f"Error occurred while launching {file_path}: {output.stderr}" + # Verify help message + assert help_message in str(output.stdout), f"Help Message: {help_message} is not present" diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_AssetProcessorBatch.py b/AutomatedTesting/Gem/PythonTests/smoke/test_AssetProcessorBatch.py new file mode 100644 index 0000000000..ab541b94c1 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_AssetProcessorBatch.py @@ -0,0 +1,43 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + +""" +LY-124061 : CLI tool - AssetProcessorBatch +Launch AssetProcessorBatch and Shutdown AssetProcessorBatch without any crash +""" + + +# Import builtin libraries +import pytest +import os +import sys + +sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../assetpipeline/") + +# Import fixtures +from ap_fixtures.asset_processor_fixture import asset_processor as asset_processor + + +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +@pytest.mark.usefixtures("automatic_process_killer") +@pytest.mark.usefixtures("asset_processor") +@pytest.mark.SUITE_smoke +class TestsAssetProcessorBatchs(object): + @pytest.mark.test_case_id("LY-124061") + def test_AssetProcessorBatch(self, asset_processor): + """ + Test Launching AssetProcessorBatch and verifies that is shuts down without issue + """ + # Create a sample asset root so we don't process every asset for every platform + asset_processor.create_temp_asset_root() + # Launch AssetProcessorBatch, assert batch processing success + result, _ = asset_processor.batch_process() + assert result, "AP Batch failed" diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_AzTestRunner.py b/AutomatedTesting/Gem/PythonTests/smoke/test_AzTestRunner.py new file mode 100644 index 0000000000..d1bc44fe2f --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_AzTestRunner.py @@ -0,0 +1,46 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + +""" +LY-124062 : CLI tool - AzTestRunner +Launch AzTestRunner and Verify the help message +""" + +import os +import pytest +import subprocess +import ly_test_tools.environment.process_utils as process_utils + + +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +@pytest.mark.usefixtures("automatic_process_killer") +@pytest.mark.SUITE_smoke +class TestAzTestRunner(object): + @pytest.fixture(autouse=True) + def setup_teardown(self, request): + def teardown(): + process_utils.kill_processes_named("AzTestRunner", True) + + request.addfinalizer(teardown) + + @pytest.mark.test_case_id("LY-124062") + def test_AzTestRunner(self, request, editor, build_directory): + file_path = os.path.join(build_directory, "AzTestRunner") + help_message = "OKAY Symbol found: AzRunUnitTests" + # Launch AzTestRunner + output = subprocess.run( + [file_path, "AzTestRunner.Tests", "AzRunUnitTests", "--gtest_list_tests"], capture_output=True + ) + assert ( + len(output.stderr) == 0 and output.returncode == 0 + ), f"Error occurred while launching {file_path}: {output.stderr}" + # Verify help message + assert help_message in str(output.stdout), f"Help Message: {help_message} is not present" diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_Editor_NewExistingLevels.py b/AutomatedTesting/Gem/PythonTests/smoke/test_Editor_NewExistingLevels.py new file mode 100644 index 0000000000..85d53d098d --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_Editor_NewExistingLevels.py @@ -0,0 +1,34 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + +import pytest +import os +import sys + +sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../automatedtesting_shared") + +from automatedtesting_shared.base import TestAutomationBase +import ly_test_tools.environment.file_system as file_system + + +@pytest.mark.SUITE_smoke +@pytest.mark.parametrize("launcher_platform", ["windows_editor"]) +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +@pytest.mark.parametrize("level", ["temp_level"]) +class TestAutomation(TestAutomationBase): + def test_Editor_NewExistingLevels(self, request, workspace, editor, level, project, launcher_platform): + def teardown(): + file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True) + request.addfinalizer(teardown) + file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True) + + from . import Editor_NewExistingLevels as test_module + self._run_test(request, workspace, editor, test_module) diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_PythonBindingsExample.py b/AutomatedTesting/Gem/PythonTests/smoke/test_PythonBindingsExample.py new file mode 100644 index 0000000000..140e76fc96 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_PythonBindingsExample.py @@ -0,0 +1,45 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + +""" +LY-124064 : CLI tool - PythonBindingsExample +Launch PythonBindingsExample and Verify the help message +""" + +import os +import pytest +import subprocess +import ly_test_tools.environment.process_utils as process_utils + + +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +@pytest.mark.usefixtures("automatic_process_killer") +@pytest.mark.SUITE_smoke +class TestPythonBindingsExample(object): + @pytest.fixture(autouse=True) + def setup_teardown(self, request): + def teardown(): + process_utils.kill_processes_named("PythonBindingsExample", True) + + request.addfinalizer(teardown) + + @pytest.mark.test_case_id("LY-124064") + def test_PythonBindingsExample(self, request, editor, build_directory): + file_path = os.path.join(build_directory, "PythonBindingsExample") + help_message = "--help Prints the help text" + # Launch PythonBindingsExample + output = subprocess.run([file_path, "-help"], capture_output=True) + assert ( + len(output.stderr) == 0 and output.returncode == 1 + ), f"Error occurred while launching {file_path}: {output.stderr}" + # Verify help message + assert help_message in str(output.stdout), f"Help Message: {help_message} is not present" + diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_SerializeContextTools.py b/AutomatedTesting/Gem/PythonTests/smoke/test_SerializeContextTools.py new file mode 100644 index 0000000000..763d84625d --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_SerializeContextTools.py @@ -0,0 +1,44 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + +""" +LY-124066 : CLI tool - SerializeContextTools +Launch SerializeContextTools and Verify the help message +""" + +import os +import pytest +import subprocess +import ly_test_tools.environment.process_utils as process_utils + + +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +@pytest.mark.usefixtures("automatic_process_killer") +@pytest.mark.SUITE_smoke +class TestSerializeContextTools(object): + @pytest.fixture(autouse=True) + def setup_teardown(self, request): + def teardown(): + process_utils.kill_processes_named("SerializeContextTools", True) + + request.addfinalizer(teardown) + + @pytest.mark.test_case_id("LY-124066") + def test_SerializeContextTools(self, request, editor, build_directory): + file_path = os.path.join(build_directory, "SerializeContextTools") + help_message = "Converts a file with an ObjectStream to the new JSON" + # Launch SerializeContextTools + output = subprocess.run([file_path, "-help"], capture_output=True) + assert ( + len(output.stderr) == 0 and output.returncode == 0 + ), f"Error occurred while launching {file_path}: {output.stderr}" + # Verify help message + assert help_message in str(output.stdout), f"Help Message: {help_message} is not present" diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_Statictool_Scripts.py b/AutomatedTesting/Gem/PythonTests/smoke/test_Statictool_Scripts.py new file mode 100644 index 0000000000..d1ae1d4ee0 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_Statictool_Scripts.py @@ -0,0 +1,48 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + +""" +LY-124058: Static tool scripts +Launch Static tool and Verify the help message +""" + +import os +import pytest +import subprocess +import sys + + +def verify_help_message(static_tool): + help_message = ["--help", "show this help message and exit"] + output = subprocess.run([sys.executable, static_tool, "-h"], capture_output=True) + assert ( + len(output.stderr) == 0 and output.returncode == 0 + ), f"Error occurred while launching {static_tool}: {output.stderr}" + # verify help message + for message in help_message: + assert message in str(output.stdout), f"Help Message: {message} is not present" + + +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +@pytest.mark.usefixtures("automatic_process_killer") +@pytest.mark.SUITE_smoke +class TestStatictoolScripts(object): + @pytest.mark.test_case_id("LY-124058") + def test_Statictool_Scripts(self, request, editor): + static_tools = [ + os.path.join(editor.workspace.paths.engine_root(), "scripts", "bundler", "gen_shaders.py"), + os.path.join(editor.workspace.paths.engine_root(), "scripts", "bundler", "get_shader_list.py"), + os.path.join(editor.workspace.paths.engine_root(), "scripts", "bundler", "pak_shaders.py"), + ] + + for tool in static_tools: + verify_help_message(tool) + \ No newline at end of file From 4ce7e117a9347e5dc2fc8204bc8ffd6d44530920 Mon Sep 17 00:00:00 2001 From: darapan Date: Wed, 5 May 2021 00:51:06 -0700 Subject: [PATCH 016/330] "Updating cmakelist.txt to remove extra intendation" --- AutomatedTesting/Gem/PythonTests/CMakeLists.txt | 4 ++-- .../Gem/PythonTests/smoke/test_Statictool_Scripts.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt index 26c425fd79..e6fff224d3 100644 --- a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt @@ -316,9 +316,9 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) PATH ${CMAKE_CURRENT_LIST_DIR}/smoke TIMEOUT 3600 RUNTIME_DEPENDENCIES - AZ::AssetProcessor + AZ::AssetProcessor AZ::PythonBindingsExample Legacy::Editor AutomatedTesting.GameLauncher AutomatedTesting.Assets - + \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_Statictool_Scripts.py b/AutomatedTesting/Gem/PythonTests/smoke/test_Statictool_Scripts.py index d1ae1d4ee0..86f5d67478 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/test_Statictool_Scripts.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_Statictool_Scripts.py @@ -45,4 +45,4 @@ class TestStatictoolScripts(object): for tool in static_tools: verify_help_message(tool) - \ No newline at end of file + \ No newline at end of file From 7eb6cc10b6559511927028d9f6c5514b973a99bb Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Wed, 5 May 2021 08:57:39 +0100 Subject: [PATCH 017/330] Made change affect all modifiable containers. --- .../Serialization/EditContextConstants.inl | 2 -- .../UI/PropertyEditor/PropertyRowWidget.cpp | 22 +++++++++++++------ .../UI/PropertyEditor/PropertyRowWidget.hxx | 5 +++-- Gems/Vegetation/Code/Source/Descriptor.cpp | 2 -- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl b/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl index 481ae5a91c..90b9ba5afd 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl +++ b/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl @@ -62,8 +62,6 @@ namespace AZ const static AZ::Crc32 ButtonTooltip = AZ_CRC("ButtonTooltip", 0x1605a7d2); const static AZ::Crc32 CheckboxTooltip = AZ_CRC("CheckboxTooltip", 0x1159eb78); const static AZ::Crc32 CheckboxDefaultValue = AZ_CRC("CheckboxDefaultValue", 0x03f117e6); - //! Emboldens the text and adds a line above this item within the RPE. - const static AZ::Crc32 RPESectionSeparator = AZ_CRC("RPESectionSeparator", 0xc6249a95); //! Affects the display order of a node relative to it's parent/children. Higher values display further down (after) lower values. Default is 0, negative values are allowed. Must be applied as an attribute to the EditorData element const static AZ::Crc32 DisplayOrder = AZ_CRC("DisplayOrder", 0x23660ec2); //! Specifies whether the UI should support multi-edit for aggregate instances of this property diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp index 5b02e81e6d..faeae0a06d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp @@ -315,8 +315,6 @@ namespace AzToolsFramework } } - m_isSectionSeparator = false; - RefreshAttributesFromNode(true); // --------------------- HANDLER discovery: @@ -962,10 +960,6 @@ namespace AzToolsFramework { HandleChangeNotifyAttribute(reader, m_sourceNode ? m_sourceNode->GetParent() : nullptr, m_editingCompleteNotifiers); } - else if (attributeName == AZ::Edit::Attributes::RPESectionSeparator) - { - m_isSectionSeparator = true; - } } void PropertyRowWidget::SetReadOnlyQueryFunction(const ReadOnlyQueryFunction& readOnlyQueryFunction) @@ -1340,7 +1334,7 @@ namespace AzToolsFramework bool PropertyRowWidget::IsSectionSeparator() const { - return m_isSectionSeparator; + return CanBeReordered(); } bool PropertyRowWidget::GetAppendDefaultLabelToName() @@ -1686,6 +1680,20 @@ namespace AzToolsFramework m_nameLabel->setFilter(m_currentFilterString); } + bool PropertyRowWidget::CanChildrenBeReordered() const + { + return m_containerEditable; + } + + bool PropertyRowWidget::CanBeReordered() const + { + if (!m_parentRow) + { + return false; + } + + return m_parentRow->CanChildrenBeReordered(); + } } #include "UI/PropertyEditor/moc_PropertyRowWidget.cpp" diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx index 2fb695fca5..b6c94dc98b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx @@ -149,6 +149,9 @@ namespace AzToolsFramework QLabel* GetNameLabel() { return m_nameLabel; } void SetIndentSize(int w); void SetAsCustom(bool custom) { m_custom = custom; } + + bool CanChildrenBeReordered() const; + bool CanBeReordered() const; protected: int CalculateLabelWidth() const; @@ -232,8 +235,6 @@ namespace AzToolsFramework int m_treeIndentation = 14; int m_leafIndentation = 16; - bool m_isSectionSeparator = false; - QIcon m_iconOpen; QIcon m_iconClosed; diff --git a/Gems/Vegetation/Code/Source/Descriptor.cpp b/Gems/Vegetation/Code/Source/Descriptor.cpp index 45ec25038d..ecda8415e7 100644 --- a/Gems/Vegetation/Code/Source/Descriptor.cpp +++ b/Gems/Vegetation/Code/Source/Descriptor.cpp @@ -170,8 +170,6 @@ namespace Vegetation { edit->Class( "Vegetation Descriptor", "Details used to create vegetation instances") - ->ClassElement(AZ::Edit::ClassElements::EditorData, "") - ->Attribute(AZ::Edit::Attributes::RPESectionSeparator, true) // For this ComboBox to actually work, there is a PropertyHandler registration in EditorVegetationSystemComponent.cpp ->DataElement(AZ::Edit::UIHandlers::ComboBox, &Descriptor::m_spawnerType, "Instance Spawner", "The type of instances to spawn") ->Attribute(AZ::Edit::Attributes::GenericValueList, &Descriptor::GetSpawnerTypeList) From 0e666d046c37a305fa89bb335f7bc43bd4a89356 Mon Sep 17 00:00:00 2001 From: darapan Date: Wed, 5 May 2021 00:57:51 -0700 Subject: [PATCH 018/330] "Adding new line" --- AutomatedTesting/Gem/PythonTests/CMakeLists.txt | 1 + AutomatedTesting/Gem/PythonTests/smoke/__init__.py | 2 +- .../Gem/PythonTests/smoke/test_Statictool_Scripts.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt index e6fff224d3..8dfac40c43 100644 --- a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt @@ -321,4 +321,5 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) Legacy::Editor AutomatedTesting.GameLauncher AutomatedTesting.Assets + \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/smoke/__init__.py b/AutomatedTesting/Gem/PythonTests/smoke/__init__.py index 6ed3dc4bda..79f8fa4422 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/__init__.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/__init__.py @@ -7,4 +7,4 @@ distribution (the "License"). All use of this software is governed by the Licens or, if provided, by the license below or the license accompanying this file. Do not remove or modify any license notices. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" \ No newline at end of file +""" diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_Statictool_Scripts.py b/AutomatedTesting/Gem/PythonTests/smoke/test_Statictool_Scripts.py index 86f5d67478..92be4abeb9 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/test_Statictool_Scripts.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_Statictool_Scripts.py @@ -45,4 +45,5 @@ class TestStatictoolScripts(object): for tool in static_tools: verify_help_message(tool) + \ No newline at end of file From 2f153f994e2b5be015f661bf81d99e96c3947bab Mon Sep 17 00:00:00 2001 From: darapan Date: Wed, 5 May 2021 01:12:38 -0700 Subject: [PATCH 019/330] "Adding new line" --- AutomatedTesting/Gem/PythonTests/CMakeLists.txt | 1 - AutomatedTesting/Gem/PythonTests/smoke/__init__.py | 2 +- .../Gem/PythonTests/smoke/test_Statictool_Scripts.py | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt index 8dfac40c43..e6fff224d3 100644 --- a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt @@ -321,5 +321,4 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) Legacy::Editor AutomatedTesting.GameLauncher AutomatedTesting.Assets - \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/smoke/__init__.py b/AutomatedTesting/Gem/PythonTests/smoke/__init__.py index 79f8fa4422..6ed3dc4bda 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/__init__.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/__init__.py @@ -7,4 +7,4 @@ distribution (the "License"). All use of this software is governed by the Licens or, if provided, by the license below or the license accompanying this file. Do not remove or modify any license notices. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" +""" \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_Statictool_Scripts.py b/AutomatedTesting/Gem/PythonTests/smoke/test_Statictool_Scripts.py index 92be4abeb9..8eac2ff099 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/test_Statictool_Scripts.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_Statictool_Scripts.py @@ -45,5 +45,3 @@ class TestStatictoolScripts(object): for tool in static_tools: verify_help_message(tool) - - \ No newline at end of file From 2f0ed6cfb21aac2b99b6b5f303286bccba3ea814 Mon Sep 17 00:00:00 2001 From: darapan Date: Wed, 5 May 2021 01:17:20 -0700 Subject: [PATCH 020/330] "" --- AutomatedTesting/Gem/PythonTests/CMakeLists.txt | 1 - AutomatedTesting/Gem/PythonTests/smoke/__init__.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt index e6fff224d3..400dca33ce 100644 --- a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt @@ -321,4 +321,3 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) Legacy::Editor AutomatedTesting.GameLauncher AutomatedTesting.Assets - \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/smoke/__init__.py b/AutomatedTesting/Gem/PythonTests/smoke/__init__.py index 6ed3dc4bda..79f8fa4422 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/__init__.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/__init__.py @@ -7,4 +7,4 @@ distribution (the "License"). All use of this software is governed by the Licens or, if provided, by the license below or the license accompanying this file. Do not remove or modify any license notices. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" \ No newline at end of file +""" From 682bdf684743ddc094cf32ab02a5246b63191eef Mon Sep 17 00:00:00 2001 From: darapan Date: Wed, 5 May 2021 08:50:34 -0700 Subject: [PATCH 021/330] "Changing testsuite" --- AutomatedTesting/Gem/PythonTests/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt index 400dca33ce..92dcc7dc7f 100644 --- a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt @@ -310,8 +310,8 @@ endif() ## Smoke ## if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_pytest( - NAME AutomatedTesting::SmokeTest_Periodic - TEST_SUITE periodic + NAME AutomatedTesting::SmokeTest + TEST_SUITE smoke TEST_SERIAL PATH ${CMAKE_CURRENT_LIST_DIR}/smoke TIMEOUT 3600 From 2b511b68a2538326b199a088d7a4d4cfa94581c4 Mon Sep 17 00:00:00 2001 From: darapan Date: Wed, 5 May 2021 10:26:11 -0700 Subject: [PATCH 022/330] "Adding AssetBundlerBatch test" --- .../smoke/test_AssetBundlerBatch.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 AutomatedTesting/Gem/PythonTests/smoke/test_AssetBundlerBatch.py diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_AssetBundlerBatch.py b/AutomatedTesting/Gem/PythonTests/smoke/test_AssetBundlerBatch.py new file mode 100644 index 0000000000..d361c62f5f --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_AssetBundlerBatch.py @@ -0,0 +1,44 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + +""" +LY-124060 : CLI tool - AssetBundlerBatch +Launch AssetBundlerBatch and Verify the help message +""" + +import os +import pytest +import subprocess +import ly_test_tools.environment.process_utils as process_utils + + +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +@pytest.mark.usefixtures("automatic_process_killer") +@pytest.mark.SUITE_smoke +class TestAssetBundlerBatch(object): + @pytest.fixture(autouse=True) + def setup_teardown(self, request): + def teardown(): + process_utils.kill_processes_named("AssetBundlerBatch", True) + + request.addfinalizer(teardown) + + @pytest.mark.test_case_id("LY-124060") + def test_AssetBundlerBatch(self, request, editor, build_directory): + file_path = os.path.join(build_directory, "AssetBundlerBatch") + help_message = "Specifies the Seed List file to operate on by path" + # Launch AssetBundlerBatch + output = subprocess.run([file_path, "--help"], capture_output=True) + assert ( + len(output.stderr) == 0 and output.returncode == 0 + ), f"Error occurred while launching {file_path}: {output.stderr}" + # Verify help message + assert help_message in str(output.stdout), f"Help Message: {help_message} is not present" From aa51233536a55816324da9d41fff6afe4e3970c9 Mon Sep 17 00:00:00 2001 From: puvvadar Date: Thu, 6 May 2021 16:18:13 -0700 Subject: [PATCH 023/330] Add Asset serialization for Ctrl+G and related net interfaces --- .../AzNetworking/TcpTransport/TcpSocket.cpp | 4 +- .../AutoGen/Multiplayer.AutoPackets.xml | 6 ++ .../MultiplayerEditorSystemComponent.cpp | 93 +++++++++++++++++-- .../Editor/MultiplayerEditorSystemComponent.h | 2 + .../Source/MultiplayerSystemComponent.cpp | 21 +++++ .../Code/Source/MultiplayerSystemComponent.h | 4 +- .../Code/Source/MultiplayerToolsModule.cpp | 10 ++ .../Code/Source/MultiplayerToolsModule.h | 5 +- 8 files changed, 132 insertions(+), 13 deletions(-) diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp index 78020cb09d..e8c30d0816 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocket.cpp @@ -116,8 +116,8 @@ namespace AzNetworking int32_t TcpSocket::Receive(uint8_t* outData, uint32_t size) const { - AZ_Assert(size > 0, "Invalid data size for send"); - AZ_Assert(outData != nullptr, "NULL data pointer passed to send"); + AZ_Assert(size > 0, "Invalid data size for receive"); + AZ_Assert(outData != nullptr, "NULL data pointer passed to receive"); if (!IsOpen()) { return SocketOpResultErrorNotOpen; diff --git a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml index 5de466899c..21faefa880 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml @@ -57,4 +57,10 @@ + + + + + + diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index 0850b858a2..15f00bdf80 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -10,23 +10,32 @@ * */ +#include #include +#include +#include #include #include +#include #include #include #include #include +#include #include namespace Multiplayer { + static const AZStd::string_view s_networkInterfaceName("MultiplayerEditorServerInterface"); + using namespace AzNetworking; AZ_CVAR(bool, editorsv_enabled, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether Editor launching a local server to connect to is supported"); AZ_CVAR(AZ::CVarFixedString, editorsv_process, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The server executable that should be run. Empty to use the current project's ServerLauncher"); + AZ_CVAR(AZ::CVarFixedString, sv_serveraddr, "127.0.0.1", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the server to connect to"); + AZ_CVAR(uint16_t, sv_port, 30091, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that the multiplayer editor gem will bind to for traffic"); void MultiplayerEditorSystemComponent::Reflect(AZ::ReflectContext* context) { @@ -61,6 +70,16 @@ namespace Multiplayer { AzFramework::GameEntityContextEventBus::Handler::BusConnect(); AzToolsFramework::EditorEvents::Bus::Handler::BusConnect(); + + // Setup a network interface handled by MultiplayerSystemComponent + if (m_editorNetworkInterface == nullptr) + { + AZ::Entity* systemEntity = this->GetEntity(); + MultiplayerSystemComponent* mpSysComponent = systemEntity->FindComponent(); + + m_editorNetworkInterface = AZ::Interface::Get()->CreateNetworkInterface( + AZ::Name(s_networkInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *mpSysComponent); + } } void MultiplayerEditorSystemComponent::Deactivate() @@ -97,25 +116,52 @@ namespace Multiplayer m_serverProcess->TerminateProcess(0); m_serverProcess = nullptr; } + if (m_editorNetworkInterface) + { + // Disconnect the interface, connection management will clean it up + m_editorNetworkInterface->Disconnect(m_editorConnId, AzNetworking::DisconnectReason::TerminatedByUser); + m_editorConnId = AzNetworking::InvalidConnectionId; + } break; } } void MultiplayerEditorSystemComponent::OnGameEntitiesStarted() { + auto prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); + if (!prefabEditorEntityOwnershipInterface) + { + AZ_Error("MultiplayerEditor", prefabEditorEntityOwnershipInterface != nullptr, "PrefabEditorEntityOwnershipInterface unavailable"); + } + const AZStd::vector>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData(); + + AZStd::vector buffer; + AZ::IO::ByteContainerStream byteStream(&buffer); + + // Serialize Asset information and AssetData into a potentially large buffer + for (auto asset : assetData) + { + AZ::Data::AssetId assetId = asset.GetId(); + AZ::Data::AssetType assetType = asset.GetType(); + const AZStd::string& assetHint = asset.GetHint(); + AZ::IO::SizeType assetHintSize = assetHint.size(); + AZ::Data::AssetLoadBehavior assetLoadBehavior = asset.GetAutoLoadBehavior(); + + byteStream.Write(sizeof(AZ::Data::AssetId), reinterpret_cast(&assetId)); + byteStream.Write(sizeof(AZ::Data::AssetType), reinterpret_cast(&assetType)); + byteStream.Write(sizeof(assetHintSize), reinterpret_cast(&assetHintSize)); + byteStream.Write(assetHint.size(), assetHint.c_str()); + byteStream.Write(sizeof(AZ::Data::AssetLoadBehavior), reinterpret_cast(&assetLoadBehavior)); + + AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, asset.GetData(), asset.GetData()->GetType()); + } + // BeginGameMode and Prefab Processing have completed at this point IMultiplayerTools* mpTools = AZ::Interface::Get(); if (editorsv_enabled && mpTools != nullptr && mpTools->DidProcessNetworkPrefabs()) { AZ::TickBus::Handler::BusConnect(); - auto prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); - if (!prefabEditorEntityOwnershipInterface) - { - AZ_Error("MultiplayerEditor", prefabEditorEntityOwnershipInterface != nullptr, "PrefabEditorEntityOwnershipInterface unavailable"); - } - const AZStd::vector>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData(); - if (assetData.size() > 0) { // Assemble the server's path @@ -154,6 +200,39 @@ namespace Multiplayer processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE); } } + + // Now that the server has launched, attempt to connect the NetworkInterface + const AZ::CVarFixedString remoteAddress = sv_serveraddr; + m_editorConnId = m_editorNetworkInterface->Connect( + AzNetworking::IpAddress(remoteAddress.c_str(), sv_port, AzNetworking::ProtocolType::Tcp)); + + // Read the buffer into EditorServerInit packets until we've flushed the whole thing + byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN); + + while (byteStream.GetCurPos() < byteStream.GetLength()) + { + MultiplayerPackets::EditorServerInit packet; + AzNetworking::TcpPacketEncodingBuffer& outBuffer = packet.ModifyAssetData(); + + // Size the packet's buffer appropriately + size_t readSize = TcpPacketEncodingBuffer::GetCapacity(); + size_t byteStreamSize = byteStream.GetLength() - byteStream.GetCurPos(); + if (byteStreamSize < readSize) + { + readSize = byteStreamSize; + } + + outBuffer.Resize(readSize); + byteStream.Read(readSize, outBuffer.GetBuffer()); + + // If we've run out of buffer, mark that we're done + if (byteStream.GetCurPos() == byteStream.GetLength()) + { + packet.SetLastUpdate(true); + } + m_editorNetworkInterface->SendReliablePacket(m_editorConnId, packet); + } + } void MultiplayerEditorSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h index 31ecdf83a3..d43d8747b9 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h @@ -81,5 +81,7 @@ namespace Multiplayer IEditor* m_editor = nullptr; AzFramework::ProcessWatcher* m_serverProcess = nullptr; + AzNetworking::ConnectionId m_editorConnId; + AzNetworking::INetworkInterface* m_editorNetworkInterface = nullptr; }; } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index 03c661ab03..3c1e142d96 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -57,7 +57,9 @@ namespace Multiplayer using namespace AzNetworking; static const AZStd::string_view s_networkInterfaceName("MultiplayerNetworkInterface"); + static const AZStd::string_view s_networkEditorInterfaceName("MultiplayerEditorNetworkInterface"); static constexpr uint16_t DefaultServerPort = 30090; + static constexpr uint16_t DefaultServerEditorPort = 30091; AZ_CVAR(uint16_t, cl_clientport, 0, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port to bind to for game traffic when connecting to a remote host, a value of 0 will select any available port"); AZ_CVAR(AZ::CVarFixedString, cl_serveraddr, "127.0.0.1", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the remote server or host to connect to"); @@ -397,6 +399,19 @@ namespace Multiplayer return false; } + bool MultiplayerSystemComponent::HandleRequest + ( + [[maybe_unused]] AzNetworking::IConnection* connection, + [[maybe_unused]] const IPacketHeader& packetHeader, + [[maybe_unused]] MultiplayerPackets::EditorServerInit& packet + ) + { +#if !defined(_RELEASE) + // Support Editor Server Init for all non-release targets +#endif + return true; + } + ConnectResult MultiplayerSystemComponent::ValidateConnect ( [[maybe_unused]] const IpAddress& remoteAddress, @@ -492,6 +507,12 @@ namespace Multiplayer { if (multiplayerType == MultiplayerAgentType::ClientServer || multiplayerType == MultiplayerAgentType::DedicatedServer) { +#if !defined(_RELEASE) + m_networkEditorInterface = AZ::Interface::Get()->CreateNetworkInterface( + AZ::Name(s_networkEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this); + m_networkEditorInterface->Listen(DefaultServerEditorPort); +#endif + m_initEvent.Signal(m_networkInterface); const AZ::Aabb worldBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-16384.0f), AZ::Vector3(16384.0f)); diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index f25e530b61..e77fa129b0 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -72,7 +72,8 @@ namespace Multiplayer bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::NotifyClientMigration& packet); bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::EntityMigration& packet); bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::ReadyForEntityUpdates& packet); - + bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::EditorServerInit& packet); + //! IConnectionListener interface //! @{ AzNetworking::ConnectResult ValidateConnect(const AzNetworking::IpAddress& remoteAddress, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override; @@ -109,6 +110,7 @@ namespace Multiplayer AZ_CONSOLEFUNC(MultiplayerSystemComponent, DumpStats, AZ::ConsoleFunctorFlags::Null, "Dumps stats for the current multiplayer session"); AzNetworking::INetworkInterface* m_networkInterface = nullptr; + AzNetworking::INetworkInterface* m_networkEditorInterface = nullptr; AZ::ConsoleCommandInvokedEvent::Handler m_consoleCommandHandler; AZ::ThreadSafeDeque m_cvarCommands; diff --git a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp index a5df3c1dc5..ae91999a0c 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.cpp @@ -24,6 +24,16 @@ namespace Multiplayer NetworkPrefabProcessor::Reflect(context); } + void MultiplayerToolsSystemComponent::Activate() + { + AZ::Interface::Register(this); + } + + void MultiplayerToolsSystemComponent::Deactivate() + { + AZ::Interface::Unregister(this); + } + bool MultiplayerToolsSystemComponent::DidProcessNetworkPrefabs() { return m_didProcessNetPrefabs; diff --git a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h index 82d0415c5a..181a971150 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerToolsModule.h @@ -31,9 +31,8 @@ namespace Multiplayer ~MultiplayerToolsSystemComponent() override = default; /// AZ::Component overrides. - void Activate() override {}; - - void Deactivate() override {}; + void Activate() override; + void Deactivate() override; bool DidProcessNetworkPrefabs() override; From e2ca84ceb253d0930a006b45c66f745d4f597c8a Mon Sep 17 00:00:00 2001 From: moudgils Date: Fri, 7 May 2021 17:41:24 -0700 Subject: [PATCH 024/330] More fixes to shaders with the latest dxc --- .../Assets/Materials/Types/EnhancedPBR_ForwardPass.shader | 3 +-- .../DiffuseProbeGridDownsample_nomsaa.azsl | 4 ++-- Gems/Atom/RHI/Code/Source/RHI.Edit/Utils.cpp | 2 +- .../Code/Source/Platform/Windows/Vulkan_Traits_Windows.h | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.shader b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.shader index 7964e3c84a..764b67c82f 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.shader +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.shader @@ -31,8 +31,7 @@ }, "CompilerHints" : { - "DisableOptimizations" : true, - "DxcGenerateDebugInfo" : true + "DisableOptimizations" : true }, "ProgramSettings": diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseProbeGridDownsample_nomsaa.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseProbeGridDownsample_nomsaa.azsl index f10fc67da5..d2e73927e0 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseProbeGridDownsample_nomsaa.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseProbeGridDownsample_nomsaa.azsl @@ -70,8 +70,8 @@ PSOutput MainPS(VSOutput IN) { for (uint x = 0; x < ImageScale; ++x) { - float depth = PassSrg::m_depth.Load(int3(screenCoords, 0), int2(x, y)).r; - float4 encodedNormal = PassSrg::m_normal.Load(int3(screenCoords, 0), int2(x, y)); + float depth = PassSrg::m_depth.Load(int3(screenCoords + int2(x, y), 0)).r; + float4 encodedNormal = PassSrg::m_normal.Load(int3(screenCoords + int2(x, y), 0)); // take the closest depth sample to ensure we're getting the normal closest to the viewer // (larger depth value due to reverse depth) diff --git a/Gems/Atom/RHI/Code/Source/RHI.Edit/Utils.cpp b/Gems/Atom/RHI/Code/Source/RHI.Edit/Utils.cpp index 957c076592..00b5dada69 100644 --- a/Gems/Atom/RHI/Code/Source/RHI.Edit/Utils.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI.Edit/Utils.cpp @@ -308,7 +308,7 @@ namespace AZ uint32_t exitCode = 0; bool timedOut = false; - const AZStd::sys_time_t maxWaitTimeSeconds = 240; + const AZStd::sys_time_t maxWaitTimeSeconds = 120; const AZStd::sys_time_t startTimeSeconds = AZStd::GetTimeNowSecond(); const AZStd::sys_time_t startTime = AZStd::GetTimeNowTicks(); diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/Platform/Windows/Vulkan_Traits_Windows.h b/Gems/Atom/RHI/Vulkan/Code/Source/Platform/Windows/Vulkan_Traits_Windows.h index c7c9902115..c1314aaf66 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/Platform/Windows/Vulkan_Traits_Windows.h +++ b/Gems/Atom/RHI/Vulkan/Code/Source/Platform/Windows/Vulkan_Traits_Windows.h @@ -11,7 +11,7 @@ */ #pragma once -#define AZ_TRAIT_ATOM_SHADERBUILDER_DXC "Builders/DirectXShaderCompilerAz/dxc.exe" +#define AZ_TRAIT_ATOM_SHADERBUILDER_DXC "Builders/DirectXShaderCompiler/dxc.exe" #define AZ_TRAIT_ATOM_VULKAN_DISABLE_DUAL_SOURCE_BLENDING 0 #define AZ_TRAIT_ATOM_VULKAN_DLL "vulkan.dll" #define AZ_TRAIT_ATOM_VULKAN_DLL_1 "vulkan-1.dll" From 1f3d0beb387a68ac5ac87c63aafa5105d1f253b6 Mon Sep 17 00:00:00 2001 From: antonmic Date: Fri, 7 May 2021 18:51:20 -0700 Subject: [PATCH 025/330] work in progress --- .../Materials/Types/StandardPBR.materialtype | 15 + .../Types/StandardPBR_ForwardPass.azsl | 15 +- .../Types/StandardPBR_LowEndForward.azsl | 15 + .../Types/StandardPBR_LowEndForward.shader | 53 +++ .../StandardPBR_LowEndForward_EDS.shader | 53 +++ .../Feature/Common/Assets/Passes/Forward.pass | 16 - .../Assets/Passes/LightAdaptationParent.pass | 146 ++++++++ .../Common/Assets/Passes/LowEndForward.pass | 133 +++++++ .../Common/Assets/Passes/LowEndPipeline.pass | 344 ++++++++++++++++++ .../Common/Assets/Passes/OpaqueParent.pass | 11 +- .../Assets/Passes/PassTemplates.azasset | 12 + .../Assets/Passes/PostProcessParent.pass | 90 +---- .../Feature/Common/Assets/Passes/SkyBox.pass | 5 - .../Atom/Features/PBR/ForwardPassOutput.azsli | 17 + .../PBR/LowEndForwardPassOutput.azsli | 32 ++ .../Atom/Features/ShaderQualityOptions.azsli | 24 ++ .../Reflections/ReflectionComposite.azsl | 15 +- .../Common/Assets/Shaders/SkyBox/SkyBox.azsl | 2 - .../atom_feature_common_asset_files.cmake | 10 + 19 files changed, 887 insertions(+), 121 deletions(-) create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.shader create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward_EDS.shader create mode 100644 Gems/Atom/Feature/Common/Assets/Passes/LightAdaptationParent.pass create mode 100644 Gems/Atom/Feature/Common/Assets/Passes/LowEndForward.pass create mode 100644 Gems/Atom/Feature/Common/Assets/Passes/LowEndPipeline.pass create mode 100644 Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LowEndForwardPassOutput.azsli create mode 100644 Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype index e071a793a5..fa5c94c606 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype @@ -77,6 +77,13 @@ ], "properties": { "general": [ + { + "id": "useLowEndShader", + "displayName": "Use Low End", + "description": "Whether to use the low end shader.", + "type": "Bool", + "defaultValue": false + }, { "id": "applySpecularAA", "displayName": "Apply Specular AA", @@ -1175,6 +1182,14 @@ "file": "./StandardPBR_ForwardPass_EDS.shader", "tag": "ForwardPass_EDS" }, + { + "file": "./StandardPBR_LowEndForward.shader", + "tag": "LowEndForward" + }, + { + "file": "./StandardPBR_LowEndForward_EDS.shader", + "tag": "LowEndForward_EDS" + }, { "file": "Shaders/Shadow/Shadowmap.shader", "tag": "Shadowmap" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl index d3bc72d162..bb4e4cdaab 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl @@ -10,6 +10,8 @@ * */ +#include "Atom/Features/ShaderQualityOptions.azsli" + #include "StandardPBR_Common.azsli" // SRGs @@ -306,13 +308,18 @@ ForwardPassOutputWithDepth StandardPbr_ForwardPassPS(VSOutput IN, bool isFrontFa PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth); +#ifdef UNIFIED_FORWARD_OUTPUT + OUT.m_color.rgb = lightingOutput.m_diffuseColor.rgb + lightingOutput.m_specularColor.rgb; + OUT.m_color.a = 1.0f; + OUT.m_depth = depth; +#else OUT.m_diffuseColor = lightingOutput.m_diffuseColor; OUT.m_specularColor = lightingOutput.m_specularColor; OUT.m_specularF0 = lightingOutput.m_specularF0; OUT.m_albedo = lightingOutput.m_albedo; OUT.m_normal = lightingOutput.m_normal; OUT.m_depth = depth; - +#endif return OUT; } @@ -324,12 +331,16 @@ ForwardPassOutput StandardPbr_ForwardPassPS_EDS(VSOutput IN, bool isFrontFace : PbrLightingOutput lightingOutput = ForwardPassPS_Common(IN, isFrontFace, depth); +#ifdef UNIFIED_FORWARD_OUTPUT + OUT.m_color.rgb = lightingOutput.m_diffuseColor.rgb + lightingOutput.m_specularColor.rgb; + OUT.m_color.a = 1.0f; +#else OUT.m_diffuseColor = lightingOutput.m_diffuseColor; OUT.m_specularColor = lightingOutput.m_specularColor; OUT.m_specularF0 = lightingOutput.m_specularF0; OUT.m_albedo = lightingOutput.m_albedo; OUT.m_normal = lightingOutput.m_normal; - +#endif return OUT; } diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl new file mode 100644 index 0000000000..a690cbf84a --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl @@ -0,0 +1,15 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#define QUALITY_LOW_END 1 + +#include "StandardPBR_ForwardPass.azsl" diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.shader b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.shader new file mode 100644 index 0000000000..19538e5db3 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.shader @@ -0,0 +1,53 @@ +{ + "Source" : "./StandardPBR_LowEndForward.azsl", + + "DepthStencilState" : + { + "Depth" : + { + "Enable" : true, + "CompareFunc" : "GreaterEqual" + }, + "Stencil" : + { + "Enable" : true, + "ReadMask" : "0x00", + "WriteMask" : "0xFF", + "FrontFace" : + { + "Func" : "Always", + "DepthFailOp" : "Keep", + "FailOp" : "Keep", + "PassOp" : "Replace" + }, + "BackFace" : + { + "Func" : "Always", + "DepthFailOp" : "Keep", + "FailOp" : "Keep", + "PassOp" : "Replace" + } + } + }, + + "CompilerHints" : { + "DisableOptimizations" : false + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "StandardPbr_ForwardPassVS", + "type": "Vertex" + }, + { + "name": "StandardPbr_ForwardPassPS", + "type": "Fragment" + } + ] + }, + + "DrawList" : "lowEndForward" +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward_EDS.shader b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward_EDS.shader new file mode 100644 index 0000000000..1b5f014d0e --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward_EDS.shader @@ -0,0 +1,53 @@ +{ + "Source" : "./StandardPBR_LowEndForward.azsl", + + "DepthStencilState" : + { + "Depth" : + { + "Enable" : true, + "CompareFunc" : "GreaterEqual" + }, + "Stencil" : + { + "Enable" : true, + "ReadMask" : "0x00", + "WriteMask" : "0xFF", + "FrontFace" : + { + "Func" : "Always", + "DepthFailOp" : "Keep", + "FailOp" : "Keep", + "PassOp" : "Replace" + }, + "BackFace" : + { + "Func" : "Always", + "DepthFailOp" : "Keep", + "FailOp" : "Keep", + "PassOp" : "Replace" + } + } + }, + + "CompilerHints" : { + "DisableOptimizations" : false + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "StandardPbr_ForwardPassVS", + "type": "Vertex" + }, + { + "name": "StandardPbr_ForwardPassPS_EDS", + "type": "Fragment" + } + ] + }, + + "DrawList" : "lowEndForward" +} diff --git a/Gems/Atom/Feature/Common/Assets/Passes/Forward.pass b/Gems/Atom/Feature/Common/Assets/Passes/Forward.pass index 31a8ed1879..3dcc90ac5c 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/Forward.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/Forward.pass @@ -148,22 +148,6 @@ }, "LoadAction": "Clear" } - }, - { - "Name": "ScatterDistanceOutput", - "SlotType": "Output", - "ScopeAttachmentUsage": "RenderTarget", - "LoadStoreAction": { - "ClearValue": { - "Value": [ - 0.0, - 0.0, - 0.0, - 0.0 - ] - }, - "LoadAction": "Clear" - } } ], "ImageAttachments": [ diff --git a/Gems/Atom/Feature/Common/Assets/Passes/LightAdaptationParent.pass b/Gems/Atom/Feature/Common/Assets/Passes/LightAdaptationParent.pass new file mode 100644 index 0000000000..3e804d23e2 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Passes/LightAdaptationParent.pass @@ -0,0 +1,146 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "LightAdaptationParentTemplate", + "PassClass": "ParentPass", + "Slots": [ + // Inputs... + { + "Name": "LightingInput", + "SlotType": "Input" + }, + // SwapChain here is only used to reference the frame height and format + { + "Name": "SwapChainOutput", + "SlotType": "InputOutput" + }, + // Outputs... + { + "Name": "Output", + "SlotType": "Output" + }, + // Debug Outputs... + { + "Name": "LuminanceMipChainOutput", + "SlotType": "Output" + } + ], + "Connections": [ + { + "LocalSlot": "Output", + "AttachmentRef": { + "Pass": "DisplayMapperPass", + "Attachment": "Output" + } + }, + { + "LocalSlot": "LuminanceMipChainOutput", + "AttachmentRef": { + "Pass": "DownsampleLuminanceMipChain", + "Attachment": "MipChainInputOutput" + } + } + ], + "PassRequests": [ + { + "Name": "DownsampleLuminanceMinAvgMax", + "TemplateName": "DownsampleLuminanceMinAvgMaxCS", + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "LightingInput" + } + } + ] + }, + { + "Name": "DownsampleLuminanceMipChain", + "TemplateName": "DownsampleMipChainTemplate", + "Connections": [ + { + "LocalSlot": "MipChainInputOutput", + "AttachmentRef": { + "Pass": "DownsampleLuminanceMinAvgMax", + "Attachment": "Output" + } + } + ], + "PassData": { + "$type": "DownsampleMipChainPassData", + "ShaderAsset": { + "FilePath": "Shaders/PostProcessing/DownsampleMinAvgMaxCS.shader" + } + } + }, + { + "Name": "EyeAdaptationPass", + "TemplateName": "EyeAdaptationTemplate", + "Enabled": false, + "Connections": [ + { + "LocalSlot": "SceneLuminanceInput", + "AttachmentRef": { + "Pass": "DownsampleLuminanceMipChain", + "Attachment": "MipChainInputOutput" + } + } + ] + }, + { + "Name": "LookModificationTransformPass", + "TemplateName": "LookModificationTransformTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "LightingInput" + } + }, + { + "LocalSlot": "EyeAdaptationDataInput", + "AttachmentRef": { + "Pass": "EyeAdaptationPass", + "Attachment": "EyeAdaptationDataInputOutput" + } + }, + { + "LocalSlot": "SwapChainOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + }, + { + "Name": "DisplayMapperPass", + "TemplateName": "DisplayMapperTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "LookModificationTransformPass", + "Attachment": "Output" + } + }, + { + "LocalSlot": "SwapChainOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + } + ] + } + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Passes/LowEndForward.pass b/Gems/Atom/Feature/Common/Assets/Passes/LowEndForward.pass new file mode 100644 index 0000000000..4b865fcb6d --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Passes/LowEndForward.pass @@ -0,0 +1,133 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "LowEndForwardPassTemplate", + "PassClass": "RasterPass", + "Slots": [ + // Inputs... + { + "Name": "BRDFTextureInput", + "ShaderInputName": "m_brdfMap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + { + "Name": "DirectionalLightShadowmap", + "ShaderInputName": "m_directionalLightShadowmap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "ExponentialShadowmapDirectional", + "ShaderInputName": "m_directionalLightExponentialShadowmap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "ProjectedShadowmap", + "ShaderInputName": "m_projectedShadowmaps", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "ExponentialShadowmapProjected", + "ShaderInputName": "m_projectedExponentialShadowmap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "TileLightData", + "SlotType": "Input", + "ShaderInputName": "m_tileLightData", + "ScopeAttachmentUsage": "Shader" + }, + { + "Name": "LightListRemapped", + "SlotType": "Input", + "ShaderInputName": "m_lightListRemapped", + "ScopeAttachmentUsage": "Shader" + }, + // Input/Outputs... + { + "Name": "DepthStencilInputOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "DepthStencil" + }, + // Outputs... + { + "Name": "LightingOutput", + "SlotType": "Output", + "ScopeAttachmentUsage": "RenderTarget", + "LoadStoreAction": { + "ClearValue": { + "Value": [ + 0.0, + 0.0, + 0.0, + 0.0 + ] + }, + "LoadAction": "Clear" + } + } + ], + "ImageAttachments": [ + { + "Name": "LightingAttachment", + "SizeSource": { + "Source": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + }, + "MultisampleSource": { + "Pass": "This", + "Attachment": "DepthStencilInputOutput" + }, + "ImageDescriptor": { + "Format": "R16G16B16A16_FLOAT", + "SharedQueueMask": "Graphics" + } + }, + { + "Name": "BRDFTexture", + "Lifetime": "Imported", + "AssetRef": { + "FilePath": "Textures/BRDFTexture.attimage" + } + } + ], + "Connections": [ + { + "LocalSlot": "LightingOutput", + "AttachmentRef": { + "Pass": "This", + "Attachment": "LightingAttachment" + } + }, + { + "LocalSlot": "BRDFTextureInput", + "AttachmentRef": { + "Pass": "This", + "Attachment": "BRDFTexture" + } + } + ] + } + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Passes/LowEndPipeline.pass b/Gems/Atom/Feature/Common/Assets/Passes/LowEndPipeline.pass new file mode 100644 index 0000000000..b19569fb9d --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Passes/LowEndPipeline.pass @@ -0,0 +1,344 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "LowEndPipelineTemplate", + "PassClass": "ParentPass", + "Slots": [ + { + "Name": "SwapChainOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget" + } + ], + "PassRequests": [ + { + "Name": "MorphTargetPass", + "TemplateName": "MorphTargetPassTemplate" + }, + { + "Name": "SkinningPass", + "TemplateName": "SkinningPassTemplate", + "Connections": [ + { + "LocalSlot": "SkinnedMeshOutputStream", + "AttachmentRef": { + "Pass": "MorphTargetPass", + "Attachment": "MorphTargetDeltaOutput" + } + } + ] + }, + { + "Name": "DepthPrePass", + "TemplateName": "DepthMSAAParentTemplate", + "Connections": [ + { + "LocalSlot": "SkinnedMeshes", + "AttachmentRef": { + "Pass": "SkinningPass", + "Attachment": "SkinnedMeshOutputStream" + } + }, + { + "LocalSlot": "SwapChainOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + }, + { + "Name": "LightCullingPass", + "TemplateName": "LightCullingParentTemplate", + "Connections": [ + { + "LocalSlot": "SkinnedMeshes", + "AttachmentRef": { + "Pass": "SkinningPass", + "Attachment": "SkinnedMeshOutputStream" + } + }, + { + "LocalSlot": "DepthMSAA", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "DepthMSAA" + } + }, + { + "LocalSlot": "SwapChainOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + }, + { + "Name": "ShadowPass", + "TemplateName": "ShadowParentTemplate", + "Connections": [ + { + "LocalSlot": "SkinnedMeshes", + "AttachmentRef": { + "Pass": "SkinningPass", + "Attachment": "SkinnedMeshOutputStream" + } + }, + { + "LocalSlot": "SwapChainOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + }, + { + "Name": "ForwardPass", + "TemplateName": "LowEndForwardPassTemplate", + "Connections": [ + // Inputs... + { + "LocalSlot": "DirectionalLightShadowmap", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "DirectionalShadowmap" + } + }, + { + "LocalSlot": "ExponentialShadowmapDirectional", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "DirectionalESM" + } + }, + { + "LocalSlot": "ProjectedShadowmap", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "ProjectedShadowmap" + } + }, + { + "LocalSlot": "ExponentialShadowmapProjected", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "ProjectedESM" + } + }, + { + "LocalSlot": "TileLightData", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "TileLightData" + } + }, + { + "LocalSlot": "LightListRemapped", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "LightListRemapped" + } + }, + // Input/Outputs... + { + "LocalSlot": "DepthStencilInputOutput", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "DepthMSAA" + } + } + ], + "PassData": { + "$type": "RasterPassData", + "DrawListTag": "lowEndForward", + "PipelineViewTag": "MainCamera", + "PassSrgAsset": { + "FilePath": "shaderlib/atom/features/pbr/forwardpasssrg.azsli:PassSrg" + } + } + }, + { + "Name": "SkyBoxPass", + "TemplateName": "SkyBoxTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "SpecularInputOutput", + "AttachmentRef": { + "Pass": "ForwardPass", + "Attachment": "LightingOutput" + } + }, + { + "LocalSlot": "SkyBoxDepth", + "AttachmentRef": { + "Pass": "ForwardPass", + "Attachment": "DepthStencilInputOutput" + } + } + ] + }, + { + "Name": "MSAAResolvePass", + "TemplateName": "MSAAResolveColorTemplate", + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "SkyBoxPass", + "Attachment": "SpecularInputOutput" + } + } + ] + }, + { + "Name": "TransparentPass", + "TemplateName": "TransparentParentTemplate", + "Connections": [ + { + "LocalSlot": "DirectionalShadowmap", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "DirectionalShadowmap" + } + }, + { + "LocalSlot": "DirectionalESM", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "DirectionalESM" + } + }, + { + "LocalSlot": "ProjectedShadowmap", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "ProjectedShadowmap" + } + }, + { + "LocalSlot": "ProjectedESM", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "ProjectedESM" + } + }, + { + "LocalSlot": "TileLightData", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "TileLightData" + } + }, + { + "LocalSlot": "LightListRemapped", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "LightListRemapped" + } + }, + { + "LocalSlot": "DepthStencil", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "Depth" + } + }, + { + "LocalSlot": "InputOutput", + "AttachmentRef": { + "Pass": "MSAAResolvePass", + "Attachment": "Output" + } + } + ] + }, + { + "Name": "LightAdaptation", + "TemplateName": "LightAdaptationParentTemplate", + "Connections": [ + { + "LocalSlot": "LightingInput", + "AttachmentRef": { + "Pass": "TransparentPass", + "Attachment": "InputOutput" + } + }, + { + "LocalSlot": "SwapChainOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + }, + { + "Name": "AuxGeomPass", + "TemplateName": "AuxGeomPassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "ColorInputOutput", + "AttachmentRef": { + "Pass": "LightAdaptation", + "Attachment": "Output" + } + }, + { + "LocalSlot": "DepthInputOutput", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "Depth" + } + } + ], + "PassData": { + "$type": "RasterPassData", + "DrawListTag": "auxgeom", + "PipelineViewTag": "MainCamera" + } + }, + { + "Name": "UIPass", + "TemplateName": "UIParentTemplate", + "Connections": [ + { + "LocalSlot": "InputOutput", + "AttachmentRef": { + "Pass": "AuxGeomPass", + "Attachment": "ColorInputOutput" + } + } + ] + }, + { + "Name": "CopyToSwapChain", + "TemplateName": "FullscreenCopyTemplate", + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "UIPass", + "Attachment": "InputOutput" + } + }, + { + "LocalSlot": "Output", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "SwapChainOutput" + } + } + ] + } + ] + } + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Passes/OpaqueParent.pass b/Gems/Atom/Feature/Common/Assets/Passes/OpaqueParent.pass index dda120e164..40d6a51e77 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/OpaqueParent.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/OpaqueParent.pass @@ -315,13 +315,6 @@ "Attachment": "SpecularInputOutput" } }, - { - "LocalSlot": "ReflectionInputOutput", - "AttachmentRef": { - "Pass": "ReflectionsPass", - "Attachment": "ReflectionOutput" - } - }, { "LocalSlot": "SkyBoxDepth", "AttachmentRef": { @@ -338,8 +331,8 @@ { "LocalSlot": "ReflectionInput", "AttachmentRef": { - "Pass": "SkyBoxPass", - "Attachment": "ReflectionInputOutput" + "Pass": "ReflectionsPass", + "Attachment": "ReflectionOutput" } }, { diff --git a/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset b/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset index b83ab65ff2..2421d7fbe7 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset +++ b/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset @@ -483,6 +483,18 @@ { "Name": "UIParentTemplate", "Path": "Passes/UIParent.pass" + }, + { + "Name": "LightAdaptationParentTemplate", + "Path": "Passes/LightAdaptationParent.pass" + }, + { + "Name": "LowEndForwardPassTemplate", + "Path": "Passes/LowEndForward.pass" + }, + { + "Name": "LowEndPipelineTemplate", + "Path": "Passes/LowEndPipeline.pass" } ] } diff --git a/Gems/Atom/Feature/Common/Assets/Passes/PostProcessParent.pass b/Gems/Atom/Feature/Common/Assets/Passes/PostProcessParent.pass index 37b1ee5c5a..36f7f1e985 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/PostProcessParent.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/PostProcessParent.pass @@ -40,7 +40,7 @@ { "LocalSlot": "Output", "AttachmentRef": { - "Pass": "DisplayMapperPass", + "Pass": "LightAdaptation", "Attachment": "Output" } }, @@ -54,8 +54,8 @@ { "LocalSlot": "LuminanceMipChainOutput", "AttachmentRef": { - "Pass": "DownsampleLuminanceMipChain", - "Attachment": "MipChainInputOutput" + "Pass": "LightAdaptation", + "Attachment": "LuminanceMipChainOutput" } } ], @@ -115,94 +115,16 @@ } ] }, - // Everything before this point deals in raw lighting values - // --------------------------------------------------------- - // Everything after starts to map to values we see on screen { - "Name": "DownsampleLuminanceMinAvgMax", - "TemplateName": "DownsampleLuminanceMinAvgMaxCS", + "Name": "LightAdaptation", + "TemplateName": "LightAdaptationParentTemplate", "Connections": [ { - "LocalSlot": "Input", + "LocalSlot": "LightingInput", "AttachmentRef": { "Pass": "BloomPass", "Attachment": "InputOutput" } - } - ] - }, - { - "Name": "DownsampleLuminanceMipChain", - "TemplateName": "DownsampleMipChainTemplate", - "Connections": [ - { - "LocalSlot": "MipChainInputOutput", - "AttachmentRef": { - "Pass": "DownsampleLuminanceMinAvgMax", - "Attachment": "Output" - } - } - ], - "PassData": { - "$type": "DownsampleMipChainPassData", - "ShaderAsset": { - "FilePath": "Shaders/PostProcessing/DownsampleMinAvgMaxCS.shader" - } - } - }, - { - "Name": "EyeAdaptationPass", - "TemplateName": "EyeAdaptationTemplate", - "Enabled": false, - "Connections": [ - { - "LocalSlot": "SceneLuminanceInput", - "AttachmentRef": { - "Pass": "DownsampleLuminanceMipChain", - "Attachment": "MipChainInputOutput" - } - } - ] - }, - { - "Name": "LookModificationTransformPass", - "TemplateName": "LookModificationTransformTemplate", - "Enabled": true, - "Connections": [ - { - "LocalSlot": "Input", - "AttachmentRef": { - "Pass": "BloomPass", - "Attachment": "InputOutput" - } - }, - { - "LocalSlot": "EyeAdaptationDataInput", - "AttachmentRef": { - "Pass": "EyeAdaptationPass", - "Attachment": "EyeAdaptationDataInputOutput" - } - }, - { - "LocalSlot": "SwapChainOutput", - "AttachmentRef": { - "Pass": "Parent", - "Attachment": "SwapChainOutput" - } - } - ] - }, - { - "Name": "DisplayMapperPass", - "TemplateName": "DisplayMapperTemplate", - "Enabled": true, - "Connections": [ - { - "LocalSlot": "Input", - "AttachmentRef": { - "Pass": "LookModificationTransformPass", - "Attachment": "Output" - } }, { "LocalSlot": "SwapChainOutput", diff --git a/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass b/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass index 57f442e5de..fb16271ba7 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass @@ -12,11 +12,6 @@ "SlotType": "InputOutput", "ScopeAttachmentUsage": "RenderTarget" }, - { - "Name": "ReflectionInputOutput", - "SlotType": "InputOutput", - "ScopeAttachmentUsage": "RenderTarget" - }, { "Name": "SkyBoxDepth", "SlotType": "InputOutput", diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/ForwardPassOutput.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/ForwardPassOutput.azsli index acc215f1c9..5821deb3b1 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/ForwardPassOutput.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/ForwardPassOutput.azsli @@ -10,6 +10,21 @@ * */ +#ifdef UNIFIED_FORWARD_OUTPUT + +struct ForwardPassOutput +{ + float4 m_color : SV_Target0; +}; + +struct ForwardPassOutputWithDepth +{ + float4 m_color : SV_Target0; + float m_depth : SV_Depth; +}; + +#else + struct ForwardPassOutput { float4 m_diffuseColor : SV_Target0; //!< RGB = Diffuse Lighting, A = Blend Alpha (for blended surfaces) OR A = special encoding of surfaceScatteringFactor, m_subsurfaceScatteringQuality, o_enableSubsurfaceScattering @@ -30,3 +45,5 @@ struct ForwardPassOutputWithDepth float4 m_normal : SV_Target4; float m_depth : SV_Depth; }; + +#endif diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LowEndForwardPassOutput.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LowEndForwardPassOutput.azsli new file mode 100644 index 0000000000..acc215f1c9 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LowEndForwardPassOutput.azsli @@ -0,0 +1,32 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +struct ForwardPassOutput +{ + float4 m_diffuseColor : SV_Target0; //!< RGB = Diffuse Lighting, A = Blend Alpha (for blended surfaces) OR A = special encoding of surfaceScatteringFactor, m_subsurfaceScatteringQuality, o_enableSubsurfaceScattering + float4 m_specularColor : SV_Target1; //!< RGB = Specular Lighting, A = Unused + float4 m_albedo : SV_Target2; //!< RGB = Surface albedo pre-multiplied by other factors that will be multiplied later by diffuse GI, A = specularOcclusion + float4 m_specularF0 : SV_Target3; //!< RGB = Specular F0, A = roughness + float4 m_normal : SV_Target4; //!< RGB10 = EncodeNormalSignedOctahedron(worldNormal), A2 = multiScatterCompensationEnabled +}; + +struct ForwardPassOutputWithDepth +{ + // See above for descriptions of special encodings + + float4 m_diffuseColor : SV_Target0; + float4 m_specularColor : SV_Target1; + float4 m_albedo : SV_Target2; + float4 m_specularF0 : SV_Target3; + float4 m_normal : SV_Target4; + float m_depth : SV_Depth; +}; diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli new file mode 100644 index 0000000000..907e67ada5 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli @@ -0,0 +1,24 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +// These are a list of quality options to specify as macros (either in azsl or in shader files) +// +// QUALITY_LOW_END + +#ifdef QUALITY_LOW_END + +#define UNIFIED_FORWARD_OUTPUT 1 + +#endif + diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionComposite.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionComposite.azsl index 92f8c3f638..865d657d85 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionComposite.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionComposite.azsl @@ -53,13 +53,22 @@ PSOutput MainPS(VSOutput IN) uint width, height, samples; PassSrg::m_reflection.GetDimensions(width, height, samples); + float nonZeroSamples = 0.0f; for (uint sampleIndex = 0; sampleIndex < samples; ++sampleIndex) { - reflection += PassSrg::m_reflection.Load(IN.m_position.xy, sampleIndex).rgb; + float3 reflectionSample = PassSrg::m_reflection.Load(IN.m_position.xy, sampleIndex).rgb; + if(any(reflectionSample)) + { + reflection += reflectionSample; + nonZeroSamples += 1.0f; + } + } + + if(nonZeroSamples != 0.0f) + { + reflection /= nonZeroSamples; } - reflection /= samples; - PSOutput OUT; OUT.m_color = float4(reflection, 1.0f); return OUT; diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl index 1ee30a4f98..a7de426374 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl @@ -102,7 +102,6 @@ float3 GetCubemapCoords(float3 original) struct PSOutput { float4 m_specular : SV_Target0; - float4 m_reflection : SV_Target1; }; PSOutput MainPS(VSOutput input) @@ -163,6 +162,5 @@ PSOutput MainPS(VSOutput input) PSOutput OUT; OUT.m_specular = float4(color, 1.0); - OUT.m_reflection = float4(color, 1.0); return OUT; } diff --git a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake index f14fb4f4a0..6902d456ff 100644 --- a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake +++ b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake @@ -52,6 +52,8 @@ set(FILES Materials/Types/StandardPBR_ForwardPass_EDS.shader Materials/Types/StandardPBR_HandleOpacityDoubleSided.lua Materials/Types/StandardPBR_HandleOpacityMode.lua + Materials/Types/StandardPBR_LowEndForward.azsl + Materials/Types/StandardPBR_LowEndForward.shader Materials/Types/StandardPBR_ParallaxState.lua Materials/Types/StandardPBR_Roughness.lua Materials/Types/StandardPBR_ShaderEnable.lua @@ -116,6 +118,7 @@ set(FILES Passes/DiffuseProbeGridBlendDistance.pass Passes/DiffuseProbeGridBlendIrradiance.pass Passes/DiffuseProbeGridBorderUpdate.pass + Passes/DiffuseProbeGridClassification.pass Passes/DiffuseProbeGridDownsample.pass Passes/DiffuseProbeGridRayTracing.pass Passes/DiffuseProbeGridRelocation.pass @@ -144,6 +147,7 @@ set(FILES Passes/FullscreenCopy.pass Passes/FullscreenOutputOnly.pass Passes/ImGui.pass + Passes/LightAdaptationParent.pass Passes/LightCulling.pass Passes/LightCullingHeatmap.pass Passes/LightCullingParent.pass @@ -152,6 +156,8 @@ set(FILES Passes/LightCullingTilePrepareMSAA.pass Passes/LookModificationComposite.pass Passes/LookModificationTransform.pass + Passes/LowEndForward.pass + Passes/LowEndPipeline.pass Passes/LuminanceHeatmap.pass Passes/LuminanceHistogramGenerator.pass Passes/MainPipeline.pass @@ -179,8 +185,10 @@ set(FILES Passes/ReflectionScreenSpace.pass Passes/ReflectionScreenSpaceBlur.pass Passes/ReflectionScreenSpaceBlurHorizontal.pass + Passes/ReflectionScreenSpaceBlurMobile.pass Passes/ReflectionScreenSpaceBlurVertical.pass Passes/ReflectionScreenSpaceComposite.pass + Passes/ReflectionScreenSpaceMobile.pass Passes/ReflectionScreenSpaceTrace.pass Passes/Reflections_nomsaa.pass Passes/ShadowParent.pass @@ -205,6 +213,7 @@ set(FILES ShaderLib/Atom/Features/IndirectRendering.azsli ShaderLib/Atom/Features/MatrixUtility.azsli ShaderLib/Atom/Features/ParallaxMapping.azsli + ShaderLib/Atom/Features/ShaderQualityOptions.azsli ShaderLib/Atom/Features/SphericalHarmonicsUtility.azsli ShaderLib/Atom/Features/SrgSemantics.azsli ShaderLib/Atom/Features/ColorManagement/TransformColor.azsli @@ -234,6 +243,7 @@ set(FILES ShaderLib/Atom/Features/PBR/Hammersley.azsli ShaderLib/Atom/Features/PBR/LightingOptions.azsli ShaderLib/Atom/Features/PBR/LightingUtils.azsli + ShaderLib/Atom/Features/PBR/LowEndForwardPassOutput.azsli ShaderLib/Atom/Features/PBR/TransparentPassSrg.azsli ShaderLib/Atom/Features/PBR/Lighting/DualSpecularLighting.azsli ShaderLib/Atom/Features/PBR/Lighting/EnhancedLighting.azsli From 24aa0f852179f94bce8ae354b66c6804628bd1b7 Mon Sep 17 00:00:00 2001 From: antonmic Date: Fri, 7 May 2021 20:56:40 -0700 Subject: [PATCH 026/330] skybox pass separation for single vs double output --- .../Common/Assets/Passes/OpaqueParent.pass | 13 ++++-- .../Assets/Passes/PassTemplates.azasset | 4 ++ .../Feature/Common/Assets/Passes/SkyBox.pass | 5 +++ .../Assets/Passes/SkyBox_TwoOutputs.pass | 43 +++++++++++++++++++ .../Reflections/ReflectionComposite.azsl | 15 ++----- .../Common/Assets/Shaders/SkyBox/SkyBox.azsl | 11 +++++ .../Shaders/SkyBox/SkyBox_TwoOutputs.azsl | 15 +++++++ .../Shaders/SkyBox/SkyBox_TwoOutputs.shader | 22 ++++++++++ 8 files changed, 113 insertions(+), 15 deletions(-) create mode 100644 Gems/Atom/Feature/Common/Assets/Passes/SkyBox_TwoOutputs.pass create mode 100644 Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.azsl create mode 100644 Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.shader diff --git a/Gems/Atom/Feature/Common/Assets/Passes/OpaqueParent.pass b/Gems/Atom/Feature/Common/Assets/Passes/OpaqueParent.pass index 40d6a51e77..a691fe2534 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/OpaqueParent.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/OpaqueParent.pass @@ -305,7 +305,7 @@ }, { "Name": "SkyBoxPass", - "TemplateName": "SkyBoxTemplate", + "TemplateName": "SkyBoxTwoOutputsTemplate", "Enabled": true, "Connections": [ { @@ -315,6 +315,13 @@ "Attachment": "SpecularInputOutput" } }, + { + "LocalSlot": "ReflectionInputOutput", + "AttachmentRef": { + "Pass": "ReflectionsPass", + "Attachment": "ReflectionOutput" + } + }, { "LocalSlot": "SkyBoxDepth", "AttachmentRef": { @@ -331,8 +338,8 @@ { "LocalSlot": "ReflectionInput", "AttachmentRef": { - "Pass": "ReflectionsPass", - "Attachment": "ReflectionOutput" + "Pass": "SkyBoxPass", + "Attachment": "ReflectionInputOutput" } }, { diff --git a/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset b/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset index 2421d7fbe7..c56e8932b1 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset +++ b/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset @@ -92,6 +92,10 @@ "Name": "SkyBoxTemplate", "Path": "Passes/SkyBox.pass" }, + { + "Name": "SkyBoxTwoOutputsTemplate", + "Path": "Passes/SkyBox_TwoOutputs.pass" + }, { "Name": "UIPassTemplate", "Path": "Passes/UI.pass" diff --git a/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass b/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass index fb16271ba7..57f442e5de 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass @@ -12,6 +12,11 @@ "SlotType": "InputOutput", "ScopeAttachmentUsage": "RenderTarget" }, + { + "Name": "ReflectionInputOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget" + }, { "Name": "SkyBoxDepth", "SlotType": "InputOutput", diff --git a/Gems/Atom/Feature/Common/Assets/Passes/SkyBox_TwoOutputs.pass b/Gems/Atom/Feature/Common/Assets/Passes/SkyBox_TwoOutputs.pass new file mode 100644 index 0000000000..0ed7b39288 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Passes/SkyBox_TwoOutputs.pass @@ -0,0 +1,43 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "SkyBoxTwoOutputsTemplate", + "PassClass": "FullScreenTriangle", + "Slots": [ + { + "Name": "SpecularInputOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget" + }, + { + "Name": "ReflectionInputOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget" + }, + { + "Name": "SkyBoxDepth", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "DepthStencil" + } + ], + "PassData": { + "$type": "FullscreenTrianglePassData", + "ShaderAsset": { + "FilePath": "shaders/skybox/skybox_twooutputs.shader" + }, + "PipelineViewTag": "MainCamera", + "ShaderDataMappings": { + "FloatMappings": [ + { + "Name": "m_sunIntensityMultiplier", + "Value": 1.0 + } + ] + } + } + } + } +} diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionComposite.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionComposite.azsl index 865d657d85..92f8c3f638 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionComposite.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionComposite.azsl @@ -53,22 +53,13 @@ PSOutput MainPS(VSOutput IN) uint width, height, samples; PassSrg::m_reflection.GetDimensions(width, height, samples); - float nonZeroSamples = 0.0f; for (uint sampleIndex = 0; sampleIndex < samples; ++sampleIndex) { - float3 reflectionSample = PassSrg::m_reflection.Load(IN.m_position.xy, sampleIndex).rgb; - if(any(reflectionSample)) - { - reflection += reflectionSample; - nonZeroSamples += 1.0f; - } - } - - if(nonZeroSamples != 0.0f) - { - reflection /= nonZeroSamples; + reflection += PassSrg::m_reflection.Load(IN.m_position.xy, sampleIndex).rgb; } + reflection /= samples; + PSOutput OUT; OUT.m_color = float4(reflection, 1.0f); return OUT; diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl index a7de426374..4b3e9536b7 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl @@ -10,6 +10,11 @@ * */ +// Static Options: +// +// SKYBOX_TWO_OUTPUTS - Allows the skybox to render to two rendertargets instead of one + + #include #include #include @@ -102,6 +107,9 @@ float3 GetCubemapCoords(float3 original) struct PSOutput { float4 m_specular : SV_Target0; +#ifdef SKYBOX_TWO_OUTPUTS + float4 m_reflection : SV_Target1; +#endif }; PSOutput MainPS(VSOutput input) @@ -162,5 +170,8 @@ PSOutput MainPS(VSOutput input) PSOutput OUT; OUT.m_specular = float4(color, 1.0); +#ifdef SKYBOX_TWO_OUTPUTS + OUT.m_reflection = float4(color, 1.0); +#endif return OUT; } diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.azsl new file mode 100644 index 0000000000..99d7b45e4c --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.azsl @@ -0,0 +1,15 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#define SKYBOX_TWO_OUTPUTS + +#include "SkyBox.azsl" diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.shader b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.shader new file mode 100644 index 0000000000..ec80d4a20e --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.shader @@ -0,0 +1,22 @@ +{ + "Source" : "SkyBox_TwoOutputs", + + "DepthStencilState" : { + "Depth" : { "Enable" : true, "CompareFunc" : "GreaterEqual" } + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "MainVS", + "type": "Vertex" + }, + { + "name": "MainPS", + "type": "Fragment" + } + ] + } +} From cb245730a1f214c4891817b11bb617166f73c7b7 Mon Sep 17 00:00:00 2001 From: antonmic Date: Sun, 9 May 2021 23:01:24 -0700 Subject: [PATCH 027/330] work in progress --- .../Types/StandardPBR_LowEndForward.azsl | 2 ++ .../Feature/Common/Assets/Passes/Forward.pass | 20 ------------------- .../Feature/Common/Assets/Passes/SkyBox.pass | 5 ----- .../Shaders/SkyBox/SkyBox_TwoOutputs.azsl | 2 ++ .../atom_feature_common_asset_files.cmake | 4 ++++ 5 files changed, 8 insertions(+), 25 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl index a690cbf84a..c87faffcbe 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_LowEndForward.azsl @@ -10,6 +10,8 @@ * */ +// NOTE: This file is a temporary workaround until .shader files can #define macros for their .azsl files + #define QUALITY_LOW_END 1 #include "StandardPBR_ForwardPass.azsl" diff --git a/Gems/Atom/Feature/Common/Assets/Passes/Forward.pass b/Gems/Atom/Feature/Common/Assets/Passes/Forward.pass index 3dcc90ac5c..b66e3bb4e1 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/Forward.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/Forward.pass @@ -222,19 +222,6 @@ "AssetRef": { "FilePath": "Textures/BRDFTexture.attimage" } - }, - { - "Name": "ScatterDistanceImage", - "SizeSource": { - "Source": { - "Pass": "Parent", - "Attachment": "SwapChainOutput" - } - }, - "ImageDescriptor": { - "Format": "R11G11B10_FLOAT", - "SharedQueueMask": "Graphics" - } } ], "Connections": [ @@ -279,13 +266,6 @@ "Pass": "This", "Attachment": "BRDFTexture" } - }, - { - "LocalSlot": "ScatterDistanceOutput", - "AttachmentRef": { - "Pass": "This", - "Attachment": "ScatterDistanceImage" - } } ] } diff --git a/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass b/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass index 57f442e5de..fb16271ba7 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/SkyBox.pass @@ -12,11 +12,6 @@ "SlotType": "InputOutput", "ScopeAttachmentUsage": "RenderTarget" }, - { - "Name": "ReflectionInputOutput", - "SlotType": "InputOutput", - "ScopeAttachmentUsage": "RenderTarget" - }, { "Name": "SkyBoxDepth", "SlotType": "InputOutput", diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.azsl index 99d7b45e4c..feacd2f44f 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox_TwoOutputs.azsl @@ -10,6 +10,8 @@ * */ +// NOTE: This file is a temporary workaround until .shader files can #define macros for their .azsl files + #define SKYBOX_TWO_OUTPUTS #include "SkyBox.azsl" diff --git a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake index 6902d456ff..7419c1e669 100644 --- a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake +++ b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake @@ -54,6 +54,7 @@ set(FILES Materials/Types/StandardPBR_HandleOpacityMode.lua Materials/Types/StandardPBR_LowEndForward.azsl Materials/Types/StandardPBR_LowEndForward.shader + Materials/Types/StandardPBR_LowEndForward_EDS.shader Materials/Types/StandardPBR_ParallaxState.lua Materials/Types/StandardPBR_Roughness.lua Materials/Types/StandardPBR_ShaderEnable.lua @@ -194,6 +195,7 @@ set(FILES Passes/ShadowParent.pass Passes/Skinning.pass Passes/SkyBox.pass + Passes/SkyBox_TwoOutputs.pass Passes/SMAA1xApplyLinearHDRColor.pass Passes/SMAA1xApplyPerceptualColor.pass Passes/SMAABlendingWeightCalculation.pass @@ -483,4 +485,6 @@ set(FILES Shaders/SkinnedMesh/LinearSkinningPassSRG.azsli Shaders/SkyBox/SkyBox.azsl Shaders/SkyBox/SkyBox.shader + Shaders/SkyBox/SkyBox_TwoOutputs.azsl + Shaders/SkyBox/SkyBox_TwoOutputs.shader ) From d1eae23347c095333ed5c83a934a407de192d9c2 Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Mon, 10 May 2021 17:26:46 +0100 Subject: [PATCH 028/330] reduced cursor size. --- .../Components/FancyDocking.cpp | 2 +- .../Components/Widgets/TabWidget.cpp | 4 +- .../img/UI20/Cursors/Grab_release.svg | 40 +++++++++---------- .../Components/img/UI20/Cursors/Grabbing.svg | 40 +++++++++++-------- 4 files changed, 47 insertions(+), 39 deletions(-) diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp index b89a220b1a..1a7512a4fc 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp @@ -160,7 +160,7 @@ namespace AzQtComponents QObject::connect(m_dropZoneHoverFadeInTimer, &QTimer::timeout, this, &FancyDocking::onDropZoneHoverFadeInUpdate); m_dropZoneHoverFadeInTimer->setInterval(g_FancyDockingConstants.dropZoneHoverFadeUpdateIntervalMS); QIcon dragIcon = QIcon(QStringLiteral(":/Cursors/Grabbing.svg")); - m_dragCursor = QCursor(dragIcon.pixmap(32), 10, 5); + m_dragCursor = QCursor(dragIcon.pixmap(16), 5, 2); } FancyDocking::~FancyDocking() diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.cpp index a0006d7979..48fb47d7e6 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/TabWidget.cpp @@ -422,10 +422,10 @@ namespace AzQtComponents AzQtComponents::Style::addClass(this, g_emptyStyleClass); QIcon icon = QIcon(QStringLiteral(":/Cursors/Grab_release.svg")); - m_hoverCursor = QCursor(icon.pixmap(32), 10, 5); + m_hoverCursor = QCursor(icon.pixmap(16), 5, 2); icon = QIcon(QStringLiteral(":/Cursors/Grabbing.svg")); - m_dragCursor = QCursor(icon.pixmap(32), 10, 5); + m_dragCursor = QCursor(icon.pixmap(16), 5, 2); this->setCursor(m_hoverCursor); } diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grab_release.svg b/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grab_release.svg index c0da9b802f..ad89e7d1b1 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grab_release.svg +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grab_release.svg @@ -1,5 +1,5 @@ - + diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grabbing.svg b/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grabbing.svg index e70be77d51..96de4e997b 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grabbing.svg +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/img/UI20/Cursors/Grabbing.svg @@ -1,23 +1,31 @@ - + - - - - - + + + + + + + + + From 92ef82f9331dee683f3d4df7869c27d913321420 Mon Sep 17 00:00:00 2001 From: pereslav Date: Mon, 10 May 2021 19:52:23 +0100 Subject: [PATCH 029/330] Added handling parented net entities --- .../EntityReplicationManager.cpp | 6 ++--- .../NetworkEntity/NetworkEntityManager.cpp | 26 ++++++++++++++++++- .../Pipeline/NetworkPrefabProcessor.cpp | 4 +++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp index 74bdcd5cf0..64eb3fcc6a 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp @@ -550,10 +550,10 @@ namespace Multiplayer { replicatorEntity = entityList[0]; } - - AZ_Assert(replicatorEntity != nullptr, "Failed to create entity from prefab %s", prefabEntityId.m_prefabName.GetCStr()); - if (replicatorEntity == nullptr) + else { + AZ_Assert(false, "There should be exactly one created entity out of prefab %s, index %d. Got: %d", + prefabEntityId.m_prefabName.GetCStr(), prefabEntityId.m_entityOffset, entityList.size()); return false; } } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index ce55f66caa..3bcd613d8b 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -334,15 +334,39 @@ namespace Multiplayer const AzFramework::Spawnable::EntityList& entities = spawnable.GetEntities(); size_t entitiesSize = entities.size(); + using EntityIdMap = AZStd::unordered_map; + EntityIdMap originalToCloneIdMap; + for (size_t i = 0; i < entitiesSize; ++i) { - AZ::Entity* clone = serializeContext->CloneObject(entities[i].get()); + AZ::Entity* originalEntity = entities[i].get(); + AZ::Entity* clone = serializeContext->CloneObject(originalEntity); AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); + clone->SetId(AZ::Entity::MakeId()); + originalToCloneIdMap[originalEntity->GetId()] = clone->GetId(); + NetBindComponent* netBindComponent = clone->FindComponent(); if (netBindComponent != nullptr) { + // Update TransformComponent parent Id. It is guaranteed for the entities array to be sorted from parent->child here. + auto* transformComponent = clone->FindComponent(); + AZ::EntityId parentId = transformComponent->GetParentId(); + if (parentId.IsValid()) + { + auto it = originalToCloneIdMap.find(parentId); + if (it != originalToCloneIdMap.end()) + { + transformComponent->SetParentRelative(it->second); + } + else + { + AZ_Warning("NetworkEntityManager", false, "Entity %s doesn't have the parent entity %s present in network.spawnable", + clone->GetName().c_str(), parentId.ToString().data()); + } + } + PrefabEntityId prefabEntityId; prefabEntityId.m_prefabName = m_networkPrefabLibrary.GetPrefabNameFromAssetId(spawnable.GetId()); prefabEntityId.m_entityOffset = aznumeric_cast(i); diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp index 4962d16fb4..56201bd5fd 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp @@ -59,6 +59,7 @@ namespace Multiplayer return result; } + void NetworkPrefabProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab) { using namespace AzToolsFramework::Prefab; @@ -175,6 +176,9 @@ namespace Multiplayer (*it)->InvalidateDependencies(); (*it)->EvaluateDependencies(); } + + SpawnableUtils::SortEntitiesByTransformHierarchy(*networkSpawnable); + context.GetProcessedObjects().push_back(AZStd::move(object)); } else From 89bb0edb3082b40de3c9b3737ad31e845b4cdf75 Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Mon, 10 May 2021 23:04:56 -0700 Subject: [PATCH 030/330] ATOM-15518 Change Multilayer PBR To Use Lerp Base Blending Changed to lerp-based blending, with an implicit base layer. Renamed some variables to be more clear (blendWeight instead of blendMask, since the weights could come from vertex colors instead of a texture). Updated DefaultBlendMask_layers.png to better suit the new layering model. It has a black background and overlapping R, G, and B areas. Updated some of the test materials UV transforms to better fit the new DefaultBlendMask_layers image. Added a new test object, which is a plane that has painted vertices. Note that I updated test criteria in AtomSampleViewer to account for these changes as well. --- .../Types/StandardMultilayerPBR.materialtype | 2 +- .../Types/StandardMultilayerPBR_Common.azsli | 77 +++++++++++-------- ...tandardMultilayerPBR_DepthPass_WithPS.azsl | 2 +- .../StandardMultilayerPBR_ForwardPass.azsl | 39 +++++----- ...tandardMultilayerPBR_Shadowmap_WithPS.azsl | 2 +- .../Textures/DefaultBlendMask_layers.png | 4 +- .../001_ManyFeatures.material | 12 ++- .../002_ParallaxPdo.material | 3 +- ...aterial => 003_Debug_BlendSource.material} | 2 +- .../TestData/Objects/PaintedPlane.fbx | 3 + 10 files changed, 86 insertions(+), 60 deletions(-) rename Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/{003_Debug_BlendMaskValues.material => 003_Debug_BlendSource.material} (86%) create mode 100644 Gems/Atom/TestData/TestData/Objects/PaintedPlane.fbx diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype index e2119dcf12..3e95846b6e 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype @@ -204,7 +204,7 @@ "displayName": "Debug Draw Mode", "description": "Enables various debug view features.", "type": "Enum", - "enumValues": [ "None", "BlendMaskValues", "DepthMaps" ], + "enumValues": [ "None", "BlendSource", "DepthMaps" ], "defaultValue": "None", "connection": { "type": "ShaderOption", diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli index ba0eaf2ac1..f1f6d90e14 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli @@ -42,7 +42,7 @@ COMMON_SRG_INPUTS_PARALLAX(prefix) ShaderResourceGroup MaterialSrg : SRG_PerMaterial { - Texture2D m_blendMaskTexture; + Texture2D m_blendMaskTexture; uint m_blendMaskUvIndex; // Auto-generate material SRG fields for common inputs for each layer @@ -113,7 +113,7 @@ ShaderResourceGroup MaterialSrg : SRG_PerMaterial // ------ Shader Options ---------------------------------------- -enum class DebugDrawMode { None, BlendMaskValues, DepthMaps }; +enum class DebugDrawMode { None, BlendSource, DepthMaps }; option DebugDrawMode o_debugDrawMode; enum class BlendMaskSource { TextureMap, VertexColors, Fallback }; @@ -127,6 +127,10 @@ option bool o_blendMask_isBound; // ------ Blend Utilities ---------------------------------------- +// This is mainly used to pass extra data to the GetDepth callback function during the parallax depth search. +// But since we have it, we use it in some other functions as well rather than passing it around. +static float3 s_blendMaskFromVertexStream; + //! Returns the BlendMaskSource that will actually be used when rendering (not necessarily the same BlendMaskSource specified by the user) BlendMaskSource GetFinalBlendMaskSource() { @@ -151,40 +155,63 @@ BlendMaskSource GetFinalBlendMaskSource() } } -//! Return the final blend mask values to be used for rendering, based on the available data and configuration. -float3 GetBlendMaskValues(float2 uv, float3 vertexBlendMask) +//! Return the raw blend source values directly from the blend mask or vertex colors, depending on the available data and configuration. +//! layer1 is an implicit base layer +//! layer2 is weighted by r +//! layer3 is weighted by g +//! b is reserved for perhaps a dedicated puddle layer +float3 GetBlendSourceValues(float2 uv) { - float3 blendMaskValues; + float3 blendSourceValues = float3(0,0,0); switch(GetFinalBlendMaskSource()) { case BlendMaskSource::TextureMap: - blendMaskValues = MaterialSrg::m_blendMaskTexture.Sample(MaterialSrg::m_sampler, uv).rgb; + blendSourceValues = MaterialSrg::m_blendMaskTexture.Sample(MaterialSrg::m_sampler, uv).rgb; break; case BlendMaskSource::VertexColors: - blendMaskValues = vertexBlendMask; - break; - case BlendMaskSource::Fallback: - blendMaskValues = float3(1,1,1); + blendSourceValues = s_blendMaskFromVertexStream; break; } - blendMaskValues = blendMaskValues / (blendMaskValues.r + blendMaskValues.g + blendMaskValues.b); + return blendSourceValues; +} + +//! Return the final blend mask values to be used for rendering, based on the available data and configuration. +//! @return The blend weights for each layer. +//! Even though layer1 not explicitly specified in the blend source data, it is explicitly included with the returned values. +//! layer1 = r +//! layer2 = g +//! layer3 = b +float3 GetBlendWeights(float2 uv) +{ + float3 blendSourceValues = GetBlendSourceValues(uv); - return blendMaskValues; + // Calculate blend weights such that multiplying and adding them with layer data is equivalent + // to lerping between each layer. + // final = lerp(final, layer1, blendWeights.r) + // final = lerp(final, layer2, blendWeights.g) + // final = lerp(final, layer3, blendWeights.b) + + float3 blendWeights; + blendWeights.b = blendSourceValues.g; + blendWeights.g = (1.0 - blendSourceValues.g) * blendSourceValues.r; + blendWeights.r = (1.0 - blendSourceValues.g) * (1.0 - blendSourceValues.r); + + return blendWeights; } -float BlendLayers(float layer1, float layer2, float layer3, float3 blendMaskValues) +float BlendLayers(float layer1, float layer2, float layer3, float3 blendWeights) { - return dot(float3(layer1, layer2, layer3), blendMaskValues); + return dot(float3(layer1, layer2, layer3), blendWeights); } -float2 BlendLayers(float2 layer1, float2 layer2, float2 layer3, float3 blendMaskValues) +float2 BlendLayers(float2 layer1, float2 layer2, float2 layer3, float3 blendWeights) { - return layer1 * blendMaskValues.r + layer2 * blendMaskValues.g + layer3 * blendMaskValues.b; + return layer1 * blendWeights.r + layer2 * blendWeights.g + layer3 * blendWeights.b; } -float3 BlendLayers(float3 layer1, float3 layer2, float3 layer3, float3 blendMaskValues) +float3 BlendLayers(float3 layer1, float3 layer2, float3 layer3, float3 blendWeights) { - return layer1 * blendMaskValues.r + layer2 * blendMaskValues.g + layer3 * blendMaskValues.b; + return layer1 * blendWeights.r + layer2 * blendWeights.g + layer3 * blendWeights.b; } // ------ Parallax Utilities ---------------------------------------- @@ -203,16 +230,6 @@ bool ShouldHandleParallaxInDepthShaders() return ShouldHandleParallax() && o_parallax_enablePixelDepthOffset; } -// These static values are used to pass extra data to the GetDepth callback function during the parallax depth search. -static float3 s_blendMaskFromVertexStream; - -//! Setup static variables that are needed by the GetDepth callback function -//! @param vertexBlendMask the blend mask values from the vertex input stream. -void GetDepth_Setup(float3 vertexBlendMask) -{ - s_blendMaskFromVertexStream = vertexBlendMask; -} - // Callback function for ParallaxMapping.azsli DepthResult GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy) { @@ -260,8 +277,8 @@ DepthResult GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy) // Note, when the blend source is BlendMaskSource::VertexColors, parallax will not be able to blend correctly between layers. It will end up using the same blend mask values // for every UV position when searching for the intersection. This leads to smearing artifacts at the transition point, but these won't be so noticeable as long as // you have a small depth factor relative to the size of the blend transition. - float3 blendMaskValues = GetBlendMaskValues(uv, s_blendMaskFromVertexStream); + float3 blendWeights = GetBlendWeights(uv); - float depth = BlendLayers(layerDepthValues.r, layerDepthValues.g, layerDepthValues.b, blendMaskValues); + float depth = BlendLayers(layerDepthValues.r, layerDepthValues.g, layerDepthValues.b, blendWeights); return DepthResultAbsolute(depth); } diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl index ae156d7313..178deca8a2 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_DepthPass_WithPS.azsl @@ -108,7 +108,7 @@ PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace) float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) }; PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1); - GetDepth_Setup(IN.m_blendMask); + s_blendMaskFromVertexStream = IN.m_blendMask; float depth; diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl index a83ea629e4..a690c0569a 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl @@ -134,6 +134,8 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float { depth = IN.m_position.z; + s_blendMaskFromVertexStream = IN.m_blendMask; + // ------- Tangents & Bitangets ------- // We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space. @@ -156,15 +158,14 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // ------- Debug Modes ------- - if(o_debugDrawMode == DebugDrawMode::BlendMaskValues) + if(o_debugDrawMode == DebugDrawMode::BlendSource) { - float3 blendMaskValues = GetBlendMaskValues(IN.m_uv[MaterialSrg::m_blendMaskUvIndex], IN.m_blendMask); - return DebugOutput(blendMaskValues); + float3 blendSource = GetBlendSourceValues(IN.m_uv[MaterialSrg::m_blendMaskUvIndex]); + return DebugOutput(blendSource); } if(o_debugDrawMode == DebugDrawMode::DepthMaps) { - GetDepth_Setup(IN.m_blendMask); float depth = GetNormalizedDepth(-MaterialSrg::m_displacementMax, -MaterialSrg::m_displacementMin, IN.m_uv[MaterialSrg::m_parallaxUvIndex], float2(0,0), float2(0,0)); return DebugOutput(float3(depth,depth,depth)); } @@ -176,8 +177,6 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // Parallax mapping's non uniform uv transformations break screen space subsurface scattering, disable it when subsurface scatteirng is enabled if(ShouldHandleParallax()) { - GetDepth_Setup(IN.m_blendMask); - float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); @@ -218,13 +217,13 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // ------- Calculate Layer Blend Mask Values ------- // Now that any parallax has been calculated, we calculate the blend factors for any layers that are impacted by the parallax. - float3 blendMaskValues = GetBlendMaskValues(IN.m_uv[MaterialSrg::m_blendMaskUvIndex], IN.m_blendMask); + float3 blendWeights = GetBlendWeights(IN.m_uv[MaterialSrg::m_blendMaskUvIndex]); // ------- Normal ------- - float3 layer1_normalFactor = MaterialSrg::m_layer1_m_normalFactor * blendMaskValues.r; - float3 layer2_normalFactor = MaterialSrg::m_layer2_m_normalFactor * blendMaskValues.g; - float3 layer3_normalFactor = MaterialSrg::m_layer3_m_normalFactor * blendMaskValues.b; + float3 layer1_normalFactor = MaterialSrg::m_layer1_m_normalFactor * blendWeights.r; + float3 layer2_normalFactor = MaterialSrg::m_layer2_m_normalFactor * blendWeights.g; + float3 layer3_normalFactor = MaterialSrg::m_layer3_m_normalFactor * blendWeights.b; float3x3 layer1_uvMatrix = MaterialSrg::m_layer1_m_normalMapUvIndex == 0 ? MaterialSrg::m_layer1_m_uvMatrix : CreateIdentity3x3(); float3x3 layer2_uvMatrix = MaterialSrg::m_layer2_m_normalMapUvIndex == 0 ? MaterialSrg::m_layer2_m_uvMatrix : CreateIdentity3x3(); float3x3 layer3_uvMatrix = MaterialSrg::m_layer3_m_normalMapUvIndex == 0 ? MaterialSrg::m_layer3_m_uvMatrix : CreateIdentity3x3(); @@ -249,7 +248,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float float3 layer1_baseColor = BlendBaseColor(layer1_sampledColor, MaterialSrg::m_layer1_m_baseColor.rgb, MaterialSrg::m_layer1_m_baseColorFactor, o_layer1_o_baseColorTextureBlendMode, o_layer1_o_baseColor_useTexture); float3 layer2_baseColor = BlendBaseColor(layer2_sampledColor, MaterialSrg::m_layer2_m_baseColor.rgb, MaterialSrg::m_layer2_m_baseColorFactor, o_layer2_o_baseColorTextureBlendMode, o_layer2_o_baseColor_useTexture); float3 layer3_baseColor = BlendBaseColor(layer3_sampledColor, MaterialSrg::m_layer3_m_baseColor.rgb, MaterialSrg::m_layer3_m_baseColorFactor, o_layer3_o_baseColorTextureBlendMode, o_layer3_o_baseColor_useTexture); - float3 baseColor = BlendLayers(layer1_baseColor, layer2_baseColor, layer3_baseColor, blendMaskValues); + float3 baseColor = BlendLayers(layer1_baseColor, layer2_baseColor, layer3_baseColor, blendWeights); if(o_parallax_highlightClipping && displacementIsClipped) { @@ -264,7 +263,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float float layer1_metallic = GetMetallicInput(MaterialSrg::m_layer1_m_metallicMap, MaterialSrg::m_sampler, uvLayer1[MaterialSrg::m_layer1_m_metallicMapUvIndex], MaterialSrg::m_layer1_m_metallicFactor, o_layer1_o_metallic_useTexture); float layer2_metallic = GetMetallicInput(MaterialSrg::m_layer2_m_metallicMap, MaterialSrg::m_sampler, uvLayer2[MaterialSrg::m_layer2_m_metallicMapUvIndex], MaterialSrg::m_layer2_m_metallicFactor, o_layer2_o_metallic_useTexture); float layer3_metallic = GetMetallicInput(MaterialSrg::m_layer3_m_metallicMap, MaterialSrg::m_sampler, uvLayer3[MaterialSrg::m_layer3_m_metallicMapUvIndex], MaterialSrg::m_layer3_m_metallicFactor, o_layer3_o_metallic_useTexture); - metallic = BlendLayers(layer1_metallic, layer2_metallic, layer3_metallic, blendMaskValues); + metallic = BlendLayers(layer1_metallic, layer2_metallic, layer3_metallic, blendWeights); } // ------- Specular ------- @@ -272,7 +271,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float float layer1_specularF0Factor = GetSpecularInput(MaterialSrg::m_layer1_m_specularF0Map, MaterialSrg::m_sampler, uvLayer1[MaterialSrg::m_layer1_m_specularF0MapUvIndex], MaterialSrg::m_layer1_m_specularF0Factor, o_layer1_o_specularF0_useTexture); float layer2_specularF0Factor = GetSpecularInput(MaterialSrg::m_layer2_m_specularF0Map, MaterialSrg::m_sampler, uvLayer2[MaterialSrg::m_layer2_m_specularF0MapUvIndex], MaterialSrg::m_layer2_m_specularF0Factor, o_layer2_o_specularF0_useTexture); float layer3_specularF0Factor = GetSpecularInput(MaterialSrg::m_layer3_m_specularF0Map, MaterialSrg::m_sampler, uvLayer3[MaterialSrg::m_layer3_m_specularF0MapUvIndex], MaterialSrg::m_layer3_m_specularF0Factor, o_layer3_o_specularF0_useTexture); - float specularF0Factor = BlendLayers(layer1_specularF0Factor, layer2_specularF0Factor, layer3_specularF0Factor, blendMaskValues); + float specularF0Factor = BlendLayers(layer1_specularF0Factor, layer2_specularF0Factor, layer3_specularF0Factor, blendWeights); surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic); @@ -281,7 +280,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float float layer1_roughness = GetRoughnessInput(MaterialSrg::m_layer1_m_roughnessMap, MaterialSrg::m_sampler, uvLayer1[MaterialSrg::m_layer1_m_roughnessMapUvIndex], MaterialSrg::m_layer1_m_roughnessFactor, MaterialSrg::m_layer1_m_roughnessLowerBound, MaterialSrg::m_layer1_m_roughnessUpperBound, o_layer1_o_roughness_useTexture); float layer2_roughness = GetRoughnessInput(MaterialSrg::m_layer2_m_roughnessMap, MaterialSrg::m_sampler, uvLayer2[MaterialSrg::m_layer2_m_roughnessMapUvIndex], MaterialSrg::m_layer2_m_roughnessFactor, MaterialSrg::m_layer2_m_roughnessLowerBound, MaterialSrg::m_layer2_m_roughnessUpperBound, o_layer2_o_roughness_useTexture); float layer3_roughness = GetRoughnessInput(MaterialSrg::m_layer3_m_roughnessMap, MaterialSrg::m_sampler, uvLayer3[MaterialSrg::m_layer3_m_roughnessMapUvIndex], MaterialSrg::m_layer3_m_roughnessFactor, MaterialSrg::m_layer3_m_roughnessLowerBound, MaterialSrg::m_layer3_m_roughnessUpperBound, o_layer3_o_roughness_useTexture); - surface.roughnessLinear = BlendLayers(layer1_roughness, layer2_roughness, layer3_roughness, blendMaskValues); + surface.roughnessLinear = BlendLayers(layer1_roughness, layer2_roughness, layer3_roughness, blendWeights); surface.CalculateRoughnessA(); @@ -314,19 +313,19 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float float3 layer1_emissive = GetEmissiveInput(MaterialSrg::m_layer1_m_emissiveMap, MaterialSrg::m_sampler, uvLayer1[MaterialSrg::m_layer1_m_emissiveMapUvIndex], MaterialSrg::m_layer1_m_emissiveIntensity, MaterialSrg::m_layer1_m_emissiveColor.rgb, o_layer1_o_emissiveEnabled, o_layer1_o_emissive_useTexture); float3 layer2_emissive = GetEmissiveInput(MaterialSrg::m_layer2_m_emissiveMap, MaterialSrg::m_sampler, uvLayer2[MaterialSrg::m_layer2_m_emissiveMapUvIndex], MaterialSrg::m_layer2_m_emissiveIntensity, MaterialSrg::m_layer2_m_emissiveColor.rgb, o_layer2_o_emissiveEnabled, o_layer2_o_emissive_useTexture); float3 layer3_emissive = GetEmissiveInput(MaterialSrg::m_layer3_m_emissiveMap, MaterialSrg::m_sampler, uvLayer3[MaterialSrg::m_layer3_m_emissiveMapUvIndex], MaterialSrg::m_layer3_m_emissiveIntensity, MaterialSrg::m_layer3_m_emissiveColor.rgb, o_layer3_o_emissiveEnabled, o_layer3_o_emissive_useTexture); - lightingData.emissiveLighting = BlendLayers(layer1_emissive, layer2_emissive, layer3_emissive, blendMaskValues); + lightingData.emissiveLighting = BlendLayers(layer1_emissive, layer2_emissive, layer3_emissive, blendWeights); // ------- Occlusion ------- float layer1_diffuseAmbientOcclusion = GetOcclusionInput(MaterialSrg::m_layer1_m_diffuseOcclusionMap, MaterialSrg::m_sampler, uvLayer1[MaterialSrg::m_layer1_m_diffuseOcclusionMapUvIndex], MaterialSrg::m_layer1_m_diffuseOcclusionFactor, o_layer1_o_diffuseOcclusion_useTexture); float layer2_diffuseAmbientOcclusion = GetOcclusionInput(MaterialSrg::m_layer2_m_diffuseOcclusionMap, MaterialSrg::m_sampler, uvLayer2[MaterialSrg::m_layer2_m_diffuseOcclusionMapUvIndex], MaterialSrg::m_layer2_m_diffuseOcclusionFactor, o_layer2_o_diffuseOcclusion_useTexture); float layer3_diffuseAmbientOcclusion = GetOcclusionInput(MaterialSrg::m_layer3_m_diffuseOcclusionMap, MaterialSrg::m_sampler, uvLayer3[MaterialSrg::m_layer3_m_diffuseOcclusionMapUvIndex], MaterialSrg::m_layer3_m_diffuseOcclusionFactor, o_layer3_o_diffuseOcclusion_useTexture); - lightingData.diffuseAmbientOcclusion = BlendLayers(layer1_diffuseAmbientOcclusion, layer2_diffuseAmbientOcclusion, layer3_diffuseAmbientOcclusion, blendMaskValues); + lightingData.diffuseAmbientOcclusion = BlendLayers(layer1_diffuseAmbientOcclusion, layer2_diffuseAmbientOcclusion, layer3_diffuseAmbientOcclusion, blendWeights); float layer1_specularOcclusion = GetOcclusionInput(MaterialSrg::m_layer1_m_specularOcclusionMap, MaterialSrg::m_sampler, uvLayer1[MaterialSrg::m_layer1_m_specularOcclusionMapUvIndex], MaterialSrg::m_layer1_m_specularOcclusionFactor, o_layer1_o_specularOcclusion_useTexture); float layer2_specularOcclusion = GetOcclusionInput(MaterialSrg::m_layer2_m_specularOcclusionMap, MaterialSrg::m_sampler, uvLayer2[MaterialSrg::m_layer2_m_specularOcclusionMapUvIndex], MaterialSrg::m_layer2_m_specularOcclusionFactor, o_layer2_o_specularOcclusion_useTexture); float layer3_specularOcclusion = GetOcclusionInput(MaterialSrg::m_layer3_m_specularOcclusionMap, MaterialSrg::m_sampler, uvLayer3[MaterialSrg::m_layer3_m_specularOcclusionMapUvIndex], MaterialSrg::m_layer3_m_specularOcclusionFactor, o_layer3_o_specularOcclusion_useTexture); - lightingData.specularOcclusion = BlendLayers(layer1_specularOcclusion, layer2_specularOcclusion, layer3_specularOcclusion, blendMaskValues); + lightingData.specularOcclusion = BlendLayers(layer1_specularOcclusion, layer2_specularOcclusion, layer3_specularOcclusion, blendWeights); // ------- Clearcoat ------- @@ -385,11 +384,11 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // --- Blend Layers --- - surface.clearCoat.factor = BlendLayers(layer1_clearCoatFactor, layer2_clearCoatFactor, layer3_clearCoatFactor, blendMaskValues); - surface.clearCoat.roughness = BlendLayers(layer1_clearCoatRoughness, layer2_clearCoatRoughness, layer3_clearCoatRoughness, blendMaskValues); + surface.clearCoat.factor = BlendLayers(layer1_clearCoatFactor, layer2_clearCoatFactor, layer3_clearCoatFactor, blendWeights); + surface.clearCoat.roughness = BlendLayers(layer1_clearCoatRoughness, layer2_clearCoatRoughness, layer3_clearCoatRoughness, blendWeights); // [GFX TODO][ATOM-14592] This is not the right way to blend the normals. We need to use ReorientTangentSpaceNormal(), and that requires GetClearCoatInputs() to return the normal in TS instead of WS. - surface.clearCoat.normal = BlendLayers(layer1_clearCoatNormal, layer2_clearCoatNormal, layer3_clearCoatNormal, blendMaskValues); + surface.clearCoat.normal = BlendLayers(layer1_clearCoatNormal, layer2_clearCoatNormal, layer3_clearCoatNormal, blendWeights); surface.clearCoat.normal = normalize(surface.clearCoat.normal); // manipulate base layer f0 if clear coat is enabled diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl index 325937b228..2ab4c50841 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Shadowmap_WithPS.azsl @@ -107,7 +107,7 @@ PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace) float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) }; PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1); - GetDepth_Setup(IN.m_blendMask); + s_blendMaskFromVertexStream = IN.m_blendMask; float depth; diff --git a/Gems/Atom/Feature/Common/Assets/Textures/DefaultBlendMask_layers.png b/Gems/Atom/Feature/Common/Assets/Textures/DefaultBlendMask_layers.png index d1606516af..5e60261dd7 100644 --- a/Gems/Atom/Feature/Common/Assets/Textures/DefaultBlendMask_layers.png +++ b/Gems/Atom/Feature/Common/Assets/Textures/DefaultBlendMask_layers.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f74ffab6ee15906158d27cbd2e5556e8fcdfd7820c0d0fd4b403de8a7af81662 -size 5651 +oid sha256:6660fa05dbf1e90298472fb41d99fff80a80de64ee88d17af4d0df3bdafb1ff6 +size 52877 diff --git a/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures.material b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures.material index b2a3eac890..b85705fcb8 100644 --- a/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures.material +++ b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures.material @@ -134,8 +134,14 @@ "enable": true }, "uv": { - "offsetU": -0.2800000011920929, - "rotateDegrees": 39.599998474121097 + "center": [ + 0.10000000149011612, + 0.20000000298023225 + ], + "offsetU": 0.23000000417232514, + "offsetV": -0.23999999463558198, + "rotateDegrees": 39.599998474121097, + "scale": 1.100000023841858 } } -} +} \ No newline at end of file diff --git a/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/002_ParallaxPdo.material b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/002_ParallaxPdo.material index e7903a8c91..f6c881aee5 100644 --- a/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/002_ParallaxPdo.material +++ b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/002_ParallaxPdo.material @@ -30,6 +30,7 @@ "layer2_parallax": { "enable": true, "factor": 0.05299999937415123, + "offset": -0.024000000208616258, "textureMap": "TestData/Textures/cc0/Rock030_2K_Displacement.jpg" }, "layer2_roughness": { @@ -49,4 +50,4 @@ "pdo": true } } -} +} \ No newline at end of file diff --git a/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/003_Debug_BlendMaskValues.material b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/003_Debug_BlendSource.material similarity index 86% rename from Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/003_Debug_BlendMaskValues.material rename to Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/003_Debug_BlendSource.material index 94a1ec6a30..cdd21212c9 100644 --- a/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/003_Debug_BlendMaskValues.material +++ b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/003_Debug_BlendSource.material @@ -5,7 +5,7 @@ "propertyLayoutVersion": 3, "properties": { "general": { - "debugDrawMode": "BlendMaskValues" + "debugDrawMode": "BlendSource" } } } diff --git a/Gems/Atom/TestData/TestData/Objects/PaintedPlane.fbx b/Gems/Atom/TestData/TestData/Objects/PaintedPlane.fbx new file mode 100644 index 0000000000..be30ca4639 --- /dev/null +++ b/Gems/Atom/TestData/TestData/Objects/PaintedPlane.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:053a7cd73b37c815900f87abd524830e6f23eee75d3b72fb866e86498d528159 +size 36156 From 7276253c4225dd09dc0208693815abffe7ac672c Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Tue, 11 May 2021 08:20:44 +0100 Subject: [PATCH 031/330] renamed function --- .../AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp | 4 ++-- .../AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp index faeae0a06d..85784d61e0 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp @@ -126,7 +126,7 @@ namespace AzToolsFramework { QStylePainter p(this); - if (IsSectionSeparator()) + if (IsReorderableRow()) { const QPen linePen(QColor(0x3B3E3F)); p.setPen(linePen); @@ -1332,7 +1332,7 @@ namespace AzToolsFramework return canBeTopLevel(this); } - bool PropertyRowWidget::IsSectionSeparator() const + bool PropertyRowWidget::IsReorderableRow() const { return CanBeReordered(); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx index b6c94dc98b..58bf3bb9f0 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx @@ -83,7 +83,7 @@ namespace AzToolsFramework PropertyRowWidget* GetParentRow() const { return m_parentRow; } int GetLevel() const; bool IsTopLevel() const; - bool IsSectionSeparator() const; + bool IsReorderableRow() const; // Remove the default label and append the text to the name label. bool GetAppendDefaultLabelToName(); From 8e4d0d73dcea7f50d05a9318011de9a07e0d88e6 Mon Sep 17 00:00:00 2001 From: antonmic Date: Tue, 11 May 2021 01:29:53 -0700 Subject: [PATCH 032/330] Good working state, but material always emmits low end draw item --- .../Materials/Types/StandardPBR.materialtype | 24 ++-- .../Atom/Features/PBR/Lights/Ibl.azsli | 108 ++++++------------ .../Atom/Features/ShaderQualityOptions.azsli | 3 +- .../Code/Include/Atom/RPI.Public/Pass/Pass.h | 1 + .../RPI/Code/Source/RPI.Public/Pass/Pass.cpp | 4 +- 5 files changed, 59 insertions(+), 81 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype index 1612bf43b0..47d8a9d9d5 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype @@ -1304,9 +1304,11 @@ "textureProperty": "baseColor.textureMap", "useTextureProperty": "baseColor.useTexture", "dependentProperties": ["baseColor.textureMapUv", "baseColor.textureBlendMode"], - "shaderTags": [ + "shaderTags": [ "ForwardPass", - "ForwardPass_EDS" + "ForwardPass_EDS", + "LowEndForward", + "LowEndForward_EDS" ], "shaderOption": "o_baseColor_useTexture" } @@ -1317,9 +1319,11 @@ "textureProperty": "metallic.textureMap", "useTextureProperty": "metallic.useTexture", "dependentProperties": ["metallic.textureMapUv"], - "shaderTags": [ + "shaderTags": [ "ForwardPass", - "ForwardPass_EDS" + "ForwardPass_EDS", + "LowEndForward", + "LowEndForward_EDS" ], "shaderOption": "o_metallic_useTexture" } @@ -1330,9 +1334,11 @@ "textureProperty": "specularF0.textureMap", "useTextureProperty": "specularF0.useTexture", "dependentProperties": ["specularF0.textureMapUv"], - "shaderTags": [ + "shaderTags": [ "ForwardPass", - "ForwardPass_EDS" + "ForwardPass_EDS", + "LowEndForward", + "LowEndForward_EDS" ], "shaderOption": "o_specularF0_useTexture" } @@ -1343,9 +1349,11 @@ "textureProperty": "normal.textureMap", "useTextureProperty": "normal.useTexture", "dependentProperties": ["normal.textureMapUv", "normal.factor", "normal.flipX", "normal.flipY"], - "shaderTags": [ + "shaderTags": [ "ForwardPass", - "ForwardPass_EDS" + "ForwardPass_EDS", + "LowEndForward", + "LowEndForward_EDS" ], "shaderOption": "o_normal_useTexture" } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli index 7400005508..721c48835d 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli @@ -18,32 +18,30 @@ #include #include -void ApplyIblDiffuse( +float3 GetIblDiffuse( float3 normal, float3 albedo, - float3 diffuseResponse, - out float3 outDiffuse) + float3 diffuseResponse) { float3 irradianceDir = MultiplyVectorQuaternion(normal, SceneSrg::m_iblOrientation); float3 diffuseSample = SceneSrg::m_diffuseEnvMap.Sample(SceneSrg::m_samplerEnv, GetCubemapCoords(irradianceDir)).rgb; - outDiffuse = diffuseResponse * albedo * diffuseSample; + return diffuseResponse * albedo * diffuseSample; } -void ApplyIblSpecular( +float3 GetIblSpecular( float3 position, float3 normal, float3 specularF0, float roughnessLinear, float3 dirToCamera, - float2 brdf, - out float3 outSpecular) + float2 brdf) { float3 reflectDir = reflect(-dirToCamera, normal); reflectDir = MultiplyVectorQuaternion(reflectDir, SceneSrg::m_iblOrientation); // global - outSpecular = SceneSrg::m_specularEnvMap.SampleLevel(SceneSrg::m_samplerEnv, GetCubemapCoords(reflectDir), GetRoughnessMip(roughnessLinear)).rgb; + float3 outSpecular = SceneSrg::m_specularEnvMap.SampleLevel(SceneSrg::m_samplerEnv, GetCubemapCoords(reflectDir), GetRoughnessMip(roughnessLinear)).rgb; outSpecular *= (specularF0 * brdf.x + brdf.y); // reflection probe @@ -72,86 +70,54 @@ void ApplyIblSpecular( outSpecular = lerp(outSpecular, probeSpecular, blendAmount); } + return outSpecular; } void ApplyIBL(Surface surface, inout LightingData lightingData) { - if (o_opacity_mode == OpacityMode::Blended || o_opacity_mode == OpacityMode::TintedTransparent) +#ifdef FORCE_IBL_IN_FORWARD_PASS + bool useDiffuseIbl = true; + bool useSpecularIbl = true; + bool useIbl = true; +#else + bool useDiffuseIbl = (o_opacity_mode == OpacityMode::Blended || o_opacity_mode == OpacityMode::TintedTransparent); + bool useSpecularIbl = (useDiffuseIbl || o_meshUseForwardPassIBLSpecular || o_materialUseForwardPassIBLSpecular); + bool useIbl = o_enableIBL && (useDiffuseIbl || useSpecularIbl); +#endif + + if(useIbl) { - // transparencies currently require IBL in the forward pass - if (o_enableIBL) + float iblExposureFactor = pow(2.0, SceneSrg::m_iblExposure); + + if(useDiffuseIbl) { - float3 iblDiffuse = 0.0f; - ApplyIblDiffuse( - surface.normal, - surface.albedo, - lightingData.diffuseResponse, - iblDiffuse); - - float3 iblSpecular = 0.0f; - ApplyIblSpecular( - surface.position, - surface.normal, - surface.specularF0, - surface.roughnessLinear, - lightingData.dirToCamera, - lightingData.brdf, - iblSpecular); - - // Adjust IBL lighting by exposure. - float iblExposureFactor = pow(2.0, SceneSrg::m_iblExposure); + float3 iblDiffuse = GetIblDiffuse(surface.normal, surface.albedo, lightingData.diffuseResponse); lightingData.diffuseLighting += (iblDiffuse * iblExposureFactor * lightingData.diffuseAmbientOcclusion); - lightingData.specularLighting += (iblSpecular * iblExposureFactor); } - } - else if (o_meshUseForwardPassIBLSpecular || o_materialUseForwardPassIBLSpecular) - { - if (o_enableIBL) - { - float3 iblSpecular = 0.0f; - ApplyIblSpecular( - surface.position, - surface.normal, - surface.specularF0, - surface.roughnessLinear, - lightingData.dirToCamera, - lightingData.brdf, - iblSpecular); + if(useSpecularIbl) + { + float3 iblSpecular = GetIblSpecular(surface.position, surface.normal, surface.specularF0, surface.roughnessLinear, lightingData.dirToCamera, lightingData.brdf); iblSpecular *= lightingData.multiScatterCompensation; - if (o_clearCoat_feature_enabled) + if (o_clearCoat_feature_enabled && surface.clearCoat.factor > 0.0f) { - if (surface.clearCoat.factor > 0.0f) - { - float clearCoatNdotV = saturate(dot(surface.clearCoat.normal, lightingData.dirToCamera)); - clearCoatNdotV = max(clearCoatNdotV, 0.01f); // [GFX TODO][ATOM-4466] This is a current band-aid for specular noise at grazing angles. - float2 clearCoatBrdf = PassSrg::m_brdfMap.Sample(PassSrg::LinearSampler, GetBRDFTexCoords(surface.clearCoat.roughness, clearCoatNdotV)).rg; - - // clear coat uses fixed IOR = 1.5 represents polyurethane which is the most common material for gloss clear coat - // coat layer assumed to be dielectric thus don't need multiple scattering compensation - float3 clearCoatSpecularF0 = float3(0.04f, 0.04f, 0.04f); - float3 clearCoatIblSpecular = 0.0f; + float clearCoatNdotV = saturate(dot(surface.clearCoat.normal, lightingData.dirToCamera)); + clearCoatNdotV = max(clearCoatNdotV, 0.01f); // [GFX TODO][ATOM-4466] This is a current band-aid for specular noise at grazing angles. + float2 clearCoatBrdf = PassSrg::m_brdfMap.Sample(PassSrg::LinearSampler, GetBRDFTexCoords(surface.clearCoat.roughness, clearCoatNdotV)).rg; - ApplyIblSpecular( - surface.position, - surface.clearCoat.normal, - clearCoatSpecularF0, - surface.clearCoat.roughness, - lightingData.dirToCamera, - clearCoatBrdf, - clearCoatIblSpecular); + // clear coat uses fixed IOR = 1.5 represents polyurethane which is the most common material for gloss clear coat + // coat layer assumed to be dielectric thus don't need multiple scattering compensation + float3 clearCoatSpecularF0 = float3(0.04f, 0.04f, 0.04f); + float3 clearCoatIblSpecular = GetIblSpecular(surface.position, surface.clearCoat.normal, clearCoatSpecularF0, surface.clearCoat.roughness, lightingData.dirToCamera, clearCoatBrdf); - clearCoatIblSpecular *= surface.clearCoat.factor; + clearCoatIblSpecular *= surface.clearCoat.factor; - // attenuate base layer energy - float3 clearCoatResponse = FresnelSchlickWithRoughness(clearCoatNdotV, clearCoatSpecularF0, surface.clearCoat.roughness) * surface.clearCoat.factor; - iblSpecular = iblSpecular * (1.0 - clearCoatResponse) * (1.0 - clearCoatResponse) + clearCoatIblSpecular; - } + // attenuate base layer energy + float3 clearCoatResponse = FresnelSchlickWithRoughness(clearCoatNdotV, clearCoatSpecularF0, surface.clearCoat.roughness) * surface.clearCoat.factor; + iblSpecular = iblSpecular * (1.0 - clearCoatResponse) * (1.0 - clearCoatResponse) + clearCoatIblSpecular; } - - float iblExposureFactor = pow(2.0f, SceneSrg::m_iblExposure); lightingData.specularLighting += (iblSpecular * iblExposureFactor); } } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli index 907e67ada5..d6fb259548 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli @@ -18,7 +18,8 @@ #ifdef QUALITY_LOW_END -#define UNIFIED_FORWARD_OUTPUT 1 +#define UNIFIED_FORWARD_OUTPUT 1 +#define FORCE_IBL_IN_FORWARD_PASS 1 #endif diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Pass.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Pass.h index b0d6bd4117..1cba71ae7e 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Pass.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Pass.h @@ -381,6 +381,7 @@ namespace AZ uint64_t m_createdByPassRequest : 1; uint64_t m_initialized : 1; uint64_t m_enabled : 1; + uint64_t m_parentEnabled : 1; uint64_t m_alreadyCreated : 1; uint64_t m_alreadyReset : 1; uint64_t m_alreadyPrepared : 1; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp index 9401d1a9e0..f93d661b0f 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp @@ -93,11 +93,12 @@ namespace AZ void Pass::SetEnabled(bool enabled) { m_flags.m_enabled = enabled; + OnHierarchyChange(); } bool Pass::IsEnabled() const { - return m_flags.m_enabled; + return m_flags.m_enabled && (m_flags.m_parentEnabled || m_parent == nullptr); } // --- Error Logging --- @@ -140,6 +141,7 @@ namespace AZ } // Set new tree depth and path + m_flags.m_parentEnabled = m_parent->IsEnabled(); m_treeDepth = m_parent->m_treeDepth + 1; m_path = ConcatPassName(m_parent->m_path, m_name); m_flags.m_partOfHierarchy = m_parent->m_flags.m_partOfHierarchy; From b08643d9da90d0215ed5b22efcc3716fdaa90622 Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Tue, 11 May 2021 11:24:51 +0100 Subject: [PATCH 033/330] Use renamed functions in stylesheet. --- .../AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx | 2 +- Code/Sandbox/Editor/Style/Editor.qss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx index 58bf3bb9f0..1c17cab69f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx @@ -44,7 +44,7 @@ namespace AzToolsFramework Q_PROPERTY(bool hasChildRows READ HasChildRows); Q_PROPERTY(bool isTopLevel READ IsTopLevel); Q_PROPERTY(int getLevel READ GetLevel); - Q_PROPERTY(bool isSectionSeparator READ IsSectionSeparator); + Q_PROPERTY(bool canBeReordered READ CanBeReordered); Q_PROPERTY(bool appendDefaultLabelToName READ GetAppendDefaultLabelToName WRITE AppendDefaultLabelToName) public: AZ_CLASS_ALLOCATOR(PropertyRowWidget, AZ::SystemAllocator, 0) diff --git a/Code/Sandbox/Editor/Style/Editor.qss b/Code/Sandbox/Editor/Style/Editor.qss index 7887560105..fa7d67dd43 100644 --- a/Code/Sandbox/Editor/Style/Editor.qss +++ b/Code/Sandbox/Editor/Style/Editor.qss @@ -38,7 +38,7 @@ AzToolsFramework--ComponentPaletteWidget > QTreeView background-color: #222222; } -AzToolsFramework--PropertyRowWidget[isSectionSeparator="true"] QLabel#Name +AzToolsFramework--PropertyRowWidget[canBeReordered="true"] QLabel#Name { font-weight: bold; } From dbe16c6a164cf89342749efbbd2d0785d473a37a Mon Sep 17 00:00:00 2001 From: darapan Date: Tue, 11 May 2021 05:07:16 -0700 Subject: [PATCH 034/330] "fixing review comments" --- ...s.py => Editor_NewExistingLevels_Works.py} | 17 +++++--------- .../Gem/PythonTests/smoke/ImportPathHelper.py | 16 ------------- ....py => test_CLITool_AssetBuilder_Works.py} | 7 +++--- ...> test_CLITool_AssetBundlerBatch_Works.py} | 7 +++--- ...test_CLITool_AssetProcessorBatch_Works.py} | 23 +++++-------------- ....py => test_CLITool_AzTestRunner_Works.py} | 7 +++--- ...st_CLITool_PythonBindingsExample_Works.py} | 8 +++---- ...st_CLITool_SerializeContextTools_Works.py} | 7 +++--- ...=> test_Editor_NewExistingLevels_Works.py} | 6 +++-- ...> test_StaticTools_GenPakShaders_Works.py} | 7 +++--- 10 files changed, 34 insertions(+), 71 deletions(-) rename AutomatedTesting/Gem/PythonTests/smoke/{Editor_NewExistingLevels.py => Editor_NewExistingLevels_Works.py} (93%) delete mode 100644 AutomatedTesting/Gem/PythonTests/smoke/ImportPathHelper.py rename AutomatedTesting/Gem/PythonTests/smoke/{test_AssetBuilder.py => test_CLITool_AssetBuilder_Works.py} (89%) rename AutomatedTesting/Gem/PythonTests/smoke/{test_AssetBundlerBatch.py => test_CLITool_AssetBundlerBatch_Works.py} (89%) rename AutomatedTesting/Gem/PythonTests/smoke/{test_AssetProcessorBatch.py => test_CLITool_AssetProcessorBatch_Works.py} (54%) rename AutomatedTesting/Gem/PythonTests/smoke/{test_AzTestRunner.py => test_CLITool_AzTestRunner_Works.py} (90%) rename AutomatedTesting/Gem/PythonTests/smoke/{test_PythonBindingsExample.py => test_CLITool_PythonBindingsExample_Works.py} (87%) rename AutomatedTesting/Gem/PythonTests/smoke/{test_SerializeContextTools.py => test_CLITool_SerializeContextTools_Works.py} (88%) rename AutomatedTesting/Gem/PythonTests/smoke/{test_Editor_NewExistingLevels.py => test_Editor_NewExistingLevels_Works.py} (88%) rename AutomatedTesting/Gem/PythonTests/smoke/{test_Statictool_Scripts.py => test_StaticTools_GenPakShaders_Works.py} (90%) diff --git a/AutomatedTesting/Gem/PythonTests/smoke/Editor_NewExistingLevels.py b/AutomatedTesting/Gem/PythonTests/smoke/Editor_NewExistingLevels_Works.py similarity index 93% rename from AutomatedTesting/Gem/PythonTests/smoke/Editor_NewExistingLevels.py rename to AutomatedTesting/Gem/PythonTests/smoke/Editor_NewExistingLevels_Works.py index 4e4a037ec9..ce46da494c 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/Editor_NewExistingLevels.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/Editor_NewExistingLevels_Works.py @@ -9,9 +9,7 @@ remove or modify any license notices. This file is distributed on an "AS IS" BAS WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -Test case ID: LY-123945 Test Case Title: Create Test for UI apps- Editor -URL of the test case: https://jira.agscollab.com/browse/LY-123945 """ @@ -30,7 +28,7 @@ class Tests(): # fmt: on -def Editor_NewExistingLevels(): +def Editor_NewExistingLevels_Works(): """ Summary: Perform the below operations on Editor @@ -69,9 +67,9 @@ def Editor_NewExistingLevels(): """ import os - import hydra_editor_utils as hydra - from utils import TestHelper as helper - from utils import Report + import editor_python_test_tools.hydra_editor_utils as hydra + from editor_python_test_tools.utils import TestHelper as helper + from editor_python_test_tools.utils import Report import azlmbr.bus as bus import azlmbr.editor as editor import azlmbr.legacy.general as general @@ -146,10 +144,7 @@ def Editor_NewExistingLevels(): if __name__ == "__main__": - import ImportPathHelper as imports - imports.init() + from editor_python_test_tools.utils import Report - from utils import Report - - Report.start_test(Editor_NewExistingLevels) + Report.start_test(Editor_NewExistingLevels_Works) diff --git a/AutomatedTesting/Gem/PythonTests/smoke/ImportPathHelper.py b/AutomatedTesting/Gem/PythonTests/smoke/ImportPathHelper.py deleted file mode 100644 index 70bed6e526..0000000000 --- a/AutomatedTesting/Gem/PythonTests/smoke/ImportPathHelper.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. - -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" - -def init(): - import os - import sys - sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../automatedtesting_shared') - sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../EditorPythonTestTools/editor_python_test_tools') diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_AssetBuilder.py b/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AssetBuilder_Works.py similarity index 89% rename from AutomatedTesting/Gem/PythonTests/smoke/test_AssetBuilder.py rename to AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AssetBuilder_Works.py index da2abff50f..e20809b1b8 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/test_AssetBuilder.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AssetBuilder_Works.py @@ -10,7 +10,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. """ """ -LY-124059 : CLI tool - AssetBuilder +CLI tool - AssetBuilder Launch AssetBuilder and Verify the help message """ @@ -23,7 +23,7 @@ import ly_test_tools.environment.process_utils as process_utils @pytest.mark.parametrize("project", ["AutomatedTesting"]) @pytest.mark.usefixtures("automatic_process_killer") @pytest.mark.SUITE_smoke -class TestAssetBuilder(object): +class TestCLIToolAssetBuilderWorks(object): @pytest.fixture(autouse=True) def setup_teardown(self, request): def teardown(): @@ -31,8 +31,7 @@ class TestAssetBuilder(object): request.addfinalizer(teardown) - @pytest.mark.test_case_id("LY-124059") - def test_AssetBuilder(self, request, editor, build_directory): + def test_CLITool_AssetBuilder_Works(self, request, editor, build_directory): file_path = os.path.join(build_directory, "AssetBuilder") help_message = "AssetBuilder is part of the Asset Processor" # Launch AssetBuilder diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_AssetBundlerBatch.py b/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AssetBundlerBatch_Works.py similarity index 89% rename from AutomatedTesting/Gem/PythonTests/smoke/test_AssetBundlerBatch.py rename to AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AssetBundlerBatch_Works.py index d361c62f5f..8a20715b3f 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/test_AssetBundlerBatch.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AssetBundlerBatch_Works.py @@ -10,7 +10,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. """ """ -LY-124060 : CLI tool - AssetBundlerBatch +CLI tool - AssetBundlerBatch Launch AssetBundlerBatch and Verify the help message """ @@ -23,7 +23,7 @@ import ly_test_tools.environment.process_utils as process_utils @pytest.mark.parametrize("project", ["AutomatedTesting"]) @pytest.mark.usefixtures("automatic_process_killer") @pytest.mark.SUITE_smoke -class TestAssetBundlerBatch(object): +class TestCLIToolAssetBundlerBatchWorks(object): @pytest.fixture(autouse=True) def setup_teardown(self, request): def teardown(): @@ -31,8 +31,7 @@ class TestAssetBundlerBatch(object): request.addfinalizer(teardown) - @pytest.mark.test_case_id("LY-124060") - def test_AssetBundlerBatch(self, request, editor, build_directory): + def test_CLITool_AssetBundlerBatch_Works(self, request, editor, build_directory): file_path = os.path.join(build_directory, "AssetBundlerBatch") help_message = "Specifies the Seed List file to operate on by path" # Launch AssetBundlerBatch diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_AssetProcessorBatch.py b/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AssetProcessorBatch_Works.py similarity index 54% rename from AutomatedTesting/Gem/PythonTests/smoke/test_AssetProcessorBatch.py rename to AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AssetProcessorBatch_Works.py index ab541b94c1..30e681050c 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/test_AssetProcessorBatch.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AssetProcessorBatch_Works.py @@ -10,34 +10,23 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. """ """ -LY-124061 : CLI tool - AssetProcessorBatch +CLI tool - AssetProcessorBatch Launch AssetProcessorBatch and Shutdown AssetProcessorBatch without any crash """ # Import builtin libraries import pytest -import os -import sys - -sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../assetpipeline/") - -# Import fixtures -from ap_fixtures.asset_processor_fixture import asset_processor as asset_processor +from ly_test_tools.o3de.asset_processor import AssetProcessor @pytest.mark.parametrize("project", ["AutomatedTesting"]) @pytest.mark.usefixtures("automatic_process_killer") -@pytest.mark.usefixtures("asset_processor") @pytest.mark.SUITE_smoke -class TestsAssetProcessorBatchs(object): - @pytest.mark.test_case_id("LY-124061") - def test_AssetProcessorBatch(self, asset_processor): +class TestsCLIToolAssetProcessorBatchWorks(object): + def test_CLITool_AssetProcessorBatch_Works(self, workspace): """ Test Launching AssetProcessorBatch and verifies that is shuts down without issue """ - # Create a sample asset root so we don't process every asset for every platform - asset_processor.create_temp_asset_root() - # Launch AssetProcessorBatch, assert batch processing success - result, _ = asset_processor.batch_process() - assert result, "AP Batch failed" + asset_processor = AssetProcessor(workspace) + asset_processor.batch_process() diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_AzTestRunner.py b/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AzTestRunner_Works.py similarity index 90% rename from AutomatedTesting/Gem/PythonTests/smoke/test_AzTestRunner.py rename to AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AzTestRunner_Works.py index d1bc44fe2f..5983cd7496 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/test_AzTestRunner.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AzTestRunner_Works.py @@ -10,7 +10,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. """ """ -LY-124062 : CLI tool - AzTestRunner +CLI tool - AzTestRunner Launch AzTestRunner and Verify the help message """ @@ -23,7 +23,7 @@ import ly_test_tools.environment.process_utils as process_utils @pytest.mark.parametrize("project", ["AutomatedTesting"]) @pytest.mark.usefixtures("automatic_process_killer") @pytest.mark.SUITE_smoke -class TestAzTestRunner(object): +class TestCLIToolAzTestRunnerWorks(object): @pytest.fixture(autouse=True) def setup_teardown(self, request): def teardown(): @@ -31,8 +31,7 @@ class TestAzTestRunner(object): request.addfinalizer(teardown) - @pytest.mark.test_case_id("LY-124062") - def test_AzTestRunner(self, request, editor, build_directory): + def test_CLITool_AzTestRunner_Works(self, request, editor, build_directory): file_path = os.path.join(build_directory, "AzTestRunner") help_message = "OKAY Symbol found: AzRunUnitTests" # Launch AzTestRunner diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_PythonBindingsExample.py b/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_PythonBindingsExample_Works.py similarity index 87% rename from AutomatedTesting/Gem/PythonTests/smoke/test_PythonBindingsExample.py rename to AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_PythonBindingsExample_Works.py index 140e76fc96..68d12d0e70 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/test_PythonBindingsExample.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_PythonBindingsExample_Works.py @@ -10,7 +10,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. """ """ -LY-124064 : CLI tool - PythonBindingsExample +CLI tool - PythonBindingsExample Launch PythonBindingsExample and Verify the help message """ @@ -23,7 +23,7 @@ import ly_test_tools.environment.process_utils as process_utils @pytest.mark.parametrize("project", ["AutomatedTesting"]) @pytest.mark.usefixtures("automatic_process_killer") @pytest.mark.SUITE_smoke -class TestPythonBindingsExample(object): +class TestCLIToolPythonBindingsExampleWorks(object): @pytest.fixture(autouse=True) def setup_teardown(self, request): def teardown(): @@ -31,8 +31,7 @@ class TestPythonBindingsExample(object): request.addfinalizer(teardown) - @pytest.mark.test_case_id("LY-124064") - def test_PythonBindingsExample(self, request, editor, build_directory): + def test_CLITool_PythonBindingsExample_Works(self, request, editor, build_directory): file_path = os.path.join(build_directory, "PythonBindingsExample") help_message = "--help Prints the help text" # Launch PythonBindingsExample @@ -42,4 +41,3 @@ class TestPythonBindingsExample(object): ), f"Error occurred while launching {file_path}: {output.stderr}" # Verify help message assert help_message in str(output.stdout), f"Help Message: {help_message} is not present" - diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_SerializeContextTools.py b/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_SerializeContextTools_Works.py similarity index 88% rename from AutomatedTesting/Gem/PythonTests/smoke/test_SerializeContextTools.py rename to AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_SerializeContextTools_Works.py index 763d84625d..94178ca9d6 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/test_SerializeContextTools.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_SerializeContextTools_Works.py @@ -10,7 +10,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. """ """ -LY-124066 : CLI tool - SerializeContextTools +CLI tool - SerializeContextTools Launch SerializeContextTools and Verify the help message """ @@ -23,7 +23,7 @@ import ly_test_tools.environment.process_utils as process_utils @pytest.mark.parametrize("project", ["AutomatedTesting"]) @pytest.mark.usefixtures("automatic_process_killer") @pytest.mark.SUITE_smoke -class TestSerializeContextTools(object): +class TestCLIToolSerializeContextToolsWorks(object): @pytest.fixture(autouse=True) def setup_teardown(self, request): def teardown(): @@ -31,8 +31,7 @@ class TestSerializeContextTools(object): request.addfinalizer(teardown) - @pytest.mark.test_case_id("LY-124066") - def test_SerializeContextTools(self, request, editor, build_directory): + def test_CLITool_SerializeContextTools_Works(self, request, editor, build_directory): file_path = os.path.join(build_directory, "SerializeContextTools") help_message = "Converts a file with an ObjectStream to the new JSON" # Launch SerializeContextTools diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_Editor_NewExistingLevels.py b/AutomatedTesting/Gem/PythonTests/smoke/test_Editor_NewExistingLevels_Works.py similarity index 88% rename from AutomatedTesting/Gem/PythonTests/smoke/test_Editor_NewExistingLevels.py rename to AutomatedTesting/Gem/PythonTests/smoke/test_Editor_NewExistingLevels_Works.py index 85d53d098d..510734a4a2 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/test_Editor_NewExistingLevels.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_Editor_NewExistingLevels_Works.py @@ -24,11 +24,13 @@ import ly_test_tools.environment.file_system as file_system @pytest.mark.parametrize("project", ["AutomatedTesting"]) @pytest.mark.parametrize("level", ["temp_level"]) class TestAutomation(TestAutomationBase): - def test_Editor_NewExistingLevels(self, request, workspace, editor, level, project, launcher_platform): + def test_Editor_NewExistingLevels_Works(self, request, workspace, editor, level, project, launcher_platform): def teardown(): file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True) + request.addfinalizer(teardown) file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True) - from . import Editor_NewExistingLevels as test_module + from . import Editor_NewExistingLevels_Works as test_module + self._run_test(request, workspace, editor, test_module) diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_Statictool_Scripts.py b/AutomatedTesting/Gem/PythonTests/smoke/test_StaticTools_GenPakShaders_Works.py similarity index 90% rename from AutomatedTesting/Gem/PythonTests/smoke/test_Statictool_Scripts.py rename to AutomatedTesting/Gem/PythonTests/smoke/test_StaticTools_GenPakShaders_Works.py index 8eac2ff099..05b91a602d 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/test_Statictool_Scripts.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_StaticTools_GenPakShaders_Works.py @@ -10,7 +10,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. """ """ -LY-124058: Static tool scripts +Static tool scripts Launch Static tool and Verify the help message """ @@ -34,9 +34,8 @@ def verify_help_message(static_tool): @pytest.mark.parametrize("project", ["AutomatedTesting"]) @pytest.mark.usefixtures("automatic_process_killer") @pytest.mark.SUITE_smoke -class TestStatictoolScripts(object): - @pytest.mark.test_case_id("LY-124058") - def test_Statictool_Scripts(self, request, editor): +class TestStaticToolsGenPakShadersWorks(object): + def test_StaticTools_GenPakShaders_Works(self, request, editor): static_tools = [ os.path.join(editor.workspace.paths.engine_root(), "scripts", "bundler", "gen_shaders.py"), os.path.join(editor.workspace.paths.engine_root(), "scripts", "bundler", "get_shader_list.py"), From d48df87bda8d653025e4a9c6fc8f45e873cbf443 Mon Sep 17 00:00:00 2001 From: darapan Date: Tue, 11 May 2021 05:20:06 -0700 Subject: [PATCH 035/330] "Updating Cmake" --- AutomatedTesting/Gem/PythonTests/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt index 127db8e7e6..2a0a7f8008 100644 --- a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt @@ -399,3 +399,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) Legacy::Editor AutomatedTesting.GameLauncher AutomatedTesting.Assets + COMPONENT + Smoke + ) +endif() \ No newline at end of file From 006f4d2e8294da878161ce9db571e55b84fe2dc9 Mon Sep 17 00:00:00 2001 From: darapan Date: Tue, 11 May 2021 05:34:38 -0700 Subject: [PATCH 036/330] "Adding Ap Test" --- .../Gem/PythonTests/CMakeLists.txt | 2 +- .../test_UIApps_AssetProcessor_CheckIdle.py | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 AutomatedTesting/Gem/PythonTests/smoke/test_UIApps_AssetProcessor_CheckIdle.py diff --git a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt index 2a0a7f8008..e91190f8e7 100644 --- a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt @@ -402,4 +402,4 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) COMPONENT Smoke ) -endif() \ No newline at end of file +endif() diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_UIApps_AssetProcessor_CheckIdle.py b/AutomatedTesting/Gem/PythonTests/smoke/test_UIApps_AssetProcessor_CheckIdle.py new file mode 100644 index 0000000000..0f5f870461 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_UIApps_AssetProcessor_CheckIdle.py @@ -0,0 +1,41 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + +""" +UI Apps: AssetProcessor +Open AssetProcessor, Wait until AssetProcessor is Idle +Close AssetProcessor. +""" + + +import pytest +from ly_test_tools.o3de.asset_processor import AssetProcessor + + +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +@pytest.mark.usefixtures("automatic_process_killer") +@pytest.mark.SUITE_smoke +class TestsUIAppsAssetProcessorCheckIdle(object): + @pytest.fixture(autouse=True) + def setup_teardown(self, request): + self.asset_processor = None + + def teardown(): + self.asset_processor.stop() + + request.addfinalizer(teardown) + + def test_UIApps_AssetProcessor_CheckIdle(self, workspace): + """ + Test Launching AssetProcessorBatch and verifies that is shuts down without issue + """ + self.asset_processor = AssetProcessor(workspace) + self.asset_processor.gui_process() From f7641f3d3845dad3acb219b727521a4ab26fc8a1 Mon Sep 17 00:00:00 2001 From: darapan Date: Tue, 11 May 2021 10:56:42 -0700 Subject: [PATCH 037/330] "Changing TIMEOUT in cmakelist.txt" --- AutomatedTesting/Gem/PythonTests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt index e91190f8e7..a81630b880 100644 --- a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt @@ -392,7 +392,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) TEST_SUITE smoke TEST_SERIAL PATH ${CMAKE_CURRENT_LIST_DIR}/smoke - TIMEOUT 3600 + TIMEOUT 1500 RUNTIME_DEPENDENCIES AZ::AssetProcessor AZ::PythonBindingsExample From 85e4f0d65ff5d5b7b6e1ba9759cace1907f60634 Mon Sep 17 00:00:00 2001 From: rgba16f <82187279+rgba16f@users.noreply.github.com> Date: Tue, 11 May 2021 16:24:28 -0500 Subject: [PATCH 038/330] Fix AzFramework::g_defaultSceneEntityDebugDisplayId not working for the AtomDebugDisplayViewportInstance --- Code/Sandbox/Editor/EditorViewportWidget.cpp | 9 +++++ .../Code/Source/AtomBridgeSystemComponent.cpp | 38 +++++++------------ 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/Code/Sandbox/Editor/EditorViewportWidget.cpp b/Code/Sandbox/Editor/EditorViewportWidget.cpp index 3803867870..631a93af92 100644 --- a/Code/Sandbox/Editor/EditorViewportWidget.cpp +++ b/Code/Sandbox/Editor/EditorViewportWidget.cpp @@ -452,6 +452,15 @@ void EditorViewportWidget::Update() return; } + static bool sentOnWindowCreated = false; + if (!sentOnWindowCreated && windowHandle()->isActive()) + { + sentOnWindowCreated = true; + AzFramework::WindowSystemNotificationBus::Broadcast( + &AzFramework::WindowSystemNotificationBus::Handler::OnWindowCreated, + reinterpret_cast(winId())); + } + m_updatingCameraPosition = true; auto transform = LYTransformToAZTransform(m_Camera.GetMatrix()); m_renderViewport->GetViewportContext()->SetCameraTransform(transform); diff --git a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomBridgeSystemComponent.cpp b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomBridgeSystemComponent.cpp index 90731488f4..9148cdba6f 100644 --- a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomBridgeSystemComponent.cpp +++ b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomBridgeSystemComponent.cpp @@ -91,13 +91,9 @@ namespace AZ AZ_UNUSED(dependent); } - static const AZ::Crc32 mainViewportEntityDebugDisplayId = AZ_CRC_CE("MainViewportEntityDebugDisplayId"); - void AtomBridgeSystemComponent::Init() { -#if defined(ENABLE_ATOM_DEBUG_DISPLAY) && ENABLE_ATOM_DEBUG_DISPLAY AZ::RPI::ViewportContextManagerNotificationsBus::Handler::BusConnect(); -#endif } void AtomBridgeSystemComponent::Activate() @@ -112,9 +108,7 @@ namespace AZ void AtomBridgeSystemComponent::Deactivate() { -#if defined(ENABLE_ATOM_DEBUG_DISPLAY) && ENABLE_ATOM_DEBUG_DISPLAY AZ::RPI::ViewportContextManagerNotificationsBus::Handler::BusDisconnect(); -#endif RPI::Scene* scene = RPI::RPISystemInterface::Get()->GetDefaultScene().get(); // Check if scene is emptry since scene might be released already when running AtomSampleViewer if (scene) @@ -193,36 +187,32 @@ namespace AZ renderPipeline = bootstrapScene->GetDefaultRenderPipeline(); renderPipeline->SetDefaultView(m_view); - - auto auxGeomFP = bootstrapScene->GetFeatureProcessor(); - if (auxGeomFP) - { - auxGeomFP->GetOrCreateDrawQueueForView(m_view.get()); - } - -#if defined(ENABLE_ATOM_DEBUG_DISPLAY) && ENABLE_ATOM_DEBUG_DISPLAY - // Make default AtomDebugDisplayViewportInterface for the scene - AZStd::shared_ptr mainEntityDebugDisplay = AZStd::make_shared(mainViewportEntityDebugDisplayId); - m_activeViewportsList[mainViewportEntityDebugDisplayId] = mainEntityDebugDisplay; -#endif } + else + { + m_view = renderPipeline->GetDefaultView(); + } + auto auxGeomFP = bootstrapScene->GetFeatureProcessor(); + if (auxGeomFP) + { + auxGeomFP->GetOrCreateDrawQueueForView(m_view.get()); + } + + // Make default AtomDebugDisplayViewportInterface for the scene + AZStd::shared_ptr mainEntityDebugDisplay = AZStd::make_shared(AzFramework::g_defaultSceneEntityDebugDisplayId); + m_activeViewportsList[AzFramework::g_defaultSceneEntityDebugDisplayId] = mainEntityDebugDisplay; } void AtomBridgeSystemComponent::OnViewportContextAdded(AZ::RPI::ViewportContextPtr viewportContext) { -#if defined(ENABLE_ATOM_DEBUG_DISPLAY) && ENABLE_ATOM_DEBUG_DISPLAY AZStd::shared_ptr viewportDebugDisplay = AZStd::make_shared(viewportContext); m_activeViewportsList[viewportContext->GetId()] = viewportDebugDisplay; -#endif } void AtomBridgeSystemComponent::OnViewportContextRemoved(AzFramework::ViewportId viewportId) { -#if defined(ENABLE_ATOM_DEBUG_DISPLAY) && ENABLE_ATOM_DEBUG_DISPLAY + AZ_Assert(viewportId != AzFramework::g_defaultSceneEntityDebugDisplayId, "Error trying to remove the default scene draw instance"); m_activeViewportsList.erase(viewportId); -#else - AZ_UNUSED(viewportId); -#endif } From 9775822778ec2cf8003ea86f455906f75bce1c14 Mon Sep 17 00:00:00 2001 From: scottr Date: Tue, 11 May 2021 16:09:16 -0700 Subject: [PATCH 039/330] [cpack_installer] remove wxs file ext from lfs filter --- .gitattributes | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 1755def66a..55b43e4ba7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -115,5 +115,4 @@ *.wav filter=lfs diff=lfs merge=lfs -text *.webm filter=lfs diff=lfs merge=lfs -text *.wem filter=lfs diff=lfs merge=lfs -text -*.wxs filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text From 9faf35b529d942dffe1b9fce7d9310be8de9edf8 Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Tue, 11 May 2021 17:09:41 -0700 Subject: [PATCH 040/330] Refactor in prepration for ATOM-14688 "Disable Individual Layers" Refactored StandardMultilayerPBR to collate all the code for each layer into a couple structs and utility functions. This makes the code easier to maintain, and in particular will make it easy for me to add Enable flags for the layers in a subsequent commit. Also removed subsurface scattering and translucency from StandardMultilayerPBR, according to ATOM-4120 "Stabilize Standard PBR Regarding Subsurface and Translucency". Squashed commit of the following: commit a6052d6ad4f70183d0ce72e84c7dc5512dc24d5e Author: Chris Santora Date: Tue May 11 16:32:15 2021 -0700 Got the refactor finally working. I had change it to blend the baseColor, spec factor, and metalness before converting to albedo and spec, in order to get exactly the same results as before. commit 42d6da7f405097dea07b6ed0426d6a662b61440d Author: Chris Santora Date: Tue May 11 15:58:38 2021 -0700 Fixed clear coat issue due to LightingData initialized too late. commit 358194a5caf6f9eb99b0e5345ad5f7768b244a93 Author: Chris Santora Date: Tue May 11 15:18:30 2021 -0700 Fixed a couple issues. commit adb431f8113b945057959db288a7ee2dd825dd69 Author: Chris Santora Date: Tue May 11 12:42:12 2021 -0700 WIP refactor of StandardMultilayerPBR to collate the code for each layer. Also removed subsurface scattering from multilayer. --- .../Types/StandardMultilayerPBR.materialtype | 207 --------- .../Types/StandardMultilayerPBR_Common.azsli | 18 - .../StandardMultilayerPBR_ForwardPass.azsl | 400 ++++++++++-------- .../PBR/Surfaces/StandardSurface.azsli | 14 + 4 files changed, 245 insertions(+), 394 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype index 3e95846b6e..90a599c6f9 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype @@ -23,11 +23,6 @@ "displayName": "UVs", "description": "Properties for configuring UV transforms for the entire material, including the blend masks." }, - { - "id": "subsurfaceScattering", - "displayName": "Subsurface Scattering", - "description": "Properties for configuring subsurface scattering effects." - }, { // Note: this property group is used in the DiffuseGlobalIllumination pass, it is not read by the StandardPBR shader "id": "irradiance", @@ -467,183 +462,6 @@ "step": 0.1 } ], - "subsurfaceScattering": [ - { - "id": "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", - "id": "o_enableSubsurfaceScattering" - } - }, - { - "id": "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", - "id": "m_subsurfaceScatteringFactor" - } - }, - { - "id": "influenceMap", - "displayName": " Influence Map", - "description": "Use texture map to control the strength of subsurface scattering", - "type": "Image", - "connection": { - "type": "ShaderInput", - "id": "m_subsurfaceScatteringInfluenceMap" - } - }, - { - "id": "useInfluenceMap", - "displayName": " Use Influence Map", - "description": "Whether to use the texture map as influence mask.", - "type": "Bool", - "defaultValue": true - }, - { - "id": "influenceMapUv", - "displayName": " UV", - "description": "Influence map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "id": "m_subsurfaceScatteringInfluenceMapUvIndex" - } - }, - { - "id": "scatterColor", - "displayName": " Scatter color", - "description": "Color of volume light traveled through", - "type": "Color", - "defaultValue": [ 1.0, 0.27, 0.13 ] - }, - { - "id": "scatterDistance", - "displayName": " Scatter distance", - "description": "How far light traveled inside the volume", - "type": "float", - "defaultValue": 8, - "min": 0.0, - "softMax": 20.0 - }, - { - "id": "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", - "id": "m_subsurfaceScatteringQuality" - } - }, - { - "id": "transmissionMode", - "displayName": "Transmission", - "description": "Algorithm used for calculating transmission", - "type": "Enum", - "enumValues": [ "None", "ThickObject", "ThinObject" ], - "defaultValue": "None", - "connection": { - "type": "ShaderOption", - "id": "o_transmission_mode" - } - }, - { - "id": "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 - }, - { - "id": "thicknessMap", - "displayName": " Thickness Map", - "description": "Use a greyscale texture for per pixel thickness", - "type": "Image", - "connection": { - "type": "ShaderInput", - "id": "m_transmissionThicknessMap" - } - }, - { - "id": "useThicknessMap", - "displayName": " Use Thickness Map", - "description": "Whether to use the thickness map", - "type": "Bool", - "defaultValue": true - }, - { - "id": "thicknessMapUv", - "displayName": " UV", - "description": "Thickness map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "id": "m_transmissionThicknessMapUvIndex" - } - }, - { - "id": "transmissionTint", - "displayName": " Transmission Tint", - "description": "Color of the volume light travelling through", - "type": "Color", - "defaultValue": [ 1.0, 0.8, 0.6 ] - }, - { - "id": "transmissionPower", - "displayName": " Power", - "description": "How much transmitted light scatter radially ", - "type": "float", - "defaultValue": 6.0, - "min": 0.0, - "softMax": 20.0 - }, - { - "id": "transmissionDistortion", - "displayName": " Distortion", - "description": "How much light direction distorted towards surface normal", - "type": "float", - "defaultValue": 0.1, - "min": 0.0, - "max": 1.0 - }, - { - "id": "transmissionAttenuation", - "displayName": " Attenuation", - "description": "How fast transmitted light fade with thickness", - "type": "float", - "defaultValue": 4.0, - "min": 0.0, - "softMax": 20.0 - }, - { - "id": "transmissionScale", - "displayName": " Scale", - "description": "Strength of transmission", - "type": "float", - "defaultValue": 3.0, - "min": 0.0, - "softMax": 20.0 - } - ], "irradiance": [ // Note: this property group is used in the DiffuseGlobalIllumination pass, it is not read by the StandardPBR shader { @@ -2844,25 +2662,6 @@ "file": "StandardMultilayerPBR_ShaderEnable.lua" } }, - { - // Preprocess & build parameter set for subsurface scattering and translucency - "type": "HandleSubsurfaceScatteringParameters", - "args": { - "mode": "subsurfaceScattering.transmissionMode", - "scale" : "subsurfaceScattering.transmissionScale", - "power" : "subsurfaceScattering.transmissionPower", - "distortion" : "subsurfaceScattering.transmissionDistortion", - "attenuation" : "subsurfaceScattering.transmissionAttenuation", - "tintColor" : "subsurfaceScattering.transmissionTint", - "thickness" : "subsurfaceScattering.thickness", - "enabled": "subsurfaceScattering.enableSubsurfaceScattering", - "scatterDistanceColor" : "subsurfaceScattering.scatterColor", - "scatterDistanceIntensity" : "subsurfaceScattering.scatterDistance", - "scatterDistanceShaderInput" : "m_scatterDistance", - "parametersShaderInput" : "m_transmissionParams", - "tintThickenssShaderInput" : "m_transmissionTintThickness" - } - }, { "type": "Lua", "args": { @@ -2875,12 +2674,6 @@ "file": "StandardMultilayerPBR_Parallax.lua" } }, - { - "type": "Lua", - "args": { - "file": "StandardPBR_SubsurfaceState.lua" - } - }, //############################################################################################## // Layer 1 Functors //############################################################################################## diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli index f1f6d90e14..8c7b49214b 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli @@ -91,24 +91,6 @@ ShaderResourceGroup MaterialSrg : SRG_PerMaterial MipFilter = Linear; }; - // Parameters for subsurface scattering - float m_subsurfaceScatteringFactor; - float m_subsurfaceScatteringQuality; - float3 m_scatterDistance; - Texture2D m_subsurfaceScatteringInfluenceMap; - uint m_subsurfaceScatteringInfluenceMapUvIndex; - - // Parameters for transmission - - // Elements of m_transmissionParams: - // Thick object mode: (attenuation coefficient, power, distortion, scale) - // Thin object mode: (float3 scatter distance, scale) - float4 m_transmissionParams; - - // (float3 TintColor, thickness) - float4 m_transmissionTintThickness; - Texture2D m_transmissionThicknessMap; - uint m_transmissionThicknessMapUvIndex; } // ------ Shader Options ---------------------------------------- diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl index a690c0569a..9e5a2e623a 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl @@ -55,7 +55,6 @@ DEFINE_LAYER_OPTIONS(o_layer1_) DEFINE_LAYER_OPTIONS(o_layer2_) DEFINE_LAYER_OPTIONS(o_layer3_) -#include "MaterialInputs/SubsurfaceInput.azsli" #include "MaterialInputs/TransmissionInput.azsli" #include "StandardMultilayerPBR_Common.azsli" @@ -127,6 +126,181 @@ VSOutput ForwardPassVS(VSInput IN) return OUT; } +//! Collects all the raw Standard material inputs for a single layer. See ProcessStandardMaterialInputs(). +struct StandardMaterialInputs +{ + COMMON_SRG_INPUTS_BASE_COLOR() + COMMON_SRG_INPUTS_ROUGHNESS() + COMMON_SRG_INPUTS_METALLIC() + COMMON_SRG_INPUTS_SPECULAR_F0() + COMMON_SRG_INPUTS_NORMAL() + COMMON_SRG_INPUTS_CLEAR_COAT() + COMMON_SRG_INPUTS_OCCLUSION() + COMMON_SRG_INPUTS_EMISSIVE() + // Note parallax is omitted here because that requires special handling. + + bool m_normal_useTexture; + bool m_baseColor_useTexture; + bool m_metallic_useTexture; + bool m_specularF0_useTexture; + bool m_roughness_useTexture; + bool m_emissiveEnabled; + bool m_emissive_useTexture; + bool m_diffuseOcclusion_useTexture; + bool m_specularOcclusion_useTexture; + bool m_clearCoatEnabled; + bool m_clearCoat_factor_useTexture; + bool m_clearCoat_roughness_useTexture; + bool m_clearCoat_normal_useTexture; + + TextureBlendMode m_baseColorTextureBlendMode; + + float2 m_vertexUv[UvSetCount]; + float3x3 m_uvMatrix; + float m_normal; + float3 m_tangents[UvSetCount]; + float3 m_bitangents[UvSetCount]; + + sampler m_sampler; + + bool m_isFrontFace; +}; + +//! Holds the final processed material inputs, after all flags have been checked, textures have been sampled, factors have been applied, etc. +//! This data is ready to be copied into a Surface and/or LightingData struct for the lighting system to consume. +class ProcessedMaterialInputs +{ + float3 m_normalTS; //!< Normal in tangent-space + float3 m_baseColor; + float3 m_specularF0Factor; + float m_metallic; + float m_roughness; + float3 m_emissiveLighting; + float m_diffuseAmbientOcclusion; + float m_specularOcclusion; + ClearCoatSurfaceData m_clearCoat; + + void InitializeToZero() + { + m_normalTS = float3(0,0,0); + m_baseColor = float3(0,0,0); + m_specularF0Factor = float3(0,0,0); + m_metallic = 0.0f; + m_roughness = 0.0f; + m_emissiveLighting = float3(0,0,0); + m_diffuseAmbientOcclusion = 0; + m_specularOcclusion = 0; + m_clearCoat.InitializeToZero(); + } +}; + +//! Processes the set of Standard material inputs for a single layer. +//! The FILL_STANDARD_MATERIAL_INPUTS() macro below can be used to fill the StandardMaterialInputs struct. +ProcessedMaterialInputs ProcessStandardMaterialInputs(StandardMaterialInputs inputs) +{ + ProcessedMaterialInputs result; + + float2 transformedUv[UvSetCount]; + transformedUv[0] = mul(inputs.m_uvMatrix, float3(inputs.m_vertexUv[0], 1.0)).xy; + transformedUv[1] = inputs.m_vertexUv[1]; + + float3x3 normalUvMatrix = inputs.m_normalMapUvIndex == 0 ? inputs.m_uvMatrix : CreateIdentity3x3(); + result.m_normalTS = GetNormalInputTS(inputs.m_normalMap, inputs.m_sampler, transformedUv[inputs.m_normalMapUvIndex], inputs.m_flipNormalX, inputs.m_flipNormalY, normalUvMatrix, inputs.m_normal_useTexture, inputs.m_normalFactor); + + float3 sampledBaseColor = GetBaseColorInput(inputs.m_baseColorMap, inputs.m_sampler, transformedUv[inputs.m_baseColorMapUvIndex], inputs.m_baseColor.rgb, inputs.m_baseColor_useTexture); + result.m_baseColor = BlendBaseColor(sampledBaseColor, inputs.m_baseColor.rgb, inputs.m_baseColorFactor, inputs.m_baseColorTextureBlendMode, inputs.m_baseColor_useTexture); + result.m_specularF0Factor = GetSpecularInput(inputs.m_specularF0Map, inputs.m_sampler, transformedUv[inputs.m_specularF0MapUvIndex], inputs.m_specularF0Factor, inputs.m_specularF0_useTexture); + result.m_metallic = GetMetallicInput(inputs.m_metallicMap, inputs.m_sampler, transformedUv[inputs.m_metallicMapUvIndex], inputs.m_metallicFactor, inputs.m_metallic_useTexture); + result.m_roughness = GetRoughnessInput(inputs.m_roughnessMap, MaterialSrg::m_sampler, transformedUv[inputs.m_roughnessMapUvIndex], inputs.m_roughnessFactor, inputs.m_roughnessLowerBound, inputs.m_roughnessUpperBound, inputs.m_roughness_useTexture); + + result.m_emissiveLighting = GetEmissiveInput(inputs.m_emissiveMap, inputs.m_sampler, transformedUv[inputs.m_emissiveMapUvIndex], inputs.m_emissiveIntensity, inputs.m_emissiveColor.rgb, inputs.m_emissiveEnabled, inputs.m_emissive_useTexture); + result.m_diffuseAmbientOcclusion = GetOcclusionInput(inputs.m_diffuseOcclusionMap, inputs.m_sampler, transformedUv[inputs.m_diffuseOcclusionMapUvIndex], inputs.m_diffuseOcclusionFactor, inputs.m_diffuseOcclusion_useTexture); + result.m_specularOcclusion = GetOcclusionInput(inputs.m_specularOcclusionMap, MaterialSrg::m_sampler, transformedUv[inputs.m_specularOcclusionMapUvIndex], inputs.m_specularOcclusionFactor, inputs.m_specularOcclusion_useTexture); + + result.m_clearCoat.InitializeToZero(); + if(inputs.m_clearCoatEnabled) + { + float3x3 clearCoatUvMatrix = inputs.m_clearCoatNormalMapUvIndex == 0 ? inputs.m_uvMatrix : CreateIdentity3x3(); + + GetClearCoatInputs(inputs.m_clearCoatInfluenceMap, transformedUv[inputs.m_clearCoatInfluenceMapUvIndex], inputs.m_clearCoatFactor, inputs.m_clearCoat_factor_useTexture, + inputs.m_clearCoatRoughnessMap, transformedUv[inputs.m_clearCoatRoughnessMapUvIndex], inputs.m_clearCoatRoughness, inputs.m_clearCoat_roughness_useTexture, + inputs.m_clearCoatNormalMap, transformedUv[inputs.m_clearCoatNormalMapUvIndex], inputs.m_normal, inputs.m_clearCoat_normal_useTexture, inputs.m_clearCoatNormalStrength, + clearCoatUvMatrix, inputs.m_tangents[inputs.m_clearCoatNormalMapUvIndex], inputs.m_bitangents[inputs.m_clearCoatNormalMapUvIndex], + inputs.m_sampler, inputs.m_isFrontFace, + result.m_clearCoat.factor, result.m_clearCoat.roughness, result.m_clearCoat.normal); + } + + return result; +} + +//! Fills a StandardMaterialInputs struct with data from the MaterialSrg, shader options, and local vertex data. +#define FILL_STANDARD_MATERIAL_INPUTS(inputs, srgLayerPrefix, optionsLayerPrefix, blendWeight) \ + inputs.m_sampler = MaterialSrg::m_sampler; \ + inputs.m_vertexUv = IN.m_uv; \ + inputs.m_uvMatrix = srgLayerPrefix##m_uvMatrix; \ + inputs.m_normal = IN.m_normal; \ + inputs.m_tangents = tangents; \ + inputs.m_bitangents = bitangents; \ + inputs.m_isFrontFace = isFrontFace; \ + \ + inputs.m_normalMapUvIndex = srgLayerPrefix##m_normalMapUvIndex; \ + inputs.m_normalMap = srgLayerPrefix##m_normalMap; \ + inputs.m_flipNormalX = srgLayerPrefix##m_flipNormalX; \ + inputs.m_flipNormalY = srgLayerPrefix##m_flipNormalY; \ + inputs.m_normal_useTexture = optionsLayerPrefix##o_normal_useTexture; \ + inputs.m_normalFactor = srgLayerPrefix##m_normalFactor * blendWeight; \ + inputs.m_baseColorMap = srgLayerPrefix##m_baseColorMap; \ + inputs.m_baseColorMapUvIndex = srgLayerPrefix##m_baseColorMapUvIndex; \ + inputs.m_baseColor = srgLayerPrefix##m_baseColor; \ + inputs.m_baseColor_useTexture = optionsLayerPrefix##o_baseColor_useTexture; \ + inputs.m_baseColorFactor = srgLayerPrefix##m_baseColorFactor; \ + inputs.m_baseColorTextureBlendMode = optionsLayerPrefix##o_baseColorTextureBlendMode; \ + inputs.m_metallicMap = srgLayerPrefix##m_metallicMap; \ + inputs.m_metallicMapUvIndex = srgLayerPrefix##m_metallicMapUvIndex; \ + inputs.m_metallicFactor = srgLayerPrefix##m_metallicFactor; \ + inputs.m_metallic_useTexture = optionsLayerPrefix##o_metallic_useTexture; \ + inputs.m_specularF0Map = srgLayerPrefix##m_specularF0Map; \ + inputs.m_specularF0MapUvIndex = srgLayerPrefix##m_specularF0MapUvIndex; \ + inputs.m_specularF0Factor = srgLayerPrefix##m_specularF0Factor; \ + inputs.m_specularF0_useTexture = optionsLayerPrefix##o_specularF0_useTexture; \ + inputs.m_roughnessMap = srgLayerPrefix##m_roughnessMap; \ + inputs.m_roughnessMapUvIndex = srgLayerPrefix##m_roughnessMapUvIndex; \ + inputs.m_roughnessFactor = srgLayerPrefix##m_roughnessFactor; \ + inputs.m_roughnessLowerBound = srgLayerPrefix##m_roughnessLowerBound; \ + inputs.m_roughnessUpperBound = srgLayerPrefix##m_roughnessUpperBound; \ + inputs.m_roughness_useTexture = optionsLayerPrefix##o_roughness_useTexture; \ + \ + inputs.m_emissiveMap = srgLayerPrefix##m_emissiveMap; \ + inputs.m_emissiveMapUvIndex = srgLayerPrefix##m_emissiveMapUvIndex; \ + inputs.m_emissiveIntensity = srgLayerPrefix##m_emissiveIntensity; \ + inputs.m_emissiveColor = srgLayerPrefix##m_emissiveColor; \ + inputs.m_emissiveEnabled = optionsLayerPrefix##o_emissiveEnabled; \ + inputs.m_emissive_useTexture = optionsLayerPrefix##o_emissive_useTexture; \ + \ + inputs.m_diffuseOcclusionMap = srgLayerPrefix##m_diffuseOcclusionMap; \ + inputs.m_diffuseOcclusionMapUvIndex = srgLayerPrefix##m_diffuseOcclusionMapUvIndex; \ + inputs.m_diffuseOcclusionFactor = srgLayerPrefix##m_diffuseOcclusionFactor; \ + inputs.m_diffuseOcclusion_useTexture = optionsLayerPrefix##o_diffuseOcclusion_useTexture; \ + \ + inputs.m_specularOcclusionMap = srgLayerPrefix##m_specularOcclusionMap; \ + inputs.m_specularOcclusionMapUvIndex = srgLayerPrefix##m_specularOcclusionMapUvIndex; \ + inputs.m_specularOcclusionFactor = srgLayerPrefix##m_specularOcclusionFactor; \ + inputs.m_specularOcclusion_useTexture = optionsLayerPrefix##o_specularOcclusion_useTexture; \ + \ + inputs.m_clearCoatEnabled = o_clearCoat_feature_enabled && optionsLayerPrefix##o_clearCoat_enabled; \ + inputs.m_clearCoatInfluenceMap = srgLayerPrefix##m_clearCoatInfluenceMap; \ + inputs.m_clearCoatInfluenceMapUvIndex = srgLayerPrefix##m_clearCoatInfluenceMapUvIndex; \ + inputs.m_clearCoatFactor = srgLayerPrefix##m_clearCoatFactor; \ + inputs.m_clearCoat_factor_useTexture = optionsLayerPrefix##o_clearCoat_factor_useTexture; \ + inputs.m_clearCoatRoughnessMap = srgLayerPrefix##m_clearCoatRoughnessMap; \ + inputs.m_clearCoatRoughnessMapUvIndex = srgLayerPrefix##m_clearCoatRoughnessMapUvIndex; \ + inputs.m_clearCoatRoughness = srgLayerPrefix##m_clearCoatRoughness; \ + inputs.m_clearCoat_roughness_useTexture = optionsLayerPrefix##o_clearCoat_roughness_useTexture; \ + inputs.m_clearCoatNormalMap = srgLayerPrefix##m_clearCoatNormalMap; \ + inputs.m_clearCoatNormalMapUvIndex = srgLayerPrefix##m_clearCoatNormalMapUvIndex; \ + inputs.m_clearCoat_normal_useTexture = optionsLayerPrefix##o_clearCoat_normal_useTexture; \ + inputs.m_clearCoatNormalStrength = srgLayerPrefix##m_clearCoatNormalStrength; + // ---------- Pixel Shader ---------- @@ -174,7 +348,6 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float bool displacementIsClipped = false; - // Parallax mapping's non uniform uv transformations break screen space subsurface scattering, disable it when subsurface scatteirng is enabled if(ShouldHandleParallax()) { float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); @@ -197,108 +370,70 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float } } - Surface surface; - surface.position = IN.m_worldPosition; - - // ------- Setup the per-layer UV transforms ------- - - float2 uvLayer1[UvSetCount]; - float2 uvLayer2[UvSetCount]; - float2 uvLayer3[UvSetCount]; - - // Only UV0 will be applied transforms from each layer. - uvLayer1[0] = mul(MaterialSrg::m_layer1_m_uvMatrix, float3(IN.m_uv[0], 1.0)).xy; - uvLayer2[0] = mul(MaterialSrg::m_layer2_m_uvMatrix, float3(IN.m_uv[0], 1.0)).xy; - uvLayer3[0] = mul(MaterialSrg::m_layer3_m_uvMatrix, float3(IN.m_uv[0], 1.0)).xy; - uvLayer1[1] = IN.m_uv[1]; - uvLayer2[1] = IN.m_uv[1]; - uvLayer3[1] = IN.m_uv[1]; - // ------- Calculate Layer Blend Mask Values ------- // Now that any parallax has been calculated, we calculate the blend factors for any layers that are impacted by the parallax. float3 blendWeights = GetBlendWeights(IN.m_uv[MaterialSrg::m_blendMaskUvIndex]); - // ------- Normal ------- - - float3 layer1_normalFactor = MaterialSrg::m_layer1_m_normalFactor * blendWeights.r; - float3 layer2_normalFactor = MaterialSrg::m_layer2_m_normalFactor * blendWeights.g; - float3 layer3_normalFactor = MaterialSrg::m_layer3_m_normalFactor * blendWeights.b; - float3x3 layer1_uvMatrix = MaterialSrg::m_layer1_m_normalMapUvIndex == 0 ? MaterialSrg::m_layer1_m_uvMatrix : CreateIdentity3x3(); - float3x3 layer2_uvMatrix = MaterialSrg::m_layer2_m_normalMapUvIndex == 0 ? MaterialSrg::m_layer2_m_uvMatrix : CreateIdentity3x3(); - float3x3 layer3_uvMatrix = MaterialSrg::m_layer3_m_normalMapUvIndex == 0 ? MaterialSrg::m_layer3_m_uvMatrix : CreateIdentity3x3(); - float3 layer1_normalTS = GetNormalInputTS(MaterialSrg::m_layer1_m_normalMap, MaterialSrg::m_sampler, uvLayer1[MaterialSrg::m_layer1_m_normalMapUvIndex], MaterialSrg::m_layer1_m_flipNormalX, MaterialSrg::m_layer1_m_flipNormalY, layer1_uvMatrix, o_layer1_o_normal_useTexture, layer1_normalFactor); - float3 layer2_normalTS = GetNormalInputTS(MaterialSrg::m_layer2_m_normalMap, MaterialSrg::m_sampler, uvLayer2[MaterialSrg::m_layer2_m_normalMapUvIndex], MaterialSrg::m_layer2_m_flipNormalX, MaterialSrg::m_layer2_m_flipNormalY, layer2_uvMatrix, o_layer2_o_normal_useTexture, layer2_normalFactor); - float3 layer3_normalTS = GetNormalInputTS(MaterialSrg::m_layer3_m_normalMap, MaterialSrg::m_sampler, uvLayer3[MaterialSrg::m_layer3_m_normalMapUvIndex], MaterialSrg::m_layer3_m_flipNormalX, MaterialSrg::m_layer3_m_flipNormalY, layer3_uvMatrix, o_layer3_o_normal_useTexture, layer3_normalFactor); - - float3 normalTS = ReorientTangentSpaceNormal(layer1_normalTS, layer2_normalTS); - normalTS = ReorientTangentSpaceNormal(normalTS, layer3_normalTS); - // [GFX TODO][ATOM-14591]: This will only work if the normal maps all use the same UV stream. We would need to add support for having them in different UV streams. - surface.normal = normalize(TangentSpaceToWorld(normalTS, IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex])); - - // ------- Base Color ------- - - float2 layer1_baseColorUv = uvLayer1[MaterialSrg::m_layer1_m_baseColorMapUvIndex]; - float2 layer2_baseColorUv = uvLayer2[MaterialSrg::m_layer2_m_baseColorMapUvIndex]; - float2 layer3_baseColorUv = uvLayer3[MaterialSrg::m_layer3_m_baseColorMapUvIndex]; - - float3 layer1_sampledColor = GetBaseColorInput(MaterialSrg::m_layer1_m_baseColorMap, MaterialSrg::m_sampler, layer1_baseColorUv, MaterialSrg::m_layer1_m_baseColor.rgb, o_layer1_o_baseColor_useTexture); - float3 layer2_sampledColor = GetBaseColorInput(MaterialSrg::m_layer2_m_baseColorMap, MaterialSrg::m_sampler, layer2_baseColorUv, MaterialSrg::m_layer2_m_baseColor.rgb, o_layer2_o_baseColor_useTexture); - float3 layer3_sampledColor = GetBaseColorInput(MaterialSrg::m_layer3_m_baseColorMap, MaterialSrg::m_sampler, layer3_baseColorUv, MaterialSrg::m_layer3_m_baseColor.rgb, o_layer3_o_baseColor_useTexture); - float3 layer1_baseColor = BlendBaseColor(layer1_sampledColor, MaterialSrg::m_layer1_m_baseColor.rgb, MaterialSrg::m_layer1_m_baseColorFactor, o_layer1_o_baseColorTextureBlendMode, o_layer1_o_baseColor_useTexture); - float3 layer2_baseColor = BlendBaseColor(layer2_sampledColor, MaterialSrg::m_layer2_m_baseColor.rgb, MaterialSrg::m_layer2_m_baseColorFactor, o_layer2_o_baseColorTextureBlendMode, o_layer2_o_baseColor_useTexture); - float3 layer3_baseColor = BlendBaseColor(layer3_sampledColor, MaterialSrg::m_layer3_m_baseColor.rgb, MaterialSrg::m_layer3_m_baseColorFactor, o_layer3_o_baseColorTextureBlendMode, o_layer3_o_baseColor_useTexture); - float3 baseColor = BlendLayers(layer1_baseColor, layer2_baseColor, layer3_baseColor, blendWeights); - - if(o_parallax_highlightClipping && displacementIsClipped) + // ------- Layer 1 (base layer) ----------- + + ProcessedMaterialInputs lightingInputLayer1; { - ApplyParallaxClippingHighlight(baseColor); + StandardMaterialInputs inputs; + FILL_STANDARD_MATERIAL_INPUTS(inputs, MaterialSrg::m_layer1_, o_layer1_, blendWeights.r) + lightingInputLayer1 = ProcessStandardMaterialInputs(inputs); } + + // ----------- Layer 2 ----------- - // ------- Metallic ------- - - float metallic = 0; - if(!o_enableSubsurfaceScattering) // If subsurface scattering is enabled skip texture lookup for metallic, as this quantity won't be used anyway + ProcessedMaterialInputs lightingInputLayer2; { - float layer1_metallic = GetMetallicInput(MaterialSrg::m_layer1_m_metallicMap, MaterialSrg::m_sampler, uvLayer1[MaterialSrg::m_layer1_m_metallicMapUvIndex], MaterialSrg::m_layer1_m_metallicFactor, o_layer1_o_metallic_useTexture); - float layer2_metallic = GetMetallicInput(MaterialSrg::m_layer2_m_metallicMap, MaterialSrg::m_sampler, uvLayer2[MaterialSrg::m_layer2_m_metallicMapUvIndex], MaterialSrg::m_layer2_m_metallicFactor, o_layer2_o_metallic_useTexture); - float layer3_metallic = GetMetallicInput(MaterialSrg::m_layer3_m_metallicMap, MaterialSrg::m_sampler, uvLayer3[MaterialSrg::m_layer3_m_metallicMapUvIndex], MaterialSrg::m_layer3_m_metallicFactor, o_layer3_o_metallic_useTexture); - metallic = BlendLayers(layer1_metallic, layer2_metallic, layer3_metallic, blendWeights); + StandardMaterialInputs inputs; + FILL_STANDARD_MATERIAL_INPUTS(inputs, MaterialSrg::m_layer2_, o_layer2_, blendWeights.g) + lightingInputLayer2 = ProcessStandardMaterialInputs(inputs); } - // ------- Specular ------- - - float layer1_specularF0Factor = GetSpecularInput(MaterialSrg::m_layer1_m_specularF0Map, MaterialSrg::m_sampler, uvLayer1[MaterialSrg::m_layer1_m_specularF0MapUvIndex], MaterialSrg::m_layer1_m_specularF0Factor, o_layer1_o_specularF0_useTexture); - float layer2_specularF0Factor = GetSpecularInput(MaterialSrg::m_layer2_m_specularF0Map, MaterialSrg::m_sampler, uvLayer2[MaterialSrg::m_layer2_m_specularF0MapUvIndex], MaterialSrg::m_layer2_m_specularF0Factor, o_layer2_o_specularF0_useTexture); - float layer3_specularF0Factor = GetSpecularInput(MaterialSrg::m_layer3_m_specularF0Map, MaterialSrg::m_sampler, uvLayer3[MaterialSrg::m_layer3_m_specularF0MapUvIndex], MaterialSrg::m_layer3_m_specularF0Factor, o_layer3_o_specularF0_useTexture); - float specularF0Factor = BlendLayers(layer1_specularF0Factor, layer2_specularF0Factor, layer3_specularF0Factor, blendWeights); - - surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic); + // ----------- Layer 3 ----------- - // ------- Roughness ------- + ProcessedMaterialInputs lightingInputLayer3; + { + StandardMaterialInputs inputs; + FILL_STANDARD_MATERIAL_INPUTS(inputs, MaterialSrg::m_layer3_, o_layer3_, blendWeights.b) + lightingInputLayer3 = ProcessStandardMaterialInputs(inputs); + } - float layer1_roughness = GetRoughnessInput(MaterialSrg::m_layer1_m_roughnessMap, MaterialSrg::m_sampler, uvLayer1[MaterialSrg::m_layer1_m_roughnessMapUvIndex], MaterialSrg::m_layer1_m_roughnessFactor, MaterialSrg::m_layer1_m_roughnessLowerBound, MaterialSrg::m_layer1_m_roughnessUpperBound, o_layer1_o_roughness_useTexture); - float layer2_roughness = GetRoughnessInput(MaterialSrg::m_layer2_m_roughnessMap, MaterialSrg::m_sampler, uvLayer2[MaterialSrg::m_layer2_m_roughnessMapUvIndex], MaterialSrg::m_layer2_m_roughnessFactor, MaterialSrg::m_layer2_m_roughnessLowerBound, MaterialSrg::m_layer2_m_roughnessUpperBound, o_layer2_o_roughness_useTexture); - float layer3_roughness = GetRoughnessInput(MaterialSrg::m_layer3_m_roughnessMap, MaterialSrg::m_sampler, uvLayer3[MaterialSrg::m_layer3_m_roughnessMapUvIndex], MaterialSrg::m_layer3_m_roughnessFactor, MaterialSrg::m_layer3_m_roughnessLowerBound, MaterialSrg::m_layer3_m_roughnessUpperBound, o_layer3_o_roughness_useTexture); - surface.roughnessLinear = BlendLayers(layer1_roughness, layer2_roughness, layer3_roughness, blendWeights); + // ------- Combine all layers --------- + + Surface surface; + surface.position = IN.m_worldPosition; + surface.transmission.InitializeToZero(); - surface.CalculateRoughnessA(); + // ------- Combine Normals --------- - // ------- Subsurface ------- + float3 normalTS = lightingInputLayer1.m_normalTS; + normalTS = ReorientTangentSpaceNormal(normalTS, lightingInputLayer2.m_normalTS); + normalTS = ReorientTangentSpaceNormal(normalTS, lightingInputLayer3.m_normalTS); + // [GFX TODO][ATOM-14591]: This will only work if the normal maps all use the same UV stream. We would need to add support for having them in different UV streams. + surface.normal = normalize(TangentSpaceToWorld(normalTS, IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex])); + + // ------- Combine Albedo, roughness, specular, roughness --------- - float2 subsurfaceUv = IN.m_uv[MaterialSrg::m_subsurfaceScatteringInfluenceMapUvIndex]; - float surfaceScatteringFactor = GetSubsurfaceInput(MaterialSrg::m_subsurfaceScatteringInfluenceMap, MaterialSrg::m_sampler, subsurfaceUv, MaterialSrg::m_subsurfaceScatteringFactor); + float3 baseColor = BlendLayers(lightingInputLayer1.m_baseColor, lightingInputLayer2.m_baseColor, lightingInputLayer3.m_baseColor, blendWeights); + float3 specularF0Factor = BlendLayers(lightingInputLayer1.m_specularF0Factor, lightingInputLayer2.m_specularF0Factor, lightingInputLayer3.m_specularF0Factor, blendWeights); + float3 metallic = BlendLayers(lightingInputLayer1.m_metallic, lightingInputLayer2.m_metallic, lightingInputLayer3.m_metallic, blendWeights); + + if(o_parallax_highlightClipping && displacementIsClipped) + { + ApplyParallaxClippingHighlight(baseColor); + } - // ------- Transmission ------- + surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic); - float2 transmissionUv = IN.m_uv[MaterialSrg::m_transmissionThicknessMapUvIndex]; - float4 transmissionTintThickness = GeTransmissionInput(MaterialSrg::m_transmissionThicknessMap, MaterialSrg::m_sampler, transmissionUv, MaterialSrg::m_transmissionTintThickness); - surface.transmission.tint = transmissionTintThickness.rgb; - surface.transmission.thickness = transmissionTintThickness.w; - surface.transmission.transmissionParams = MaterialSrg::m_transmissionParams; + surface.roughnessLinear = BlendLayers(lightingInputLayer1.m_roughness, lightingInputLayer2.m_roughness, lightingInputLayer3.m_roughness, blendWeights); + surface.CalculateRoughnessA(); + + // ------- Init and Combine Lighting Data ------- - // ------- Lighting Data ------- - LightingData lightingData; // Light iterator @@ -307,88 +442,22 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // Directional light shadow coordinates lightingData.shadowCoords = IN.m_shadowCoords; - - // ------- Emissive ------- - float3 layer1_emissive = GetEmissiveInput(MaterialSrg::m_layer1_m_emissiveMap, MaterialSrg::m_sampler, uvLayer1[MaterialSrg::m_layer1_m_emissiveMapUvIndex], MaterialSrg::m_layer1_m_emissiveIntensity, MaterialSrg::m_layer1_m_emissiveColor.rgb, o_layer1_o_emissiveEnabled, o_layer1_o_emissive_useTexture); - float3 layer2_emissive = GetEmissiveInput(MaterialSrg::m_layer2_m_emissiveMap, MaterialSrg::m_sampler, uvLayer2[MaterialSrg::m_layer2_m_emissiveMapUvIndex], MaterialSrg::m_layer2_m_emissiveIntensity, MaterialSrg::m_layer2_m_emissiveColor.rgb, o_layer2_o_emissiveEnabled, o_layer2_o_emissive_useTexture); - float3 layer3_emissive = GetEmissiveInput(MaterialSrg::m_layer3_m_emissiveMap, MaterialSrg::m_sampler, uvLayer3[MaterialSrg::m_layer3_m_emissiveMapUvIndex], MaterialSrg::m_layer3_m_emissiveIntensity, MaterialSrg::m_layer3_m_emissiveColor.rgb, o_layer3_o_emissiveEnabled, o_layer3_o_emissive_useTexture); - lightingData.emissiveLighting = BlendLayers(layer1_emissive, layer2_emissive, layer3_emissive, blendWeights); + lightingData.emissiveLighting = BlendLayers(lightingInputLayer1.m_emissiveLighting, lightingInputLayer2.m_emissiveLighting, lightingInputLayer3.m_emissiveLighting, blendWeights); + lightingData.specularOcclusion = BlendLayers(lightingInputLayer1.m_specularOcclusion, lightingInputLayer2.m_specularOcclusion, lightingInputLayer3.m_specularOcclusion, blendWeights); + lightingData.diffuseAmbientOcclusion = BlendLayers(lightingInputLayer1.m_diffuseAmbientOcclusion, lightingInputLayer2.m_diffuseAmbientOcclusion, lightingInputLayer3.m_diffuseAmbientOcclusion, blendWeights); - // ------- Occlusion ------- - - float layer1_diffuseAmbientOcclusion = GetOcclusionInput(MaterialSrg::m_layer1_m_diffuseOcclusionMap, MaterialSrg::m_sampler, uvLayer1[MaterialSrg::m_layer1_m_diffuseOcclusionMapUvIndex], MaterialSrg::m_layer1_m_diffuseOcclusionFactor, o_layer1_o_diffuseOcclusion_useTexture); - float layer2_diffuseAmbientOcclusion = GetOcclusionInput(MaterialSrg::m_layer2_m_diffuseOcclusionMap, MaterialSrg::m_sampler, uvLayer2[MaterialSrg::m_layer2_m_diffuseOcclusionMapUvIndex], MaterialSrg::m_layer2_m_diffuseOcclusionFactor, o_layer2_o_diffuseOcclusion_useTexture); - float layer3_diffuseAmbientOcclusion = GetOcclusionInput(MaterialSrg::m_layer3_m_diffuseOcclusionMap, MaterialSrg::m_sampler, uvLayer3[MaterialSrg::m_layer3_m_diffuseOcclusionMapUvIndex], MaterialSrg::m_layer3_m_diffuseOcclusionFactor, o_layer3_o_diffuseOcclusion_useTexture); - lightingData.diffuseAmbientOcclusion = BlendLayers(layer1_diffuseAmbientOcclusion, layer2_diffuseAmbientOcclusion, layer3_diffuseAmbientOcclusion, blendWeights); - - float layer1_specularOcclusion = GetOcclusionInput(MaterialSrg::m_layer1_m_specularOcclusionMap, MaterialSrg::m_sampler, uvLayer1[MaterialSrg::m_layer1_m_specularOcclusionMapUvIndex], MaterialSrg::m_layer1_m_specularOcclusionFactor, o_layer1_o_specularOcclusion_useTexture); - float layer2_specularOcclusion = GetOcclusionInput(MaterialSrg::m_layer2_m_specularOcclusionMap, MaterialSrg::m_sampler, uvLayer2[MaterialSrg::m_layer2_m_specularOcclusionMapUvIndex], MaterialSrg::m_layer2_m_specularOcclusionFactor, o_layer2_o_specularOcclusion_useTexture); - float layer3_specularOcclusion = GetOcclusionInput(MaterialSrg::m_layer3_m_specularOcclusionMap, MaterialSrg::m_sampler, uvLayer3[MaterialSrg::m_layer3_m_specularOcclusionMapUvIndex], MaterialSrg::m_layer3_m_specularOcclusionFactor, o_layer3_o_specularOcclusion_useTexture); - lightingData.specularOcclusion = BlendLayers(layer1_specularOcclusion, layer2_specularOcclusion, layer3_specularOcclusion, blendWeights); + lightingData.CalculateMultiscatterCompensation(surface.specularF0, o_specularF0_enableMultiScatterCompensation); - // ------- Clearcoat ------- + // ------- Combine Clearcoat ------- if(o_clearCoat_feature_enabled) { - // --- Layer 1 --- - - float layer1_clearCoatFactor = 0.0f; - float layer1_clearCoatRoughness = 0.0f; - float3 layer1_clearCoatNormal = float3(0.0, 0.0, 0.0); - if(o_layer1_o_clearCoat_enabled) - { - float3x3 layer1_uvMatrix = MaterialSrg::m_layer1_m_clearCoatNormalMapUvIndex == 0 ? MaterialSrg::m_layer1_m_uvMatrix : CreateIdentity3x3(); - - GetClearCoatInputs(MaterialSrg::m_layer1_m_clearCoatInfluenceMap, uvLayer1[MaterialSrg::m_layer1_m_clearCoatInfluenceMapUvIndex], MaterialSrg::m_layer1_m_clearCoatFactor, o_layer1_o_clearCoat_factor_useTexture, - MaterialSrg::m_layer1_m_clearCoatRoughnessMap, uvLayer1[MaterialSrg::m_layer1_m_clearCoatRoughnessMapUvIndex], MaterialSrg::m_layer1_m_clearCoatRoughness, o_layer1_o_clearCoat_roughness_useTexture, - MaterialSrg::m_layer1_m_clearCoatNormalMap, uvLayer1[MaterialSrg::m_layer1_m_clearCoatNormalMapUvIndex], IN.m_normal, o_layer1_o_clearCoat_normal_useTexture, MaterialSrg::m_layer1_m_clearCoatNormalStrength, - layer1_uvMatrix, tangents[MaterialSrg::m_layer1_m_clearCoatNormalMapUvIndex], bitangents[MaterialSrg::m_layer1_m_clearCoatNormalMapUvIndex], - MaterialSrg::m_sampler, isFrontFace, - layer1_clearCoatFactor, layer1_clearCoatRoughness, layer1_clearCoatNormal); - } - - // --- Layer 2 --- - - float layer2_clearCoatFactor = 0.0f; - float layer2_clearCoatRoughness = 0.0f; - float3 layer2_clearCoatNormal = float3(0.0, 0.0, 0.0); - if(o_layer2_o_clearCoat_enabled) - { - float3x3 layer2_uvMatrix = MaterialSrg::m_layer2_m_clearCoatNormalMapUvIndex == 0 ? MaterialSrg::m_layer2_m_uvMatrix : CreateIdentity3x3(); - - GetClearCoatInputs(MaterialSrg::m_layer2_m_clearCoatInfluenceMap, uvLayer2[MaterialSrg::m_layer2_m_clearCoatInfluenceMapUvIndex], MaterialSrg::m_layer2_m_clearCoatFactor, o_layer2_o_clearCoat_factor_useTexture, - MaterialSrg::m_layer2_m_clearCoatRoughnessMap, uvLayer2[MaterialSrg::m_layer2_m_clearCoatRoughnessMapUvIndex], MaterialSrg::m_layer2_m_clearCoatRoughness, o_layer2_o_clearCoat_roughness_useTexture, - MaterialSrg::m_layer2_m_clearCoatNormalMap, uvLayer2[MaterialSrg::m_layer2_m_clearCoatNormalMapUvIndex], IN.m_normal, o_layer2_o_clearCoat_normal_useTexture, MaterialSrg::m_layer2_m_clearCoatNormalStrength, - layer2_uvMatrix, tangents[MaterialSrg::m_layer2_m_clearCoatNormalMapUvIndex], bitangents[MaterialSrg::m_layer2_m_clearCoatNormalMapUvIndex], - MaterialSrg::m_sampler, isFrontFace, - layer2_clearCoatFactor, layer2_clearCoatRoughness, layer2_clearCoatNormal); - } - - // --- Layer 3 --- - - float layer3_clearCoatFactor = 0.0f; - float layer3_clearCoatRoughness = 0.0f; - float3 layer3_clearCoatNormal = float3(0.0, 0.0, 0.0); - if(o_layer3_o_clearCoat_enabled) - { - float3x3 layer3_uvMatrix = MaterialSrg::m_layer3_m_clearCoatNormalMapUvIndex == 0 ? MaterialSrg::m_layer3_m_uvMatrix : CreateIdentity3x3(); - - GetClearCoatInputs(MaterialSrg::m_layer3_m_clearCoatInfluenceMap, uvLayer3[MaterialSrg::m_layer3_m_clearCoatInfluenceMapUvIndex], MaterialSrg::m_layer3_m_clearCoatFactor, o_layer3_o_clearCoat_factor_useTexture, - MaterialSrg::m_layer3_m_clearCoatRoughnessMap, uvLayer3[MaterialSrg::m_layer3_m_clearCoatRoughnessMapUvIndex], MaterialSrg::m_layer3_m_clearCoatRoughness, o_layer3_o_clearCoat_roughness_useTexture, - MaterialSrg::m_layer3_m_clearCoatNormalMap, uvLayer3[MaterialSrg::m_layer3_m_clearCoatNormalMapUvIndex], IN.m_normal, o_layer3_o_clearCoat_normal_useTexture, MaterialSrg::m_layer3_m_clearCoatNormalStrength, - layer3_uvMatrix, tangents[MaterialSrg::m_layer3_m_clearCoatNormalMapUvIndex], bitangents[MaterialSrg::m_layer3_m_clearCoatNormalMapUvIndex], - MaterialSrg::m_sampler, isFrontFace, - layer3_clearCoatFactor, layer3_clearCoatRoughness, layer3_clearCoatNormal); - } - - // --- Blend Layers --- - - surface.clearCoat.factor = BlendLayers(layer1_clearCoatFactor, layer2_clearCoatFactor, layer3_clearCoatFactor, blendWeights); - surface.clearCoat.roughness = BlendLayers(layer1_clearCoatRoughness, layer2_clearCoatRoughness, layer3_clearCoatRoughness, blendWeights); + surface.clearCoat.factor = BlendLayers(lightingInputLayer1.m_clearCoat.factor, lightingInputLayer2.m_clearCoat.factor, lightingInputLayer3.m_clearCoat.factor, blendWeights); + surface.clearCoat.roughness = BlendLayers(lightingInputLayer1.m_clearCoat.roughness, lightingInputLayer2.m_clearCoat.roughness, lightingInputLayer3.m_clearCoat.roughness, blendWeights); // [GFX TODO][ATOM-14592] This is not the right way to blend the normals. We need to use ReorientTangentSpaceNormal(), and that requires GetClearCoatInputs() to return the normal in TS instead of WS. - surface.clearCoat.normal = BlendLayers(layer1_clearCoatNormal, layer2_clearCoatNormal, layer3_clearCoatNormal, blendWeights); + surface.clearCoat.normal = BlendLayers(lightingInputLayer1.m_clearCoat.normal, lightingInputLayer2.m_clearCoat.normal, lightingInputLayer3.m_clearCoat.normal, blendWeights); surface.clearCoat.normal = normalize(surface.clearCoat.normal); // manipulate base layer f0 if clear coat is enabled @@ -408,11 +477,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // Clear coat layer has fixed IOR = 1.5 and transparent => F0 = (1.5 - 1)^2 / (1.5 + 1)^2 = 0.04 lightingData.diffuseResponse *= 1.0 - (FresnelSchlickWithRoughness(lightingData.NdotV, float3(0.04, 0.04, 0.04), surface.clearCoat.roughness) * surface.clearCoat.factor); } - - // ------- Multiscatter ------- - - lightingData.CalculateMultiscatterCompensation(surface.specularF0, o_specularF0_enableMultiScatterCompensation); - + // ------- Lighting Calculation ------- // Apply Decals @@ -425,17 +490,14 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float ApplyIBL(surface, lightingData); // Finalize Lighting - lightingData.FinalizeLighting(surface.transmission.tint); + lightingData.FinalizeLighting(0); const float alpha = 1.0; PbrLightingOutput lightingOutput = GetPbrLightingOutput(surface, lightingData, alpha); - // Pack factor and quality, drawback: because of precision limit of float16 cannot represent exact 1, maximum representable value is 0.9961 - uint factorAndQuality = dot(round(float2(saturate(surfaceScatteringFactor), MaterialSrg::m_subsurfaceScatteringQuality) * 255), float2(256, 1)); - lightingOutput.m_diffuseColor.w = factorAndQuality * (o_enableSubsurfaceScattering ? 1.0 : -1.0); - + lightingOutput.m_diffuseColor.w = -1; // Disable subsurface scattering return lightingOutput; } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/StandardSurface.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/StandardSurface.azsli index bb63d27df0..85d9370d2b 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/StandardSurface.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/StandardSurface.azsli @@ -32,6 +32,8 @@ class Surface float roughnessA; //!< Actual roughness value ( a.k.a. "alpha roughness") to be used in microfacet calculations float roughnessA2; //!< Alpha roughness ^ 2 (i.e. roughnessA * roughnessA), used in GGX, cached here for perfromance + void InitializeToZero(); + //! Applies specular anti-aliasing to roughnessA2 void ApplySpecularAA(); @@ -43,6 +45,18 @@ class Surface }; +void Surface::InitializeToZero() +{ + clearCoat.InitializeToZero(); + transmission.InitializeToZero(); + position = float3(0,0,0); + normal = float3(0,0,0); + albedo = float3(0,0,0); + specularF0 = float3(0,0,0); + roughnessLinear = 0.0f; + roughnessA = 0.0f; + roughnessA2 = 0.0f; +} // Specular Anti-Aliasing technique from this paper: // http://www.jp.square-enix.com/tech/library/pdf/ImprovedGeometricSpecularAA.pdf From 275bb1bfeccabb816880960c6afe61ff80e9fb58 Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Tue, 11 May 2021 17:25:53 -0700 Subject: [PATCH 041/330] ATOM-14688 Disable Individual Layers Added flags for enabling StandardMultilayerPBR layers 2 and 3. This makes it easy to create a two-layer material, or to flip layers off and on for debugging. --- .../Types/StandardMultilayerPBR.materialtype | 22 +++++++ .../Types/StandardMultilayerPBR_Common.azsli | 62 +++++++++++++------ .../StandardMultilayerPBR_ForwardPass.azsl | 20 +++++- .../001_ManyFeatures.material | 4 ++ .../001_ManyFeatures_Layer2Off.material | 11 ++++ .../001_ManyFeatures_Layer3Off.material | 11 ++++ .../002_ParallaxPdo.material | 4 ++ 7 files changed, 113 insertions(+), 21 deletions(-) create mode 100644 Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures_Layer2Off.material create mode 100644 Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures_Layer3Off.material diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype index 90a599c6f9..b49c66346f 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype @@ -295,6 +295,28 @@ } ], "blend": [ + { + "id": "enableLayer2", + "displayName": "Enable Layer 2", + "description": "Whether to enable layer 2.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "id": "o_layer2_enabled" + } + }, + { + "id": "enableLayer3", + "displayName": "Enable Layer 3", + "description": "Whether to enable layer 3.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "id": "o_layer3_enabled" + } + }, { "id": "blendSource", "displayName": "Blend Source", diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli index 8c7b49214b..d2314efefe 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli @@ -95,6 +95,9 @@ ShaderResourceGroup MaterialSrg : SRG_PerMaterial // ------ Shader Options ---------------------------------------- +option bool o_layer2_enabled; +option bool o_layer3_enabled; + enum class DebugDrawMode { None, BlendSource, DepthMaps }; option DebugDrawMode o_debugDrawMode; @@ -146,14 +149,27 @@ float3 GetBlendSourceValues(float2 uv) { float3 blendSourceValues = float3(0,0,0); - switch(GetFinalBlendMaskSource()) + if(o_layer2_enabled || o_layer3_enabled) { - case BlendMaskSource::TextureMap: - blendSourceValues = MaterialSrg::m_blendMaskTexture.Sample(MaterialSrg::m_sampler, uv).rgb; - break; - case BlendMaskSource::VertexColors: - blendSourceValues = s_blendMaskFromVertexStream; - break; + switch(GetFinalBlendMaskSource()) + { + case BlendMaskSource::TextureMap: + blendSourceValues = MaterialSrg::m_blendMaskTexture.Sample(MaterialSrg::m_sampler, uv).rgb; + break; + case BlendMaskSource::VertexColors: + blendSourceValues = s_blendMaskFromVertexStream; + break; + } + + if(!o_layer2_enabled) + { + blendSourceValues.r = 0.0; + } + + if(!o_layer3_enabled) + { + blendSourceValues.g = 0.0; + } } return blendSourceValues; @@ -167,18 +183,26 @@ float3 GetBlendSourceValues(float2 uv) //! layer3 = b float3 GetBlendWeights(float2 uv) { - float3 blendSourceValues = GetBlendSourceValues(uv); + float3 blendWeights; + + if(o_layer2_enabled || o_layer3_enabled) + { + float3 blendSourceValues = GetBlendSourceValues(uv); - // Calculate blend weights such that multiplying and adding them with layer data is equivalent - // to lerping between each layer. - // final = lerp(final, layer1, blendWeights.r) - // final = lerp(final, layer2, blendWeights.g) - // final = lerp(final, layer3, blendWeights.b) + // Calculate blend weights such that multiplying and adding them with layer data is equivalent + // to lerping between each layer. + // final = lerp(final, layer1, blendWeights.r) + // final = lerp(final, layer2, blendWeights.g) + // final = lerp(final, layer3, blendWeights.b) - float3 blendWeights; - blendWeights.b = blendSourceValues.g; - blendWeights.g = (1.0 - blendSourceValues.g) * blendSourceValues.r; - blendWeights.r = (1.0 - blendSourceValues.g) * (1.0 - blendSourceValues.r); + blendWeights.b = blendSourceValues.g; + blendWeights.g = (1.0 - blendSourceValues.g) * blendSourceValues.r; + blendWeights.r = (1.0 - blendSourceValues.g) * (1.0 - blendSourceValues.r); + } + else + { + blendWeights = float3(1,0,0); + } return blendWeights; } @@ -230,7 +254,7 @@ DepthResult GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy) layerDepthValues.r -= MaterialSrg::m_layer1_m_depthOffset; } - if(o_layer2_o_useDepthMap) + if(o_layer2_enabled && o_layer2_o_useDepthMap) { float2 layerUv = uv; if(MaterialSrg::m_parallaxUvIndex == 0) @@ -243,7 +267,7 @@ DepthResult GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy) layerDepthValues.g -= MaterialSrg::m_layer2_m_depthOffset; } - if(o_layer3_o_useDepthMap) + if(o_layer3_enabled && o_layer3_o_useDepthMap) { float2 layerUv = uv; if(MaterialSrg::m_parallaxUvIndex == 0) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl index 9e5a2e623a..8d5d32940d 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl @@ -387,20 +387,30 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // ----------- Layer 2 ----------- ProcessedMaterialInputs lightingInputLayer2; + if(o_layer2_enabled) { StandardMaterialInputs inputs; FILL_STANDARD_MATERIAL_INPUTS(inputs, MaterialSrg::m_layer2_, o_layer2_, blendWeights.g) lightingInputLayer2 = ProcessStandardMaterialInputs(inputs); } + else + { + lightingInputLayer2.InitializeToZero(); + } // ----------- Layer 3 ----------- ProcessedMaterialInputs lightingInputLayer3; + if(o_layer3_enabled) { StandardMaterialInputs inputs; FILL_STANDARD_MATERIAL_INPUTS(inputs, MaterialSrg::m_layer3_, o_layer3_, blendWeights.b) lightingInputLayer3 = ProcessStandardMaterialInputs(inputs); } + else + { + lightingInputLayer3.InitializeToZero(); + } // ------- Combine all layers --------- @@ -411,8 +421,14 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // ------- Combine Normals --------- float3 normalTS = lightingInputLayer1.m_normalTS; - normalTS = ReorientTangentSpaceNormal(normalTS, lightingInputLayer2.m_normalTS); - normalTS = ReorientTangentSpaceNormal(normalTS, lightingInputLayer3.m_normalTS); + if(o_layer2_enabled) + { + normalTS = ReorientTangentSpaceNormal(normalTS, lightingInputLayer2.m_normalTS); + } + if(o_layer3_enabled) + { + normalTS = ReorientTangentSpaceNormal(normalTS, lightingInputLayer3.m_normalTS); + } // [GFX TODO][ATOM-14591]: This will only work if the normal maps all use the same UV stream. We would need to add support for having them in different UV streams. surface.normal = normalize(TangentSpaceToWorld(normalTS, IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex])); diff --git a/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures.material b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures.material index b85705fcb8..d9a4aabe2a 100644 --- a/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures.material +++ b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures.material @@ -4,6 +4,10 @@ "parentMaterial": "", "propertyLayoutVersion": 3, "properties": { + "blend": { + "enableLayer2": true, + "enableLayer3": true + }, "layer1_baseColor": { "color": [ 0.3495536744594574, diff --git a/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures_Layer2Off.material b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures_Layer2Off.material new file mode 100644 index 0000000000..d91cfb34eb --- /dev/null +++ b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures_Layer2Off.material @@ -0,0 +1,11 @@ +{ + "description": "", + "materialType": "Materials/Types/StandardMultilayerPBR.materialtype", + "parentMaterial": "TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures.material", + "propertyLayoutVersion": 3, + "properties": { + "blend": { + "enableLayer2": false + } + } +} \ No newline at end of file diff --git a/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures_Layer3Off.material b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures_Layer3Off.material new file mode 100644 index 0000000000..3ee48df612 --- /dev/null +++ b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures_Layer3Off.material @@ -0,0 +1,11 @@ +{ + "description": "", + "materialType": "Materials/Types/StandardMultilayerPBR.materialtype", + "parentMaterial": "TestData/Materials/StandardMultilayerPbrTestCases/001_ManyFeatures.material", + "propertyLayoutVersion": 3, + "properties": { + "blend": { + "enableLayer3": false + } + } +} \ No newline at end of file diff --git a/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/002_ParallaxPdo.material b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/002_ParallaxPdo.material index f6c881aee5..0bf4177db9 100644 --- a/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/002_ParallaxPdo.material +++ b/Gems/Atom/TestData/TestData/Materials/StandardMultilayerPbrTestCases/002_ParallaxPdo.material @@ -4,6 +4,10 @@ "parentMaterial": "", "propertyLayoutVersion": 3, "properties": { + "blend": { + "enableLayer2": true, + "enableLayer3": true + }, "layer1_baseColor": { "textureMap": "TestData/Textures/cc0/bark1_col.jpg" }, From c777e2e35301cd0054fe39e8fdccb5e632d48a21 Mon Sep 17 00:00:00 2001 From: scottr Date: Tue, 11 May 2021 18:07:19 -0700 Subject: [PATCH 042/330] [cpack_installer] some cpack cleanup and prep for online installer support (pre/post build steps) --- cmake/Packaging.cmake | 28 +++++++++++++------ .../Platform/Windows/PackagingPostBuild.cmake | 12 ++++++++ .../Platform/Windows/Packaging_windows.cmake | 8 ++++-- .../Windows/platform_windows_files.cmake | 1 + 4 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 cmake/Platform/Windows/PackagingPostBuild.cmake diff --git a/cmake/Packaging.cmake b/cmake/Packaging.cmake index 4f6565edc7..e398ea7509 100644 --- a/cmake/Packaging.cmake +++ b/cmake/Packaging.cmake @@ -13,28 +13,38 @@ if(NOT PAL_TRAIT_BUILD_CPACK_SUPPORTED) return() endif() -ly_get_absolute_pal_filename(pal_dir ${CMAKE_SOURCE_DIR}/cmake/Platform/${PAL_HOST_PLATFORM_NAME}) -include(${pal_dir}/Packaging_${PAL_HOST_PLATFORM_NAME_LOWERCASE}.cmake) - -# if we get here and the generator hasn't been set, then a non fatal error occurred disabling packaging support -if(NOT CPACK_GENERATOR) - return() -endif() +# set the common cpack variables first so they are accessible via configure_file +# when the platforms specific properties are applied below +set(LY_INSTALLER_DOWNLOAD_URL "" CACHE PATH "URL embded into the installer to download additional artifacts") set(CPACK_PACKAGE_VENDOR "${PROJECT_NAME}") set(CPACK_PACKAGE_VERSION "${LY_VERSION_STRING}") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Installation Tool") string(TOLOWER ${PROJECT_NAME} _project_name_lower) -set(CPACK_PACKAGE_FILE_NAME "${_project_name_lower}_installer") +set(CPACK_PACKAGE_FILE_NAME "${_project_name_lower}_${LY_VERSION_STRING}_installer") set(DEFAULT_LICENSE_NAME "Apache-2.0") -set(DEFAULT_LICENSE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt") +set(DEFAULT_LICENSE_FILE "${CMAKE_SOURCE_DIR}/LICENSE.txt") set(CPACK_RESOURCE_FILE_LICENSE ${DEFAULT_LICENSE_FILE}) set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_VENDOR}/${CPACK_PACKAGE_VERSION}") +# custom cpack cache variables for use in pre/post build scripts +set(CPACK_SOURCE_DIR ${CMAKE_SOURCE_DIR}/cmake) +set(CPACK_BINARY_DIR ${CMAKE_BINARY_DIR}/installer) +set(CPACK_DOWNLOAD_URL ${LY_INSTALLER_DOWNLOAD_URL}) + +# attempt to apply platform specific settings +ly_get_absolute_pal_filename(pal_dir ${CMAKE_SOURCE_DIR}/cmake/Platform/${PAL_HOST_PLATFORM_NAME}) +include(${pal_dir}/Packaging_${PAL_HOST_PLATFORM_NAME_LOWERCASE}.cmake) + +# if we get here and the generator hasn't been set, then a non fatal error occurred disabling packaging support +if(NOT CPACK_GENERATOR) + return() +endif() + # IMPORTANT: required to be included AFTER setting all property overrides include(CPack REQUIRED) diff --git a/cmake/Platform/Windows/PackagingPostBuild.cmake b/cmake/Platform/Windows/PackagingPostBuild.cmake new file mode 100644 index 0000000000..fe57904003 --- /dev/null +++ b/cmake/Platform/Windows/PackagingPostBuild.cmake @@ -0,0 +1,12 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +message(STATUS "Hello from CPack post build!") diff --git a/cmake/Platform/Windows/Packaging_windows.cmake b/cmake/Platform/Windows/Packaging_windows.cmake index 8aa6f2386d..ba3ce011a4 100644 --- a/cmake/Platform/Windows/Packaging_windows.cmake +++ b/cmake/Platform/Windows/Packaging_windows.cmake @@ -32,7 +32,7 @@ set(CPACK_GENERATOR "WIX") # however, they are unique for each run. instead, let's do the auto generation here and add it to # the cache for run persistence. an additional cache file will be used to store the information on # the original generation so we still have the ability to detect if they are still being used. -set(_guid_cache_file "${CMAKE_BINARY_DIR}/installer/wix_guid_cache.cmake") +set(_guid_cache_file "${CPACK_BINARY_DIR}/wix_guid_cache.cmake") if(NOT EXISTS ${_guid_cache_file}) set(_wix_guid_namespace "6D43F57A-2917-4AD9-B758-1F13CDB08593") @@ -89,4 +89,8 @@ endif() set(CPACK_WIX_PRODUCT_GUID ${LY_WIX_PRODUCT_GUID}) set(CPACK_WIX_UPGRADE_GUID ${LY_WIX_UPGRADE_GUID}) -set(CPACK_WIX_TEMPLATE "${CMAKE_SOURCE_DIR}/cmake/Platform/Windows/PackagingTemplate.wxs.in") +set(CPACK_WIX_TEMPLATE "${CPACK_SOURCE_DIR}/Platform/Windows/PackagingTemplate.wxs.in") + +set(CPACK_POST_BUILD_SCRIPTS + ${CPACK_SOURCE_DIR}/Platform/Windows/PackagingPostBuild.cmake +) diff --git a/cmake/Platform/Windows/platform_windows_files.cmake b/cmake/Platform/Windows/platform_windows_files.cmake index 2fc869b43e..579621d5ea 100644 --- a/cmake/Platform/Windows/platform_windows_files.cmake +++ b/cmake/Platform/Windows/platform_windows_files.cmake @@ -24,5 +24,6 @@ set(FILES PALDetection_windows.cmake Install_windows.cmake Packaging_windows.cmake + PackagingPostBuild.cmake PackagingTemplate.wxs.in ) From f5e91c6e4284f00e64db5cc4ba4e0678be111b1c Mon Sep 17 00:00:00 2001 From: antonmic Date: Wed, 12 May 2021 10:39:18 -0700 Subject: [PATCH 043/330] Added low end shaders in StandardPBR_ShaderEnable.lua --- .../Assets/Materials/Types/StandardPBR_ShaderEnable.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ShaderEnable.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ShaderEnable.lua index 7c3d989c35..2733713122 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ShaderEnable.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ShaderEnable.lua @@ -29,26 +29,33 @@ function Process(context) local depthPass = context:GetShaderByTag("DepthPass") local shadowMap = context:GetShaderByTag("Shadowmap") local forwardPassEDS = context:GetShaderByTag("ForwardPass_EDS") + local lowEndForwardEDS = context:GetShaderByTag("LowEndForward_EDS") + local depthPassWithPS = context:GetShaderByTag("DepthPass_WithPS") local shadowMapWitPS = context:GetShaderByTag("Shadowmap_WithPS") local forwardPass = context:GetShaderByTag("ForwardPass") + local lowEndForward = context:GetShaderByTag("LowEndForward") if parallaxEnabled and parallaxPdoEnabled then depthPass:SetEnabled(false) shadowMap:SetEnabled(false) forwardPassEDS:SetEnabled(false) + lowEndForwardEDS:SetEnabled(false) depthPassWithPS:SetEnabled(true) shadowMapWitPS:SetEnabled(true) forwardPass:SetEnabled(true) + lowEndForward:SetEnabled(true) else depthPass:SetEnabled(opacityMode == OpacityMode_Opaque) shadowMap:SetEnabled(opacityMode == OpacityMode_Opaque) forwardPassEDS:SetEnabled((opacityMode == OpacityMode_Opaque) or (opacityMode == OpacityMode_Blended) or (opacityMode == OpacityMode_TintedTransparent)) + lowEndForwardEDS:SetEnabled((opacityMode == OpacityMode_Opaque) or (opacityMode == OpacityMode_Blended) or (opacityMode == OpacityMode_TintedTransparent)) depthPassWithPS:SetEnabled(opacityMode == OpacityMode_Cutout) shadowMapWitPS:SetEnabled(opacityMode == OpacityMode_Cutout) forwardPass:SetEnabled(opacityMode == OpacityMode_Cutout) + lowEndForward:SetEnabled(opacityMode == OpacityMode_Cutout) end context:GetShaderByTag("DepthPassTransparentMin"):SetEnabled((opacityMode == OpacityMode_Blended) or (opacityMode == OpacityMode_TintedTransparent)) From 92b7099d78953eef8552633201b98d8f07597529 Mon Sep 17 00:00:00 2001 From: antonmic Date: Wed, 12 May 2021 11:10:13 -0700 Subject: [PATCH 044/330] Some clean up --- .../Common/Assets/Materials/Types/StandardPBR.materialtype | 7 ------- .../Assets/Materials/Types/StandardPBR_ForwardPass.azsl | 4 ++-- .../Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli | 3 +++ .../ShaderLib/Atom/Features/ShaderQualityOptions.azsli | 4 +--- Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl | 6 ++---- 5 files changed, 8 insertions(+), 16 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype index 47d8a9d9d5..a9a3e9e09b 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype @@ -77,13 +77,6 @@ ], "properties": { "general": [ - { - "id": "useLowEndShader", - "displayName": "Use Low End", - "description": "Whether to use the low end shader.", - "type": "Bool", - "defaultValue": false - }, { "id": "applySpecularAA", "displayName": "Apply Specular AA", diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl index 1e6fafda9b..7ae5934d4f 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl @@ -321,7 +321,7 @@ ForwardPassOutputWithDepth StandardPbr_ForwardPassPS(VSOutput IN, bool isFrontFa #ifdef UNIFIED_FORWARD_OUTPUT OUT.m_color.rgb = lightingOutput.m_diffuseColor.rgb + lightingOutput.m_specularColor.rgb; - OUT.m_color.a = 1.0f; + OUT.m_color.a = lightingOutput.m_diffuseColor.a; OUT.m_depth = depth; #else OUT.m_diffuseColor = lightingOutput.m_diffuseColor; @@ -344,7 +344,7 @@ ForwardPassOutput StandardPbr_ForwardPassPS_EDS(VSOutput IN, bool isFrontFace : #ifdef UNIFIED_FORWARD_OUTPUT OUT.m_color.rgb = lightingOutput.m_diffuseColor.rgb + lightingOutput.m_specularColor.rgb; - OUT.m_color.a = 1.0f; + OUT.m_color.a = lightingOutput.m_diffuseColor.a; #else OUT.m_diffuseColor = lightingOutput.m_diffuseColor; OUT.m_specularColor = lightingOutput.m_specularColor; diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli index 721c48835d..3e3544fe9e 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli @@ -12,6 +12,9 @@ #pragma once +// --- Static Options Available --- +// FORCE_IBL_IN_FORWARD_PASS - forces IBL lighting to be run in the forward pass, used in pipelines that don't have a reflection pass + #include #include diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli index d6fb259548..6e89269f8d 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ShaderQualityOptions.azsli @@ -12,9 +12,7 @@ #pragma once -// These are a list of quality options to specify as macros (either in azsl or in shader files) -// -// QUALITY_LOW_END +// This file translates quality option macros like QUALITY_LOW_END to their relevant settings #ifdef QUALITY_LOW_END diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl index 4b3e9536b7..1bebb2ec47 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/SkyBox/SkyBox.azsl @@ -10,10 +10,8 @@ * */ -// Static Options: -// -// SKYBOX_TWO_OUTPUTS - Allows the skybox to render to two rendertargets instead of one - +// --- Static Options Available --- +// SKYBOX_TWO_OUTPUTS - Skybox renders to two rendertargets instead of one (SkyBox_TwoOutputs.pass writes to specular and reflection targets) #include #include From b52388f5ebfb5af09505a99fe1e416ae12907dd0 Mon Sep 17 00:00:00 2001 From: antonmic Date: Wed, 12 May 2021 11:11:45 -0700 Subject: [PATCH 045/330] Remove unused file --- .../PBR/LowEndForwardPassOutput.azsli | 32 ------------------- 1 file changed, 32 deletions(-) delete mode 100644 Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LowEndForwardPassOutput.azsli diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LowEndForwardPassOutput.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LowEndForwardPassOutput.azsli deleted file mode 100644 index acc215f1c9..0000000000 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/LowEndForwardPassOutput.azsli +++ /dev/null @@ -1,32 +0,0 @@ -/* -* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -* its licensors. -* -* For complete copyright and license terms please see the LICENSE at the root of this -* distribution (the "License"). All use of this software is governed by the License, -* or, if provided, by the license below or the license accompanying this file. Do not -* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* -*/ - -struct ForwardPassOutput -{ - float4 m_diffuseColor : SV_Target0; //!< RGB = Diffuse Lighting, A = Blend Alpha (for blended surfaces) OR A = special encoding of surfaceScatteringFactor, m_subsurfaceScatteringQuality, o_enableSubsurfaceScattering - float4 m_specularColor : SV_Target1; //!< RGB = Specular Lighting, A = Unused - float4 m_albedo : SV_Target2; //!< RGB = Surface albedo pre-multiplied by other factors that will be multiplied later by diffuse GI, A = specularOcclusion - float4 m_specularF0 : SV_Target3; //!< RGB = Specular F0, A = roughness - float4 m_normal : SV_Target4; //!< RGB10 = EncodeNormalSignedOctahedron(worldNormal), A2 = multiScatterCompensationEnabled -}; - -struct ForwardPassOutputWithDepth -{ - // See above for descriptions of special encodings - - float4 m_diffuseColor : SV_Target0; - float4 m_specularColor : SV_Target1; - float4 m_albedo : SV_Target2; - float4 m_specularF0 : SV_Target3; - float4 m_normal : SV_Target4; - float m_depth : SV_Depth; -}; From 53188a12da7d3ce90de64a0d184b6a5f9df613d8 Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Wed, 12 May 2021 17:06:57 -0700 Subject: [PATCH 046/330] Made StandardMultilayerPBR hide a layer's property groups when that layer is disabled. ATOM-14688 Disable Individual Layers - Added new SetMaterialPropertyGroupVisibility functions to the material functors. - Updated the MaterialFunctor::EditorContext to include parameters for handling material property group metadata. - Updated the material inspector(s) to apply the property group visiblity changes from the material functor, to hide or show the property groups. - Moved some code from MaterialPropertyDescriptor.h/cpp to a new MaterialDynamicMetadata.h/cpp, since these aren't really related to the MaterialPropertyDescriptor code. It's more for material functors to use. - Also fixed the casing for the "GetMaterialPropertyValue_Image" lua function, since I was already in this code (ATOM-14793 "Fix Inconsistent Casing For LuaMaterialFunctorRuntimeContext") Tested in MaterialEditor and in in the main Editor's MaterialComponent property override inspector. --- .../DetailMapsCommonFunctor.lua | 8 +- .../Materials/Types/Skin_WrinkleMaps.lua | 4 +- .../Types/StandardMultilayerPBR.materialtype | 6 ++ .../StandardMultilayerPBR_LayerEnable.lua | 49 +++++++++ ...StandardMultilayerPBR_ParallaxPerLayer.lua | 4 +- .../Types/StandardPBR_ClearCoatState.lua | 4 +- .../Types/StandardPBR_EmissiveState.lua | 4 +- .../Types/StandardPBR_HandleOpacityMode.lua | 2 +- .../Types/StandardPBR_ParallaxState.lua | 4 +- .../Materials/Types/StandardPBR_Roughness.lua | 4 +- .../Types/StandardPBR_SubsurfaceState.lua | 4 +- .../RPI.Reflect/Material/LuaMaterialFunctor.h | 2 + .../Material/MaterialDynamicMetadata.h | 100 ++++++++++++++++++ .../RPI.Reflect/Material/MaterialFunctor.h | 20 +++- .../Material/MaterialPropertyDescriptor.h | 47 -------- .../RPI.Public/Material/MaterialSystem.cpp | 1 + .../Material/LuaMaterialFunctor.cpp | 17 ++- .../Material/MaterialDynamicMetadata.cpp | 50 +++++++++ .../RPI.Reflect/Material/MaterialFunctor.cpp | 96 ++++++++++++----- .../Material/MaterialPropertyDescriptor.cpp | 14 --- .../Material/LuaMaterialFunctorTests.cpp | 76 ++++++++++++- .../MaterialPropertySerializerTests.cpp | 1 + .../RPI/Code/atom_rpi_reflect_files.cmake | 2 + .../DynamicProperty/DynamicProperty.h | 6 +- .../Inspector/InspectorRequestBus.h | 6 ++ .../Inspector/InspectorWidget.h | 3 + .../Code/Source/Inspector/InspectorWidget.cpp | 27 +++++ .../MaterialDocumentNotificationBus.h | 7 ++ .../Document/MaterialDocumentRequestBus.h | 5 + .../Code/Source/Document/MaterialDocument.cpp | 58 ++++++++-- .../Code/Source/Document/MaterialDocument.h | 21 +++- .../MaterialInspector/MaterialInspector.cpp | 16 ++- .../MaterialInspector/MaterialInspector.h | 1 + .../EditorMaterialComponentInspector.cpp | 25 +++-- 34 files changed, 553 insertions(+), 141 deletions(-) create mode 100644 Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_LayerEnable.lua create mode 100644 Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialDynamicMetadata.h create mode 100644 Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialDynamicMetadata.cpp diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialInputs/DetailMapsCommonFunctor.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialInputs/DetailMapsCommonFunctor.lua index 15e4b4f416..e1a3dd6f29 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialInputs/DetailMapsCommonFunctor.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialInputs/DetailMapsCommonFunctor.lua @@ -35,16 +35,16 @@ end function Process(context) local isFeatureEnabled = context:GetMaterialPropertyValue_bool("detailLayerGroup.enableDetailLayer") - local blendMaskTexture = context:GetMaterialPropertyValue_image("detailLayerGroup.blendDetailMask") + local blendMaskTexture = context:GetMaterialPropertyValue_Image("detailLayerGroup.blendDetailMask") local blendMaskTextureEnabled = context:GetMaterialPropertyValue_bool("detailLayerGroup.enableDetailMaskTexture") context:SetShaderOptionValue_bool("o_detail_blendMask_useTexture", isFeatureEnabled and blendMaskTextureEnabled and blendMaskTexture ~= nil) local baseColorDetailEnabled = context:GetMaterialPropertyValue_bool("detailLayerGroup.enableBaseColor") - local baseColorDetailTexture = context:GetMaterialPropertyValue_image("detailLayerGroup.baseColorDetailMap") + local baseColorDetailTexture = context:GetMaterialPropertyValue_Image("detailLayerGroup.baseColorDetailMap") context:SetShaderOptionValue_bool("o_detail_baseColor_useTexture", isFeatureEnabled and baseColorDetailEnabled and baseColorDetailTexture ~= nil) local normalDetailEnabled = context:GetMaterialPropertyValue_bool("detailLayerGroup.enableNormals") - local normalDetailTexture = context:GetMaterialPropertyValue_image("detailLayerGroup.normalDetailMap") + local normalDetailTexture = context:GetMaterialPropertyValue_Image("detailLayerGroup.normalDetailMap") context:SetShaderOptionValue_bool("o_detail_normal_useTexture", isFeatureEnabled and normalDetailEnabled and normalDetailTexture ~= nil) end @@ -78,7 +78,7 @@ function ProcessEditor(context) context:SetMaterialPropertyVisibility("detailUV.rotateDegrees", mainVisibility) context:SetMaterialPropertyVisibility("detailUV.scale", mainVisibility) - local blendMaskTexture = context:GetMaterialPropertyValue_image("detailLayerGroup.blendDetailMask") + local blendMaskTexture = context:GetMaterialPropertyValue_Image("detailLayerGroup.blendDetailMask") if(nil == blendMaskTexture) then context:SetMaterialPropertyVisibility("detailLayerGroup.enableDetailMaskTexture", MaterialPropertyVisibility_Hidden) context:SetMaterialPropertyVisibility("detailLayerGroup.blendDetailMaskUv", MaterialPropertyVisibility_Hidden) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin_WrinkleMaps.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin_WrinkleMaps.lua index d77918f521..9e1bc3763a 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin_WrinkleMaps.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin_WrinkleMaps.lua @@ -65,8 +65,8 @@ function Process(context) for i=1,MAX_WRINKLE_LAYER_COUNT do if(i <= count) then - isBaseColorTextureMissing = isBaseColorEnabled and nil == context:GetMaterialPropertyValue_image("wrinkleLayers.baseColorMap" .. i) - isNormalTextureMissing = isNormalEnabled and nil == context:GetMaterialPropertyValue_image("wrinkleLayers.normalMap" .. i) + isBaseColorTextureMissing = isBaseColorEnabled and nil == context:GetMaterialPropertyValue_Image("wrinkleLayers.baseColorMap" .. i) + isNormalTextureMissing = isNormalEnabled and nil == context:GetMaterialPropertyValue_Image("wrinkleLayers.normalMap" .. i) context:SetShaderOptionValue_bool("o_wrinkleLayers_baseColor_useTexture" .. i, not isBaseColorTextureMissing) context:SetShaderOptionValue_bool("o_wrinkleLayers_normal_useTexture" .. i, not isNormalTextureMissing) else diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype index b49c66346f..a2854e8902 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype @@ -2684,6 +2684,12 @@ "file": "StandardMultilayerPBR_ShaderEnable.lua" } }, + { + "type": "Lua", + "args": { + "file": "StandardMultilayerPBR_LayerEnable.lua" + } + }, { "type": "Lua", "args": { diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_LayerEnable.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_LayerEnable.lua new file mode 100644 index 0000000000..f60aac6149 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_LayerEnable.lua @@ -0,0 +1,49 @@ +-------------------------------------------------------------------------------------- +-- +-- All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +-- its licensors. +-- +-- For complete copyright and license terms please see the LICENSE at the root of this +-- distribution (the "License"). All use of this software is governed by the License, +-- or, if provided, by the license below or the license accompanying this file. Do not +-- remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- +-- +---------------------------------------------------------------------------------------------------- + +-- This functor hides the properties for disabled material layers. + +function GetMaterialPropertyDependencies() + return { + "blend.enableLayer2", + "blend.enableLayer3" + } +end + +function SetLayerVisibility(context, layerNamePrefix, isVisible) + + local visibility = MaterialPropertyGroupVisibility_Enabled + if(not isVisible) then + visibility = MaterialPropertyGroupVisibility_Hidden + end + + context:SetMaterialPropertyGroupVisibility(layerNamePrefix .. "baseColor", visibility) + context:SetMaterialPropertyGroupVisibility(layerNamePrefix .. "metallic", visibility) + context:SetMaterialPropertyGroupVisibility(layerNamePrefix .. "roughness", visibility) + context:SetMaterialPropertyGroupVisibility(layerNamePrefix .. "specularF0", visibility) + context:SetMaterialPropertyGroupVisibility(layerNamePrefix .. "normal", visibility) + context:SetMaterialPropertyGroupVisibility(layerNamePrefix .. "clearCoat", visibility) + context:SetMaterialPropertyGroupVisibility(layerNamePrefix .. "occlusion", visibility) + context:SetMaterialPropertyGroupVisibility(layerNamePrefix .. "emissive", visibility) + context:SetMaterialPropertyGroupVisibility(layerNamePrefix .. "parallax", visibility) + context:SetMaterialPropertyGroupVisibility(layerNamePrefix .. "uv", visibility) +end + +function ProcessEditor(context) + local enableLayer2 = context:GetMaterialPropertyValue_bool("blend.enableLayer2") + local enableLayer3 = context:GetMaterialPropertyValue_bool("blend.enableLayer3") + + SetLayerVisibility(context, "layer2_", context:GetMaterialPropertyValue_bool("blend.enableLayer2")) + SetLayerVisibility(context, "layer3_", context:GetMaterialPropertyValue_bool("blend.enableLayer3")) +end diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ParallaxPerLayer.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ParallaxPerLayer.lua index 119dfed436..bd56292229 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ParallaxPerLayer.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ParallaxPerLayer.lua @@ -24,7 +24,7 @@ end function Process(context) local enable = context:GetMaterialPropertyValue_bool("parallax.enable") - local textureMap = context:GetMaterialPropertyValue_image("parallax.textureMap") + local textureMap = context:GetMaterialPropertyValue_Image("parallax.textureMap") context:SetShaderOptionValue_bool("o_useDepthMap", enable and textureMap ~= nil) end @@ -37,7 +37,7 @@ function ProcessEditor(context) context:SetMaterialPropertyVisibility("parallax.textureMap", MaterialPropertyVisibility_Hidden) end - local textureMap = context:GetMaterialPropertyValue_image("parallax.textureMap") + local textureMap = context:GetMaterialPropertyValue_Image("parallax.textureMap") local visibility = MaterialPropertyVisibility_Enabled if(not enable or textureMap == nil) then visibility = MaterialPropertyVisibility_Hidden diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ClearCoatState.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ClearCoatState.lua index 8289291ef4..ffa00d7efd 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ClearCoatState.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ClearCoatState.lua @@ -34,7 +34,7 @@ function GetShaderOptionDependencies() end function UpdateUseTextureState(context, clearCoatEnabled, textureMapPropertyName, useTexturePropertyName, shaderOptionName) - local textureMap = context:GetMaterialPropertyValue_image(textureMapPropertyName) + local textureMap = context:GetMaterialPropertyValue_Image(textureMapPropertyName) local useTextureMap = context:GetMaterialPropertyValue_bool(useTexturePropertyName) context:SetShaderOptionValue_bool(shaderOptionName, clearCoatEnabled and useTextureMap and textureMap ~= nil) end @@ -50,7 +50,7 @@ end -- Note this logic matches that of the UseTextureFunctor class. function UpdateTextureDependentPropertyVisibility(context, textureMapPropertyName, useTexturePropertyName, uvPropertyName) - local textureMap = context:GetMaterialPropertyValue_image(textureMapPropertyName) + local textureMap = context:GetMaterialPropertyValue_Image(textureMapPropertyName) local useTexture = context:GetMaterialPropertyValue_bool(useTexturePropertyName) if(textureMap == nil) then diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_EmissiveState.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_EmissiveState.lua index a8ac2e8a4a..7ee5876adb 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_EmissiveState.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_EmissiveState.lua @@ -22,7 +22,7 @@ end function Process(context) local enable = context:GetMaterialPropertyValue_bool("emissive.enable") - local textureMap = context:GetMaterialPropertyValue_image("emissive.textureMap") + local textureMap = context:GetMaterialPropertyValue_Image("emissive.textureMap") local useTextureMap = context:GetMaterialPropertyValue_bool("emissive.useTexture") context:SetShaderOptionValue_bool("o_emissiveEnabled", enable) @@ -47,7 +47,7 @@ function ProcessEditor(context) context:SetMaterialPropertyVisibility("emissive.textureMapUv", mainVisibility) if(enable) then - local textureMap = context:GetMaterialPropertyValue_image("emissive.textureMap") + local textureMap = context:GetMaterialPropertyValue_Image("emissive.textureMap") local useTextureMap = context:GetMaterialPropertyValue_bool("emissive.useTexture") if(textureMap == nil) then diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_HandleOpacityMode.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_HandleOpacityMode.lua index 6cc595d712..541b1ac1ce 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_HandleOpacityMode.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_HandleOpacityMode.lua @@ -90,7 +90,7 @@ function ProcessEditor(context) context:SetMaterialPropertyVisibility("opacity.textureMap", MaterialPropertyVisibility_Hidden) context:SetMaterialPropertyVisibility("opacity.textureMapUv", MaterialPropertyVisibility_Hidden) else - local textureMap = context:GetMaterialPropertyValue_image("opacity.textureMap") + local textureMap = context:GetMaterialPropertyValue_Image("opacity.textureMap") if(nil == textureMap) then context:SetMaterialPropertyVisibility("opacity.textureMapUv", MaterialPropertyVisibility_Disabled) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ParallaxState.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ParallaxState.lua index 0287e1105e..e6689da327 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ParallaxState.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ParallaxState.lua @@ -22,7 +22,7 @@ end function Process(context) local enable = context:GetMaterialPropertyValue_bool("parallax.enable") - local textureMap = context:GetMaterialPropertyValue_image("parallax.textureMap") + local textureMap = context:GetMaterialPropertyValue_Image("parallax.textureMap") context:SetShaderOptionValue_bool("o_parallax_feature_enabled", enable) context:SetShaderOptionValue_bool("o_useDepthMap", enable and textureMap ~= nil) end @@ -36,7 +36,7 @@ function ProcessEditor(context) context:SetMaterialPropertyVisibility("parallax.textureMap", MaterialPropertyVisibility_Hidden) end - local textureMap = context:GetMaterialPropertyValue_image("parallax.textureMap") + local textureMap = context:GetMaterialPropertyValue_Image("parallax.textureMap") local visibility = MaterialPropertyVisibility_Enabled if(not enable or textureMap == nil) then visibility = MaterialPropertyVisibility_Hidden diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Roughness.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Roughness.lua index 4887bc47e8..222e69cd3d 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Roughness.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Roughness.lua @@ -21,13 +21,13 @@ function GetShaderOptionDependencies() end function Process(context) - local textureMap = context:GetMaterialPropertyValue_image("roughness.textureMap") + local textureMap = context:GetMaterialPropertyValue_Image("roughness.textureMap") local useTexture = context:GetMaterialPropertyValue_bool("roughness.useTexture") context:SetShaderOptionValue_bool("o_roughness_useTexture", useTexture and textureMap ~= nil) end function ProcessEditor(context) - local textureMap = context:GetMaterialPropertyValue_image("roughness.textureMap") + local textureMap = context:GetMaterialPropertyValue_Image("roughness.textureMap") local useTexture = context:GetMaterialPropertyValue_bool("roughness.useTexture") if(nil == textureMap) then diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_SubsurfaceState.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_SubsurfaceState.lua index fb07ac89a3..d8a69ba355 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_SubsurfaceState.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_SubsurfaceState.lua @@ -35,7 +35,7 @@ TransmissionMode_ThickObject = 1 TransmissionMode_ThinObject = 2 function UpdateUseTextureState(context, subsurfaceScatteringEnabled, textureMapPropertyName, useTexturePropertyName, shaderOptionName) - local textureMap = context:GetMaterialPropertyValue_image(textureMapPropertyName) + local textureMap = context:GetMaterialPropertyValue_Image(textureMapPropertyName) local useTextureMap = context:GetMaterialPropertyValue_bool(useTexturePropertyName) context:SetShaderOptionValue_bool(shaderOptionName, subsurfaceScatteringEnabled and useTextureMap and textureMap ~= nil) end @@ -53,7 +53,7 @@ function UpdateTextureDependentPropertyVisibility(context, featureEnabled, textu context:SetMaterialPropertyVisibility(useTexturePropertyName, MaterialPropertyVisibility_Hidden) context:SetMaterialPropertyVisibility(uvPropertyName, MaterialPropertyVisibility_Hidden) else - local textureMap = context:GetMaterialPropertyValue_image(textureMapPropertyName) + local textureMap = context:GetMaterialPropertyValue_Image(textureMapPropertyName) local useTextureMap = context:GetMaterialPropertyValue_bool(useTexturePropertyName) if(textureMap == nil) then diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/LuaMaterialFunctor.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/LuaMaterialFunctor.h index 44de79ff22..396ba14810 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/LuaMaterialFunctor.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/LuaMaterialFunctor.h @@ -329,6 +329,8 @@ namespace AZ bool SetMaterialPropertySoftMaxValue(const char* name, Type value); bool SetMaterialPropertyDescription(const char* name, const char* description); + + bool SetMaterialPropertyGroupVisibility(const char* name, MaterialPropertyGroupVisibility visibility); private: diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialDynamicMetadata.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialDynamicMetadata.h new file mode 100644 index 0000000000..b74e330665 --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialDynamicMetadata.h @@ -0,0 +1,100 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#include +#include +#include + +namespace AZ +{ + namespace RPI + { + // Normally we wouldn't want editor-related code mixed in with runtime code, but + // since this data can be modified dynamically, keeping it in the runtime makes + // the overall material functor design simpler and more user-friendly. + + + //! Visibility for each material property. + //! If the data field is empty, use default as editable. + enum class MaterialPropertyVisibility : uint32_t + { + Enabled, //!< The property is visible and editable + Disabled, //!< The property is visible but non-editable + Hidden, //!< The property is invisible + + Default = Enabled + }; + + struct MaterialPropertyRange + { + MaterialPropertyRange() = default; + MaterialPropertyRange( + const MaterialPropertyValue& max, + const MaterialPropertyValue& min, + const MaterialPropertyValue& softMax, + const MaterialPropertyValue& softMin + ) + : m_max(max) + , m_min(min) + , m_softMax(softMax) + , m_softMin(softMin) + {} + + MaterialPropertyValue m_max; + MaterialPropertyValue m_min; + MaterialPropertyValue m_softMax; + MaterialPropertyValue m_softMin; + }; + + //! Used by material functors to dynamically control property metadata in tools. + //! For example, show/hide a property based on some other 'enable' flag property. + struct MaterialPropertyDynamicMetadata + { + AZ_TYPE_INFO(MaterialPropertyDynamicMetadata, "{A89F215F-3235-499F-896C-9E63ACC1D657}"); + + AZ::RPI::MaterialPropertyVisibility m_visibility; + AZStd::string m_description; + AZ::RPI::MaterialPropertyRange m_propertyRange; + }; + + //! Visibility for each material property group. + enum class MaterialPropertyGroupVisibility : uint32_t + { + // Note it's helpful to keep these values aligned with MaterialPropertyVisibility in part because in lua it would be easy to accidentally use + // MaterialPropertyVisibility instead of MaterialPropertyGroupVisibility resulting in sneaky bugs. Also, if the enums end up being the same in + // the future, we could just merge them into one. + + Enabled, //!< The property is visible and editable + //Disabled, //!< The property is visible but non-editable (reserved for possible future use, to match MaterialPropertyVisibility) + Hidden=2, //!< The property is invisible + + Default = Enabled + }; + + //! Used by material functors to dynamically control property group metadata in tools. + //! For example, show/hide an entire property group based on some 'enable' flag property. + struct MaterialPropertyGroupDynamicMetadata + { + AZ_TYPE_INFO(MaterialPropertyGroupDynamicMetadata, "{F94009F7-48A3-4CE0-AF64-D5A86890ACD4}"); + + AZ::RPI::MaterialPropertyGroupVisibility m_visibility; + }; + + void ReflectMaterialDynamicMetadata(ReflectContext* context); + + } // namespace RPI + + AZ_TYPE_INFO_SPECIALIZE(RPI::MaterialPropertyVisibility, "{318B43A2-79E3-4502-8FD0-5815209EA123}"); + AZ_TYPE_INFO_SPECIALIZE(RPI::MaterialPropertyGroupVisibility, "{B803958B-DE64-4FBF-AC00-CF781611BE37}"); +} // namespace AZ + diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialFunctor.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialFunctor.h index d17c7634a2..6b9b34aa1b 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialFunctor.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialFunctor.h @@ -18,6 +18,7 @@ #include #include #include +#include namespace AZ { @@ -147,6 +148,8 @@ namespace AZ public: const MaterialPropertyDynamicMetadata* GetMaterialPropertyMetadata(const Name& propertyName) const; const MaterialPropertyDynamicMetadata* GetMaterialPropertyMetadata(const MaterialPropertyIndex& index) const; + + const MaterialPropertyGroupDynamicMetadata* GetMaterialPropertyGroupMetadata(const Name& propertyName) const; //! Get the property value. The type must be one of those in MaterialPropertyValue. //! Otherwise, a compile error will be reported. @@ -178,6 +181,8 @@ namespace AZ bool SetMaterialPropertySoftMaxValue(const Name& propertyName, const MaterialPropertyValue& max); bool SetMaterialPropertySoftMaxValue(const MaterialPropertyIndex& index, const MaterialPropertyValue& max); + + bool SetMaterialPropertyGroupVisibility(const Name& propertyGroupName, MaterialPropertyGroupVisibility visibility); // [GFX TODO][ATOM-4168] Replace the workaround for unlink-able RPI.Public classes in MaterialFunctor // const AZStd::vector&, AZStd::unordered_map&, RHI::ConstPtr @@ -185,18 +190,23 @@ namespace AZ EditorContext( const AZStd::vector& propertyValues, RHI::ConstPtr materialPropertiesLayout, - AZStd::unordered_map& metadata, - AZStd::unordered_set& outChangedProperties, + AZStd::unordered_map& propertyMetadata, + AZStd::unordered_map& propertyGroupMetadata, + AZStd::unordered_set& updatedPropertiesOut, + AZStd::unordered_set& updatedPropertyGroupsOut, const MaterialPropertyFlags* materialPropertyDependencies ); private: - AZStd::list_iterator> QueryMaterialMetadata(const Name& propertyName) const; + AZStd::list_iterator> QueryMaterialPropertyMetadata(const Name& propertyName) const; + AZStd::list_iterator> QueryMaterialPropertyGroupMetadata(const Name& propertyGroupName) const; const AZStd::vector& m_materialPropertyValues; RHI::ConstPtr m_materialPropertiesLayout; - AZStd::unordered_map& m_metadata; - AZStd::unordered_set& m_outChangedProperties; + AZStd::unordered_map& m_propertyMetadata; + AZStd::unordered_map& m_propertyGroupMetadata; + AZStd::unordered_set& m_updatedPropertiesOut; + AZStd::unordered_set& m_updatedPropertyGroupsOut; const MaterialPropertyFlags* m_materialPropertyDependencies = nullptr; }; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h index d73ce2334b..fab72d5f96 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h @@ -81,52 +81,6 @@ namespace AZ AZStd::string GetMaterialPropertyDataTypeString(AZ::TypeId typeId); - //! Visibility for each material property. - //! If the data field is empty, use default as editable. - enum class MaterialPropertyVisibility : uint32_t - { - Enabled, //< The property is visible and editable - Disabled, //< The property is visible but non-editable - Hidden, //< The property is invisible - - Default = Enabled - }; - - struct MaterialPropertyRange - { - MaterialPropertyRange() = default; - MaterialPropertyRange( - const MaterialPropertyValue& max, - const MaterialPropertyValue& min, - const MaterialPropertyValue& softMax, - const MaterialPropertyValue& softMin - ) - : m_max(max) - , m_min(min) - , m_softMax(softMax) - , m_softMin(softMin) - {} - - MaterialPropertyValue m_max; - MaterialPropertyValue m_min; - MaterialPropertyValue m_softMax; - MaterialPropertyValue m_softMin; - }; - - //! Used by material functors to dynamically control property metadata in tools. - //! For example, show/hide a property based on some other 'enable' flag property. - //! Normally we wouldn't want editor-related code mixed in with runtime code, but - //! since this data can be modified dynamically, keeping it in the runtime makes - //! the overall material functor design simpler and more user-friendly. - struct MaterialPropertyDynamicMetadata - { - AZ_TYPE_INFO(MaterialPropertyDynamicMetadata, "{A89F215F-3235-499F-896C-9E63ACC1D657}"); - - AZ::RPI::MaterialPropertyVisibility m_visibility; - AZStd::string m_description; - AZ::RPI::MaterialPropertyRange m_propertyRange; - }; - //! A material property is any data input to a material, like a bool, float, Vector, Image, Buffer, etc. //! This descriptor defines a single input property, including it's name ID, and how it maps //! to the shader system. @@ -171,7 +125,6 @@ namespace AZ } // namespace RPI AZ_TYPE_INFO_SPECIALIZE(RPI::MaterialPropertyOutputType, "{42A6E5E8-0FE6-4D7B-884A-1F478E4ADD97}"); - AZ_TYPE_INFO_SPECIALIZE(RPI::MaterialPropertyVisibility, "{318B43A2-79E3-4502-8FD0-5815209EA123}"); AZ_TYPE_INFO_SPECIALIZE(RPI::MaterialPropertyDataType, "{3D903D5C-C6AA-452E-A2F8-8948D30833FF}"); } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Material/MaterialSystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Material/MaterialSystem.cpp index e7e04b271d..f1751a02e2 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Material/MaterialSystem.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Material/MaterialSystem.cpp @@ -32,6 +32,7 @@ namespace AZ MaterialPropertiesLayout::Reflect(context); MaterialFunctor::Reflect(context); LuaMaterialFunctor::Reflect(context); + ReflectMaterialDynamicMetadata(context); } void MaterialSystem::GetAssetHandlers(AssetHandlerPtrList& assetHandlers) diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/LuaMaterialFunctor.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/LuaMaterialFunctor.cpp index 7ab3489666..7db5f12560 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/LuaMaterialFunctor.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/LuaMaterialFunctor.cpp @@ -63,6 +63,7 @@ namespace AZ behaviorContext->Class(); MaterialPropertyDescriptor::Reflect(behaviorContext); + ReflectMaterialDynamicMetadata(behaviorContext); LuaMaterialFunctorRenderStates::Reflect(behaviorContext); LuaMaterialFunctorShaderItem::Reflect(behaviorContext); @@ -255,7 +256,7 @@ namespace AZ // Specialize for type Image* because that will be more intuitive within Lua. // The script can then check the result for nil without calling "get()". - // For example, "GetMaterialPropertyValue_image(name) == nil" rather than "GetMaterialPropertyValue_image(name):get() == nil" + // For example, "GetMaterialPropertyValue_Image(name) == nil" rather than "GetMaterialPropertyValue_Image(name):get() == nil" template<> Image* LuaMaterialFunctorCommonContext::GetMaterialPropertyValue(const char* name) const { @@ -278,7 +279,7 @@ namespace AZ ->Method("GetMaterialPropertyValue_Vector3", &LuaMaterialFunctorRuntimeContext::GetMaterialPropertyValue) ->Method("GetMaterialPropertyValue_Vector4", &LuaMaterialFunctorRuntimeContext::GetMaterialPropertyValue) ->Method("GetMaterialPropertyValue_Color", &LuaMaterialFunctorRuntimeContext::GetMaterialPropertyValue) - ->Method("GetMaterialPropertyValue_image", &LuaMaterialFunctorRuntimeContext::GetMaterialPropertyValue) + ->Method("GetMaterialPropertyValue_Image", &LuaMaterialFunctorRuntimeContext::GetMaterialPropertyValue) ->Method("SetShaderConstant_bool", &LuaMaterialFunctorRuntimeContext::SetShaderConstant) ->Method("SetShaderConstant_int", &LuaMaterialFunctorRuntimeContext::SetShaderConstant) ->Method("SetShaderConstant_uint", &LuaMaterialFunctorRuntimeContext::SetShaderConstant) @@ -436,7 +437,7 @@ namespace AZ ->Method("GetMaterialPropertyValue_Vector3", &LuaMaterialFunctorEditorContext::GetMaterialPropertyValue) ->Method("GetMaterialPropertyValue_Vector4", &LuaMaterialFunctorEditorContext::GetMaterialPropertyValue) ->Method("GetMaterialPropertyValue_Color", &LuaMaterialFunctorEditorContext::GetMaterialPropertyValue) - ->Method("GetMaterialPropertyValue_image", &LuaMaterialFunctorEditorContext::GetMaterialPropertyValue) + ->Method("GetMaterialPropertyValue_Image", &LuaMaterialFunctorEditorContext::GetMaterialPropertyValue) ->Method("SetMaterialPropertyVisibility", &LuaMaterialFunctorEditorContext::SetMaterialPropertyVisibility) ->Method("SetMaterialPropertyDescription", &LuaMaterialFunctorEditorContext::SetMaterialPropertyDescription) ->Method("SetMaterialPropertyMinValue_int", &LuaMaterialFunctorEditorContext::SetMaterialPropertyMinValue) @@ -451,6 +452,7 @@ namespace AZ ->Method("SetMaterialPropertySoftMaxValue_int", &LuaMaterialFunctorEditorContext::SetMaterialPropertySoftMaxValue) ->Method("SetMaterialPropertySoftMaxValue_uint", &LuaMaterialFunctorEditorContext::SetMaterialPropertySoftMaxValue) ->Method("SetMaterialPropertySoftMaxValue_float", &LuaMaterialFunctorEditorContext::SetMaterialPropertySoftMaxValue) + ->Method("SetMaterialPropertyGroupVisibility", &LuaMaterialFunctorEditorContext::SetMaterialPropertyGroupVisibility) ; } @@ -524,6 +526,15 @@ namespace AZ return m_editorContextImpl->SetMaterialPropertySoftMaxValue(index, value); } + + bool LuaMaterialFunctorEditorContext::SetMaterialPropertyGroupVisibility(const char* name, MaterialPropertyGroupVisibility visibility) + { + if (m_editorContextImpl) + { + return m_editorContextImpl->SetMaterialPropertyGroupVisibility(Name{m_propertyNamePrefix + name}, visibility); + } + return false; + } bool LuaMaterialFunctorEditorContext::SetMaterialPropertyVisibility(const char* name, MaterialPropertyVisibility visibility) { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialDynamicMetadata.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialDynamicMetadata.cpp new file mode 100644 index 0000000000..5c8f4ccc02 --- /dev/null +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialDynamicMetadata.cpp @@ -0,0 +1,50 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include + +namespace AZ +{ + namespace RPI + { + void ReflectMaterialDynamicMetadata(ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Enum() + ->Value("Enabled", MaterialPropertyVisibility::Enabled) + ->Value("Disabled", MaterialPropertyVisibility::Disabled) + ->Value("Hidden", MaterialPropertyVisibility::Hidden) + ; + + serializeContext->Enum() + ->Value("Enabled", MaterialPropertyGroupVisibility::Enabled) + ->Value("Hidden", MaterialPropertyGroupVisibility::Hidden) + ; + } + + if (auto* behaviorContext = azrtti_cast(context)) + { + behaviorContext + ->Enum<(int)MaterialPropertyVisibility::Enabled>("MaterialPropertyVisibility_Enabled") + ->Enum<(int)MaterialPropertyVisibility::Disabled>("MaterialPropertyVisibility_Disabled") + ->Enum<(int)MaterialPropertyVisibility::Hidden>("MaterialPropertyVisibility_Hidden"); + + behaviorContext + ->Enum<(int)MaterialPropertyGroupVisibility::Enabled>("MaterialPropertyGroupVisibility_Enabled") + ->Enum<(int)MaterialPropertyGroupVisibility::Hidden>("MaterialPropertyGroupVisibility_Hidden"); + } + } + + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialFunctor.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialFunctor.cpp index 32772e9dd5..ab18a1fb66 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialFunctor.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialFunctor.cpp @@ -142,21 +142,25 @@ namespace AZ MaterialFunctor::EditorContext::EditorContext( const AZStd::vector& propertyValues, RHI::ConstPtr materialPropertiesLayout, - AZStd::unordered_map& metadata, - AZStd::unordered_set& outChangedProperties, + AZStd::unordered_map& propertyMetadata, + AZStd::unordered_map& propertyGroupMetadata, + AZStd::unordered_set& updatedPropertiesOut, + AZStd::unordered_set& updatedPropertyGroupsOut, const MaterialPropertyFlags* materialPropertyDependencies ) : m_materialPropertyValues(propertyValues) , m_materialPropertiesLayout(materialPropertiesLayout) - , m_metadata(metadata) - , m_outChangedProperties(outChangedProperties) + , m_propertyMetadata(propertyMetadata) + , m_propertyGroupMetadata(propertyGroupMetadata) + , m_updatedPropertiesOut(updatedPropertiesOut) + , m_updatedPropertyGroupsOut(updatedPropertyGroupsOut) , m_materialPropertyDependencies(materialPropertyDependencies) {} const MaterialPropertyDynamicMetadata* MaterialFunctor::EditorContext::GetMaterialPropertyMetadata(const Name& propertyName) const { - auto it = QueryMaterialMetadata(propertyName); - if (it == m_metadata.end()) + auto it = QueryMaterialPropertyMetadata(propertyName); + if (it == m_propertyMetadata.end()) { return nullptr; } @@ -168,11 +172,38 @@ namespace AZ const Name& name = m_materialPropertiesLayout->GetPropertyDescriptor(index)->GetName(); return GetMaterialPropertyMetadata(name); } + + const MaterialPropertyGroupDynamicMetadata* MaterialFunctor::EditorContext::GetMaterialPropertyGroupMetadata(const Name& propertyName) const + { + auto it = QueryMaterialPropertyGroupMetadata(propertyName); + if (it == m_propertyGroupMetadata.end()) + { + return nullptr; + } + return &(it->second); + } + + bool MaterialFunctor::EditorContext::SetMaterialPropertyGroupVisibility(const Name& propertyGroupName, MaterialPropertyGroupVisibility visibility) + { + auto it = QueryMaterialPropertyGroupMetadata(propertyGroupName); + if (it == m_propertyGroupMetadata.end()) + { + return false; + } + MaterialPropertyGroupVisibility originValue = it->second.m_visibility; + it->second.m_visibility = visibility; + if (originValue != visibility) + { + m_updatedPropertyGroupsOut.insert(propertyGroupName); + } + + return true; + } bool MaterialFunctor::EditorContext::SetMaterialPropertyVisibility(const Name& propertyName, MaterialPropertyVisibility visibility) { - auto it = QueryMaterialMetadata(propertyName); - if (it == m_metadata.end()) + auto it = QueryMaterialPropertyMetadata(propertyName); + if (it == m_propertyMetadata.end()) { return false; } @@ -180,7 +211,7 @@ namespace AZ it->second.m_visibility = visibility; if (originValue != visibility) { - m_outChangedProperties.insert(propertyName); + m_updatedPropertiesOut.insert(propertyName); } return true; @@ -194,8 +225,8 @@ namespace AZ bool MaterialFunctor::EditorContext::SetMaterialPropertyDescription(const Name& propertyName, AZStd::string description) { - auto it = QueryMaterialMetadata(propertyName); - if (it == m_metadata.end()) + auto it = QueryMaterialPropertyMetadata(propertyName); + if (it == m_propertyMetadata.end()) { return false; } @@ -204,7 +235,7 @@ namespace AZ it->second.m_description = description; if (origin != description) { - m_outChangedProperties.insert(propertyName); + m_updatedPropertiesOut.insert(propertyName); } return true; @@ -218,8 +249,8 @@ namespace AZ bool MaterialFunctor::EditorContext::SetMaterialPropertyMinValue(const Name& propertyName, const MaterialPropertyValue& min) { - auto it = QueryMaterialMetadata(propertyName); - if (it == m_metadata.end()) + auto it = QueryMaterialPropertyMetadata(propertyName); + if (it == m_propertyMetadata.end()) { return false; } @@ -229,7 +260,7 @@ namespace AZ if(origin != min) { - m_outChangedProperties.insert(propertyName); + m_updatedPropertiesOut.insert(propertyName); } return true; @@ -243,8 +274,8 @@ namespace AZ bool MaterialFunctor::EditorContext::SetMaterialPropertyMaxValue(const Name& propertyName, const MaterialPropertyValue& max) { - auto it = QueryMaterialMetadata(propertyName); - if (it == m_metadata.end()) + auto it = QueryMaterialPropertyMetadata(propertyName); + if (it == m_propertyMetadata.end()) { return false; } @@ -254,7 +285,7 @@ namespace AZ if (origin != max) { - m_outChangedProperties.insert(propertyName); + m_updatedPropertiesOut.insert(propertyName); } return true; @@ -268,8 +299,8 @@ namespace AZ bool MaterialFunctor::EditorContext::SetMaterialPropertySoftMinValue(const Name& propertyName, const MaterialPropertyValue& min) { - auto it = QueryMaterialMetadata(propertyName); - if (it == m_metadata.end()) + auto it = QueryMaterialPropertyMetadata(propertyName); + if (it == m_propertyMetadata.end()) { return false; } @@ -279,7 +310,7 @@ namespace AZ if (origin != min) { - m_outChangedProperties.insert(propertyName); + m_updatedPropertiesOut.insert(propertyName); } return true; @@ -293,8 +324,8 @@ namespace AZ bool MaterialFunctor::EditorContext::SetMaterialPropertySoftMaxValue(const Name& propertyName, const MaterialPropertyValue& max) { - auto it = QueryMaterialMetadata(propertyName); - if (it == m_metadata.end()) + auto it = QueryMaterialPropertyMetadata(propertyName); + if (it == m_propertyMetadata.end()) { return false; } @@ -304,7 +335,7 @@ namespace AZ if (origin != max) { - m_outChangedProperties.insert(propertyName); + m_updatedPropertiesOut.insert(propertyName); } return true; @@ -316,16 +347,27 @@ namespace AZ return SetMaterialPropertySoftMaxValue(name, max); } - AZStd::list_iterator> MaterialFunctor::EditorContext::QueryMaterialMetadata(const Name& propertyName) const + AZStd::list_iterator> MaterialFunctor::EditorContext::QueryMaterialPropertyMetadata(const Name& propertyName) const { - auto it = m_metadata.find(propertyName); - if (it == m_metadata.end()) + auto it = m_propertyMetadata.find(propertyName); + if (it == m_propertyMetadata.end()) { AZ_Error("MaterialFunctor", false, "Couldn't find metadata for material property: %s.", propertyName.GetCStr()); } return it; } + + AZStd::list_iterator> MaterialFunctor::EditorContext::QueryMaterialPropertyGroupMetadata(const Name& propertyGroupName) const + { + auto it = m_propertyGroupMetadata.find(propertyGroupName); + if (it == m_propertyGroupMetadata.end()) + { + AZ_Error("MaterialFunctor", false, "Couldn't find metadata for material property group: %s.", propertyGroupName.GetCStr()); + } + + return it; + } template const Type& MaterialFunctor::RuntimeContext::GetMaterialPropertyValue(const MaterialPropertyIndex& index) const diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialPropertyDescriptor.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialPropertyDescriptor.cpp index 367d95357b..72d658db63 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialPropertyDescriptor.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialPropertyDescriptor.cpp @@ -124,12 +124,6 @@ namespace AZ ->Value(ToString(MaterialPropertyOutputType::ShaderOption), MaterialPropertyOutputType::ShaderOption) ; - serializeContext->Enum() - ->Value("Enabled", MaterialPropertyVisibility::Enabled) - ->Value("Disabled", MaterialPropertyVisibility::Disabled) - ->Value("Hidden", MaterialPropertyVisibility::Hidden) - ; - serializeContext->Enum() ->Value(ToString(MaterialPropertyDataType::Invalid), MaterialPropertyDataType::Invalid) ->Value(ToString(MaterialPropertyDataType::Bool), MaterialPropertyDataType::Bool) @@ -153,14 +147,6 @@ namespace AZ ; } - if (auto* behaviorContext = azrtti_cast(context)) - { - behaviorContext - ->Enum<(int)MaterialPropertyVisibility::Enabled>("MaterialPropertyVisibility_Enabled") - ->Enum<(int)MaterialPropertyVisibility::Disabled>("MaterialPropertyVisibility_Disabled") - ->Enum<(int)MaterialPropertyVisibility::Hidden>("MaterialPropertyVisibility_Hidden"); - } - MaterialPropertyIndex::Reflect(context); } diff --git a/Gems/Atom/RPI/Code/Tests/Material/LuaMaterialFunctorTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/LuaMaterialFunctorTests.cpp index 76fa6bfb0e..3dcbdfce07 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/LuaMaterialFunctorTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/LuaMaterialFunctorTests.cpp @@ -748,12 +748,15 @@ namespace UnitTest MaterialPropertyDataType::UInt, "general.mode", MaterialPropertyDataType::Float, "general.value", functorScript); - + AZStd::unordered_set changedPropertyNames; - AZStd::unordered_map propertyDynamicMetadata; propertyDynamicMetadata[Name{"general.mode"}] = {}; propertyDynamicMetadata[Name{"general.value"}] = {}; + + AZStd::unordered_set changedPropertyGroupNames; + AZStd::unordered_map propertyGroupDynamicMetadata; + propertyGroupDynamicMetadata[Name{"general"}] = {}; Ptr functor = testData.GetMaterialTypeAsset()->GetMaterialFunctors()[0]; @@ -761,7 +764,9 @@ namespace UnitTest testData.GetMaterial()->GetPropertyValues(), testData.GetMaterial()->GetMaterialPropertiesLayout(), propertyDynamicMetadata, + propertyGroupDynamicMetadata, changedPropertyNames, + changedPropertyGroupNames, &functor->GetMaterialPropertyDependencies() ); @@ -814,10 +819,13 @@ namespace UnitTest functorScript); AZStd::unordered_set changedPropertyNames; - AZStd::unordered_map propertyDynamicMetadata; propertyDynamicMetadata[Name{"general.units"}] = {}; propertyDynamicMetadata[Name{"general.distance"}] = {}; + + AZStd::unordered_set changedPropertyGroupNames; + AZStd::unordered_map propertyGroupDynamicMetadata; + propertyGroupDynamicMetadata[Name{"general"}] = {}; Ptr functor = testData.GetMaterialTypeAsset()->GetMaterialFunctors()[0]; @@ -825,7 +833,9 @@ namespace UnitTest testData.GetMaterial()->GetPropertyValues(), testData.GetMaterial()->GetMaterialPropertiesLayout(), propertyDynamicMetadata, + propertyGroupDynamicMetadata, changedPropertyNames, + changedPropertyGroupNames, &functor->GetMaterialPropertyDependencies() ); @@ -845,6 +855,66 @@ namespace UnitTest EXPECT_EQ(-100.0f, propertyDynamicMetadata[Name{"general.distance"}].m_propertyRange.m_softMin.GetValue()); EXPECT_EQ(100.0f, propertyDynamicMetadata[Name{"general.distance"}].m_propertyRange.m_softMax.GetValue()); } + + TEST_F(LuaMaterialFunctorTests, LuaMaterialFunctor_EditorContext_SetMaterialPropertyGroupVisibility) + { + using namespace AZ::RPI; + + const char* functorScript = + R"( + function GetMaterialPropertyDependencies() + return { "general.mode" } + end + + function ProcessEditor(context) + local mode = context:GetMaterialPropertyValue_uint("general.mode") + + if (mode == 1) then + context:SetMaterialPropertyGroupVisibility("otherGroup", MaterialPropertyGroupVisibility_Enabled) + else + context:SetMaterialPropertyGroupVisibility("otherGroup", MaterialPropertyGroupVisibility_Hidden) + end + end + )"; + + TestMaterialData testData; + testData.Setup( + MaterialPropertyDataType::UInt, "general.mode", + MaterialPropertyDataType::Float, "otherGroup.value", + functorScript); + + AZStd::unordered_set changedPropertyNames; + AZStd::unordered_map propertyDynamicMetadata; + propertyDynamicMetadata[Name{"general.mode"}] = {}; + propertyDynamicMetadata[Name{"otherGroup.value"}] = {}; + + AZStd::unordered_set changedPropertyGroupNames; + AZStd::unordered_map propertyGroupDynamicMetadata; + propertyGroupDynamicMetadata[Name{"general"}] = {}; + propertyGroupDynamicMetadata[Name{"otherGroup"}] = {}; + + Ptr functor = testData.GetMaterialTypeAsset()->GetMaterialFunctors()[0]; + + AZ::RPI::MaterialFunctor::EditorContext context = AZ::RPI::MaterialFunctor::EditorContext( + testData.GetMaterial()->GetPropertyValues(), + testData.GetMaterial()->GetMaterialPropertiesLayout(), + propertyDynamicMetadata, + propertyGroupDynamicMetadata, + changedPropertyNames, + changedPropertyGroupNames, + &functor->GetMaterialPropertyDependencies() + ); + + testData.GetMaterial()->SetPropertyValue(testData.GetMaterialPropertyIndex(), MaterialPropertyValue{0u}); + functor->Process(context); + EXPECT_EQ(MaterialPropertyGroupVisibility::Enabled, propertyGroupDynamicMetadata[Name{"general"}].m_visibility); + EXPECT_EQ(MaterialPropertyGroupVisibility::Hidden, propertyGroupDynamicMetadata[Name{"otherGroup"}].m_visibility); + + testData.GetMaterial()->SetPropertyValue(testData.GetMaterialPropertyIndex(), MaterialPropertyValue{1u}); + functor->Process(context); + EXPECT_EQ(MaterialPropertyGroupVisibility::Enabled, propertyGroupDynamicMetadata[Name{"general"}].m_visibility); + EXPECT_EQ(MaterialPropertyGroupVisibility::Enabled, propertyGroupDynamicMetadata[Name{"otherGroup"}].m_visibility); + } TEST_F(LuaMaterialFunctorTests, LuaMaterialFunctor_RuntimeContext_SetRenderStates) { diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertySerializerTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertySerializerTests.cpp index 5a81ebb74d..d32c285780 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertySerializerTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialPropertySerializerTests.cpp @@ -29,6 +29,7 @@ namespace JsonSerializationTests { AZ::RPI::MaterialTypeSourceData::Reflect(context.get()); AZ::RPI::MaterialPropertyDescriptor::Reflect(context.get()); + AZ::RPI::ReflectMaterialDynamicMetadata(context.get()); } void Reflect(AZStd::unique_ptr& context) diff --git a/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake b/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake index d1db00aa34..307f3a046b 100644 --- a/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake +++ b/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake @@ -55,6 +55,7 @@ set(FILES Include/Atom/RPI.Reflect/Material/MaterialAsset.h Include/Atom/RPI.Reflect/Material/MaterialAssetCreatorCommon.h Include/Atom/RPI.Reflect/Material/MaterialAssetCreator.h + Include/Atom/RPI.Reflect/Material/MaterialDynamicMetadata.h Include/Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h Include/Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h Include/Atom/RPI.Reflect/Material/MaterialPropertyValue.h @@ -135,6 +136,7 @@ set(FILES Source/RPI.Reflect/Material/MaterialAssetCreatorCommon.cpp Source/RPI.Reflect/Material/MaterialAssetCreator.cpp Source/RPI.Reflect/Material/LuaMaterialFunctor.cpp + Source/RPI.Reflect/Material/MaterialDynamicMetadata.cpp Source/RPI.Reflect/Material/MaterialPropertyDescriptor.cpp Source/RPI.Reflect/Material/MaterialPropertiesLayout.cpp Source/RPI.Reflect/Material/MaterialTypeAsset.cpp diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/DynamicProperty/DynamicProperty.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/DynamicProperty/DynamicProperty.h index b17a25a675..17ca1c8d04 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/DynamicProperty/DynamicProperty.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/DynamicProperty/DynamicProperty.h @@ -38,8 +38,8 @@ namespace AtomToolsFramework Count }; - // Configures the initial state, data type, attributes, and values that describe - // the dynamic property and how it is presented + //! Configures the initial state, data type, attributes, and values that describe + //! the dynamic property and how it is presented struct DynamicPropertyConfig { AZ_TYPE_INFO(DynamicPropertyConfig, "{9CA40E92-7F03-42BE-B6AA-51F30EE5796C}"); @@ -98,7 +98,7 @@ namespace AtomToolsFramework //! Returns true if the property has a valid value. bool IsValid() const; - //! Returns the ID of the property. + //! Returns the ID of the property. const AZ::Name GetId() const; //! Returns the current property visibility. diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorRequestBus.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorRequestBus.h index 81b512faf2..0403c06f87 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorRequestBus.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorRequestBus.h @@ -44,6 +44,12 @@ namespace AtomToolsFramework const AZStd::string& groupDescription, QWidget* groupWidget) = 0; + //! Sets the visibility of a specific property group. This impacts both the header and the widget. + virtual void SetGroupVisible(const AZStd::string& groupNameId, bool visible) = 0; + + //! Returns the visibility of a specific property group. + virtual bool IsGroupVisible(const AZStd::string& groupNameId) const = 0; + //! Calls Refresh for a specific InspectorGroupWidget, allowing for non-destructive UI changes virtual void RefreshGroup(const AZStd::string& groupNameId) = 0; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorWidget.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorWidget.h index b0932b3b04..5559b51462 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorWidget.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorWidget.h @@ -57,6 +57,9 @@ namespace AtomToolsFramework const AZStd::string& groupDescription, QWidget* groupWidget) override; + void SetGroupVisible(const AZStd::string& groupNameId, bool visible) override; + bool IsGroupVisible(const AZStd::string& groupNameId) const override; + void RefreshGroup(const AZStd::string& groupNameId) override; void RebuildGroup(const AZStd::string& groupNameId) override; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp index ec5f9893bd..c1ccfb4616 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp @@ -69,6 +69,7 @@ namespace AtomToolsFramework InspectorGroupHeaderWidget* groupHeader = new InspectorGroupHeaderWidget(m_ui->m_propertyContent); groupHeader->setText(groupDisplayName.c_str()); groupHeader->setToolTip(groupDescription.c_str()); + groupHeader->setObjectName(groupNameId.c_str()); m_layout->addWidget(groupHeader); m_headers.push_back(groupHeader); @@ -81,6 +82,32 @@ namespace AtomToolsFramework OnHeaderClicked(event, groupHeader, groupWidget); }); } + + void InspectorWidget::SetGroupVisible(const AZStd::string& groupNameId, bool visible) + { + for (size_t i = 0; i < m_groups.size(); ++i) + { + if (m_groups[i]->objectName() == groupNameId.c_str()) + { + m_headers[i]->setVisible(visible); + m_groups[i]->setVisible(visible && m_headers[i]->IsExpanded()); + break; + } + } + } + + bool InspectorWidget::IsGroupVisible(const AZStd::string& groupNameId) const + { + for (auto& header : m_headers) + { + if (header->objectName() == groupNameId.c_str()) + { + return header->isVisible(); + } + } + + return false; + } void InspectorWidget::RefreshGroup(const AZStd::string& groupNameId) { diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentNotificationBus.h b/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentNotificationBus.h index 3c3c77a628..80e8054ec5 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentNotificationBus.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentNotificationBus.h @@ -18,6 +18,7 @@ #include #include +#include namespace MaterialEditor { @@ -77,6 +78,12 @@ namespace MaterialEditor //! @param documentId unique id of material document for which the notification is sent //! @param property object containing the property value and configuration that was modified virtual void OnDocumentPropertyConfigModified([[maybe_unused]] const AZ::Uuid& documentId, [[maybe_unused]] const AtomToolsFramework::DynamicProperty& property) {} + + //! Signal that the property group visibility has been changed. + //! @param documentId unique id of material document for which the notification is sent + //! @param groupId id of the group that changed + //! @param visible whether the property group is visible + virtual void OnDocumentPropertyGroupVisibilityChanged([[maybe_unused]] const AZ::Uuid& documentId, [[maybe_unused]] const AZ::Name& groupId, [[maybe_unused]] bool visible) {} }; using MaterialDocumentNotificationBus = AZ::EBus; diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentRequestBus.h b/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentRequestBus.h index dad21a6348..c71d500d8c 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentRequestBus.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentRequestBus.h @@ -19,6 +19,7 @@ #include #include +#include namespace AZ { @@ -67,6 +68,10 @@ namespace MaterialEditor //! Returns a property object //! If the document is not open or the id can't be found, an invalid property is returned. virtual const AtomToolsFramework::DynamicProperty& GetProperty(const AZ::Name& propertyFullName) const = 0; + + //! Returns whether a property group is visible + //! If the document is not open or the id can't be found, returns false. + virtual bool IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const = 0; //! Modify material property value virtual void SetPropertyValue(const AZ::Name& propertyFullName, const AZStd::any& value) = 0; diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp index 288530a4e4..fec70763dd 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp @@ -117,6 +117,24 @@ namespace MaterialEditor const AtomToolsFramework::DynamicProperty& property = it->second; return property; } + + bool MaterialDocument::IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const + { + if (!IsOpen()) + { + AZ_Error("MaterialDocument", false, "Material document is not open."); + return false; + } + + const auto it = m_propertyGroupVisibility.find(propertyGroupFullName); + if (it == m_propertyGroupVisibility.end()) + { + AZ_Error("MaterialDocument", false, "Material document property group could not be found: '%s'.", propertyGroupFullName.GetCStr()); + return false; + } + + return it->second; + } void MaterialDocument::SetPropertyValue(const AZ::Name& propertyFullName, const AZStd::any& value) { @@ -153,8 +171,12 @@ namespace MaterialEditor Recompile(); - AZStd::unordered_set changedPropertyNames = RunEditorMaterialFunctors(dirtyFlags); - for (const Name& changedPropertyName : changedPropertyNames) + EditorMaterialFunctorResult result = RunEditorMaterialFunctors(dirtyFlags); + for (const Name& changedPropertyGroupName : result.m_updatedPropertyGroups) + { + MaterialDocumentNotificationBus::Broadcast(&MaterialDocumentNotificationBus::Events::OnDocumentPropertyGroupVisibilityChanged, m_id, changedPropertyGroupName, IsPropertyGroupVisible(changedPropertyGroupName)); + } + for (const Name& changedPropertyName : result.m_updatedProperties) { MaterialDocumentNotificationBus::Broadcast(&MaterialDocumentNotificationBus::Events::OnDocumentPropertyConfigModified, m_id, GetProperty(changedPropertyName)); } @@ -782,6 +804,12 @@ namespace MaterialEditor return true; }); + // Populate the property group visibility map + for (MaterialTypeSourceData::GroupDefinition& group : m_materialTypeSourceData.GetGroupDefinitionsInDisplayOrder()) + { + m_propertyGroupVisibility[AZ::Name{group.m_nameId}] = true; + } + // Adding properties for material type and parent as part of making dynamic // properties and the inspector more general purpose. // This allows the read only properties to appear in the inspector like any @@ -914,16 +942,26 @@ namespace MaterialEditor } } - AZStd::unordered_set MaterialDocument::RunEditorMaterialFunctors(AZ::RPI::MaterialPropertyFlags dirtyFlags) + MaterialDocument::EditorMaterialFunctorResult MaterialDocument::RunEditorMaterialFunctors(AZ::RPI::MaterialPropertyFlags dirtyFlags) { - AZStd::unordered_set changedPropertyNames; + EditorMaterialFunctorResult result; + AZStd::unordered_map propertyDynamicMetadata; + AZStd::unordered_map propertyGroupDynamicMetadata; for (auto& propertyPair : m_properties) { AtomToolsFramework::DynamicProperty& property = propertyPair.second; AtomToolsFramework::ConvertToPropertyMetaData(propertyDynamicMetadata[property.GetId()], property.GetConfig()); } + for (auto& groupPair : m_propertyGroupVisibility) + { + AZ::RPI::MaterialPropertyGroupDynamicMetadata& metadata = propertyGroupDynamicMetadata[AZ::Name{groupPair.first}]; + bool visible = groupPair.second; + metadata.m_visibility = visible ? + AZ::RPI::MaterialPropertyGroupVisibility::Enabled : AZ::RPI::MaterialPropertyGroupVisibility::Hidden; + } + for (AZ::RPI::Ptr& functor : m_editorFunctors) { const AZ::RPI::MaterialPropertyFlags& materialPropertyDependencies = functor->GetMaterialPropertyDependencies(); @@ -935,7 +973,9 @@ namespace MaterialEditor m_materialInstance->GetPropertyValues(), m_materialInstance->GetMaterialPropertiesLayout(), propertyDynamicMetadata, - changedPropertyNames, + propertyGroupDynamicMetadata, + result.m_updatedProperties, + result.m_updatedPropertyGroups, &materialPropertyDependencies ); functor->Process(context); @@ -950,7 +990,13 @@ namespace MaterialEditor property.SetConfig(propertyConfig); } - return changedPropertyNames; + for (auto& updatedPropertyGroup : result.m_updatedPropertyGroups) + { + bool visible = propertyGroupDynamicMetadata[updatedPropertyGroup].m_visibility == AZ::RPI::MaterialPropertyGroupVisibility::Enabled; + m_propertyGroupVisibility[updatedPropertyGroup] = visible; + } + + return result; } } // namespace MaterialEditor diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h index 3959e45ad7..42f48c95cd 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h @@ -56,6 +56,7 @@ namespace MaterialEditor const AZ::RPI::MaterialTypeSourceData* GetMaterialTypeSourceData() const override; const AZStd::any& GetPropertyValue(const AZ::Name& propertyFullName) const override; const AtomToolsFramework::DynamicProperty& GetProperty(const AZ::Name& propertyFullName) const override; + bool IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const override; void SetPropertyValue(const AZ::Name& propertyFullName, const AZStd::any& value) override; bool Open(AZStd::string_view loadPath) override; bool Rebuild() override; @@ -79,11 +80,14 @@ namespace MaterialEditor // Predicate for evaluating properties using PropertyFilterFunction = AZStd::function; - // Map of documenmt's property + // Map of document's properties using PropertyMap = AZStd::unordered_map; // Map of raw property values for undo/redo comparison and storage using PropertyValueMap = AZStd::unordered_map; + + // Map of document's property group visibility flags + using PropertyGroupVisibilityMap = AZStd::unordered_map; // Function to be bound for undo and redo using UndoRedoFunction = AZStd::function; @@ -119,10 +123,16 @@ namespace MaterialEditor void RestorePropertyValues(const PropertyValueMap& propertyValues); + struct EditorMaterialFunctorResult + { + AZStd::unordered_set m_updatedProperties; + AZStd::unordered_set m_updatedPropertyGroups; + }; + // Run editor material functor to update editor metadata. // @param dirtyFlags indicates which properties have changed, and thus which MaterialFunctors need to be run. - // @return names for the set of properties that have been changed or need update. - AZStd::unordered_set RunEditorMaterialFunctors(AZ::RPI::MaterialPropertyFlags dirtyFlags); + // @return names for the set of properties and groups that have been changed or need update. + EditorMaterialFunctorResult RunEditorMaterialFunctors(AZ::RPI::MaterialPropertyFlags dirtyFlags); // Unique id of this material document AZ::Uuid m_id = AZ::Uuid::CreateRandom(); @@ -153,6 +163,9 @@ namespace MaterialEditor // Collection of all material's properties PropertyMap m_properties; + + // Collection of all material's property groups + PropertyGroupVisibilityMap m_propertyGroupVisibility; // Material functors that run in editor. See MaterialFunctor.h for details. AZStd::vector> m_editorFunctors; @@ -175,7 +188,7 @@ namespace MaterialEditor int m_undoHistoryIndex = 0; AZStd::any m_invalidValue; - + AtomToolsFramework::DynamicProperty m_invalidProperty; }; } // namespace MaterialEditor 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 b066c3c7dd..0fd3a8e2a8 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 } } - void MaterialInspector::OnDocumentPropertyConfigModified(const AZ::Uuid& documentId, const AtomToolsFramework::DynamicProperty& property) + void MaterialInspector::OnDocumentPropertyConfigModified(const AZ::Uuid&, const AtomToolsFramework::DynamicProperty& property) { for (auto& groupPair : m_groups) { @@ -197,18 +197,28 @@ namespace MaterialEditor if (reflectedProperty.GetVisibility() != property.GetVisibility()) { reflectedProperty.SetConfig(property.GetConfig()); - AtomToolsFramework::InspectorRequestBus::Event(documentId, &AtomToolsFramework::InspectorRequestBus::Events::RebuildGroup, groupPair.first); + RebuildGroup(groupPair.first); } else { reflectedProperty.SetConfig(property.GetConfig()); - AtomToolsFramework::InspectorRequestBus::Event(documentId, &AtomToolsFramework::InspectorRequestBus::Events::RefreshGroup, groupPair.first); + RefreshGroup(groupPair.first); } return; } } } } + + void MaterialInspector::OnDocumentPropertyGroupVisibilityChanged(const AZ::Uuid&, const AZ::Name& groupId, bool visible) + { + auto groupIter = m_groups.find(groupId.GetStringView()); + + if(groupIter != m_groups.end()) + { + SetGroupVisible(groupIter->first, visible); + } + } void MaterialInspector::BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode) { diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.h b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.h index efe979e5ea..65de41f240 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.h @@ -50,6 +50,7 @@ namespace MaterialEditor void OnDocumentOpened(const AZ::Uuid& documentId) override; void OnDocumentPropertyValueModified(const AZ::Uuid& documentId, const AtomToolsFramework::DynamicProperty& property) override; void OnDocumentPropertyConfigModified(const AZ::Uuid& documentId, const AtomToolsFramework::DynamicProperty& property) override; + void OnDocumentPropertyGroupVisibilityChanged(const AZ::Uuid& documentId, const AZ::Name& groupId, bool visible) override; // AzToolsFramework::IPropertyEditorNotify overrides... void BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode) override; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp index 03533e142f..62f8a58fab 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp @@ -288,15 +288,17 @@ namespace AZ void MaterialPropertyInspector::RunEditorMaterialFunctors() { AZStd::unordered_set changedPropertyNames; + AZStd::unordered_set changedPropertyGroupNames; // Convert editor property configuration data into material property meta data so that it can be used to execute functors AZStd::unordered_map propertyDynamicMetadata; - for (auto& group : m_groups) + AZStd::unordered_map propertyGroupDynamicMetadata; + for (auto& groupPair : m_groups) { - for (auto& property : group.second.m_properties) - { - AtomToolsFramework::ConvertToPropertyMetaData(propertyDynamicMetadata[property.GetId()], property.GetConfig()); - } + AZ::RPI::MaterialPropertyGroupDynamicMetadata& metadata = propertyGroupDynamicMetadata[AZ::Name{groupPair.first}]; + + metadata.m_visibility = IsGroupVisible(groupPair.first) ? + AZ::RPI::MaterialPropertyGroupVisibility::Enabled : AZ::RPI::MaterialPropertyGroupVisibility::Hidden; } for (AZ::RPI::Ptr& functor : m_editorFunctors) @@ -310,7 +312,9 @@ namespace AZ m_materialInstance->GetPropertyValues(), m_materialInstance->GetMaterialPropertiesLayout(), propertyDynamicMetadata, + propertyGroupDynamicMetadata, changedPropertyNames, + changedPropertyGroupNames, &materialPropertyDependencies ); functor->Process(context); @@ -319,9 +323,16 @@ namespace AZ m_dirtyPropertyFlags.reset(); // Apply any changes to material property meta data back to the editor property configurations - for (auto& group : m_groups) + for (auto& groupPair : m_groups) { - for (auto& property : group.second.m_properties) + AZ::Name groupName{groupPair.first}; + + if (changedPropertyGroupNames.find(groupName) != changedPropertyGroupNames.end()) + { + SetGroupVisible(groupPair.first, propertyGroupDynamicMetadata[groupName].m_visibility == AZ::RPI::MaterialPropertyGroupVisibility::Enabled); + } + + for (auto& property : groupPair.second.m_properties) { AtomToolsFramework::DynamicPropertyConfig propertyConfig = property.GetConfig(); From b225dcfa79a4ee9bfe48456899dba0632e632e24 Mon Sep 17 00:00:00 2001 From: moudgils Date: Wed, 12 May 2021 19:37:44 -0700 Subject: [PATCH 047/330] Update cmake to use the new Dxc binaries for windows --- cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake index bdc93d9a0e..9b202f2eb2 100644 --- a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake +++ b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake @@ -1,4 +1,4 @@ -# +# # All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or # its licensors. # @@ -28,8 +28,8 @@ ly_associate_package(PACKAGE_NAME expat-2.1.0-multiplatform ly_associate_package(PACKAGE_NAME zstd-1.35-multiplatform TARGETS zstd PACKAGE_HASH 45d466c435f1095898578eedde85acf1fd27190e7ea99aeaa9acfd2f09e12665) ly_associate_package(PACKAGE_NAME SQLite-3.32.2-rev3-multiplatform TARGETS SQLite PACKAGE_HASH dd4d3de6cbb4ce3d15fc504ba0ae0587e515dc89a25228037035fc0aef4831f4) ly_associate_package(PACKAGE_NAME SPIRVCross-2020.04.20-rev1-multiplatform TARGETS SPIRVCross PACKAGE_HASH 7c8c0eaa0166c26745c62d2238525af7e27ac058a5db3defdbaec1878e8798dd) -ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxc-2020.08.07-rev1-multiplatform TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 04a6850ce03d4c16e19ed206f7093d885276dfb74047e6aa99f0a834c8b7cc73) ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxcAz-5.0.0_az-rev1-multiplatform TARGETS DirectXShaderCompilerDxcAz PACKAGE_HASH 94f24989a7a371d840b513aa5ffaff02747b3d19b119bc1f899427e29978f753) +ly_associate_package(PACKAGE_NAME DirectXShaderCompiler-2021.05.05-rev1-windows TARGETS DirectXShaderCompilerDxc PACKAGE_HASH b2e34c4a19b8a996c1e488aeb83233abe1985b6502ef644516ef692029b98f6d) ly_associate_package(PACKAGE_NAME azslc-1.7.20-rev1-multiplatform TARGETS azslc PACKAGE_HASH 45d55f28bea2ef823ed3204f60df52e5e329f42923923d4555fdbdf3bea0af60) ly_associate_package(PACKAGE_NAME glad-2.0.0-beta-rev2-multiplatform TARGETS glad PACKAGE_HASH ff97ee9664e97d0854b52a3734c2289329d9f2b4cd69478df6d0ca1f1c9392ee) ly_associate_package(PACKAGE_NAME lux_core-2.2-rev5-multiplatform TARGETS lux_core PACKAGE_HASH c8c13cf7bc351643e1abd294d0841b24dee60e51647dff13db7aec396ad1e0b5) From 124ca1618a2e463e98ca4e7c5d3aa67793c4676a Mon Sep 17 00:00:00 2001 From: mriegger Date: Wed, 12 May 2021 20:51:56 -0700 Subject: [PATCH 048/330] Making shadow res of 1024 and bicubic pcf the default --- .../CommonFeatures/CoreLights/AreaLightComponentConfig.h | 2 +- .../CoreLights/DirectionalLightComponentConfig.h | 4 ++-- .../Code/Source/CoreLights/EditorAreaLightComponent.cpp | 6 +++--- .../Source/CoreLights/EditorDirectionalLightComponent.cpp | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/AreaLightComponentConfig.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/AreaLightComponentConfig.h index fe40cabc12..31cdb34ddd 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/AreaLightComponentConfig.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/AreaLightComponentConfig.h @@ -62,7 +62,7 @@ namespace AZ bool m_enableShadow = false; ShadowmapSize m_shadowmapMaxSize = ShadowmapSize::Size256; ShadowFilterMethod m_shadowFilterMethod = ShadowFilterMethod::None; - PcfMethod m_pcfMethod = PcfMethod::BoundarySearch; + PcfMethod m_pcfMethod = PcfMethod::Bicubic; float m_boundaryWidthInDegrees = 0.25f; uint16_t m_predictionSampleCount = 4; uint16_t m_filteringSampleCount = 12; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h index 7de2857541..237c1f3016 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h @@ -61,7 +61,7 @@ namespace AZ float m_shadowFarClipDistance = 100.f; //! Width/Height of shadowmap images. - ShadowmapSize m_shadowmapSize = MaxShadowmapImageSize; + ShadowmapSize m_shadowmapSize = ShadowmapSize::Size1024; //! Number of cascades. uint32_t m_cascadeCount = 4; @@ -117,7 +117,7 @@ namespace AZ //! It is used only when the pixel is predicted as on the boundary. uint16_t m_filteringSampleCount = 32; - PcfMethod m_pcfMethod = PcfMethod::BoundarySearch; + PcfMethod m_pcfMethod = PcfMethod::Bicubic; bool IsSplitManual() const; bool IsSplitAutomatic() const; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp index a77bcfdd12..69bec21a6c 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp @@ -173,10 +173,10 @@ namespace AZ ->DataElement( Edit::UIHandlers::ComboBox, &AreaLightComponentConfig::m_pcfMethod, "Pcf method", "Type of PCF to use.\n" - " Boundary search: do several taps to first determine if we are on a shadow boundary\n" - " Bicubic: a smooth, fixed-size kernel \n") - ->EnumAttribute(PcfMethod::BoundarySearch, "Boundary search") + " Bicubic: a smooth, fixed-size kernel \n" + " Boundary search: do several taps to first determine if we are on a shadow boundary\n") ->EnumAttribute(PcfMethod::Bicubic, "Bicubic") + ->EnumAttribute(PcfMethod::BoundarySearch, "Boundary search") ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) ->Attribute(Edit::Attributes::Visibility, &AreaLightComponentConfig::SupportsShadows) ->Attribute(Edit::Attributes::ReadOnly, &AreaLightComponentConfig::IsShadowPcfDisabled); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp index 35c5522c4e..a40557f2f1 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp @@ -163,10 +163,10 @@ namespace AZ ->DataElement( Edit::UIHandlers::ComboBox, &DirectionalLightComponentConfig::m_pcfMethod, "Pcf Method", "Type of Pcf to use.\n" - " Boundary search: do several taps to first determine if we are on a shadow boundary\n" - " Bicubic: a smooth, fixed-size kernel \n") - ->EnumAttribute(PcfMethod::BoundarySearch, "Boundary Search") + " Bicubic: a smooth, fixed-size kernel \n" + " Boundary search: do several taps to first determine if we are on a shadow boundary\n") ->EnumAttribute(PcfMethod::Bicubic, "Bicubic") + ->EnumAttribute(PcfMethod::BoundarySearch, "Boundary Search") ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) ->Attribute(Edit::Attributes::ReadOnly, &DirectionalLightComponentConfig::IsShadowPcfDisabled); ; From bcea9f29a82eadf15d63daaa6184744d77c308f2 Mon Sep 17 00:00:00 2001 From: mriegger Date: Wed, 12 May 2021 22:11:49 -0700 Subject: [PATCH 049/330] Improved variable name --- .../Assets/ShaderLib/Atom/Features/LightCulling/NVLC.azsli | 6 +++--- .../Shaders/LightCulling/LightCullingTilePrepare.azsl | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/LightCulling/NVLC.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/LightCulling/NVLC.azsli index 8bcd21b19b..60d5bf38f2 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/LightCulling/NVLC.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/LightCulling/NVLC.azsli @@ -26,7 +26,7 @@ // //---------------------------------------------------------------------------------- -#define Depth_to_Z(d, unprojectZ) (unprojectZ.x / (d + unprojectZ.y)) +#define DepthBufferToViewSpace(d, unprojectZ) (unprojectZ.x / (d + unprojectZ.y)) #define NVLC_MAX_POSSIBLE_LIGHTS_PER_BIN 256 @@ -187,7 +187,7 @@ float4 RemapZToUnit(float4 z, float2 minmaxz) uint DepthSamplesToBinMask2x(float2 d, float2 minmaxz, float2 unprojectZ) { - float2 z = Depth_to_Z(d, unprojectZ); + float2 z = DepthBufferToViewSpace(d, unprojectZ); // Tile_UnitValueToBit will convert that 0 to 1 value into 0.0 to 31.99999 float2 bit = Tile_UnitValueToBit(RemapZToUnit(z, minmaxz)); @@ -207,7 +207,7 @@ uint DepthSamplesToBinMask2x(float2 d, float2 minmaxz, float2 unprojectZ) uint DepthSamplesToBinMask4x(float4 d, float2 minmaxz, float2 unprojectZ) { - float4 z = Depth_to_Z(d, unprojectZ); + float4 z = DepthBufferToViewSpace(d, unprojectZ); // Tile_UnitValueToBit will convert that 0 to 1 value into 0.0 to 31.99999 float4 bit = Tile_UnitValueToBit(RemapZToUnit(z, minmaxz)); diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/LightCulling/LightCullingTilePrepare.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/LightCulling/LightCullingTilePrepare.azsl index fba2175c9b..a8b2ab76db 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/LightCulling/LightCullingTilePrepare.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/LightCulling/LightCullingTilePrepare.azsl @@ -150,7 +150,7 @@ uint ComputeTransparentBitMask(float2 minmaxZ) return 0; } - float2 minmaxZ_transparent = Depth_to_Z(minmaxDepth_transparent, PassSrg::m_constantData.m_unprojectZ); + float2 minmaxZ_transparent = DepthBufferToViewSpace(minmaxDepth_transparent, PassSrg::m_constantData.m_unprojectZ); float2 minmaxUnit_transparent = RemapZToUnit(minmaxZ_transparent, minmaxZ); @@ -295,7 +295,7 @@ void MainCS( float2 minmaxDepth_opaque = ComputeDepthMinMaxFrom2Samples(opaqueDepthSamples); minmaxDepth_both = ExpandMinMax(minmaxDepth_opaque, minmaxDepth_transparent); UpdateMinMaxFromAllThreads(minmaxDepth_both, minmaxDepth_transparent, isPixelOnScreen); - minmaxDepth_both = Depth_to_Z(minmaxDepth_both, PassSrg::m_constantData.m_unprojectZ); + minmaxDepth_both = DepthBufferToViewSpace(minmaxDepth_both, PassSrg::m_constantData.m_unprojectZ); // if zNear == zFar we want to map z == zNear to 0-bit, so we have to keep zNear without modifications minmaxDepth_both.y = IncrementULP(minmaxDepth_both.y); @@ -313,7 +313,7 @@ void MainCS( float2 minmaxDepth_opaque = ComputeDepthMinMaxFrom4Samples(opaqueDepthSamples); minmaxDepth_both = ExpandMinMax(minmaxDepth_opaque, minmaxDepth_transparent); UpdateMinMaxFromAllThreads(minmaxDepth_both, minmaxDepth_transparent, isPixelOnScreen); - minmaxDepth_both = Depth_to_Z(minmaxDepth_both, PassSrg::m_constantData.m_unprojectZ); + minmaxDepth_both = DepthBufferToViewSpace(minmaxDepth_both, PassSrg::m_constantData.m_unprojectZ); // if zNear == zFar we want to map z == zNear to 0-bit, so we have to keep zNear without modifications minmaxDepth_both.y = IncrementULP(minmaxDepth_both.y); From e429c8e06a1638eb8ac2e29ae4fa5d5d0c2ec1c7 Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Wed, 12 May 2021 23:28:05 -0700 Subject: [PATCH 050/330] Fixed issues after merging latest main, as well as some edge cases I didn't notice before. The structure of InspectorWidget::m_groups changed, so I had to update my new code accordingly. Updated the InspectorWidget::m_groups code a bit to be more readable. Discovered the initial property group visiblity state wasn't being set correctly when a material was first opened, so groups weren't initially hidden when they should have been. This had to be fixed in different ways for MaterialEditor's inspector and MaterialComponent's inspector. ATOM-14688 Disable Individual Layers --- .../Inspector/InspectorRequestBus.h | 7 ++- .../Inspector/InspectorWidget.h | 10 +++- .../Code/Source/Inspector/InspectorWidget.cpp | 50 +++++++++++-------- .../MaterialInspector/MaterialInspector.cpp | 5 ++ .../EditorMaterialComponentInspector.cpp | 7 ++- 5 files changed, 53 insertions(+), 26 deletions(-) diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorRequestBus.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorRequestBus.h index 442ff90f5b..d9b626f631 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorRequestBus.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorRequestBus.h @@ -47,8 +47,13 @@ namespace AtomToolsFramework //! Sets the visibility of a specific property group. This impacts both the header and the widget. virtual void SetGroupVisible(const AZStd::string& groupNameId, bool visible) = 0; - //! Returns the visibility of a specific property group. + //! Returns whether a specific property is visible. + //! Note this follows the same rules as QWidget::isVisible(), meaning a group could be not visible due to the widget's parents being not visible. virtual bool IsGroupVisible(const AZStd::string& groupNameId) const = 0; + + //! Returns whether a specific property is explicitly hidden. + //! Note this follows the same rules as QWidget::isHidden(), meaning a group that is hidden will not become visible automatically when the parent becomes visible. + virtual bool IsGroupHidden(const AZStd::string& groupNameId) const = 0; //! Calls Refresh for a specific InspectorGroupWidget, allowing for non-destructive UI changes virtual void RefreshGroup(const AZStd::string& groupNameId) = 0; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorWidget.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorWidget.h index 4e94de952c..5e41121b37 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorWidget.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Inspector/InspectorWidget.h @@ -59,6 +59,7 @@ namespace AtomToolsFramework void SetGroupVisible(const AZStd::string& groupNameId, bool visible) override; bool IsGroupVisible(const AZStd::string& groupNameId) const override; + bool IsGroupHidden(const AZStd::string& groupNameId) const override; void RefreshGroup(const AZStd::string& groupNameId) override; void RebuildGroup(const AZStd::string& groupNameId) override; @@ -82,6 +83,13 @@ namespace AtomToolsFramework private: QVBoxLayout* m_layout = nullptr; QScopedPointer m_ui; - AZStd::unordered_map> m_groups; + + struct GroupWidgetPair + { + InspectorGroupHeaderWidget* m_header; + QWidget* m_panel; + }; + + AZStd::unordered_map m_groups; }; } // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp index 94d4d95c24..83aed3c2ae 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp @@ -75,7 +75,7 @@ namespace AtomToolsFramework groupWidget->setParent(m_ui->m_propertyContent); m_layout->addWidget(groupWidget); - m_groups[groupNameId] = AZStd::make_pair(groupHeader, groupWidget); + m_groups[groupNameId] = {groupHeader, groupWidget}; connect(groupHeader, &InspectorGroupHeaderWidget::clicked, this, [this, groupNameId](QMouseEvent* event) { OnHeaderClicked(groupNameId, event); @@ -95,25 +95,31 @@ namespace AtomToolsFramework void InspectorWidget::SetGroupVisible(const AZStd::string& groupNameId, bool visible) { - for (size_t i = 0; i < m_groups.size(); ++i) + auto groupItr = m_groups.find(groupNameId); + if (groupItr != m_groups.end()) { - if (m_groups[i]->objectName() == groupNameId.c_str()) - { - m_headers[i]->setVisible(visible); - m_groups[i]->setVisible(visible && m_headers[i]->IsExpanded()); - break; - } + groupItr->second.m_header->setVisible(visible); + groupItr->second.m_panel->setVisible(visible && groupItr->second.m_header->IsExpanded()); } } bool InspectorWidget::IsGroupVisible(const AZStd::string& groupNameId) const { - for (auto& header : m_headers) + auto groupItr = m_groups.find(groupNameId); + if (groupItr != m_groups.end()) { - if (header->objectName() == groupNameId.c_str()) - { - return header->isVisible(); - } + return groupItr->second.m_header->isVisible(); + } + + return false; + } + + bool InspectorWidget::IsGroupHidden(const AZStd::string& groupNameId) const + { + auto groupItr = m_groups.find(groupNameId); + if (groupItr != m_groups.end()) + { + return groupItr->second.m_header->isHidden(); } return false; @@ -156,8 +162,8 @@ namespace AtomToolsFramework auto groupItr = m_groups.find(groupNameId); if (groupItr != m_groups.end()) { - groupItr->second.first->SetExpanded(true); - groupItr->second.second->setVisible(true); + groupItr->second.m_header->SetExpanded(true); + groupItr->second.m_panel->setVisible(true); } } @@ -166,23 +172,23 @@ namespace AtomToolsFramework auto groupItr = m_groups.find(groupNameId); if (groupItr != m_groups.end()) { - groupItr->second.first->SetExpanded(false); - groupItr->second.second->setVisible(false); + groupItr->second.m_header->SetExpanded(false); + groupItr->second.m_panel->setVisible(false); } } bool InspectorWidget::IsGroupExpanded(const AZStd::string& groupNameId) const { auto groupItr = m_groups.find(groupNameId); - return groupItr != m_groups.end() ? groupItr->second.first->IsExpanded() : false; + return groupItr != m_groups.end() ? groupItr->second.m_header->IsExpanded() : false; } void InspectorWidget::ExpandAll() { for (auto& groupPair : m_groups) { - groupPair.second.first->SetExpanded(true); - groupPair.second.second->setVisible(true); + groupPair.second.m_header->SetExpanded(true); + groupPair.second.m_panel->setVisible(true); } } @@ -190,8 +196,8 @@ namespace AtomToolsFramework { for (auto& groupPair : m_groups) { - groupPair.second.first->SetExpanded(false); - groupPair.second.second->setVisible(false); + groupPair.second.m_header->SetExpanded(false); + groupPair.second.m_panel->setVisible(false); } } 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 0492c9c150..19dfc6154c 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp @@ -198,6 +198,11 @@ namespace MaterialEditor &group, &group, group.TYPEINFO_Uuid(), this, this, GetGroupSaveStateKey(groupNameId), [this](const auto source, const auto target) { return CompareInstanceNodeProperties(source, target); }); AddGroup(groupNameId, groupDisplayName, groupDescription, propertyGroupWidget); + + bool isGroupVisible = false; + MaterialDocumentRequestBus::EventResult( + isGroupVisible, m_documentId, &MaterialDocumentRequestBus::Events::IsPropertyGroupVisible, AZ::Name{groupNameId}); + SetGroupVisible(groupNameId, isGroupVisible); } } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp index 59e29d29f1..31d69569db 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp @@ -305,8 +305,11 @@ namespace AZ { AZ::RPI::MaterialPropertyGroupDynamicMetadata& metadata = propertyGroupDynamicMetadata[AZ::Name{groupPair.first}]; - metadata.m_visibility = IsGroupVisible(groupPair.first) ? - AZ::RPI::MaterialPropertyGroupVisibility::Enabled : AZ::RPI::MaterialPropertyGroupVisibility::Hidden; + // It's significant that we check IsGroupHidden rather than IsGroupVisisble, because it follows the same rules as QWidget::isHidden(). + // We don't care whether the widget and all its parents are visible, we only care about whether the group was hidden within the context + // of the material property inspector. + metadata.m_visibility = IsGroupHidden(groupPair.first) ? + AZ::RPI::MaterialPropertyGroupVisibility::Hidden : AZ::RPI::MaterialPropertyGroupVisibility::Enabled; } for (AZ::RPI::Ptr& functor : m_editorFunctors) From 36a79aca8a353316f560f3079ff2b66cb5120996 Mon Sep 17 00:00:00 2001 From: sphrose <82213493+sphrose@users.noreply.github.com> Date: Thu, 13 May 2021 10:56:39 +0100 Subject: [PATCH 051/330] Remove redundant function. --- .../UI/PropertyEditor/PropertyRowWidget.cpp | 7 +------ .../UI/PropertyEditor/PropertyRowWidget.hxx | 1 - 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp index 85784d61e0..890253b528 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp @@ -126,7 +126,7 @@ namespace AzToolsFramework { QStylePainter p(this); - if (IsReorderableRow()) + if (CanBeReordered()) { const QPen linePen(QColor(0x3B3E3F)); p.setPen(linePen); @@ -1332,11 +1332,6 @@ namespace AzToolsFramework return canBeTopLevel(this); } - bool PropertyRowWidget::IsReorderableRow() const - { - return CanBeReordered(); - } - bool PropertyRowWidget::GetAppendDefaultLabelToName() { return false; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx index 1c17cab69f..79121403c9 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx @@ -83,7 +83,6 @@ namespace AzToolsFramework PropertyRowWidget* GetParentRow() const { return m_parentRow; } int GetLevel() const; bool IsTopLevel() const; - bool IsReorderableRow() const; // Remove the default label and append the text to the name label. bool GetAppendDefaultLabelToName(); From 68711fce7599826e3b6feacb44adc6e00dd0ce53 Mon Sep 17 00:00:00 2001 From: pereslav Date: Thu, 13 May 2021 17:23:37 +0100 Subject: [PATCH 052/330] Added network prefab processor test --- Gems/Multiplayer/Code/CMakeLists.txt | 47 +++++++- Gems/Multiplayer/Code/Tests/MainTools.cpp | 55 +++++++++ .../Code/Tests/PrefabProcessingTests.cpp | 106 ++++++++++++++++++ .../Code/multiplayer_tools_tests_files.cmake | 15 +++ 4 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 Gems/Multiplayer/Code/Tests/MainTools.cpp create mode 100644 Gems/Multiplayer/Code/Tests/PrefabProcessingTests.cpp create mode 100644 Gems/Multiplayer/Code/multiplayer_tools_tests_files.cmake diff --git a/Gems/Multiplayer/Code/CMakeLists.txt b/Gems/Multiplayer/Code/CMakeLists.txt index 4eeee15c47..7a4eaeb014 100644 --- a/Gems/Multiplayer/Code/CMakeLists.txt +++ b/Gems/Multiplayer/Code/CMakeLists.txt @@ -59,6 +59,26 @@ ly_add_target( ) if (PAL_TRAIT_BUILD_HOST_TOOLS) + ly_add_target( + NAME Multiplayer.Tools.Static STATIC + NAMESPACE Gem + FILES_CMAKE + multiplayer_tools_files.cmake + COMPILE_DEFINITIONS + PUBLIC + MULTIPLAYER_TOOLS + INCLUDE_DIRECTORIES + PRIVATE + . + Source + ${pal_source_dir} + PUBLIC + Include + BUILD_DEPENDENCIES + PUBLIC + AZ::AzToolsFramework + Gem::Multiplayer.Static + ) ly_add_target( NAME Multiplayer.Tools MODULE @@ -74,8 +94,7 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) Include BUILD_DEPENDENCIES PRIVATE - AZ::AzToolsFramework - Gem::Multiplayer.Static + Gem::Multiplayer.Tools.Static ) ly_add_target( @@ -145,6 +164,30 @@ if (PAL_TRAIT_BUILD_TESTS_SUPPORTED) ly_add_googletest( NAME Gem::Multiplayer.Tests ) + + if (PAL_TRAIT_BUILD_HOST_TOOLS) + ly_add_target( + NAME Multiplayer.Tools.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} + NAMESPACE Gem + FILES_CMAKE + multiplayer_tools_tests_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Tests + Source + . + BUILD_DEPENDENCIES + PRIVATE + AZ::AzTest + AZ::AzTestShared + AZ::AzToolsFrameworkTestCommon + Gem::Multiplayer.Tools.Static + ) + ly_add_googletest( + NAME Gem::Multiplayer.Tools.Tests + ) + endif() + endif() ly_add_target( diff --git a/Gems/Multiplayer/Code/Tests/MainTools.cpp b/Gems/Multiplayer/Code/Tests/MainTools.cpp new file mode 100644 index 0000000000..65a1d921a9 --- /dev/null +++ b/Gems/Multiplayer/Code/Tests/MainTools.cpp @@ -0,0 +1,55 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Multiplayer +{ + class MultiplayerToolsTestEnvironment : public AZ::Test::GemTestEnvironment + { + AZ::ComponentApplication* CreateApplicationInstance() override + { + return aznew UnitTest::ToolsTestApplication("MultiplayerToolsTest"); + } + + void AddGemsAndComponents() override + { + AZStd::vector descriptors({ + NetBindComponent::CreateDescriptor(), + NetBindMarkerComponent::CreateDescriptor(), + NetworkSpawnableHolderComponent::CreateDescriptor() + }); + + AddComponentDescriptors(descriptors); + } + }; +} // namespace UnitTest + +// Required to support running integration tests with Qt +AZTEST_EXPORT int AZ_UNIT_TEST_HOOK_NAME(int argc, char** argv) +{ + ::testing::InitGoogleMock(&argc, argv); + AzQtComponents::PrepareQtPaths(); + QApplication app(argc, argv); + AZ::Test::printUnusedParametersWarning(argc, argv); + AZ::Test::addTestEnvironments({new Multiplayer::MultiplayerToolsTestEnvironment}); + int result = RUN_ALL_TESTS(); + return result; +} diff --git a/Gems/Multiplayer/Code/Tests/PrefabProcessingTests.cpp b/Gems/Multiplayer/Code/Tests/PrefabProcessingTests.cpp new file mode 100644 index 0000000000..3d16d12f53 --- /dev/null +++ b/Gems/Multiplayer/Code/Tests/PrefabProcessingTests.cpp @@ -0,0 +1,106 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace UnitTest +{ + class PrefabProcessingTestFixture : public ::testing::Test + { + public: + static void ConvertEntitiesToPrefab(const AZStd::vector& entities, AzToolsFramework::Prefab::PrefabDom& prefabDom) + { + auto* prefabSystem = AZ::Interface::Get(); + AZStd::unique_ptr sourceInstance(prefabSystem->CreatePrefab(entities, {}, "test/path")); + ASSERT_TRUE(sourceInstance); + + auto& prefabTemplateDom = prefabSystem->FindTemplateDom(sourceInstance->GetTemplateId()); + prefabDom.CopyFrom(prefabTemplateDom, prefabDom.GetAllocator()); + } + + static AZ::Entity* CreateSourceEntity(const char* name, bool networked, const AZ::Transform& tm, AZ::Entity* parent = nullptr) + { + AZ::Entity* entity = aznew AZ::Entity(name); + auto* transformComponent = entity->CreateComponent(); + + if (parent) + { + transformComponent->SetParent(parent->GetId()); + transformComponent->SetLocalTM(tm); + } + else + { + transformComponent->SetWorldTM(tm); + } + + if(networked) + { + entity->CreateComponent(); + } + + return entity; + } + }; + + TEST_F(PrefabProcessingTestFixture, NetworkPrefabProcessor_ProcessPrefabTwoEntities_NetEntityGoesToNetSpawnable) + { + using AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext; + + AZStd::vector entities; + + const AZStd::string staticEntityName = "static_floor"; + entities.emplace_back(CreateSourceEntity(staticEntityName.c_str(), false, AZ::Transform::CreateIdentity())); + + const AZStd::string netEntityName = "networked_entity"; + entities.emplace_back(CreateSourceEntity(netEntityName.c_str(), true, AZ::Transform::CreateIdentity())); + + AzToolsFramework::Prefab::PrefabDom prefabDom; + ConvertEntitiesToPrefab(entities, prefabDom); + + const AZStd::string prefabName = "testPrefab"; + PrefabProcessorContext prefabProcessorContext{AZ::Uuid::CreateRandom()}; + prefabProcessorContext.AddPrefab(prefabName, AZStd::move(prefabDom)); + + Multiplayer::NetworkPrefabProcessor processor; + processor.Process(prefabProcessorContext); + + EXPECT_TRUE(prefabProcessorContext.HasCompletedSuccessfully()); + + const auto& processedObjects = prefabProcessorContext.GetProcessedObjects(); + EXPECT_EQ(processedObjects.size(), 1); + + const AZ::Data::AssetData& spawnableAsset = processedObjects[0].GetAsset(); + EXPECT_EQ(prefabName + ".network.spawnable", processedObjects[0].GetId()); + EXPECT_EQ(spawnableAsset.GetType(), azrtti_typeid()); + + const AzFramework::Spawnable* netSpawnable = azrtti_cast(&spawnableAsset); + const AzFramework::Spawnable::EntityList& entityList = netSpawnable->GetEntities(); + auto countEntityCallback = [](const auto& name) + { + return [name](const auto& entity) + { + return entity->GetName() == name; + }; + }; + + EXPECT_EQ(0, AZStd::count_if(entityList.begin(), entityList.end(), countEntityCallback(staticEntityName))); + EXPECT_EQ(1, AZStd::count_if(entityList.begin(), entityList.end(), countEntityCallback(netEntityName))); + } + +} // namespace UnitTest diff --git a/Gems/Multiplayer/Code/multiplayer_tools_tests_files.cmake b/Gems/Multiplayer/Code/multiplayer_tools_tests_files.cmake new file mode 100644 index 0000000000..c308b3de52 --- /dev/null +++ b/Gems/Multiplayer/Code/multiplayer_tools_tests_files.cmake @@ -0,0 +1,15 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +set(FILES + Tests/MainTools.cpp + Tests/PrefabProcessingTests.cpp +) From fdc890b0fc693c636559a4c0b900658cbd7f55b5 Mon Sep 17 00:00:00 2001 From: pereslav Date: Thu, 13 May 2021 17:27:23 +0100 Subject: [PATCH 053/330] Added comments to the test --- Gems/Multiplayer/Code/Tests/PrefabProcessingTests.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Gems/Multiplayer/Code/Tests/PrefabProcessingTests.cpp b/Gems/Multiplayer/Code/Tests/PrefabProcessingTests.cpp index 3d16d12f53..df1da11725 100644 --- a/Gems/Multiplayer/Code/Tests/PrefabProcessingTests.cpp +++ b/Gems/Multiplayer/Code/Tests/PrefabProcessingTests.cpp @@ -64,31 +64,39 @@ namespace UnitTest AZStd::vector entities; + // Create test entities: 1 networked and 1 static const AZStd::string staticEntityName = "static_floor"; entities.emplace_back(CreateSourceEntity(staticEntityName.c_str(), false, AZ::Transform::CreateIdentity())); const AZStd::string netEntityName = "networked_entity"; entities.emplace_back(CreateSourceEntity(netEntityName.c_str(), true, AZ::Transform::CreateIdentity())); + // Convert the entities into prefab. Note: This will transfer the ownership of AZ::Entity* into Prefab AzToolsFramework::Prefab::PrefabDom prefabDom; ConvertEntitiesToPrefab(entities, prefabDom); + // Add the prefab into the Prefab Processor Context const AZStd::string prefabName = "testPrefab"; PrefabProcessorContext prefabProcessorContext{AZ::Uuid::CreateRandom()}; prefabProcessorContext.AddPrefab(prefabName, AZStd::move(prefabDom)); + // Request NetworkPrefabProcessor to process the prefab Multiplayer::NetworkPrefabProcessor processor; processor.Process(prefabProcessorContext); + // Validate results EXPECT_TRUE(prefabProcessorContext.HasCompletedSuccessfully()); + // Should be 1 networked spawnable const auto& processedObjects = prefabProcessorContext.GetProcessedObjects(); EXPECT_EQ(processedObjects.size(), 1); + // Verify the name and the type of the spawnable asset const AZ::Data::AssetData& spawnableAsset = processedObjects[0].GetAsset(); EXPECT_EQ(prefabName + ".network.spawnable", processedObjects[0].GetId()); EXPECT_EQ(spawnableAsset.GetType(), azrtti_typeid()); + // Verify we have only the networked entity in the network spawnable and not the static one const AzFramework::Spawnable* netSpawnable = azrtti_cast(&spawnableAsset); const AzFramework::Spawnable::EntityList& entityList = netSpawnable->GetEntities(); auto countEntityCallback = [](const auto& name) From f478340376267f1623f7138fe2b06024d050687c Mon Sep 17 00:00:00 2001 From: amzn-mike <80125227+amzn-mike@users.noreply.github.com> Date: Wed, 12 May 2021 16:04:25 -0500 Subject: [PATCH 054/330] Data driven asset importer. Need to fix reflection --- AssetImporterSettings.json | 7 +++ .../FbxImportRequestHandler.cpp | 60 +++++++++++++++++-- .../FbxSceneBuilder/FbxImportRequestHandler.h | 16 ++++- 3 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 AssetImporterSettings.json diff --git a/AssetImporterSettings.json b/AssetImporterSettings.json new file mode 100644 index 0000000000..134484cf8d --- /dev/null +++ b/AssetImporterSettings.json @@ -0,0 +1,7 @@ +{ + "SupportedFileTypeExtensions" : [ + ".fbx", + ".stl", + ".stp" + ] +} \ No newline at end of file diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp index 155209f1b5..2210abfaf7 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp @@ -10,7 +10,14 @@ * */ +#include +#include +#include +#include #include +#include +#include +#include #include #include #include @@ -23,10 +30,47 @@ namespace AZ { namespace FbxSceneImporter { - const char* FbxImportRequestHandler::s_extension = ".fbx"; + AssetImporterSettings::AssetImporterSettings() + { + // Default supported extension in case the settings file isn't found + m_supportedFileTypeExtensions.emplace(".fbx"); + } + + void AssetImporterSettings::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context); serializeContext) + { + serializeContext->Class() + ->Version(1) + ->Field("SupportedFileTypeExtensions", &AssetImporterSettings::m_supportedFileTypeExtensions); + } + } void FbxImportRequestHandler::Activate() { + // Attempt to load the Slice Builder Settings file + AZ::IO::LocalFileIO localFileIO; + + // This will point to @assets@/SettingsFilename, which loads from the cache + // We don't really want this but it works for now + // Trying to use AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath at this point + // would fail because components seem to activate before the AP has populated its file list + AZ::IO::Path sliceBuilderSettingsIoPath(SettingsFilename); + auto result = AzFramework::FileFunc::ReadJsonFile(sliceBuilderSettingsIoPath, &localFileIO); + if (result.IsSuccess()) + { + AZ::JsonSerializationResult::ResultCode serializationResult = + AZ::JsonSerialization::Load(m_settings, result.GetValue()); + if (serializationResult.GetProcessing() == AZ::JsonSerializationResult::Processing::Halted) + { + AZ_Warning("", false, "Error in Asset Importer Settings file.\nUsing default settings."); + } + } + else + { + AZ_Warning("", false, "Failed to load Asset Importer Settings file.\nUsing default settings."); + } + BusConnect(); } @@ -37,21 +81,29 @@ namespace AZ void FbxImportRequestHandler::Reflect(ReflectContext* context) { + AssetImporterSettings::Reflect(context); + SerializeContext* serializeContext = azrtti_cast(context); if (serializeContext) { - serializeContext->Class()->Version(1); + serializeContext->Class()->Version(1)->Attribute( + AZ::Edit::Attributes::SystemComponentTags, + AZStd::vector({AssetBuilderSDK::ComponentTags::AssetBuilder})); + } } void FbxImportRequestHandler::GetSupportedFileExtensions(AZStd::unordered_set& extensions) { - extensions.insert(s_extension); + extensions.insert(m_settings.m_supportedFileTypeExtensions.begin(), m_settings.m_supportedFileTypeExtensions.end()); } Events::LoadingResult FbxImportRequestHandler::LoadAsset(Containers::Scene& scene, const AZStd::string& path, const Uuid& guid, [[maybe_unused]] RequestingApplication requester) { - if (!AzFramework::StringFunc::Path::IsExtension(path.c_str(), s_extension)) + AZStd::string extension; + AzFramework::StringFunc::Path::GetExtension(path.c_str(), extension); + + if (!m_settings.m_supportedFileTypeExtensions.contains(extension)) { return Events::LoadingResult::Ignored; } diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.h b/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.h index 8b33051f1e..3ef2823e24 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.h +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.h @@ -21,6 +21,17 @@ namespace AZ { namespace FbxSceneImporter { + struct AssetImporterSettings + { + AZ_TYPE_INFO(AssetImporterSettings, "{8BB6C7AD-BF99-44DC-9DA1-E7AD3F03DC10}"); + + AssetImporterSettings(); + + static void Reflect(AZ::ReflectContext* context); + + AZStd::unordered_set m_supportedFileTypeExtensions; + }; + class FbxImportRequestHandler : public SceneCore::BehaviorComponent , public Events::AssetImportRequestBus::Handler @@ -39,7 +50,10 @@ namespace AZ RequestingApplication requester) override; private: - static const char* s_extension; + + AssetImporterSettings m_settings; + + static constexpr const char* SettingsFilename = "AssetImporterSettings.json"; }; } // namespace FbxSceneImporter } // namespace SceneAPI From 2b538c9921ce2be2ee4fc9c9877c1dd0272c8f58 Mon Sep 17 00:00:00 2001 From: amzn-mike <80125227+amzn-mike@users.noreply.github.com> Date: Thu, 13 May 2021 09:47:21 -0500 Subject: [PATCH 055/330] Switch to using settings registry # Conflicts: # Assets/Engine/Registry/assetimporter.setreg --- AssetImporterSettings.json | 7 ---- .../SceneAPI/FbxSceneBuilder/DllMain.cpp | 6 +--- .../FbxImportRequestHandler.cpp | 35 +++++-------------- .../FbxSceneBuilder/FbxImportRequestHandler.h | 6 ++-- .../SceneBuilder/SceneBuilderComponent.cpp | 6 +++- .../SceneBuilder/SceneBuilderComponent.h | 2 ++ Registry/assetimporter.setreg | 17 +++++++++ 7 files changed, 37 insertions(+), 42 deletions(-) delete mode 100644 AssetImporterSettings.json create mode 100644 Registry/assetimporter.setreg diff --git a/AssetImporterSettings.json b/AssetImporterSettings.json deleted file mode 100644 index 134484cf8d..0000000000 --- a/AssetImporterSettings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "SupportedFileTypeExtensions" : [ - ".fbx", - ".stl", - ".stp" - ] -} \ No newline at end of file diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/DllMain.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/DllMain.cpp index 3dc14814de..d3d7b38663 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/DllMain.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/DllMain.cpp @@ -46,11 +46,6 @@ namespace AZ // Currently it's still needed to explicitly create an instance of this instead of letting // it be a normal component. This is because ResourceCompilerScene needs to return // the list of available extensions before it can start the application. - if (!g_fbxImporter) - { - g_fbxImporter = aznew AZ::SceneAPI::FbxSceneImporter::FbxImportRequestHandler(); - g_fbxImporter->Activate(); - } } void Reflect(AZ::SerializeContext* /*context*/) @@ -64,6 +59,7 @@ namespace AZ { // Global importer and behavior g_componentDescriptors.push_back(FbxSceneBuilder::FbxImporter::CreateDescriptor()); + g_componentDescriptors.push_back(FbxSceneImporter::FbxImportRequestHandler::CreateDescriptor()); // Node and attribute importers g_componentDescriptors.push_back(AssImpBitangentStreamImporter::CreateDescriptor()); diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp index 2210abfaf7..d3962cce60 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp @@ -30,12 +30,6 @@ namespace AZ { namespace FbxSceneImporter { - AssetImporterSettings::AssetImporterSettings() - { - // Default supported extension in case the settings file isn't found - m_supportedFileTypeExtensions.emplace(".fbx"); - } - void AssetImporterSettings::Reflect(AZ::ReflectContext* context) { if (auto serializeContext = azrtti_cast(context); serializeContext) @@ -48,27 +42,11 @@ namespace AZ void FbxImportRequestHandler::Activate() { - // Attempt to load the Slice Builder Settings file - AZ::IO::LocalFileIO localFileIO; - - // This will point to @assets@/SettingsFilename, which loads from the cache - // We don't really want this but it works for now - // Trying to use AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath at this point - // would fail because components seem to activate before the AP has populated its file list - AZ::IO::Path sliceBuilderSettingsIoPath(SettingsFilename); - auto result = AzFramework::FileFunc::ReadJsonFile(sliceBuilderSettingsIoPath, &localFileIO); - if (result.IsSuccess()) - { - AZ::JsonSerializationResult::ResultCode serializationResult = - AZ::JsonSerialization::Load(m_settings, result.GetValue()); - if (serializationResult.GetProcessing() == AZ::JsonSerializationResult::Processing::Halted) - { - AZ_Warning("", false, "Error in Asset Importer Settings file.\nUsing default settings."); - } - } - else + auto settingsRegistry = AZ::SettingsRegistry::Get(); + + if (settingsRegistry) { - AZ_Warning("", false, "Failed to load Asset Importer Settings file.\nUsing default settings."); + settingsRegistry->GetObject(m_settings, "/O3DE/SceneAPI/AssetImporter"); } BusConnect(); @@ -125,6 +103,11 @@ namespace AZ return Events::LoadingResult::AssetFailure; } } + + void FbxImportRequestHandler::GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided) + { + provided.emplace_back(AZ_CRC_CE("AssetImportRequestHandler")); + } } // namespace Import } // namespace SceneAPI } // namespace AZ diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.h b/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.h index 3ef2823e24..99d2061229 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.h +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.h @@ -24,9 +24,7 @@ namespace AZ struct AssetImporterSettings { AZ_TYPE_INFO(AssetImporterSettings, "{8BB6C7AD-BF99-44DC-9DA1-E7AD3F03DC10}"); - - AssetImporterSettings(); - + static void Reflect(AZ::ReflectContext* context); AZStd::unordered_set m_supportedFileTypeExtensions; @@ -49,6 +47,8 @@ namespace AZ Events::LoadingResult LoadAsset(Containers::Scene& scene, const AZStd::string& path, const Uuid& guid, RequestingApplication requester) override; + static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided); + private: AssetImporterSettings m_settings; diff --git a/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.cpp b/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.cpp index e71a5207d0..25faca3667 100644 --- a/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.cpp +++ b/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.cpp @@ -72,6 +72,11 @@ namespace SceneBuilder m_sceneBuilder.BusDisconnect(); } + void BuilderPluginComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + { + required.emplace_back(AZ_CRC_CE("AssetImportRequestHandler")); + } + void BuilderPluginComponent::Reflect(AZ::ReflectContext* context) { AZ::SerializeContext* serializeContext = azrtti_cast(context); @@ -81,5 +86,4 @@ namespace SceneBuilder ->Attribute(AZ::Edit::Attributes::SystemComponentTags, AZStd::vector({ AssetBuilderSDK::ComponentTags::AssetBuilder })); } } - } // namespace SceneBuilder diff --git a/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.h b/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.h index c1fc6ebb36..aed5e1b026 100644 --- a/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.h +++ b/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderComponent.h @@ -32,6 +32,8 @@ namespace SceneBuilder void Activate() override; void Deactivate() override; + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + private: SceneBuilderWorker m_sceneBuilder; }; diff --git a/Registry/assetimporter.setreg b/Registry/assetimporter.setreg new file mode 100644 index 0000000000..e0b0f00f6c --- /dev/null +++ b/Registry/assetimporter.setreg @@ -0,0 +1,17 @@ +{ + "O3DE": + { + "SceneAPI": + { + "AssetImporter": + { + "SupportedFileTypeExtensions": + [ + ".fbx", + ".stl", + ".stp" + ] + } + } + } +} \ No newline at end of file From 70c8ef99ef4a9b3bc2d7c69a1262304522085177 Mon Sep 17 00:00:00 2001 From: Chris Santora Date: Thu, 13 May 2021 09:39:01 -0700 Subject: [PATCH 056/330] Updates in response to code review, from gadams3. Cleaned up code around MaterialFunctor's QueryMaterialPropertyMetadata and QueryMaterialPropertyGroupMetadata. Removed unnecessary "groupHeader->setObjectName(...)" Simplified code in MaterialInspector::OnDocumentPropertyGroupVisibilityChanged. --- .../RPI.Reflect/Material/MaterialFunctor.h | 4 +- .../RPI.Reflect/Material/MaterialFunctor.cpp | 83 +++++++++---------- .../Code/Source/Inspector/InspectorWidget.cpp | 1 - .../MaterialInspector/MaterialInspector.cpp | 7 +- 4 files changed, 42 insertions(+), 53 deletions(-) diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialFunctor.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialFunctor.h index 6b9b34aa1b..83472ade77 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialFunctor.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialFunctor.h @@ -198,8 +198,8 @@ namespace AZ ); private: - AZStd::list_iterator> QueryMaterialPropertyMetadata(const Name& propertyName) const; - AZStd::list_iterator> QueryMaterialPropertyGroupMetadata(const Name& propertyGroupName) const; + MaterialPropertyDynamicMetadata* QueryMaterialPropertyMetadata(const Name& propertyName) const; + MaterialPropertyGroupDynamicMetadata* QueryMaterialPropertyGroupMetadata(const Name& propertyGroupName) const; const AZStd::vector& m_materialPropertyValues; RHI::ConstPtr m_materialPropertiesLayout; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialFunctor.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialFunctor.cpp index ab18a1fb66..17e55309fb 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialFunctor.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialFunctor.cpp @@ -159,12 +159,7 @@ namespace AZ const MaterialPropertyDynamicMetadata* MaterialFunctor::EditorContext::GetMaterialPropertyMetadata(const Name& propertyName) const { - auto it = QueryMaterialPropertyMetadata(propertyName); - if (it == m_propertyMetadata.end()) - { - return nullptr; - } - return &(it->second); + return QueryMaterialPropertyMetadata(propertyName); } const MaterialPropertyDynamicMetadata* MaterialFunctor::EditorContext::GetMaterialPropertyMetadata(const MaterialPropertyIndex& index) const @@ -175,23 +170,20 @@ namespace AZ const MaterialPropertyGroupDynamicMetadata* MaterialFunctor::EditorContext::GetMaterialPropertyGroupMetadata(const Name& propertyName) const { - auto it = QueryMaterialPropertyGroupMetadata(propertyName); - if (it == m_propertyGroupMetadata.end()) - { - return nullptr; - } - return &(it->second); + return QueryMaterialPropertyGroupMetadata(propertyName); } bool MaterialFunctor::EditorContext::SetMaterialPropertyGroupVisibility(const Name& propertyGroupName, MaterialPropertyGroupVisibility visibility) { - auto it = QueryMaterialPropertyGroupMetadata(propertyGroupName); - if (it == m_propertyGroupMetadata.end()) + MaterialPropertyGroupDynamicMetadata* metadata = QueryMaterialPropertyGroupMetadata(propertyGroupName); + if (!metadata) { return false; } - MaterialPropertyGroupVisibility originValue = it->second.m_visibility; - it->second.m_visibility = visibility; + + MaterialPropertyGroupVisibility originValue = metadata->m_visibility; + metadata->m_visibility = visibility; + if (originValue != visibility) { m_updatedPropertyGroupsOut.insert(propertyGroupName); @@ -202,13 +194,15 @@ namespace AZ bool MaterialFunctor::EditorContext::SetMaterialPropertyVisibility(const Name& propertyName, MaterialPropertyVisibility visibility) { - auto it = QueryMaterialPropertyMetadata(propertyName); - if (it == m_propertyMetadata.end()) + MaterialPropertyDynamicMetadata* metadata = QueryMaterialPropertyMetadata(propertyName); + if (!metadata) { return false; } - MaterialPropertyVisibility originValue = it->second.m_visibility; - it->second.m_visibility = visibility; + + MaterialPropertyVisibility originValue = metadata->m_visibility; + metadata->m_visibility = visibility; + if (originValue != visibility) { m_updatedPropertiesOut.insert(propertyName); @@ -225,14 +219,15 @@ namespace AZ bool MaterialFunctor::EditorContext::SetMaterialPropertyDescription(const Name& propertyName, AZStd::string description) { - auto it = QueryMaterialPropertyMetadata(propertyName); - if (it == m_propertyMetadata.end()) + MaterialPropertyDynamicMetadata* metadata = QueryMaterialPropertyMetadata(propertyName); + if (!metadata) { return false; } - AZStd::string origin = it->second.m_description; - it->second.m_description = description; + AZStd::string origin = metadata->m_description; + metadata->m_description = description; + if (origin != description) { m_updatedPropertiesOut.insert(propertyName); @@ -249,14 +244,14 @@ namespace AZ bool MaterialFunctor::EditorContext::SetMaterialPropertyMinValue(const Name& propertyName, const MaterialPropertyValue& min) { - auto it = QueryMaterialPropertyMetadata(propertyName); - if (it == m_propertyMetadata.end()) + MaterialPropertyDynamicMetadata* metadata = QueryMaterialPropertyMetadata(propertyName); + if (!metadata) { return false; } - MaterialPropertyValue origin = it->second.m_propertyRange.m_min; - it->second.m_propertyRange.m_min = min; + MaterialPropertyValue origin = metadata->m_propertyRange.m_min; + metadata->m_propertyRange.m_min = min; if(origin != min) { @@ -274,14 +269,14 @@ namespace AZ bool MaterialFunctor::EditorContext::SetMaterialPropertyMaxValue(const Name& propertyName, const MaterialPropertyValue& max) { - auto it = QueryMaterialPropertyMetadata(propertyName); - if (it == m_propertyMetadata.end()) + MaterialPropertyDynamicMetadata* metadata = QueryMaterialPropertyMetadata(propertyName); + if (!metadata) { return false; } - MaterialPropertyValue origin = it->second.m_propertyRange.m_max; - it->second.m_propertyRange.m_max = max; + MaterialPropertyValue origin = metadata->m_propertyRange.m_max; + metadata->m_propertyRange.m_max = max; if (origin != max) { @@ -299,14 +294,14 @@ namespace AZ bool MaterialFunctor::EditorContext::SetMaterialPropertySoftMinValue(const Name& propertyName, const MaterialPropertyValue& min) { - auto it = QueryMaterialPropertyMetadata(propertyName); - if (it == m_propertyMetadata.end()) + MaterialPropertyDynamicMetadata* metadata = QueryMaterialPropertyMetadata(propertyName); + if (!metadata) { return false; } - MaterialPropertyValue origin = it->second.m_propertyRange.m_softMin; - it->second.m_propertyRange.m_softMin = min; + MaterialPropertyValue origin = metadata->m_propertyRange.m_softMin; + metadata->m_propertyRange.m_softMin = min; if (origin != min) { @@ -324,14 +319,14 @@ namespace AZ bool MaterialFunctor::EditorContext::SetMaterialPropertySoftMaxValue(const Name& propertyName, const MaterialPropertyValue& max) { - auto it = QueryMaterialPropertyMetadata(propertyName); - if (it == m_propertyMetadata.end()) + MaterialPropertyDynamicMetadata* metadata = QueryMaterialPropertyMetadata(propertyName); + if (!metadata) { return false; } - MaterialPropertyValue origin = it->second.m_propertyRange.m_softMax; - it->second.m_propertyRange.m_softMax = max; + MaterialPropertyValue origin = metadata->m_propertyRange.m_softMax; + metadata->m_propertyRange.m_softMax = max; if (origin != max) { @@ -347,7 +342,7 @@ namespace AZ return SetMaterialPropertySoftMaxValue(name, max); } - AZStd::list_iterator> MaterialFunctor::EditorContext::QueryMaterialPropertyMetadata(const Name& propertyName) const + MaterialPropertyDynamicMetadata* MaterialFunctor::EditorContext::QueryMaterialPropertyMetadata(const Name& propertyName) const { auto it = m_propertyMetadata.find(propertyName); if (it == m_propertyMetadata.end()) @@ -355,10 +350,10 @@ namespace AZ AZ_Error("MaterialFunctor", false, "Couldn't find metadata for material property: %s.", propertyName.GetCStr()); } - return it; + return &it->second; } - AZStd::list_iterator> MaterialFunctor::EditorContext::QueryMaterialPropertyGroupMetadata(const Name& propertyGroupName) const + MaterialPropertyGroupDynamicMetadata* MaterialFunctor::EditorContext::QueryMaterialPropertyGroupMetadata(const Name& propertyGroupName) const { auto it = m_propertyGroupMetadata.find(propertyGroupName); if (it == m_propertyGroupMetadata.end()) @@ -366,7 +361,7 @@ namespace AZ AZ_Error("MaterialFunctor", false, "Couldn't find metadata for material property group: %s.", propertyGroupName.GetCStr()); } - return it; + return &it->second; } template diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp index 83aed3c2ae..097a819e49 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp @@ -68,7 +68,6 @@ namespace AtomToolsFramework InspectorGroupHeaderWidget* groupHeader = new InspectorGroupHeaderWidget(m_ui->m_propertyContent); groupHeader->setText(groupDisplayName.c_str()); groupHeader->setToolTip(groupDescription.c_str()); - groupHeader->setObjectName(groupNameId.c_str()); m_layout->addWidget(groupHeader); groupWidget->setObjectName(groupNameId.c_str()); 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 19dfc6154c..706d365027 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp @@ -253,12 +253,7 @@ namespace MaterialEditor void MaterialInspector::OnDocumentPropertyGroupVisibilityChanged(const AZ::Uuid&, const AZ::Name& groupId, bool visible) { - auto groupIter = m_groups.find(groupId.GetStringView()); - - if(groupIter != m_groups.end()) - { - SetGroupVisible(groupIter->first, visible); - } + SetGroupVisible(groupId.GetStringView(), visible); } void MaterialInspector::BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode) From 41ea7a1b8112e2138131063b14087f19ba364ecc Mon Sep 17 00:00:00 2001 From: greerdv Date: Thu, 13 May 2021 20:52:29 +0100 Subject: [PATCH 057/330] add stubs for non-uniform scale component mode --- .../EditorNonUniformScaleComponent.cpp | 13 +++++ .../EditorNonUniformScaleComponent.h | 21 ++++++++ .../EditorNonUniformScaleComponentMode.cpp | 33 ++++++++++++ .../EditorNonUniformScaleComponentMode.h | 52 +++++++++++++++++++ .../aztoolsframework_files.cmake | 2 + 5 files changed, 121 insertions(+) create mode 100644 Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponentMode.cpp create mode 100644 Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponentMode.h diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponent.cpp index a61f042049..0ff91f24f4 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponent.cpp @@ -16,6 +16,8 @@ #include #include +#include + namespace AzToolsFramework { namespace Components @@ -32,6 +34,7 @@ namespace AzToolsFramework serializeContext->Class() ->Version(1) ->Field("NonUniformScale", &EditorNonUniformScaleComponent::m_scale) + ->Field("ComponentMode", &EditorNonUniformScaleComponent::m_componentModeDelegate) ; if (AZ::EditContext* editContext = serializeContext->GetEditContext()) @@ -50,6 +53,9 @@ namespace AzToolsFramework ->Attribute(AZ::Edit::Attributes::Max, AZ::MaxTransformScale) ->Attribute(AZ::Edit::Attributes::Step, 0.1f) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorNonUniformScaleComponent::OnScaleChanged) + ->DataElement(AZ::Edit::UIHandlers::Default, &EditorNonUniformScaleComponent::m_componentModeDelegate, + "Component Mode", "Non-uniform Scale Component Mode") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ; } } @@ -74,10 +80,17 @@ namespace AzToolsFramework void EditorNonUniformScaleComponent::Activate() { AZ::NonUniformScaleRequestBus::Handler::BusConnect(GetEntityId()); + + // ComponentMode + m_componentModeDelegate.ConnectWithSingleComponentMode< + EditorNonUniformScaleComponent, NonUniformScaleComponentMode>( + AZ::EntityComponentIdPair(GetEntityId(), GetId()), this); } void EditorNonUniformScaleComponent::Deactivate() { + m_componentModeDelegate.Disconnect(); + AZ::NonUniformScaleRequestBus::Handler::BusDisconnect(); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponent.h index ea67ab3962..d7ce953b22 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponent.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponent.h @@ -13,6 +13,9 @@ #pragma once #include +#include +#include +#include #include namespace AzToolsFramework @@ -23,6 +26,9 @@ namespace AzToolsFramework class EditorNonUniformScaleComponent : public AzToolsFramework::Components::EditorComponentBase , public AZ::NonUniformScaleRequestBus::Handler + , public AzToolsFramework::EditorComponentSelectionRequestsBus::Handler + , public AzToolsFramework::EditorComponentSelectionNotificationsBus::Handler + , private NonUniformScaleManipulatorRequestBus::Handler { public: AZ_EDITOR_COMPONENT(EditorNonUniformScaleComponent, "{2933FB4F-B3DA-4CD1-8106-F37300730777}", EditorComponentBase); @@ -40,6 +46,18 @@ namespace AzToolsFramework void SetScale(const AZ::Vector3& scale) override; void RegisterScaleChangedEvent(AZ::NonUniformScaleChangedEvent::Handler& handler); + protected: + // EditorComponentSelectionRequestsBus overrides ... + AZ::Aabb GetEditorSelectionBoundsViewport( + [[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo) override { return AZ::Aabb::CreateNull(); }; + bool EditorSelectionIntersectRayViewport( + [[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo, + [[maybe_unused]] const AZ::Vector3& src, [[maybe_unused]] const AZ::Vector3& dir, [[maybe_unused]] float& distance) override { return false; }; + bool SupportsEditorRayIntersect() override { return true; } + + // EditorComponentSelectionNotificationsBus overrides ... + void OnAccentTypeChanged([[maybe_unused]] AzToolsFramework::EntityAccentType accent) override {}; + private: static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); @@ -52,6 +70,9 @@ namespace AzToolsFramework AZ::Vector3 m_scale = AZ::Vector3::CreateOne(); AZ::NonUniformScaleChangedEvent m_scaleChangedEvent; + + //! Responsible for detecting ComponentMode activation and creating a concrete ComponentMode. + AzToolsFramework::ComponentModeFramework::ComponentModeDelegate m_componentModeDelegate; }; } // namespace Components } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponentMode.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponentMode.cpp new file mode 100644 index 0000000000..4be0e834dd --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponentMode.cpp @@ -0,0 +1,33 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include + +namespace AzToolsFramework +{ + namespace Components + { + NonUniformScaleComponentMode::NonUniformScaleComponentMode(const AZ::EntityComponentIdPair& entityComponentIdPair, + AZ::Uuid componentType) + : EditorBaseComponentMode(entityComponentIdPair, componentType) + { + } + + NonUniformScaleComponentMode::~NonUniformScaleComponentMode() + { + } + + void NonUniformScaleComponentMode::Refresh() + { + } + } // namespace Components +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponentMode.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponentMode.h new file mode 100644 index 0000000000..d4cd0125db --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponentMode.h @@ -0,0 +1,52 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include + +namespace AzToolsFramework +{ + namespace Components + { + //! Interface for handling non-uniform scale manipulator requests. + //! Used by NonUniformScaleComponentMode. + class NonUniformScaleManipulatorRequests + : public AZ::EntityComponentBus + { + public: + + protected: + ~NonUniformScaleManipulatorRequests() = default; + }; + + //! Type to inherit to implement NonUniformScaleManipulatorRequests + using NonUniformScaleManipulatorRequestBus = AZ::EBus; + + class NonUniformScaleComponentMode + : public AzToolsFramework::ComponentModeFramework::EditorBaseComponentMode + { + public: + AZ_CLASS_ALLOCATOR(NonUniformScaleComponentMode, AZ::SystemAllocator, 0) + + NonUniformScaleComponentMode(const AZ::EntityComponentIdPair& entityComponentIdPair, AZ::Uuid componentType); + NonUniformScaleComponentMode(const NonUniformScaleComponentMode&) = delete; + NonUniformScaleComponentMode& operator=(const NonUniformScaleComponentMode&) = delete; + NonUniformScaleComponentMode(NonUniformScaleComponentMode&&) = delete; + NonUniformScaleComponentMode& operator=(NonUniformScaleComponentMode&&) = delete; + ~NonUniformScaleComponentMode(); + + // EditorBaseComponentMode + void Refresh() override; + }; + } // namespace Components +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake index 06f78ecdd3..45f52704bf 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake @@ -303,6 +303,8 @@ set(FILES ToolsComponents/AzToolsFrameworkConfigurationSystemComponent.cpp ToolsComponents/EditorNonUniformScaleComponent.h ToolsComponents/EditorNonUniformScaleComponent.cpp + ToolsComponents/EditorNonUniformScaleComponentMode.h + ToolsComponents/EditorNonUniformScaleComponentMode.cpp ToolsMessaging/EntityHighlightBus.h UI/Docking/DockWidgetUtils.cpp UI/Docking/DockWidgetUtils.h From 06682d66c1239f58d21590d46ff5ffa847eff841 Mon Sep 17 00:00:00 2001 From: gallowj Date: Fri, 30 Apr 2021 15:19:30 -0500 Subject: [PATCH 058/330] update LFS rules with changes from 1.0 --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitattributes b/.gitattributes index 1755def66a..a1609961a4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -117,3 +117,5 @@ *.wem filter=lfs diff=lfs merge=lfs -text *.wxs filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text +*.tbscene filter=lfs diff=lfs merge=lfs -text +*.spp filter=lfs diff=lfs merge=lfs -text From 42d47ca5fcb1c2e8775a389b1b4d96d098abea6e Mon Sep 17 00:00:00 2001 From: gallowj Date: Fri, 30 Apr 2021 14:07:48 -0500 Subject: [PATCH 059/330] New geometry and texture replacement for the Standford Lucy assets. --- .gitattributes | 6 ++++ .../Objects/Lucy/.wip/Brass/brass_bake.spp | 3 ++ .../Lucy/.wip/Brass/low_Default_BaseColor.png | 3 ++ .../Lucy/.wip/Brass/low_Default_Metallic.png | 3 ++ .../Lucy/.wip/Brass/low_Default_Roughness.png | 3 ++ .../Objects/Lucy/.wip/bake_channels.psd | 3 ++ .../Assets/Objects/Lucy/.wip/high.fbx | 3 ++ .../Assets/Objects/Lucy/.wip/low.fbx | 3 ++ .../Objects/Lucy/.wip/marmoset_bake.tbscene | 3 ++ .../Lucy/.wip/stone/low_Default_BaseColor.png | 3 ++ .../Objects/Lucy/.wip/stone/stone_bake.spp | 3 ++ .../Assets/Objects/Lucy/Lucy_Curvature.tif | 3 ++ .../Assets/Objects/Lucy/Lucy_High.fbx | 4 +-- .../Assets/Objects/Lucy/Lucy_Normal.png | 3 ++ .../Objects/Lucy/Lucy_Stone_BaseColor.png | 3 ++ .../Assets/Objects/Lucy/Lucy_ao.tif | 4 +-- .../Objects/Lucy/Lucy_brass_baseColor.tif | 3 -- .../Assets/Objects/Lucy/Lucy_brass_cavity.tif | 3 ++ .../Objects/Lucy/Lucy_brass_metalness.tif | 3 -- .../Objects/Lucy/Lucy_brass_roughness.tif | 3 -- .../Objects/Lucy/Lucy_bronze_BaseColor.png | 3 ++ .../Objects/Lucy/Lucy_bronze_Metallic.png | 3 ++ .../Objects/Lucy/Lucy_bronze_Roughness.png | 3 ++ .../Assets/Objects/Lucy/Lucy_convexity.tif | 3 ++ .../Assets/Objects/Lucy/Lucy_low.fbx | 4 +-- .../Assets/Objects/Lucy/Lucy_normal.tif | 3 -- .../Objects/Lucy/Lucy_stone_baseColor.tif | 3 -- .../Objects/Lucy/Lucy_stone_roughness.tif | 3 -- .../Assets/Objects/Lucy/Lucy_thickness.tif | 4 +-- .../Assets/Objects/Lucy/lucy_brass.material | 20 +++++++---- .../Assets/Objects/Lucy/lucy_stone.material | 34 +++++++++++++++---- 31 files changed, 109 insertions(+), 39 deletions(-) create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/Brass/brass_bake.spp create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/Brass/low_Default_BaseColor.png create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/Brass/low_Default_Metallic.png create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/Brass/low_Default_Roughness.png create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/bake_channels.psd create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/high.fbx create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/low.fbx create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/marmoset_bake.tbscene create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/stone/low_Default_BaseColor.png create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/stone/stone_bake.spp create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_Curvature.tif create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_Normal.png create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_Stone_BaseColor.png delete mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_brass_baseColor.tif create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_brass_cavity.tif delete mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_brass_metalness.tif delete mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_brass_roughness.tif create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_bronze_BaseColor.png create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_bronze_Metallic.png create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_bronze_Roughness.png create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_convexity.tif delete mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_normal.tif delete mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_stone_baseColor.tif delete mode 100644 Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_stone_roughness.tif diff --git a/.gitattributes b/.gitattributes index a1609961a4..d7f9d36c50 100644 --- a/.gitattributes +++ b/.gitattributes @@ -119,3 +119,9 @@ *.zip filter=lfs diff=lfs merge=lfs -text *.tbscene filter=lfs diff=lfs merge=lfs -text *.spp filter=lfs diff=lfs merge=lfs -text +Gems/Atom/Tools/MaterialEditor/Assets/MaterialEditor/ViewportModels/Hermanubis.fbx filter=lfs diff=lfs merge=lfs -text +Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_High.fbx filter=lfs diff=lfs merge=lfs -text +Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_low.fbx filter=lfs diff=lfs merge=lfs -text +Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/marmoset_bake.tbscene filter=lfs diff=lfs merge=lfs -text +Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/Brass/brass_bake.spp filter=lfs diff=lfs merge=lfs -text +Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/stone/stone_bake.spp filter=lfs diff=lfs merge=lfs -text diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/Brass/brass_bake.spp b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/Brass/brass_bake.spp new file mode 100644 index 0000000000..0252c59ba2 --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/Brass/brass_bake.spp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb8c1839f46df4623818bc1b02f626d85d8f83d77cbeabd0d56c6a5c997c3ab3 +size 495250984 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/Brass/low_Default_BaseColor.png b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/Brass/low_Default_BaseColor.png new file mode 100644 index 0000000000..7cbf4b4c23 --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/Brass/low_Default_BaseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9bb547a502aa028624e3f95491d2e459860db5fdc5551eece757c271362c902d +size 42981863 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/Brass/low_Default_Metallic.png b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/Brass/low_Default_Metallic.png new file mode 100644 index 0000000000..93d17390aa --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/Brass/low_Default_Metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:962248929564f7a3a2a25813ba8ab83d72f2f4d8db71eb495bd650ad4eca5c25 +size 8681159 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/Brass/low_Default_Roughness.png b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/Brass/low_Default_Roughness.png new file mode 100644 index 0000000000..2d971c65ed --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/Brass/low_Default_Roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c5998edda6eb34ace8b7a5f63e72f922e0daebb6aeb453142b31e605157736d +size 17366289 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/bake_channels.psd b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/bake_channels.psd new file mode 100644 index 0000000000..bfa8911b3f --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/bake_channels.psd @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a79b82df3f6f908b2ed31f9247d9befde7daf0ff53c87479920c0463cc12d437 +size 1308624196 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/high.fbx b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/high.fbx new file mode 100644 index 0000000000..fc13f2345c --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/high.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9892d5de71db58217f9c942f542c058891ab2f0949443c48111aa71fe621cae +size 128078160 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/low.fbx b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/low.fbx new file mode 100644 index 0000000000..a72ddaec16 --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/low.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:353ff22364447796ec01799a16e6ee39fbcde337a5fee4de409069316eccbb8f +size 6942256 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/marmoset_bake.tbscene b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/marmoset_bake.tbscene new file mode 100644 index 0000000000..1b7605ff10 --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/marmoset_bake.tbscene @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e934a8772f33e6c7bc11e70071c5d14147d01d123a5bed3bd51fc861047f17cb +size 305364828 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/stone/low_Default_BaseColor.png b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/stone/low_Default_BaseColor.png new file mode 100644 index 0000000000..c880cef561 --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/stone/low_Default_BaseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:247402172cb0539faaae937659507bd72087e629004e3eb2e8f67df97063a92f +size 70531613 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/stone/stone_bake.spp b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/stone/stone_bake.spp new file mode 100644 index 0000000000..a8249fbc6b --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/.wip/stone/stone_bake.spp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa4f9fbb9fc332fa0fb3f35df3b21ef72439073a854ad55d637efbed89ad3b3c +size 562609478 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_Curvature.tif b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_Curvature.tif new file mode 100644 index 0000000000..78074d3062 --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_Curvature.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f5a6f8d7abb7c7283c921d884be51addd99526fefd8c12c733d2e955b2f09f5 +size 26738740 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_High.fbx b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_High.fbx index 0080d09b92..b87971bf6d 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_High.fbx +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_High.fbx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3309c06243a350db3d7c014199febe13738d1b6bdc317ad39b0da3bff6a3e0e -size 80075536 +oid sha256:00e19e317613be5420fd78bac1159e66d1c4deeb1f32cd4fc8c20b1ea3a5ead1 +size 153114272 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_Normal.png b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_Normal.png new file mode 100644 index 0000000000..48c7a521fd --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3546ce54290269e76d0badfbf950416267681e50f215a2e32304bc2f18a1bed +size 50268306 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_Stone_BaseColor.png b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_Stone_BaseColor.png new file mode 100644 index 0000000000..5be5669778 --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_Stone_BaseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4fb55f2aafa1a9fe59a57f29d8b932903b23d3e3f5aec85ed7e15e27c25372e1 +size 70201185 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_ao.tif b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_ao.tif index c3837c208a..806d4bcd4b 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_ao.tif +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_ao.tif @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef602929d38aab39bba40219bd91648525859e305fc18081ebed6e47827bc08e -size 8566220 +oid sha256:73d77c42b909ac5e604e132b563d30f137c9f37ec3911ea6bc6620e7d7c2e0f9 +size 18394400 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_brass_baseColor.tif b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_brass_baseColor.tif deleted file mode 100644 index a660f821d8..0000000000 --- a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_brass_baseColor.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:672847ea616ac03f6d379abfe95ffc798a15afe28f8f4a8ae35b65b3de5ffed0 -size 263620560 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_brass_cavity.tif b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_brass_cavity.tif new file mode 100644 index 0000000000..d98b06d528 --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_brass_cavity.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7aa6aa6585dd07ac400bb14555761af5ba9491bf23a55dde3b3326ab92e306d1 +size 11185760 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_brass_metalness.tif b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_brass_metalness.tif deleted file mode 100644 index 42dd9adafc..0000000000 --- a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_brass_metalness.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:297146c0e20e8ab3c4c97be033fd67bc73a859a5fc999510ea615cc0512a0bb2 -size 246503792 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_brass_roughness.tif b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_brass_roughness.tif deleted file mode 100644 index dfea22ed23..0000000000 --- a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_brass_roughness.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2dfa09806f9cdf5da07d517f2a3b779bfc8455fab98663ba8b414aed42869f81 -size 13010716 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_bronze_BaseColor.png b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_bronze_BaseColor.png new file mode 100644 index 0000000000..2f5feac069 --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_bronze_BaseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4999b4a1e5067d336e08c37e4e5033cca6f1f18a7497ae5dc192572a543006c9 +size 36345892 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_bronze_Metallic.png b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_bronze_Metallic.png new file mode 100644 index 0000000000..af09391201 --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_bronze_Metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1205e13d63f9d9b23b145eaac8d3ee7dce661dad200a6a1d49202f3794f0245a +size 8265775 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_bronze_Roughness.png b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_bronze_Roughness.png new file mode 100644 index 0000000000..67bdb35f52 --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_bronze_Roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:334196335ea72ea1ded2f5bf0741707b528cc9a168ff0a5a3d9afe14323a9f9b +size 15127806 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_convexity.tif b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_convexity.tif new file mode 100644 index 0000000000..d57ac67c7d --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_convexity.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:978d846de424f74ad76a89c005a91780afe5bdda1b4995567b1459e23627b6f2 +size 19169668 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_low.fbx b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_low.fbx index 37e589872b..46f5d1cfbd 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_low.fbx +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_low.fbx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c14e8abce725d8b3e1c5e2d5b2583b25eefe2a89193bce1e79b52b9f9ba8f328 -size 10921360 +oid sha256:6a4a65d139a6088dd4ac34f3ba3f6a7a98b8fe9545150ee7d9879fbc2a55d8d4 +size 9022128 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_normal.tif b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_normal.tif deleted file mode 100644 index 9174f763ff..0000000000 --- a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_normal.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:943db0650738003073c37fd0f89863e513d4db87b958b444afafa3d229c4fa19 -size 38474832 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_stone_baseColor.tif b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_stone_baseColor.tif deleted file mode 100644 index e173f81867..0000000000 --- a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_stone_baseColor.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:03b50a97b91659b9b667ad1978d52b936425e41be1556f995a27cc695487b998 -size 21857796 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_stone_roughness.tif b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_stone_roughness.tif deleted file mode 100644 index ec9c98e162..0000000000 --- a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_stone_roughness.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:577e06f57c691f9c3be38c7430ef3f4a37a183e6a1e2086e2a6aefffc7c045a2 -size 12055428 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_thickness.tif b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_thickness.tif index 6d4672599c..1c835e2355 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_thickness.tif +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/Lucy_thickness.tif @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2fb9d31cbee218d59f27c1e71c2e44f1316e6876ec327aa4971694eec3b82b8c -size 18596496 +oid sha256:5ba4c5e434d121d517605cdff972bfba024cf4b3065bc753bb46c7be33dcd67f +size 28629316 diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/lucy_brass.material b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/lucy_brass.material index 58f195e78b..8aa1f103d5 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/lucy_brass.material +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/lucy_brass.material @@ -5,7 +5,8 @@ "propertyLayoutVersion": 3, "properties": { "occlusion": { - "diffuseTextureMap": "Objects/Lucy/Lucy_ao.tif" + "textureMap": "Objects/Lucy/Lucy_ao.tif" + "textureMapUv": "Unwrapped" }, "baseColor": { "color": [ @@ -16,18 +17,25 @@ ], "factor": 1.0, "textureBlendMode": "Lerp", - "textureMap": "Objects/Lucy/Lucy_brass_baseColor.tif" + "textureMap": "Objects/Lucy/Lucy_bronzet_BaseColor.png", + "textureMapUv": "Unwrapped" + }, + "general": { + "applySpecularAA": true }, "metallic": { - "textureMap": "Objects/Lucy/Lucy_brass_metalness.tif" + "textureMap": "Objects/Lucy/Lucy_bronze_Metallic.png", + "textureMapUv": "Unwrapped" }, "normal": { "flipY": true, - "textureMap": "Objects/Lucy/Lucy_normal.tif" + "textureMap": "Objects/Lucy/Lucy_Normal.png", + "textureMapUv": "Unwrapped" }, "roughness": { - "upperBound": 0.6767677068710327, - "textureMap": "Objects/Lucy/Lucy_brass_roughness.tif" + "textureMap": "Objects/Lucy/Lucy_bronze_Roughness.png", + "textureMapUv": "Unwrapped", + "upperBound": 0.6767677068710327 } } } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/lucy_stone.material b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/lucy_stone.material index 1ed516a864..b34bd92506 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/lucy_stone.material +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/lucy_stone.material @@ -5,29 +5,49 @@ "propertyLayoutVersion": 3, "properties": { "occlusion": { - "diffuseTextureMap": "Objects/Lucy/Lucy_ao.tif" + "textureMap": "Objects/Lucy/Lucy_ao.tif" + "textureMapUv": "Unwrapped" }, "baseColor": { "color": [ - 0.6745098233222961, - 0.48627451062202456, - 0.19607843458652497, + 1.0, + 1.0, + 1.0, 1.0 ], "factor": 1.0, "textureBlendMode": "Lerp", - "textureMap": "Objects/Lucy/Lucy_stone_baseColor.tif" + "textureMap": "Objects/Lucy/Lucy_Stone_BaseColor.png", + "textureMapUv": "Unwrapped" + }, + "clearCoat": { + "roughness": 0.10000000149011612 + }, + "general": { + "applySpecularAA": true + }, + "irradiance": { + "color": [ + 0.7304646372795105, + 0.6938735246658325, + 0.6866865158081055, + 1.0 + ] }, "metallic": { "factor": 0.0 }, "normal": { "flipY": true, - "textureMap": "Objects/Lucy/Lucy_normal.tif" + "textureMap": "Objects/Lucy/Lucy_Normal.png", + "textureMapUv": "Unwrapped" }, "roughness": { "factor": 1.0, - "textureMap": "Objects/Lucy/Lucy_stone_roughness.tif" + "lowerBound": 0.15000000596046449, + "textureMap": "Objects/Lucy/Lucy_bronze_Roughness.png", + "textureMapUv": "Unwrapped", + "upperBound": 0.7300000190734863 } } } From 49084d250ffc9b14bd60e1e35f869b94237bacde Mon Sep 17 00:00:00 2001 From: gallowj Date: Thu, 13 May 2021 16:40:51 -0500 Subject: [PATCH 060/330] updating material files --- .../CommonFeatures/Assets/Objects/Lucy/lucy_brass.material | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/lucy_brass.material b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/lucy_brass.material index 8aa1f103d5..943079f491 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/lucy_brass.material +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/lucy_brass.material @@ -17,7 +17,7 @@ ], "factor": 1.0, "textureBlendMode": "Lerp", - "textureMap": "Objects/Lucy/Lucy_bronzet_BaseColor.png", + "textureMap": "Objects/Lucy/Lucy_bronze_BaseColor.png", "textureMapUv": "Unwrapped" }, "general": { From 183790bf7948059e79ed6b7f07745230864b9bea Mon Sep 17 00:00:00 2001 From: gallowj Date: Thu, 13 May 2021 16:41:32 -0500 Subject: [PATCH 061/330] materials need properties updated to match changes with standardpbr materialType --- .../CommonFeatures/Assets/Objects/Lucy/lucy_brass.material | 6 +++--- .../CommonFeatures/Assets/Objects/Lucy/lucy_stone.material | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/lucy_brass.material b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/lucy_brass.material index 943079f491..6e490cb0b1 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/lucy_brass.material +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/lucy_brass.material @@ -5,8 +5,8 @@ "propertyLayoutVersion": 3, "properties": { "occlusion": { - "textureMap": "Objects/Lucy/Lucy_ao.tif" - "textureMapUv": "Unwrapped" + "diffuseTextureMap": "Objects/Lucy/Lucy_ao.tif", + "diffuseTextureMapUv": "Unwrapped" }, "baseColor": { "color": [ @@ -38,4 +38,4 @@ "upperBound": 0.6767677068710327 } } -} +} \ No newline at end of file diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/lucy_stone.material b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/lucy_stone.material index b34bd92506..a0e54d9d0e 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/lucy_stone.material +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/Objects/Lucy/lucy_stone.material @@ -5,8 +5,8 @@ "propertyLayoutVersion": 3, "properties": { "occlusion": { - "textureMap": "Objects/Lucy/Lucy_ao.tif" - "textureMapUv": "Unwrapped" + "diffuseTextureMap": "Objects/Lucy/Lucy_ao.tif", + "diffuseTextureMapUv": "Unwrapped" }, "baseColor": { "color": [ @@ -50,4 +50,4 @@ "upperBound": 0.7300000190734863 } } -} +} \ No newline at end of file From 6b8b953c63f23536bb89cfa198ad58a3001f50b3 Mon Sep 17 00:00:00 2001 From: gallowj Date: Thu, 29 Apr 2021 20:51:42 -0500 Subject: [PATCH 062/330] Replaced Lucy with new geometry --- .../MaterialEditor/ViewportModels/Lucy.fbx | Bin 133 -> 9021888 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Gems/Atom/Tools/MaterialEditor/Assets/MaterialEditor/ViewportModels/Lucy.fbx b/Gems/Atom/Tools/MaterialEditor/Assets/MaterialEditor/ViewportModels/Lucy.fbx index d16c98530c06aca7508eeb888efe201b309fdb9c..534f5a2cdfbf8ecf7e3dc9623ca5fc49ef4c5c1d 100644 GIT binary patch literal 9021888 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

+Ms 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